#help_index "Graphics/Sprite;Sprites"

CSprite *SpriteSetSettings(CDC *dc=NULL, CSprite *head, I64 elem_num, I64 x=0, I64 y=0,
						   CColorROPU32 *_color=NULL, I64 *_thick=NULL, I64 *_xx=NULL, I64 *_yy=NULL)
{
	CSprite			*res = head->next;
	I64				 thick = 1, xx = 0, yy = 0;
	CColorROPU32	 color = BLACK;

	if (dc)
		DCReset(dc);
	while (elem_num-- > 0 && res != head)
	{
		switch (res->type & SPG_TYPE_MASK)
		{
			case SPT_COLOR:
				color = res->c.color;
				if (dc)
					dc->color = color;
				break;

			case SPT_DITHER_COLOR:
				color = res->d.dither_color.u8[0] | res->d.dither_color.u8[1] << COLORROP_BITS | ROPF_DITHER;
				if (dc)
					dc->color = color;
				break;

			case SPT_THICK:
				thick = res->t.thick;
				if (dc)
					dc->thick = thick;
				break;

			case SPT_SHIFT:
				xx += res->p.x1;
				yy += res->p.y1;
				x  += res->p.x1;
				y  += res->p.y1;
				break;

			case SPT_PLANAR_SYMMETRY:
				if (dc)
				{
					if (DCSymmetry3Set(dc,
									   res->pp.x1 + x, res->pp.y1 + y, 0,
									   res->pp.x2 + x, res->pp.y2 + y, 0,
									   res->pp.x2 + x, res->pp.y2 + y, 1))
						dc->flags |= DCF_SYMMETRY;
					else
						dc->flags &= ~DCF_SYMMETRY;
				}
				break;
		}
		res = res->next;
	}
	if (_color)
		*_color = color;
	if (_thick)
		*_thick = thick;
	if (_xx)
		*_xx = xx;
	if (_yy)
		*_yy = yy;

	return res;
}

Bool SpritePolyPtPlot(CSprite *head, I64 x, I64 y, I64)
{
	CSprite *tmpg = CAlloc(SpriteElemQueuedBaseSize(SPT_PT));


	tmpg->type = SPT_PT;
	tmpg->p.x1 = x;
	tmpg->p.y1 = y;
	QueueInsert(tmpg, head->last);

	return TRUE;
}

CSprite *Sprite2SpriteQueue(U8 *elems)
{
	I64		 s;
	CSprite *res = CAlloc(sizeof(CSprite)), *tmpg = elems - offset(CSprite.start), *tmpg1;

	QueueInit(res);
	while (tmpg->type & SPG_TYPE_MASK)
	{
		tmpg1 = MAlloc(SpriteElemSize(tmpg) + offset(CSprite.start));
		s = SpriteElemSize(tmpg);
		MemCopy(&tmpg1->start, &tmpg->start, s);
		QueueInsert(tmpg1, res->last);
		tmpg(U8 *) += s;
	}

	return res;
}

U8 *SpriteQueue2Sprite(CSprite *head, I64 *_size=NULL)
{
	I64		 i, size = sprite_elem_base_sizes[SPT_END];
	CSprite	*tmpg = head->next;
	U8		*res, *dst;

	while (tmpg != head)
	{
		size += SpriteElemSize(tmpg);
		tmpg = tmpg->next;
	}
	if (_size)
		*_size = size;
	res = dst = MAlloc(size);
	tmpg = head->next;
	while (tmpg != head)
	{
		i = SpriteElemSize(tmpg);
		MemCopy(dst, &tmpg->start, i);
		dst += i;
		tmpg = tmpg->next;
	}
	*dst = SPT_END;

	return res;
}

U0 SpriteEdUpdate(CDoc *doc, CDocEntry *doc_ce, CSprite *head)
{
	CDocBin	*tmpb = doc_ce->bin_data;
	I64		 size;
	Bool	 unlock = DocLock(doc);

	Free(tmpb->data);
	tmpb->data = SpriteQueue2Sprite(head, &size);
	tmpb->size = size;
	if (unlock)
		DocUnlock(doc);
}

U0 SpriteSetOrigin(CSprite *head, I64 dx, I64 dy, I64 dz)
{
	I64		 i;
	I32		*ptr;
	CD3I32	*p;
	CSprite	*tmpg = head->next;

	while (tmpg != head)
	{
		if (Bt(&tmpg->type, SPf_SEL))
			switch (tmpg->type & SPG_TYPE_MASK)
			{
				case SPT_ARROW:
				case SPT_LINE:
				case SPT_PLANAR_SYMMETRY:
				case SPT_RECT:
				case SPT_ROTATED_RECT:
					tmpg->pp.x2 += dx;
					tmpg->pp.y2 += dy;
				case SPT_PT:
				case SPT_FLOOD_FILL:
				case SPT_FLOOD_FILL_NOT:
				case SPT_TEXT:
				case SPT_TEXT_BOX:
				case SPT_TEXT_DIAMOND:
				case SPT_CIRCLE:
				case SPT_BITMAP:
				case SPT_ELLIPSE:
				case SPT_POLYGON:
					tmpg->p.x1 += dx;
					tmpg->p.y1 += dy;
					break;

				case SPT_POLYLINE:
					ptr = &tmpg->nu.u;
					for (i = 0; i < tmpg->nu.num; i++)
					{
						ptr[i << 1]     += dx;
						ptr[i << 1 + 1] += dy;
					}
					break;

				case SPT_POLYPT:
					tmpg->npu.x += dx;
					tmpg->npu.y += dy;
					break;

				case SPT_BSPLINE2:
				case SPT_BSPLINE3:
				case SPT_BSPLINE2_CLOSED:
				case SPT_BSPLINE3_CLOSED:
					p = &tmpg->nu.u;
					for (i = 0; i < tmpg->nu.num; i++, p++)
					{
						p->x += dx;
						p->y += dy;
						p->z += dz;
					}
					break;

				case SPT_MESH:
					p = &tmpg->mu.u;
					for (i = 0; i < tmpg->mu.vertex_count; i++, p++)
					{
						p->x += dx;
						p->y += dy;
						p->z += dz;
					}
					break;

				case SPT_SHIFTABLE_MESH:
					tmpg->pmu.x += dx;
					tmpg->pmu.y += dy;
					tmpg->pmu.z += dz;
					break;
			}
		tmpg = tmpg->next;
	}
}

CSprite *SpriteTransformCircle(I64 *r, CSprite *tmpg)
{
	I64		 x, y, z;
	F64		 m1, arg1, m2, radius = tmpg->pr.radius << 16;
	CSprite	*tmpg1 = CAlloc(SpriteElemQueuedBaseSize(SPT_ELLIPSE));

	tmpg1->type = SPT_ELLIPSE;

	x = tmpg->pr.x1;
	y = tmpg->pr.y1;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	tmpg1->pwha.x1 = x;
	tmpg1->pwha.y1 = y;

	x = radius;
	y = 0;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	R2P(&m1, &arg1, x, y);

	x = 0;
	y = radius;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	m2 = Sqrt(x * x + y * y);

	tmpg1->pwha.width  = ToI64(m1) / 0x10000;
	tmpg1->pwha.height = ToI64(m2) / 0x10000;
	tmpg1->pwha.angle  = -arg1;

	tmpg1->type |= tmpg->type & SPF_SEL;

	return tmpg1;
}

CSprite *SpriteTransformEllipse(I64 *r, CSprite *tmpg)
{
	I64		 x, y, z;
	F64		 m1, arg1, m2, arg2, s, c, x_radius = tmpg->pwha.width << 16, y_radius = tmpg->pwha.height << 16;
	CSprite	*tmpg1 = CAlloc(SpriteElemQueuedBaseSize(tmpg->type & SPG_TYPE_MASK));

	tmpg1->type = tmpg->type;
	if (tmpg->type & SPG_TYPE_MASK == SPT_POLYGON)
		tmpg1->pwhas.sides = tmpg->pwhas.sides;

	x = tmpg->pwha.x1;
	y = tmpg->pwha.y1;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	tmpg1->pwha.x1 = x;
	tmpg1->pwha.y1 = y;

	c = Cos(-tmpg->pwha.angle);
	s = Sin(-tmpg->pwha.angle);

	x = x_radius * c;
	y = x_radius * s;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	R2P(&m1, &arg1, x, y);

	x = -y_radius * s;
	y = y_radius * c;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	R2P(&m2, &arg2, x, y);
	m2 *= Abs(Sin(arg2 - arg1));

	tmpg1->pwha.width = ToI64(m1) / 0x10000;
	if (tmpg1->pwha.width < 1)
		tmpg1->pwha.width = 1;
	tmpg1->pwha.height = ToI64(m2) / 0x10000;
	if (tmpg1->pwha.height < 1)
		tmpg1->pwha.height = 1;
	tmpg1->pwha.angle = -arg1;

	tmpg1->type |= tmpg->type & SPF_SEL;

	return tmpg1;
}

CSprite *SpriteTransformRect(I64 *r, CSprite *tmpg, F64 é)
{
	I64		 x, y, z, w, h;
	F64		 m1, arg1, m2, arg2, s, c, 
			 x_radius = (tmpg->pp.x2 - tmpg->pp.x1) << 16, 
			 y_radius = (tmpg->pp.y2 - tmpg->pp.y1) << 16;
	CSprite	*tmpg1 = CAlloc(SpriteElemQueuedBaseSize(SPT_ROTATED_RECT));

	tmpg1->type = SPT_ROTATED_RECT;

	x = tmpg->pp.x1;
	y = tmpg->pp.y1;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	tmpg1->ppa.x1 = x;
	tmpg1->ppa.y1 = y;

	c = Cos(-é);
	s = Sin(-é);

	x = x_radius * c;
	y = x_radius * s;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	R2P(&m1, &arg1, x, y);

	x = -y_radius * s;
	y = y_radius * c;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	R2P(&m2, &arg2, x, y);
	m2 *= Abs(Sin(arg2 - arg1));

	w = ToI64(m1) / 0x10000;
	if (w < 1)
		w = 1;
	h = ToI64(m2) / 0x10000;
	if (h < 1)
		h = 1;
	tmpg1->ppa.x2 = tmpg1->ppa.x1 + w;
	tmpg1->ppa.y2 = tmpg1->ppa.y1 + h;
	tmpg1->ppa.angle = -arg1;

	tmpg1->type |= tmpg->type & SPF_SEL;

	return tmpg1;
}

CSprite *SpriteTransformBitMap(I64 *r, CSprite *tmpg)
{
	CDC		*img, *dc3;
	U8		*elems;
	I64		 x, y, z, minx, maxx, miny, maxy, minz, maxz;
	CSprite	*tmpg1;

	x = tmpg->pwhu.x1;
	y = tmpg->pwhu.y1;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	minx = maxx = x;
	miny = maxy = y;
	minz = maxz = z;

	x = tmpg->pwhu.x1;
	y = tmpg->pwhu.y1 + tmpg->pwhu.height;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	if (x < minx) minx = x;
	if (x > maxx) maxx = x;
	if (y < miny) miny = y;
	if (y > maxy) maxy = y;
	if (z < minz) minz = z;
	if (z > maxz) maxz = z;

	x = tmpg->pwhu.x1 + tmpg->pwhu.width;
	y = tmpg->pwhu.y1;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	if (x < minx) minx = x;
	if (x > maxx) maxx = x;
	if (y < miny) miny = y;
	if (y > maxy) maxy = y;
	if (z < minz) minz = z;
	if (z > maxz) maxz = z;

	x = tmpg->pwhu.x1 + tmpg->pwhu.width;
	y = tmpg->pwhu.y1 + tmpg->pwhu.height;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	if (x < minx) minx = x;
	if (x > maxx) maxx = x;
	if (y < miny) miny = y;
	if (y > maxy) maxy = y;
	if (z < minz) minz = z;
	if (z > maxz) maxz = z;

	dc3 = DCNew(maxx - minx + 1, maxy - miny + 1);

	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;

	dc3->color = TRANSPARENT;
	GrRect(dc3, 0, 0, maxx - minx + 1, maxy - miny + 1);

	Free(dc3->r);
	DCMat4x4Set(dc3, r);
	dc3->flags |= DCF_TRANSFORMATION;

	dc3->x = tmpg->pwhu.x1 - minx;
	dc3->y = tmpg->pwhu.y1 - miny;
	dc3->z = -minz;
	GrBlot3(dc3, 0, 0, 0, img);
	Free(img);

	elems = DC2Sprite(dc3);
	dc3->r = NULL;
	DCDel(dc3);
	tmpg1 = CAlloc(offset(CSprite.start) + MSize(elems));
	MemCopy(tmpg1(U8 *) + offset(CSprite.start), elems, MSize(elems));
	tmpg1->type = tmpg->type;

	x = tmpg->pwhu.x1;
	y = tmpg->pwhu.y1;
	z = 0;
	Mat4x4MulXYZ(r, &x, &y, &z);
	tmpg1->pwhu.x1 = x;
	tmpg1->pwhu.y1 = y;

	return tmpg1;
}

U0 SpriteTransformQueue(CSprite *head, I64 *r)
{
	I64		 i, j, k, num, x, y, z, x1, y1, z1, x2, y2, z2, x3, y3, z3;
	I32		*ptr;
	CD3I32	*p;
	CSprite	*tmpg = head->next, head2, *tmpg1, *tmpg2, *tmpg3;

	while (tmpg != head)
	{
		if (Bt(&tmpg->type, SPf_SEL))
			switch (tmpg->type & SPG_TYPE_MASK)
			{
				case SPT_THICK:
					tmpg->t.thick *= Sqrt(Mat4x4NormSqr65536(r)) / 65536;
					if (tmpg->t.thick < 0)
						tmpg->t.thick = 0;
					break;

				case SPT_PLANAR_SYMMETRY:
				case SPT_ARROW:
				case SPT_LINE:
					x = tmpg->pp.x2;
					y = tmpg->pp.y2;
					z = 0;
					Mat4x4MulXYZ(r, &x, &y, &z);
					tmpg->pp.x2 = x;
					tmpg->pp.y2 = y;
				case SPT_PT:
				case SPT_FLOOD_FILL:
				case SPT_FLOOD_FILL_NOT:
				case SPT_TEXT:
				case SPT_TEXT_BOX:
				case SPT_TEXT_DIAMOND:
					x = tmpg->p.x1;
					y = tmpg->p.y1;
					z = 0;
					Mat4x4MulXYZ(r, &x, &y, &z);
					tmpg->p.x1 = x;
					tmpg->p.y1 = y;
					break;

				case SPT_BITMAP:
					tmpg1 = SpriteTransformBitMap(r, tmpg);
					QueueInsert(tmpg1, tmpg);
					QueueRemove(tmpg);
					Free(tmpg);
					tmpg = tmpg1;
					break;

				case SPT_ROTATED_RECT:
					tmpg1 = SpriteTransformRect(r, tmpg, tmpg->ppa.angle);
					QueueInsert(tmpg1, tmpg);
					QueueRemove(tmpg);
					Free(tmpg);
					tmpg = tmpg1;
					break;

				case SPT_RECT:
					tmpg1 = SpriteTransformRect(r, tmpg, 0);
					QueueInsert(tmpg1, tmpg);
					QueueRemove(tmpg);
					Free(tmpg);
					tmpg = tmpg1;
					break;

				case SPT_CIRCLE:
					tmpg1 = SpriteTransformCircle(r, tmpg);
					QueueInsert(tmpg1, tmpg);
					QueueRemove(tmpg);
					Free(tmpg);
					tmpg = tmpg1;
					break;

				case SPT_ELLIPSE:
				case SPT_POLYGON:
					tmpg1 = SpriteTransformEllipse(r, tmpg);
					QueueInsert(tmpg1, tmpg);
					QueueRemove(tmpg);
					Free(tmpg);
					tmpg = tmpg1;
					break;

				case SPT_POLYLINE:
					ptr = &tmpg->nu.u;
					for (i = 0; i < tmpg->nu.num; i++)
					{
						x = ptr[i << 1];
						y = ptr[i << 1 + 1];
						z = 0;
						Mat4x4MulXYZ(r, &x, &y, &z);
						ptr[i << 1]		= x;
						ptr[i << 1 + 1] = y;
					}
					break;

				case SPT_POLYPT:
					QueueInit(&head2);
					x = tmpg->npu.x;
					y = tmpg->npu.y;
					z = 0;
					x1 = x;  //
					y1 = y;  //
					z1 = z;  //unrotated cur coordinates
					Mat4x4MulXYZ(r, &x, &y, &z);
					ptr = &tmpg->npu.u;
					k = tmpg->npu.num * 3;
					x2 = x;  //
					y2 = y;  //
					z2 = z;  //rotated start coordinates

					x3 = x;  //
					y3 = y;  //
					z3 = z;  //lag 1 rotated coordinates
					for (i = 0; i < k; i += 3)
					{
						j = BFieldExtU32(ptr, i, 3);
						x1 += gr_x_offsets[j];
						y1 += gr_y_offsets[j];
						x = x1;
						y = y1;
						z = z1;
						Mat4x4MulXYZ(r, &x, &y, &z);
						Line(&head2, x3 - x2, y3 - y2, 0, x - x2, y - y2, 0, &SpritePolyPtPlot);
						x3 = x;
						y3 = y;
						z3 = z;
					}

					num = 0;
					tmpg1 = head2.next;
					x3 = 0;
					y3 = 0;
					z3 = 0;
					while (tmpg1 != &head2)
					{
						tmpg2 = tmpg1->next;
						if (tmpg1->p.x1 == x3 && tmpg1->p.y1 == y3)
						{
							QueueRemove(tmpg1);
							Free(tmpg1);
						}
						else
						{
							num++;
							x3 = tmpg1->p.x1;
							y3 = tmpg1->p.y1;
						}
						tmpg1 = tmpg2;
					}

					tmpg3 = CAlloc(SpriteElemQueuedBaseSize(SPT_POLYPT) + (num * 3 + 7) >> 3);
					tmpg3->npu.x = x2;
					tmpg3->npu.y = y2;
					ptr = &tmpg3->npu.u;
					x3 = 0;
					y3 = 0;
					z3 = 0;
					i = 0;
					tmpg1 = head2.next;
					while (tmpg1 != &head2)
					{
						tmpg2 = tmpg1->next;
						BFieldOrU32(ptr, i, polypt_map[SignI64(tmpg1->p.x1 - x3) + 1 + 3 * (SignI64(tmpg1->p.y1 - y3) + 1)]);
						i += 3;
						x3 = tmpg1->p.x1;
						y3 = tmpg1->p.y1;
						QueueRemove(tmpg1);
						Free(tmpg1);
						tmpg1 = tmpg2;
					}
					tmpg3->type = SPT_POLYPT | tmpg->type & SPF_SEL;
					tmpg3->npu.num = num;
					QueueInsert(tmpg3, tmpg);
					QueueRemove(tmpg);
					Free(tmpg);
					tmpg = tmpg3;
					break;

				case SPT_BSPLINE2:
				case SPT_BSPLINE3:
				case SPT_BSPLINE2_CLOSED:
				case SPT_BSPLINE3_CLOSED:
					p = &tmpg->nu.u;
					for (i = 0; i < tmpg->nu.num; i++, p++)
					{
						x = p->x;
						y = p->y;
						z = p->z;
						Mat4x4MulXYZ(r, &x, &y, &z);
						p->x = x;
						p->y = y;
						p->z = z;
					}
					break;

				case SPT_SHIFTABLE_MESH:
					x = tmpg->pmu.x;
					y = tmpg->pmu.y;
					z = tmpg->pmu.z;
					Mat4x4MulXYZ(r, &x, &y, &z);
					tmpg->pmu.x = x;
					tmpg->pmu.y = y;
					tmpg->pmu.z = z;
					p = &tmpg->pmu.u;
					for (i = 0; i < tmpg->pmu.vertex_count; i++, p++)
					{
						x = p->x;
						y = p->y;
						z = p->z;
						Mat4x4MulXYZ(r, &x, &y, &z);
						p->x = x;
						p->y = y;
						p->z = z;
					}
					break;

				case SPT_MESH:
					p = &tmpg->mu.u;
					for (i = 0; i < tmpg->mu.vertex_count; i++, p++)
					{
						x = p->x;
						y = p->y;
						z = p->z;
						Mat4x4MulXYZ(r, &x, &y, &z);
						p->x = x;
						p->y = y;
						p->z = z;
					}
					break;
			}
		tmpg = tmpg->next;
	}
}

I64 SpriteQueueSelCount(CSprite *head, Bool val=TRUE)
{
	I64		 res = 0;
	CSprite	*tmpg = head->next;

	val = ToBool(val);
	while (tmpg != head)
	{
		if (Bt(&tmpg->type, SPf_SEL) == val)
			res++;
		tmpg = tmpg->next;
	}

	return res;
}

I64 SpriteQueueSelAll(CSprite *head, Bool val=TRUE)
{
	I64		 res = 0;
	CSprite	*tmpg = head->next;

	while (tmpg != head)
	{
		BEqual(&tmpg->type, SPf_SEL, val);
		res++;
		tmpg = tmpg->next;
	}

	return res;
}

Bool SpriteEdText(CSprite **_head, I64 *_cur_elem_num)
{
	Bool	 res;
	CSprite	*head = *_head;
	U8		*elems = SpriteQueue2Sprite(head);
	CDoc	*doc = DocNew, *doc2, *old_put = DocPut;

	StrPrint(doc->filename.name, "AI:0x%X", doc);
	DocPrint(doc,
				"//$$PURPLE$$$$TX+CX,\"Sprite Edit as Text\"$$$$FG$$\n"
				"//$$LK+PU+CX,\"Click for Help\","
				"A=\"FI:::/Doc/SpriteEdText.DD\"$$\n\n");
	Sprite2Code(doc, elems);
	Free(elems);
	while (TRUE)
	{
		if (res = PopUpPrint("DocEd(0x%X,0x%X);", doc, 0))
		{
			Fs->put_doc = doc2 = DocNew;
			"$$WW,1$$";
			if (elems = Code2Sprite(doc))
			{
				DocDel(doc2);
				Fs->put_doc = old_put;
				QueueDel(head);
				Free(head);
				head = Sprite2SpriteQueue(elems);
				Free(elems);
				*_cur_elem_num = QueueCount(head); //TODO: Might want to improve this.
				break;
			}
			else
			{
				PopUpPrint("DocEd(0x%X,0x%X);", doc2, 0);
				DocDel(doc2);
				Fs->put_doc = old_put;
			}
		}
		else
			break;
	}
	DocDel(doc);
	if (_head)
		*_head = head;

	return res;
}

#define SPED_SEL_UNSEL_ALL		0
#define SPED_SEL				2
#define SPED_SEL_RECTS			3
#define SPED_UNSEL				4
#define SPED_UNSEL_RECTS		5
#define SPED_SHIFT_PTS			6
#define SPED_SHIFT_RECTS		7
#define SPED_SHIFT_SEL			8
#define SPED_TRANSFORM_SEL		9
#define SPED_SET_ORIGIN 		10
#define SPED_SHIFT_SUB_ORIGIN 	11
#define SPED_TEXT_ED			12
#define SPED_INS_CLIP 			13
#define SPED_MAIN_MENU			14
#define SPED_EXIT 				15

U0 GrInit3()
{
	DefineListLoad("ST_SPRITE_ED_MENU",

						"Select/Unselect All\0"
						" \0"
						"Select\0"
						"Select Rects\0"
						"Unselect\0"
						"Unselect Rects\0"
						"Shift Points\0"
						"Shift Rects\0"
						"Shift Selected\0"
						"Transform Selected\0"
						"Set Origin\0"
						"Insert Shift SubOrigin\0"
						"Edit as Text\0"
						"Insert Clip\0"
						"Main Menu\0");
}
GrInit3;

I64 PopUpSpriteEd(CSprite **_head, I64 *_cur_elem_num)
{
	U8		*st;
	CTask	*pu_task;
	I64		 res;
	CDoc	*doc = DocNew;

	DocPrint(doc,
				"$$PURPLE$$$$TX+CX,\"Sprite Edit Menu\"$$\n"
				"$$LK+PU+CX,\"Click for Help\",A=\"FI:::/Doc/SpriteEd.DD\"$$\n\n"
				"$$LTBLUE$$$$MU-UL,\"Select/Unselect All\",LE=SPED_SEL_UNSEL_ALL$$\n"
				"$$MU-UL,\"Select Elems\",LE=SPED_SEL$$\n"
				"$$MU-UL,\"Select Elems with Rects\",LE=SPED_SEL_RECTS$$\n"
				"$$MU-UL,\"Unsel Elems\",LE=SPED_UNSEL$$\n"
				"$$MU-UL,\"Unsel Elems with Rects\",LE=SPED_UNSEL_RECTS$$\n\n"
				"$$MU-UL,\"Shift Points\",LE=SPED_SHIFT_PTS$$\n"
				"$$MU-UL,\"Shift Points with Rects\",LE=SPED_SHIFT_RECTS$$\n"
				"$$MU-UL,\"Shift Selected Elems\",LE=SPED_SHIFT_SEL$$\n"
				"$$MU-UL,\"Transform Selected Elems\",LE=SPED_TRANSFORM_SEL$$\n\n"
				"$$MU-UL,\"Set Origin\",LE=SPED_SET_ORIGIN$$\n"
				"$$MU-UL,\"Insert Shift SubOrigin\",LE=SPED_SHIFT_SUB_ORIGIN$$\n\n"
				"$$MU-UL,\"Edit as Text\",LE=SPED_TEXT_ED$$\n"
				"$$MU-UL,\"Insert Clip Sprite's\",LE=SPED_INS_CLIP$$\n\n"
				"$$PURPLE$$$$MU-UL,\"+] Sprite Main Menu\",LE=SPED_MAIN_MENU$$$$LTBLUE$$\n"
				"$$MU-UL,\"Exit  Sprite\",LE=SPED_EXIT$$\n"
				"$$MU-UL,\"Abort Sprite\",LE=DOCM_CANCEL$$");

	st = MStrPrint("SpriteSideBarTask(0x%X,0x%X,0x%X);", Fs, _head, _cur_elem_num);
	PopUp(st, NULL, &pu_task);
	Free(st);
	res = PopUpMenu(doc);
	if (TaskValidate(pu_task))
	{
		*_head = SpriteSideBar2SpriteQueue(DocPut(pu_task), *_head, _cur_elem_num);
		Kill(pu_task);
	}
	DocDel(doc);

	return res;
}

#define SPEDT_SIMPLE_PT 	0
#define SPEDT_WIDTH_HEIGHT	1

#define SPEDF_SEL 			1

class CEdSprite
{
	CEdSprite	*next, *last;
	CSprite		*g;
	I32			 type, num, flags, xx, yy, zz;
	I32			*x, *y, *z, *w, *h;
};

CEdSprite *EdSpriteNew(I64 type, CSprite *tmpg)
{
	CEdSprite *res = CAlloc(sizeof(CEdSprite));

	res->g = tmpg;
	if (tmpg->type & SPF_SEL)
		res->flags |= SPEDF_SEL;
	res->type = type;

	return res;
}

U0 SpritePtQueueNew(U8 *elems, I64 x, I64 y, CEdSprite *head)
{
	I64			 i, num = 0;
	I32			*ptr;
	CD3I32		*p;
	CEdSprite	*tmpes;
	CSprite		*tmpg = elems - offset(CSprite.start);

	QueueInit(head);
	while (tmpg->type & SPG_TYPE_MASK)
	{
		switch (tmpg->type & SPG_TYPE_MASK)
		{
			case SPT_ELLIPSE:
			case SPT_POLYGON:
				tmpes = EdSpriteNew(SPEDT_WIDTH_HEIGHT, tmpg);
				tmpes->xx	= x;
				tmpes->yy	= y;
				tmpes->x	= &tmpg->pwha.x1;
				tmpes->y	= &tmpg->pwha.y1;
				tmpes->w	= &tmpg->pwha.width;
				tmpes->h	= &tmpg->pwha.height;
				tmpes->num	= num;
				QueueInsert(tmpes, head->last);
				goto pq_x1_y1;

			case SPT_RECT:
			case SPT_ROTATED_RECT:
			case SPT_LINE:
			case SPT_ARROW:
			case SPT_PLANAR_SYMMETRY:
				tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
				tmpes->xx	= x;
				tmpes->yy	= y;
				tmpes->x	= &tmpg->pp.x2;
				tmpes->y	= &tmpg->pp.y2;
				tmpes->num	= num;
				QueueInsert(tmpes, head->last);
			case SPT_TEXT:
			case SPT_TEXT_BOX:
			case SPT_TEXT_DIAMOND:
			case SPT_PT:
			case SPT_BITMAP:
			case SPT_FLOOD_FILL:
			case SPT_FLOOD_FILL_NOT:
			case SPT_CIRCLE:
pq_x1_y1:
				tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
				tmpes->xx	= x;
				tmpes->yy	= y;
				tmpes->x	= &tmpg->p.x1;
				tmpes->y	= &tmpg->p.y1;
				tmpes->num	= num;
				QueueInsert(tmpes, head->last);
				break;

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

			case SPT_POLYLINE:
				ptr = &tmpg->nu.u;
				for (i = 0; i < tmpg->nu.num; i++)
				{
					tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
					tmpes->xx	= x;
					tmpes->yy	= y;
					tmpes->x	= &ptr[i << 1];
					tmpes->y	= &ptr[i << 1 + 1];
					tmpes->num	= num;
					QueueInsert(tmpes, head->last);
				}
				break;

			case SPT_POLYPT:
				tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
				tmpes->xx	= x;
				tmpes->yy	= y;
				tmpes->x	= &tmpg->npu.x;
				tmpes->y	= &tmpg->npu.y;
				tmpes->num	= num;
				QueueInsert(tmpes, head->last);
				break;

			case SPT_BSPLINE2:
			case SPT_BSPLINE3:
			case SPT_BSPLINE2_CLOSED:
			case SPT_BSPLINE3_CLOSED:
				p = &tmpg->nu.u;
				for (i = 0; i < tmpg->nu.num; i++)
				{
					tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
					tmpes->xx	= x;
					tmpes->yy	= y;
					tmpes->x	= &p[i].x;
					tmpes->y	= &p[i].y;
					tmpes->z	= &p[i].z;
					tmpes->num	= num;
					QueueInsert(tmpes, head->last);
				}
				break;

			case SPT_MESH:
				break;

			case SPT_SHIFTABLE_MESH:
				tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
				tmpes->xx	= x;
				tmpes->yy	= y;
				tmpes->x	= &tmpg->pmu.x;
				tmpes->y	= &tmpg->pmu.y;
				tmpes->z	= &tmpg->pmu.z;
				tmpes->num	= num;
				QueueInsert(tmpes, head->last);
				break;
		}
		tmpg(U8 *) += SpriteElemSize(tmpg);
		num++;
	}
}

U0 SpriteCtrlPtsDraw(CDC *dc, CEdSprite *head)
{
	I64			 x, y;
	CEdSprite	*tmpes;

	Refresh;
	DCFill(dc);
	if (Blink(20))
	{
		tmpes = head->next;
		while (tmpes != head)
		{
			switch (tmpes->type)
			{
				case SPEDT_SIMPLE_PT:
					x = *tmpes->x + tmpes->xx;
					y = *tmpes->y + tmpes->yy;
					break;
				case SPEDT_WIDTH_HEIGHT:
					x = *tmpes->w + *tmpes->x + tmpes->xx;
					y = *tmpes->h + *tmpes->y + tmpes->yy;
					break;
			}
			if (tmpes->flags & SPEDF_SEL)
				dc->color = RED;
			else
				dc->color = BLACK;
			GrRect(dc, x - 2, y - 2, 4, 4);
			dc->color = WHITE;
			GrRect(dc, x - 1, y - 1, 2, 2);
			tmpes = tmpes->next;
		}
	}
}

U0 SpriteCtrlPtsMove(CEdSprite *head, I64 dx, I64 dy)
{
	CEdSprite *tmpes;

	tmpes = head->next;
	while (tmpes != head)
	{
		if (tmpes->flags & SPEDF_SEL)
			switch (tmpes->type)
			{
				case SPEDT_SIMPLE_PT:
					if (tmpes->x)
						*tmpes->x += dx;
					if (tmpes->y)
						*tmpes->y += dy;
					break;
				case SPEDT_WIDTH_HEIGHT:
					if (tmpes->w)
						*tmpes->w += dx;
					if (tmpes->h)
						*tmpes->h += dy;
					break;
			}
		tmpes = tmpes->next;
	}
}

Bool SpriteSelUnselShiftPts(U8 *elems, I64 x, I64 y, I64 *_cur_elem_num, I64 mode)
{
	I64			 message_code, arg1, arg2, xx, yy, xx2, yy2, dd, best_dd, cur_elem_num;
	Bool		 res = TRUE;
	CDC			*dc = DCAlias;
	CEdSprite	 head, *tmpes, *best_es;

	SpritePtQueueNew(elems, x, y, &head);
	cur_elem_num = 0;
	if (head.next != &head)
	{
		while (TRUE)
		{
			SpriteCtrlPtsDraw(dc, &head); //has Refresh
			switch (message_code = MessageScan(&arg1, &arg2, 
											   1 << MESSAGE_MS_R_UP | 1 << MESSAGE_MS_L_DOWN | 1 << MESSAGE_KEY_DOWN))
			{
				case MESSAGE_MS_L_DOWN:
					switch (mode)
					{
						case SPED_SEL:
						case SPED_UNSEL:
						case SPED_SHIFT_PTS:
							xx = arg1;
							yy = arg2;
							best_dd = I64_MAX;
							tmpes = head.next;
							while (tmpes != &head)
							{
								switch (tmpes->type)
								{
									case SPEDT_SIMPLE_PT:
										dd = SqrI64(*tmpes->x + tmpes->xx - xx) + SqrI64(*tmpes->y + tmpes->yy - yy);
										break;
									case SPEDT_WIDTH_HEIGHT:
										dd = SqrI64(*tmpes->x + *tmpes->w + tmpes->xx - xx) +
											 SqrI64(*tmpes->y + *tmpes->h + tmpes->yy - yy);
										break;
								}
								if (dd < best_dd)
								{
									best_dd = dd;
									best_es = tmpes;
								}
								tmpes = tmpes->next;
							}
							cur_elem_num = best_es->num;
							if (mode != SPED_UNSEL)
							{
								best_es->flags |= SPEDF_SEL;
								best_es->g->type |= SPF_SEL;
							}
							else
							{
								best_es->flags &= ~SPEDF_SEL;
								best_es->g->type &= ~SPF_SEL;
							}
							break;
						start:
							xx2 = xx = arg1;
							yy2 = yy = arg2;
							while (TRUE)
							{
								SpriteCtrlPtsDraw(dc, &head);
								dc->color = ROPF_DITHER + WHITE << 16 + RED;
								GrBorder(dc, xx, yy, xx2, yy2);
								if (message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_MS_MOVE | 1 << MESSAGE_MS_L_UP))
								{
									if (message_code == MESSAGE_MS_MOVE)
									{
										xx2 = arg1;
										yy2 = arg2;
									}
									else
										break;
								}
							}
							if (xx2 < xx)
								SwapI64(&xx, &xx2);
							if (yy2 < yy)
								SwapI64(&yy, &yy2);
							tmpes = head.next;
							while (tmpes != &head)
							{
								switch (tmpes->type)
								{
									case SPEDT_SIMPLE_PT:
										if (xx <= *tmpes->x + tmpes->xx <= xx2 && yy <= *tmpes->y + tmpes->yy <= yy2)
										{
											if (mode != SPED_UNSEL_RECTS)
											{
												tmpes->flags |= SPEDF_SEL;
												tmpes->g->type |= SPF_SEL;
											}
											else
											{
												tmpes->flags &= ~SPEDF_SEL;
												tmpes->g->type &= ~SPF_SEL;
											}
										}
										break;
									case SPEDT_WIDTH_HEIGHT:
										if (xx <= *tmpes->x + *tmpes->w + tmpes->xx <= xx2 &&
											yy <= *tmpes->y + *tmpes->h + tmpes->yy <= yy2)
										{
											if (mode != SPED_UNSEL_RECTS)
											{
												tmpes->flags |= SPEDF_SEL;
												tmpes->g->type |= SPF_SEL;
											}
											else
											{
												tmpes->flags &= ~SPEDF_SEL;
												tmpes->g->type &= ~SPF_SEL;
											}
										}
										break;
								}
								tmpes = tmpes->next;
							}
							case SPED_SEL_RECTS:
							case SPED_UNSEL_RECTS:
								break;
							case SPED_SHIFT_RECTS:
								do
								{
									SpriteCtrlPtsDraw(dc, &head);
									message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN | 1 << MESSAGE_MS_L_DOWN);
									if (message_code == MESSAGE_KEY_DOWN)
										goto gs_key;
								}
								while (message_code != MESSAGE_MS_L_DOWN);

								xx = arg1;
								yy = arg2;
								break;
						end:
					}
					switch (mode)
					{
						case SPED_SHIFT_PTS:
						case SPED_SHIFT_RECTS:
							do
							{
								SpriteCtrlPtsDraw(dc, &head);
								if (message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_MS_MOVE | 1 << MESSAGE_MS_L_UP))
								{
									SpriteCtrlPtsMove(&head, arg1 - xx, arg2 - yy);
									xx = arg1;
									yy = arg2;
								}
							}
							while (message_code != MESSAGE_MS_L_UP);

							tmpes = head.next;
							while (tmpes != &head)
							{
								tmpes->flags &= ~SPEDF_SEL;
								tmpes->g->type &= ~SPF_SEL;
								tmpes = tmpes->next;
							}
							break;
					}
					break;
				case MESSAGE_KEY_DOWN:
gs_key:
					switch (arg1.u8[0])
					{
						case CH_SHIFT_ESC:
							res = FALSE;
						case CH_ESC:
							MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_UP);
							goto gs_done;

						case 'p':
						case 'P':
							mode &= ~1;
							break;

						case 'r':
						case 'R':
							mode |= 1;
							break;
					}
					break;
				case MESSAGE_MS_R_UP:
					goto gs_done;
			}
		}
gs_done:
		QueueDel(&head, TRUE);
	}
	DCFill(dc);
	DCDel(dc);
	if (_cur_elem_num && res)
		*_cur_elem_num = cur_elem_num;

	return res;
}

I64 SpriteEd(CDoc *doc, CDocEntry *doc_ce, I64 x, I64 y, CSprite **_head, I64 *_cur_elem_num)
{
	CDocEntry	*doc_e2;
	CDocBin		*tmpb;
	Bool		 unlock;
	I64			 i, r[16], message_code, arg1, arg2, xx, yy, old_de_flags;
	CSprite		*head2, *next, *last, *tmpg, *insert_pt;

	old_de_flags = doc_ce->de_flags;
	tmpb = doc_ce->bin_data;
	DocUnlock(doc);
	SpriteQueueSelAll(*_head, FALSE);
	do
	{
		if (winmgr.fps < 10)
			doc_ce->de_flags |= DOCEF_DONT_DRAW;
		StrCopy(Fs->task_title, "Sprite Edit Menu");
		i=PopUpSpriteEd(_head, _cur_elem_num);
		SpriteEdUpdate(doc, doc_ce, *_head);
		if (0 <= i < SPED_EXIT)
		{
			StrCopy(Fs->task_title, DefineSub(i, "ST_SPRITE_ED_MENU"));
			switch (i)
			{
				case SPED_SEL_UNSEL_ALL:
					if (!SpriteQueueSelCount(*_head))
						SpriteQueueSelAll(*_head);
					else
						SpriteQueueSelAll(*_head, FALSE);
					break;

				case SPED_SET_ORIGIN:
					SpriteQueueSelAll(*_head);
					doc_ce->de_flags = old_de_flags;
					MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP);
					SpriteSetOrigin(*_head, x - arg1, y - arg2, 0);
					SpriteEdUpdate(doc, doc_ce, *_head);
					SpriteQueueSelAll(*_head, FALSE);
					break;

				case SPED_SHIFT_SEL:
					if (!SpriteQueueSelCount(*_head))
						SpriteQueueSelAll(*_head);
					doc_ce->de_flags = old_de_flags;
					MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_DOWN);
					xx = arg1;
					yy = arg2;
					do
					{
						message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP + 1 << MESSAGE_MS_MOVE);
						SpriteSetOrigin(*_head, arg1 - xx, arg2 - yy, 0);
						xx = arg1;
						yy = arg2;
						SpriteEdUpdate(doc, doc_ce, *_head);
					}
					while (message_code != MESSAGE_MS_L_UP);

					if (!SpriteQueueSelCount(*_head, FALSE))
						SpriteQueueSelAll(*_head, FALSE);
					break;

				case SPED_SEL:
				case SPED_SEL_RECTS:
				case SPED_UNSEL:
				case SPED_UNSEL_RECTS:
				case SPED_SHIFT_PTS:
				case SPED_SHIFT_RECTS:
					RegOneTimePopUp(ARf_CSPRITE_PTS_RECTANGLES, 
								"You can switch between points\n"
								"and rectangles with '$$GREEN$$p$$FG$$' and '$$GREEN$$r$$FG$$'.\n"
								"Press '$$GREEN$$r$$FG$$' after one rectangle\n"
								"to OR another rectangle.\n");
					doc_ce->de_flags = old_de_flags;
					if (SpriteSelUnselShiftPts(tmpb->data, x, y, _cur_elem_num, i))
					{
						QueueDel(*_head);
						Free(*_head);
						*_head = Sprite2SpriteQueue(tmpb->data);
					}
					else
						SpriteEdUpdate(doc, doc_ce, *_head);
					break;

				case SPED_TRANSFORM_SEL:
					if (!SpriteQueueSelCount(*_head))
						SpriteQueueSelAll(*_head);
					if (PopUpTransform(r))
					{
						SpriteTransformQueue(*_head, r);
						SpriteEdUpdate(doc, doc_ce, *_head);
					}
					if (!SpriteQueueSelCount(*_head, FALSE))
						SpriteQueueSelAll(*_head, FALSE);
					break;

				case SPED_SHIFT_SUB_ORIGIN:
					doc_ce->de_flags = old_de_flags;
					insert_pt = SpriteSetSettings(, *_head, *_cur_elem_num);
					tmpg = CAlloc(SpriteElemQueuedBaseSize(SPT_SHIFT));
					tmpg->type = SPT_SHIFT;
					tmpg->p.x1 = 0;
					tmpg->p.y1 = 0;
					QueueInsert(tmpg, insert_pt->last);
					MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_DOWN);
					xx = arg1;
					yy = arg2;
					do
					{
						message_code=MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP + 1 << MESSAGE_MS_MOVE);
						tmpg->p.x1 = arg1 - xx;
						tmpg->p.y1 = arg2 - yy;
						SpriteEdUpdate(doc, doc_ce, *_head);
					}
					while (message_code != MESSAGE_MS_L_UP);

					*_cur_elem_num += 1;
					break;

				case SPED_INS_CLIP:
					RegOneTimePopUp(ARf_CSPRITE_INS_CLIP, 
								"You will probably want to shift around\n"
								"the location of element groups.  Use\n"
								"'Insert shift sub-origin' after picking the\n"
								"element to insert before.  Or,\n"
								"use 'shift points'.\n");
					insert_pt = SpriteSetSettings(, *_head, *_cur_elem_num);
					unlock = DocLock(sys_clip_doc);
					doc_e2 = sys_clip_doc->head.next;
					while (doc_e2 != sys_clip_doc)
					{
						if (doc_e2->type_u8 == DOCT_SPRITE)
						{
							head2 = Sprite2SpriteQueue(doc_e2->bin_data->data);
							if (head2->next != head2)
							{
								tmpg = head2->next;
								while (tmpg != head2)
								{
									*_cur_elem_num += 1;
									tmpg = tmpg->next;
								}
								next = head2->next;
								last = head2->last;
								insert_pt->last->next = next;
								next->last = insert_pt->last;
								insert_pt->last = last;
								last->next = insert_pt;
							}
							Free(head2);
						}
						doc_e2 = doc_e2->next;
					}
					if (unlock)
						DocUnlock(sys_clip_doc);
					SpriteEdUpdate(doc, doc_ce, *_head);
					break;

				case SPED_TEXT_ED:
					if (SpriteEdText(_head, _cur_elem_num))
						SpriteEdUpdate(doc, doc_ce, *_head);
					break;
			}
		}
	}
	while (i != DOCM_CANCEL && i != SPED_EXIT && i != SPED_MAIN_MENU);

	doc_ce->de_flags = old_de_flags;

	switch (i)
	{
		case DOCM_CANCEL:
			return SPE_ABORT;
		case SPED_EXIT:
			return SPE_EXIT;
		case SPED_MAIN_MENU:
			return SPE_CONT;
	}
}

#help_index "Graphics/Sprite;Sprites;Graphics/Math/3D Transformation"
public U8 *SpriteTransform(U8 *elems, I64 *r)
{//Rotate Sprite using 4x4 matrix. Uses $LK,"fixed-point",A="FI:::/Demo/Lectures/FixedPoint.CC"$.
	U8		*res;
	CSprite	*head = Sprite2SpriteQueue(elems);

	SpriteQueueSelAll(head);
	SpriteTransformQueue(head, r);
	res = SpriteQueue2Sprite(head);
	QueueDel(head);
	Free(head);

	return res;
}
