/*
 *  ACM - 3-D draw utilities
 *  Copyright (C) 2008  Umberto Salsi
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

#include "../V/VRoman.h"
#include "../util/memory.h"

#define vpath_IMPORT
#include "vpath.h"

typedef struct {
	int s;   /* no. of points allocated in p[] */
	int n;  /* no. of points inserted in p[], n<=s */
	VPoint *p; /* array of one or more points */
} VPathTrack;

typedef struct vpath_Type {
	int s;  /* no. of tracks pointers allocated in p[] */
	int n;  /* no. of tracks pointers inserted in p[], n<=s */
	VPathTrack **p; /* array of pointers to tracks */
} vpath_Type;


static void vpath_destruct(void *vp)
{
	vpath_Type *p = vp;
	VPathTrack *t;
	int i;

	if( p == NULL )
		return;
	
	for( i = 0; i < p->n; i++ ){
		t = p->p[i];
		memory_dispose(t->p);
		memory_dispose(t);
	}

	memory_dispose(p->p);
}


vpath_Type * vpath_new()
{
	vpath_Type *p;

	p = memory_allocate(sizeof( vpath_Type ), vpath_destruct);
	p->s = 0;
	p->n = 0;
	p->p = NULL;
	return p;
}


void vpath_moveTo(vpath_Type *p, VPoint *to)
{
	VPathTrack *t;

	if( p->n > 0 && p->p[ p->n - 1 ]->n == 1 ){
		/* Previous track contains only one, unuseful point. */
		p->p[ p->n - 1 ]->p[0] = *to;
	}

	if( p->n >= p->s ){
		p->s = 2*p->s + 10;
		p->p = memory_realloc( p->p, p->s * sizeof(VPathTrack) );
	}

	t = memory_allocate(sizeof(VPathTrack), NULL);
	t->s = 5;
	t->n = 1;
	t->p = memory_allocate(t->s * sizeof(VPoint), NULL);
	t->p[0] = *to;

	p->p[ p->n++ ] = t;
}


void vpath_lineTo(vpath_Type *p, VPoint *to)
{
	VPathTrack *t;

	if( p->n == 0 ){
		/* No previous starting point available, add default: */
		vpath_moveTo(p, &(VPoint){0.0, 0.0, 0.0});
	}

	t = p->p[ p->n - 1 ];

	if( t->n >= t->s ){
		t->s = 2*t->s + 5;
		t->p = memory_realloc( t->p, t->s * sizeof(VPoint) );
	}

	t->p[ t->n++ ] = *to;
}


void vpath_arc(vpath_Type *p, VPoint *center, VPoint *final, int n)
/* FIXME: not used */
{
	VPoint s, a, b, dif, q, r;
	VPathTrack *track;
	int i;
	double t, a_len, b_len, l;

	/*
		The starting point `s' is the current pen position
	*/

	if( p->n == 0 ){
		/* No previous starting point available, add default: */
		VSetPoint(&s, 0.0, 0.0, 0.0);
		vpath_moveTo(p, &s);
	} else {
		track = p->p[ p->n - 1 ];
		s = track->p[ track->n - 1 ];
	}

	VSub(&s, center, &a);     /* a = s - center */
	VSub(final, center, &b);  /* b = final - center */
	VSub(final, &s, &dif);    /* dif = b - a */

	a_len = VMagnitude(&a);
	b_len = VMagnitude(&b);

	/*
		The points r(t) of the arc can be described in parametric form
		using t=[0,1.0] as follows:

		q(t) = a + t*(b-a)
		l(t) = |q| / ((1-t)*|a| + t*|b|)
		r(t) = center + q(t)/l(t)

		Note that r(0) is the current pen position, so we may start
		from t=1/n.
	*/
	for( i=1; i<=n; i++ ){
		t = i / (double) n;
		VAdd(&a, &(VPoint){t*dif.x, t*dif.y, t*dif.z}, &q);
		l = ((1.0-t)*a_len + t*b_len) / VMagnitude(&q);
		VSetPoint(&r, center->x + l*q.x, center->y + l*q.y, center->z + l*q.z);
		vpath_lineTo(p, &r);
	}
}


void vpath_draw_string(vpath_Type *path, char *str, int len, VMatrix *m)
{

	int	c, i, k, j;
	VGlyph_Vertex *g;
	double	x, x1, y1;
	VPoint	p;

	x = 0.0;

	for ( ; len > 0; --len, ++str) {

		c = *str;
		if( c >= 128 ){
			continue;
		}

		k = VRomanGlyph[c].path_start;
		for (i = 0; i < VRomanGlyph[c].path_count; ++ i, ++ k) {
			g = &VRomanVertex[VRomanPath[k].vertex_start];
			for (j=0; j < VRomanPath[k].vertex_count; ++j, ++g) {
				x1 = x + g->x / 25600.0;
				y1 = -g->y / 25600.0;
				VSetPoint(&p, x1, y1, 0.0);
				VTransform (&p, m, &p);
				if( j == 0 )
					vpath_moveTo(path, &p);
				else
					vpath_lineTo(path, &p);
			}
		}

		x += VRomanGlyph[c].glyph_width / 25600.0;
	}
}


void vpath_stroke(vpath_Type *p, VMatrix *m, Alib_Window *w, Alib_Pixel color)
/*
	FIXME: segments where at least a point is behind the screen
	aren't drawn, rather than being clipped as stated in the .h.
*/
{
	int i, j;
	VPathTrack *t;
	VPoint a, b;

	for( i = 0; i < p->n; i++ ){
		t = p->p[i];
		VTransform(&t->p[0], m, &a);
		for( j = 1; j < t->n; j++ ){
			VTransform(&t->p[j], m, &b);
			if( a.z < 0.0 && b.z < 0.0 )
				Alib_drawLine(w, a.x + 0.5, a.y + 0.5, b.x + 0.5, b.y + 0.5,
					color);
			a = b;
		}
	}
}


void vpath_perspective_stroke(vpath_Type *p, VMatrix *m, Viewport *v, Alib_Pixel color)
/*
	FIXME: segments where at least a point is behind the screen
	aren't drawn, rather than being clipped as stated in the .h.
*/
{
	int i, j;
	VPathTrack *t;
	VPoint a, b;
	double ka, kb;
	int a_in, b_in;
	double ax, ay, bx, by;

	/*
		Draw the segment only if within about +/85 DEG angle of view
		just to avoid overflow converting double into int when the
		arguments of AlibDrawLine() are passed.
	*/
	ax = ay = bx = by = 0.0;
	for( i = 0; i < p->n; i++ ){
		t = p->p[i];
		VTransform(&t->p[0], m, &a);
		a_in = a.z > 0.0 && a.z > 0.1 * (fabs(a.x) + fabs(a.y));
		if( a_in ){
			ka = v->dist / a.z;
			ax = v->xres * ka * a.x + v->focus.x;
			ay = v->yres * ka * a.y + v->focus.y;
		}
		for( j = 1; j < t->n; j++ ){
			VTransform(&t->p[j], m, &b);
			b_in = b.z > 0.0 && b.z > 0.1 * (fabs(b.x) + fabs(b.y));
			if( b_in ){
				kb = v->dist / b.z;
				bx = v->xres * kb * b.x + v->focus.x;
				by = v->yres * kb * b.y + v->focus.y;
			}
			if( a_in && b_in ){
				Alib_drawLine(v->w, ax, ay, bx, by, color);
			}
			ax = bx;
			ay = by;
			a_in = b_in;
		}
	}
}


/* The vpath.c module ends here. */
