#help_index "Graphics/Sprite;Sprites"
/*
$LK,"CSprite",A="MN:CSprite"$s are stored as a sequence of var
length operations with a 1-byte $LK,"type",A="MN:SPT_PT"$ leading
each operation.  They are stored, one after another,
in a chunk of memory terminated by a $LK,"zero",A="MN:SPT_END"$.
$LK,"Sprite3",A="MN:Sprite3"$() shows how the $LK,"CSprite",A="MN:CSprite"$ unions are used.

$LK,"SpriteElemSize",A="MN:SpriteElemSize"$() will return the size of a single
element, while $LK,"SpriteSize",A="MN:SpriteSize"$() will return the size
of an entire list.  Look at $LK,"sprite_elem_base_sizes",A="MN:sprite_elem_base_sizes"$.

See $MA-X+PU,"::/Apps/GrModels",LM="Cd(\"::/Apps/GrModels\");Dir;View;\n"$ for an example of
making CSprite by hand.  It uses $LK,"SPT_MESH",A="MN:SPT_MESH"$,
one of the most complicated.
*/

public U0 Sprite3(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, Bool just_one_elem=FALSE)
{//Plot a sprite into a CDC.
	CSprite			*tmpg = elems - offset(CSprite.start);
	I64				 i, j, k, x1, y1, z1, x2, y2, *old_r, *r2, old_flags = dc->flags, old_pen_width = dc->thick;
	I32				*ptr;
	CColorROPU32	 old_color = dc->color;
	CDC				*img;
	CD3I32			*p, *p2;
	CGrSym			 old_sym;

	MemCopy(&old_sym, &dc->sym, sizeof(CGrSym));
	if (dc->flags & DCF_LOCATE_NEAREST)
		dc->nearest_dist = I64_MAX;
	while (tmpg->type & SPG_TYPE_MASK)
	{
		switch (tmpg->type & SPG_TYPE_MASK)
		{
			case SPT_COLOR:
				dc->color = dc->color & ~(COLORROP_COLORS_MASK | ROPF_DITHER) | tmpg->c.color;
				break;

			case SPT_DITHER_COLOR:
				dc->color = dc->color & ~COLORROP_COLORS_MASK |
							tmpg->d.dither_color.u8[0] | tmpg->d.dither_color.u8[1] << COLORROP_BITS | ROPF_DITHER;
				break;

			case SPT_THICK:
				dc->thick = tmpg->t.thick;
				DCThickScale(dc);
				break;

			case SPT_TRANSFORM_ON:
				if (!(dc->flags & DCF_TRANSFORMATION))
				{
					x -= dc->x;
					y -= dc->y;
					z -= dc->z;
				}
				dc->flags |= DCF_TRANSFORMATION;
				break;

			case SPT_TRANSFORM_OFF:
				if (dc->flags & DCF_TRANSFORMATION)
				{
					x += dc->x;
					y += dc->y;
					z += dc->z;
				}
				dc->flags &= ~DCF_TRANSFORMATION;
				break;

			case SPT_PT:
				GrPlot3(dc, tmpg->p.x1 + x, tmpg->p.y1 + y, z);
				break;

			case SPT_TEXT:
				GrPrint3(dc, tmpg->ps.x1 + x, tmpg->ps.y1 + y, z, "%s", tmpg->ps.st);
				break;

			case SPT_TEXT_BOX:
				GrTextBox3(dc, tmpg->ps.x1 + x, tmpg->ps.y1 + y, z, tmpg->ps.st);
				break;

			case SPT_TEXT_DIAMOND:
				GrTextDiamond3(dc, tmpg->ps.x1 + x, tmpg->ps.y1 + y, z, tmpg->ps.st);
				break;

			case SPT_FLOOD_FILL:
				GrFloodFill3(dc, tmpg->p.x1 + x, tmpg->p.y1 + y, z, FALSE);
				break;

			case SPT_FLOOD_FILL_NOT:
				i = dc->color;
				dc->color = dc->color.c0;
				GrFloodFill3(dc, tmpg->p.x1 + x, tmpg->p.y1 + y, z, TRUE);
				dc->color = i;
				break;

			case SPT_SHIFT:
				x += tmpg->p.x1;
				y += tmpg->p.y1;
				break;

			case SPT_LINE:
				GrLine3(dc, tmpg->pp.x1 + x, tmpg->pp.y1 + y, z, tmpg->pp.x2 + x, tmpg->pp.y2 + y, z);
				break;

			case SPT_ARROW:
				GrArrow3(dc, tmpg->pp.x1 + x, tmpg->pp.y1 + y, z, tmpg->pp.x2 + x, tmpg->pp.y2 + y, z);
				break;

			case SPT_PLANAR_SYMMETRY:
				if (DCSymmetry3Set(dc,	tmpg->pp.x1 + x, tmpg->pp.y1 + y, z,
										tmpg->pp.x2 + x, tmpg->pp.y2 + y, z,
										tmpg->pp.x2 + x, tmpg->pp.y2 + y, z + 1))
					dc->flags |= DCF_SYMMETRY;
				else
					dc->flags &= ~DCF_SYMMETRY;
				break;

			case SPT_BITMAP:
				img = CAlloc(sizeof(CDC));
				img->width			= tmpg->pwhu.width;
				img->width_internal	= (tmpg->pwhu.width + 7) & ~7;
				img->height			= tmpg->pwhu.height;
				img->body			= &tmpg->pwhu.u;
				img->dc_signature	= DCS_SIGNATURE_VAL;
				GrBlot3(dc, tmpg->pwhu.x1 + x, tmpg->pwhu.y1 + y, z, img);
				Free(img);
				break;

			case SPT_RECT:
				GrRect3(dc, tmpg->pp.x1 + x, tmpg->pp.y1 + y, z, tmpg->pp.x2 - tmpg->pp.x1, tmpg->pp.y2 - tmpg->pp.y1);
				break;

			case SPT_ROTATED_RECT:
				x1 = tmpg->ppa.x1 + x;
				y1 = tmpg->ppa.y1 + y;
				z1 = z;
				Mat4x4MulXYZ(dc->r, &x1, &y1, &z1);
				old_r = dc->r;
				dc->flags |= DCF_TRANSFORMATION;
				r2 = Mat4x4IdentNew;
				Mat4x4RotZ(r2, -tmpg->ppa.angle);
				Mat4x4TranslationEqu(r2, x1, y1, z1);
				DCMat4x4Set(dc, Mat4x4MulMat4x4New(old_r, r2));
				GrRect3(dc, 0, 0, 0, tmpg->ppa.x2 - tmpg->ppa.x1, tmpg->ppa.y2 - tmpg->ppa.y1);
				Free(dc->r);
				Free(r2);
				DCMat4x4Set(dc, old_r);
				dc->flags = dc->flags & ~DCF_TRANSFORMATION | old_flags;
				break;

			case SPT_CIRCLE:
				GrCircle3(dc, tmpg->pr.x1 + x, tmpg->pr.y1 + y, z, tmpg->pr.radius);
				break;

			case SPT_ELLIPSE:
				GrEllipse3(dc,
						   tmpg->pwha.x1 + x, tmpg->pwha.y1 + y, z, tmpg->pwha.width, tmpg->pwha.height, tmpg->pwha.angle);
				break;

			case SPT_POLYGON:
				GrRegPoly3(dc,	tmpg->pwhas.x1 + x, tmpg->pwhas.y1 + y, z,
								tmpg->pwhas.width, tmpg->pwhas.height, tmpg->pwhas.sides, tmpg->pwhas.angle);
				break;

			case SPT_POLYLINE:
				ptr = &tmpg->nu.u;
				x1 = ptr[0];
				y1 = ptr[1];
				for (i = 1; i < tmpg->nu.num; i++)
				{
					x2 = ptr[i << 1];
					y2 = ptr[i << 1 + 1];
					GrLine3(dc, x1 + x, y1 + y, z, x2 + x, y2 + y, z);
					x1 = x2;
					y1 = y2;
				}
				break;

			case SPT_POLYPT:
				x1 = tmpg->npu.x;
				y1 = tmpg->npu.y;
				ptr = &tmpg->npu.u;
				k = tmpg->npu.num * 3;
				GrPlot3(dc, x1 + x, y1 + y, z);
				for (i = 0; i < k; i += 3) {
					j = BFieldExtU32(ptr, i, 3);
					x1 += gr_x_offsets[j];
					y1 += gr_y_offsets[j];
					GrPlot3(dc, x1 + x, y1 + y, z);
				}
				break;
			start:
				p2 = p = MAlloc(tmpg->nu.num * sizeof(CD3I32));
				MemCopy(p, &tmpg->nu.u, tmpg->nu.num * sizeof(CD3I32));
				for (i = 0; i < tmpg->nu.num; i++, p2++)
				{
					p2->x += x;
					p2->y += y;
					p2->z += z;
				}
				case SPT_BSPLINE2:
					Gr2BSpline3(dc, p, tmpg->nu.num, FALSE);
					break;

				case SPT_BSPLINE3:
					Gr3BSpline3(dc, p, tmpg->nu.num, FALSE);
					break;

				case SPT_BSPLINE2_CLOSED:
					Gr2BSpline3(dc, p, tmpg->nu.num, TRUE);
					break;

				case SPT_BSPLINE3_CLOSED:
					Gr3BSpline3(dc, p, tmpg->nu.num, TRUE);
					break;
			end:
				Free(p);
				break;

			case SPT_MESH:
				p2 = p = MAlloc(tmpg->mu.vertex_count * sizeof(CD3I32));
				MemCopy(p, &tmpg->mu.u, tmpg->mu.vertex_count * sizeof(CD3I32));
				for (i = 0; i < tmpg->mu.vertex_count; i++, p2++)
				{
					p2->x += x;
					p2->y += y;
					p2->z += z;
				}
				Gr3Mesh(dc, tmpg->mu.vertex_count, p, tmpg->mu.tri_count,
							(&tmpg->mu.u)(U8 *) + sizeof(CD3I32) * tmpg->mu.vertex_count);
				Free(p);
				break;

			case SPT_SHIFTABLE_MESH:
				if (dc->flags & DCF_TRANSFORMATION)
				{
					dc->x += tmpg->pmu.x;
					dc->y += tmpg->pmu.y;
					dc->z += tmpg->pmu.z;
					x1 = x;
					y1 = y;
					z1 = z;
				}
				else
				{
					x1 = tmpg->pmu.x + x;
					y1 = tmpg->pmu.y + y;
					z1 = tmpg->pmu.z + z;
				}
				p2 = p = MAlloc(tmpg->pmu.vertex_count * sizeof(CD3I32));
				MemCopy(p, &tmpg->pmu.u, tmpg->pmu.vertex_count * sizeof(CD3I32));
				for (i = 0; i < tmpg->pmu.vertex_count; i++, p2++)
				{
					p2->x += x1;
					p2->y += y1;
					p2->z += z1;
				}
				Gr3Mesh(dc, tmpg->pmu.vertex_count, p, tmpg->pmu.tri_count, 
							(&tmpg->pmu.u)(U8 *) + sizeof(CD3I32) * tmpg->pmu.vertex_count);
				Free(p);
				if (dc->flags & DCF_TRANSFORMATION)
				{
					dc->x -= tmpg->pmu.x;
					dc->y -= tmpg->pmu.y;
					dc->z -= tmpg->pmu.z;
				}
				break;
		}
		if (just_one_elem)
			break;
		tmpg(U8 *) += SpriteElemSize(tmpg);
	}
	MemCopy(&dc->sym, &old_sym, sizeof(CGrSym));
	dc->color = old_color;
	dc->thick = old_pen_width;
	dc->flags = dc->flags & ~(DCF_SYMMETRY | DCF_TRANSFORMATION) | old_flags & (DCF_SYMMETRY | DCF_TRANSFORMATION);
}

public U0 Sprite3B(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems)
{//Plot a sprite into a CDC, post transform xyz translation.
	I64 old_x = dc->x, old_y = dc->y, old_z = dc->z, old_flags = dc->flags & DCF_TRANSFORMATION;

	dc->x = x;
	dc->y = y;
	dc->z = z;
	dc->flags |= DCF_TRANSFORMATION;
	Sprite3(dc, 0, 0, 0, elems);
	dc->x		= old_x;
	dc->y		= old_y;
	dc->z		= old_z;
	dc->flags	= dc->flags & ~DCF_TRANSFORMATION | old_flags;
}

public U0 Sprite3Mat4x4B(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, I64 *m)
{//Plot rotated by matrix.
	I64 r[16], *old_r = dc->r, new_m[16], old_flags = dc->flags & DCF_TRANSFORMATION;

	MemCopy(new_m, m, 16 * sizeof(I64));
	dc->flags |= DCF_TRANSFORMATION;
	Mat4x4TranslationAdd(new_m, x, y, z);
	dc->r=Mat4x4MulMat4x4Equ(r, old_r, new_m);
	Sprite3(dc, 0, 0, 0, elems);
	dc->r		= old_r;
	dc->flags	= dc->flags & ~DCF_TRANSFORMATION | old_flags;
}

public U0 Sprite3XB(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, F64 è=0)
{//Plot rotated around X axis.
	I64 r[16];

	Mat4x4IdentEqu(r);
	Mat4x4RotX(r, è);
	Sprite3Mat4x4B(dc, x, y, z, elems, r);
}

public U0 Sprite3YB(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, F64 ê=0)
{//Plot rotated around Y axis.
	I64 r[16];

	Mat4x4IdentEqu(r);
	Mat4x4RotY(r, ê);
	Sprite3Mat4x4B(dc, x, y, z, elems, r);
}

public U0 Sprite3ZB(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, F64 é=0)
{//Plot rotated around Z axis.
	I64 r[16];

	Mat4x4IdentEqu(r);
	Mat4x4RotZ(r, é);
	Sprite3Mat4x4B(dc, x, y, z, elems, r);
}

public U0 SpriteExtents(U8 *elems, I64 *min_x=NULL, I64 *max_x=NULL, I64 *min_y=NULL, I64 *max_y=NULL)
{//Ignores flood fills.
	CDC *dc = DCNew(I32_MAX, I32_MAX, Fs, TRUE);

	DCExtentsInit(dc);
	Sprite3(dc, I32_MAX / 2, I32_MAX / 2, I32_MAX / 2, elems);
	if (dc->min_x <= dc->max_x)
	{
		dc->min_x -= I32_MAX / 2;
		dc->max_x -= I32_MAX / 2;
	}
	if (dc->min_y <= dc->max_y)
	{
		dc->min_y -= I32_MAX / 2;
		dc->max_y -= I32_MAX / 2;
	}
	if (min_x)
		*min_x = dc->min_x;
	if (max_x)
		*max_x = dc->max_x;
	if (min_y)
		*min_y = dc->min_y;
	if (max_y)
		*max_y = dc->max_y;
	DCDel(dc);
}

public CDC *Sprite2DC(U8 *elems)
{//Convert sprite to device context.
	CDC *res;
	I64  min_x, max_x, min_y, max_y;

	SpriteExtents(elems, &min_x, &max_x, &min_y, &max_y);
	res = DCNew(max_x - min_x + 1, max_y - min_y + 1);
	Sprite3(res, -min_x, -min_y, 0, elems);

	return res;
}

public U8 *SpriteInterpolate(F64 t, U8 *elems0, U8 *elems1)
{//The two CSprite should be ident except for points shifted around.
//t ranges from 0.0 to 1.0.
	I64		 i, t1 = GR_SCALE * t, t0 = GR_SCALE - t1;
	I32		*ptr0, *ptr1, *ptrr;
	CD3I32	*p0, *p1, *pr;
	U8		*res;
	CSprite	*tmpg0 = elems0 - offset(CSprite.start), *tmpg1 = elems1 - offset(CSprite.start), *tmpgr;

	if (t < 0.5)
	{
		i = SpriteSize(elems0), res = MAlloc(i);
		MemCopy(res, elems0, i);
	}
	else
	{
		i = SpriteSize(elems1), res = MAlloc(i);
		MemCopy(res, elems1, i);
	}
	tmpgr = res - offset(CSprite.start);
	while (tmpg0->type & SPG_TYPE_MASK)
	{
		if (tmpg0->type & SPG_TYPE_MASK != tmpg1->type & SPG_TYPE_MASK)
			throw('Graphics');
		switch (tmpg0->type & SPG_TYPE_MASK)
		{
			case SPT_ROTATED_RECT:
				tmpgr->ppa.angle = (tmpg0->ppa.angle * t0 + tmpg1->ppa.angle * t1) / GR_SCALE;
			case SPT_RECT:
			case SPT_LINE:
			case SPT_ARROW:
			case SPT_PLANAR_SYMMETRY:
				tmpgr->pp.x2 = (tmpg0->pp.x2 * t0 + tmpg1->pp.x2 * t1) >> 32;
				tmpgr->pp.y2 = (tmpg0->pp.y2 * t0 + tmpg1->pp.y2 * t1) >> 32;
			case SPT_TEXT:
			case SPT_TEXT_BOX:
			case SPT_TEXT_DIAMOND:
			case SPT_PT:
			case SPT_FLOOD_FILL:
			case SPT_FLOOD_FILL_NOT:
			case SPT_SHIFT:
				tmpgr->p.x1 = (tmpg0->p.x1 * t0 + tmpg1->p.x1 * t1) >> 32;
				tmpgr->p.y1 = (tmpg0->p.y1 * t0 + tmpg1->p.y1 * t1) >> 32;
				break;

			case SPT_CIRCLE:
				tmpgr->pr.radius = (tmpg0->pr.radius * t0 + tmpg1->pr.radius * t1) >> 32;
				tmpgr->pr.x1	 = (tmpg0->pr.x1	 * t0 + tmpg1->pr.x1	 * t1) >> 32;
				tmpgr->pr.y1	 = (tmpg0->pr.y1	 * t0 + tmpg1->pr.y1	 * t1) >> 32;
				break;

			case SPT_ELLIPSE:
			case SPT_POLYGON:
				tmpgr->pwha.x1		= (tmpg0->pwha.x1	  * t0 + tmpg1->pwha.x1		* t1) >> 32;
				tmpgr->pwha.y1		= (tmpg0->pwha.y1	  * t0 + tmpg1->pwha.y1		* t1) >> 32;
				tmpgr->pwha.width	= (tmpg0->pwha.width  * t0 + tmpg1->pwha.width	* t1) >> 32;
				tmpgr->pwha.height	= (tmpg0->pwha.height * t0 + tmpg1->pwha.height	* t1) >> 32;
				break;

			case SPT_BITMAP:
				tmpgr->pwhu.x1 = (tmpg0->pwhu.x1 * t0 + tmpg1->pwhu.x1 * t1) >> 32;
				tmpgr->pwhu.y1 = (tmpg0->pwhu.y1 * t0 + tmpg1->pwhu.y1 * t1) >> 32;
				break;

			case SPT_POLYLINE:
				ptr0 = &tmpg0->nu.u;
				ptr1 = &tmpg1->nu.u;
				ptrr = &tmpgr->nu.u;
				for (i = 0; i < tmpg0->nu.num; i++)
				{
					ptrr[i << 1]	 = (ptr0[i << 1]	 * t0 + ptr1[i << 1]	 * t1) >> 32;
					ptrr[i << 1 + 1] = (ptr0[i << 1 + 1] * t0 + ptr1[i << 1 + 1] * t1) >> 32;
				}
				break;

			case SPT_POLYPT:
				tmpgr->npu.x = (tmpg0->npu.x * t0 + tmpg1->npu.x * t1) >> 32;
				tmpgr->npu.y = (tmpg0->npu.y * t0 + tmpg1->npu.y * t1) >> 32;
				break;

			case SPT_BSPLINE2:
			case SPT_BSPLINE3:
			case SPT_BSPLINE2_CLOSED:
			case SPT_BSPLINE3_CLOSED:
				p0 = &tmpg0->nu.u;
				p1 = &tmpg1->nu.u;
				pr = &tmpgr->nu.u;
				for (i = 0; i < tmpg0->nu.num; i++)
				{
					pr[i].x = (p0[i].x * t0 + p1[i].x * t1) >> 32;
					pr[i].y = (p0[i].y * t0 + p1[i].y * t1) >> 32;
					pr[i].z = (p0[i].z * t0 + p1[i].z * t1) >> 32;
				}
				break;

			case SPT_MESH:
				p0 = &tmpg0->mu.u;
				p1 = &tmpg1->mu.u;
				pr = &tmpgr->mu.u;
				for (i = 0; i < tmpg0->mu.vertex_count; i++)
				{
					pr[i].x = (p0[i].x * t0 + p1[i].x * t1) >> 32;
					pr[i].y = (p0[i].y * t0 + p1[i].y * t1) >> 32;
					pr[i].z = (p0[i].z * t0 + p1[i].z * t1) >> 32;
				}
				break;

			case SPT_SHIFTABLE_MESH:
				p0 = &tmpg0->pmu.u;
				p1 = &tmpg1->pmu.u;
				pr = &tmpgr->pmu.u;
				for (i = 0; i < tmpg0->pmu.vertex_count; i++)
				{
					pr[i].x = (p0[i].x * t0 + p1[i].x * t1) >> 32;
					pr[i].y = (p0[i].y * t0 + p1[i].y * t1) >> 32;
					pr[i].z = (p0[i].z * t0 + p1[i].z * t1) >> 32;
				}
				break;
		}
		tmpg0(U8 *) += SpriteElemSize(tmpg0);
		tmpg1(U8 *) += SpriteElemSize(tmpg1);
		tmpgr(U8 *) += SpriteElemSize(tmpgr);
	}

	return res;
}

#help_index "Graphics/Sprite;DolDoc/Output;StdOut/DolDoc"
public CDocEntry *DocSprite(CDoc *doc=NULL, U8 *elems, U8 *format=NULL)
{//Put a sprite into a document.	You can, optionally, supply a format string
//for $LK,"DolDoc",A="FI:::/Doc/DolDocOverview.DD"$ cmd with a %d for the bin_num.
	I64			 size;
	U8			*st;
	Bool		 unlock;
	CDocEntry	*doc_e;
	CDocBin		*tmpb;

	if (!doc && !(doc = DocPut))
		return NULL;
	unlock = DocLock(doc);
	size = SpriteSize(elems);
	tmpb = CAlloc(sizeof(CDocBin), doc->mem_task);
	tmpb->size		= size;
	tmpb->data		= MAlloc(size, doc->mem_task);
	MemCopy(tmpb->data, elems, size);
	tmpb->num		= doc->cur_bin_num;
	tmpb->use_count = 1;
	QueueInsert(tmpb, doc->bin_head.last);
	if (format)
		st = MStrPrint(format, doc->cur_bin_num++);
	else
		st = MStrPrint("$$SP,\"\",BI=%d$$", doc->cur_bin_num++);
	doc_e = DocPrint(doc, "%s", st);
	Free(st);
	doc_e->bin_data = tmpb;
	if (doc_e && doc_e->de_flags & DOCEF_TAG && doc_e->tag && *doc_e->tag)
		tmpb->tag = StrNew(doc_e->tag, doc->mem_task);
	if (unlock)
		DocUnlock(doc);

	return doc_e;
}

public CDocEntry *Sprite(U8 *elems, U8 *format=NULL)
{//Put sprite to the command-line, $LK,"DocPut",A="MN:DocPut"$.
//If you set format, then include dollars ("$$SP ...$$") and leave %d for num.
	CDoc *doc;

	if (doc = DocPut)
		return DocSprite(doc, elems, format);

	return NULL;
}
