#help_index "Graphics/Mesh"
#define MESH_WORKSPACE_SIZE 4000

#define VF_SEL				1
#define VF_COPIED 			2
#define VF_IGNORE 			4

class CMeshEdVertex
{
	CMeshEdVertex	*next, *last, *copy;

	U0				 start;
	CD3I32			 p; //World coordinates of the point.
	U0				 end;

	CD3I32			 p0,
					 pt; //Transformed coordinates.	(Screen)
	I32				 num, flags;
};

#define TF_SEL		1
#define TF_COPIED 	2

class CMeshEdTri
{
	CMeshEdTri		*next, *last;

	U0				 start;
	CMeshTri		 mt;
	U0				 end;

	I32				 cpu_num, flags; //Draw different tris with different cores.
	CMeshEdVertex	*t[3];
};

class CMeshFrame
{
	I64				 mouse_z, thickness; //Mouse Z-coordinate
	I64				 ed_mode, cx, cy;
	CColorROPU32	 cur_color;
	Bool			 grid_on, flip_y, sel_rect, vertex_on, closed, pad[3];
	I64				 mp_not_done_flags; //Used for multiprocessing signaling.
	F64				 view_scale;
	CDC				*dc;
	I32				*depth_buf;
	I64				*w2s, *s2w; //Screen-to-world and world-to-screen transform matrices.
	I64				 vertex_count, tri_count; //Set by MeshSize
	CMeshEdVertex	 vertex_head, *cur_vertex, *chain_pred;
	CMeshEdTri		 tri_head, *cur_tri;
	I64				 x1, y1, x2, y2, cur_snap;
};

CMeshEdVertex *MeshVertexNew(CMeshFrame *e, I64 x, I64 y, I64 z)
{
	CMeshEdVertex *tmpv = CAlloc(sizeof(CMeshEdVertex));

	tmpv->p.x = x;
	tmpv->p.y = y;
	tmpv->p.z = z;
	QueueInsert(tmpv, e->vertex_head.last);

	return tmpv;
}

CMeshEdTri *MeshTriNew(CMeshFrame *e, CColorROPU32 color, CMeshEdVertex *v1, CMeshEdVertex *v2, CMeshEdVertex *v3)
{
	static I64  cpu_num = 0;
	CMeshEdTri *tmpt = CAlloc(sizeof(CMeshEdTri));

	tmpt->cpu_num	= cpu_num++ % mp_count;
	tmpt->mt.color	= color;
	tmpt->t[0]		= v1;
	tmpt->t[1]		= v2;
	tmpt->t[2]		= v3;
	QueueInsert(tmpt, e->tri_head.last);

	return tmpt;
}

CMeshEdVertex *MeshVertexFindScrPt(CMeshFrame *e, I64 x, I64 y)
{//Screen coordinates
	CMeshEdVertex	*res = NULL, *tmpv = e->vertex_head.next;
	I64				 dd, dz, best_dd = I64_MAX, best_dz = I64_MAX;

	while (tmpv != &e->vertex_head)
	{
		if (!(tmpv->flags & VF_IGNORE))
		{
			dd = SqrI64(x - tmpv->pt.x) + SqrI64(y - tmpv->pt.y);
			dz = AbsI64(e->mouse_z - tmpv->p.z);
			if (dd < best_dd || dd == best_dd && dz < best_dz)
			{
				res = tmpv;
				best_dd = dd;
				best_dz = dz;
			}
		}
		tmpv = tmpv->next;
	}

	return res;
}

CMeshEdVertex *MeshVertexFindNum(CMeshFrame *haystack_e, I64 needle_num)
{
	CMeshEdVertex *tmpv = haystack_e->vertex_head.next;

	while (tmpv != &haystack_e->vertex_head)
	{
		if (tmpv->num == needle_num)
			return tmpv;
		tmpv = tmpv->next;
	}

	return NULL;
}

U0 MeshTriDel(CMeshFrame *e, CMeshEdTri *tmpt)
{
	if (tmpt)
	{
		if (tmpt == e->cur_tri)
			e->cur_tri = NULL;
		QueueRemove(tmpt);
		Free(tmpt);
	}
}

U0 MeshVertexDel(CMeshFrame *e, CMeshEdVertex *tmpv)
{
	I64			 i;
	CMeshEdTri	*tmpt, *tmpt1;

	if (tmpv)
	{
		tmpt = e->tri_head.next;
		while (tmpt != &e->tri_head)
		{
			tmpt1 = tmpt->next;
			for (i = 0; i < 3; i++)
				if (tmpt->t[i] == tmpv)
					break;
			if (i < 3)
				MeshTriDel(e, tmpt);
			tmpt = tmpt1;
		}
		if (tmpv == e->cur_vertex)
			e->cur_vertex = NULL;
		if (tmpv == e->chain_pred)
			e->chain_pred = NULL;
		QueueRemove(tmpv);
		Free(tmpv);
	}
}

U0 MeshFence(CMeshFrame *e)
{
	CMeshEdVertex *tmpv, *tmpv1, *tmpv_last = NULL, *tmpv1_last = NULL,
				  *start = e->chain_pred->next, *end = e->vertex_head.last;

	tmpv = start;
	while (TRUE)
	{
		tmpv1 = MeshVertexNew(e, tmpv->p.x, tmpv->p.y, tmpv->p.z + e->thickness);
		if (tmpv_last)
		{
			MeshTriNew(e, e->cur_color, tmpv_last, tmpv, tmpv1);
			MeshTriNew(e, e->cur_color, tmpv1, tmpv1_last, tmpv_last);
		}
		tmpv_last = tmpv;
		tmpv1_last = tmpv1;
		if (tmpv == end)
			break;
		tmpv = tmpv->next;
	}
	if (e->closed && tmpv_last)
	{
		MeshTriNew(e, e->cur_color, tmpv_last, start, end->next);
		MeshTriNew(e, e->cur_color, end->next, tmpv1_last, tmpv_last);
	}
}

U0 MeshPolygon(CMeshFrame *e, CMeshEdVertex *start, CMeshEdVertex *end, Bool rev)
{
	CMeshEdVertex *tmpv, *tmpv1;

	if (start != end)
	{
		tmpv = start;
		tmpv1 = tmpv->next;
		while (tmpv1 != end)
		{
			if (rev)
				MeshTriNew(e, e->cur_color, tmpv1, tmpv, end);
			else
				MeshTriNew(e, e->cur_color, tmpv, tmpv1, end);
			tmpv = tmpv->next;
			tmpv1 = tmpv1->next;
		}
	}
}

U0 MeshPrism(CMeshFrame *e)
{
	CMeshEdVertex *start = e->chain_pred->next, *end = e->vertex_head.last;

	MeshFence(e);
	MeshPolygon(e, start, end, FALSE);
	MeshPolygon(e, end->next, e->vertex_head.last, TRUE);
}

U0 MeshVertexSelAll(CMeshFrame *e, Bool val)
{
	CMeshEdVertex *tmpv = e->vertex_head.next;

	while (tmpv != &e->vertex_head)
	{
		if (val)
			tmpv->flags |= VF_SEL;
		else
			tmpv->flags &= ~VF_SEL;
		tmpv = tmpv->next;
	}
}

U0 MeshTriSelAll(CMeshFrame *e, Bool val)
{
	CMeshEdTri *tmpt = e->tri_head.next;

	while (tmpt != &e->tri_head)
	{
		if (val)
			tmpt->flags |= TF_SEL;
		else
			tmpt->flags &= ~TF_SEL;
		tmpt = tmpt->next;
	}
}

U0 MeshVertexIgnoreSet(CMeshFrame *e, Bool val)
{
	CMeshEdVertex *tmpv = e->vertex_head.next;

	while (tmpv != &e->vertex_head)
	{
		tmpv->flags &= ~VF_IGNORE;
		if (tmpv->flags & VF_SEL && val)
			tmpv->flags |= VF_IGNORE;
		tmpv = tmpv->next;
	}
}

U0 MeshP0Capture(CMeshFrame *e)
{
	CMeshEdVertex *tmpv = e->vertex_head.next;

	while (tmpv != &e->vertex_head)
	{
		MemCopy(&tmpv->p0, &tmpv->p, sizeof(CD3I32));
		tmpv = tmpv->next;
	}
}

U0 MeshP0Offset(CMeshFrame *e, I64 dx, I64 dy, I64 dz)
{
	CMeshEdVertex *tmpv = e->vertex_head.next;

	while (tmpv != &e->vertex_head)
	{
		if (tmpv->flags & VF_SEL)
		{
			tmpv->p.x = tmpv->p0.x + dx;
			tmpv->p.y = tmpv->p0.y + dy;
			tmpv->p.z = tmpv->p0.z + dz;
		}
		tmpv = tmpv->next;
	}
}

#define SEL_MESH_EQU		0
#define SEL_MESH_OR 		1
#define SEL_MESH_AND		2

U0 MeshVertexSelRect(CMeshFrame *e, I64 sel_mode, I64 x1, I64 x2, I64 y1, I64 y2)
{
	CMeshEdVertex *tmpv = e->vertex_head.next;

	if (x1 > x2)
		SwapI64(&x1, &x2);
	if (y1 > y2)
		SwapI64(&y1, &y2);
	while (tmpv != &e->vertex_head)
	{
		if (x1 <= tmpv->pt.x <= x2 && y1 <= tmpv->pt.y <= y2)
		{
			if (sel_mode == SEL_MESH_AND)
				tmpv->flags &= ~VF_SEL;
			else
				tmpv->flags |= VF_SEL;
		}
		else if (sel_mode == SEL_MESH_EQU)
			tmpv->flags &= ~VF_SEL;
		tmpv = tmpv->next;
	}
}

U0 MeshTriSelRect(CMeshFrame *e, I64 sel_mode, I64 x1, I64 x2, I64 y1, I64 y2)
{
	CMeshEdTri *tmpt = e->tri_head.next;

	if (x1 > x2)
		SwapI64(&x1, &x2);
	if (y1 > y2)
		SwapI64(&y1, &y2);
	while (tmpt != &e->tri_head)
	{
		if (x1 <= tmpt->t[0]->pt.x <= x2 &&
			y1 <= tmpt->t[0]->pt.y <= y2 &&
			x1 <= tmpt->t[1]->pt.x <= x2 &&
			y1 <= tmpt->t[1]->pt.y <= y2 &&
			x1 <= tmpt->t[2]->pt.x <= x2 &&
			y1 <= tmpt->t[2]->pt.y <= y2)
		{
			if (sel_mode == SEL_MESH_AND)
				tmpt->flags &= ~TF_SEL;
			else
				tmpt->flags |= TF_SEL;
		}
		else
		{
			if (sel_mode == SEL_MESH_EQU)
				tmpt->flags &= ~TF_SEL;
			else if (sel_mode == SEL_MESH_AND)
			{
				if (	x1 <= tmpt->t[0]->pt.x <= x2 &&
						y1 <= tmpt->t[0]->pt.y <= y2 ||
						x1 <= tmpt->t[1]->pt.x <= x2 &&
						y1 <= tmpt->t[1]->pt.y <= y2 ||
						x1 <= tmpt->t[2]->pt.x <= x2 &&
						y1 <= tmpt->t[2]->pt.y <= y2)
					tmpt->flags &= ~TF_SEL;
			}
		}
		tmpt = tmpt->next;
	}
}

I64 MeshSelCount(CMeshFrame *e)
{
	I64				 res = 0;
	CMeshEdVertex	*tmpv = e->vertex_head.next;
	CMeshEdTri		*tmpt = e->tri_head.next;

	while (tmpv != &e->vertex_head)
	{
		if (tmpv->flags & VF_SEL)
			res++;
		tmpv = tmpv->next;
	}
	while (tmpt != &e->tri_head)
	{
		if (tmpt->flags & TF_SEL)
			res++;
		tmpt = tmpt->next;
	}
	return res;
}

U0 MeshSwapAxes(CMeshFrame *e, I64 o1, I64 o2)
{
	Bool			 unsel;
	CMeshEdVertex	*tmpv = e->vertex_head.next;

	if (!MeshSelCount(e))
	{
		MeshVertexSelAll(e, TRUE);
		unsel = TRUE;
	}
	else
		unsel = FALSE;
	while (tmpv != &e->vertex_head)
	{
		if (tmpv->flags & VF_SEL)
			SwapU32((&tmpv->p)(U8 *) + o1, (&tmpv->p)(U8 *) + o2);
		tmpv = tmpv->next;
	}
	if (unsel)
		MeshVertexSelAll(e, FALSE);
}

U0 MeshInvertAxis(CMeshFrame *e, I64 o)
{
	Bool			 unsel;
	CMeshEdVertex	*tmpv = e->vertex_head.next;

	if (!MeshSelCount(e))
	{
		MeshVertexSelAll(e, TRUE);
		unsel = TRUE;
	}
	else
		unsel = FALSE;
	while (tmpv != &e->vertex_head)
	{
		if (tmpv->flags & VF_SEL)
			*((&tmpv->p)(U8 *) + o)(I32 *) = -*((&tmpv->p)(U8 *) + o)(I32 *);
		tmpv = tmpv->next;
	}
	if (unsel)
		MeshVertexSelAll(e, FALSE);
}

U0 MeshTransformSel(CMeshFrame *e)
{
	Bool			 unsel;
	I64				 r[16], x, y, z;
	CMeshEdVertex	*tmpv = e->vertex_head.next;

	if (PopUpTransform(r))
	{
		if (!MeshSelCount(e))
		{
			MeshVertexSelAll(e, TRUE);
			unsel = TRUE;
		}
		else
			unsel = FALSE;
		while (tmpv != &e->vertex_head)
		{
			if (tmpv->flags & VF_SEL)
			{
				x = tmpv->p.x;
				y = tmpv->p.y;
				z = tmpv->p.z;
				Mat4x4MulXYZ(r, &x, &y, &z);
				tmpv->p.x = x;
				tmpv->p.y = y;
				tmpv->p.z = z;
			}
			tmpv = tmpv->next;
		}
		if (unsel)
			MeshVertexSelAll(e, FALSE);
	}
}

U0 MeshColorTris(CMeshFrame *e)
{
	Bool		 unsel;
	CMeshEdTri	*tmpt = e->tri_head.next;

	if (!MeshSelCount(e))
	{
		MeshTriSelAll(e, TRUE);
		unsel = TRUE;
	}
	else
		unsel = FALSE;
	while (tmpt != &e->tri_head)
	{
		if (tmpt->flags & TF_SEL)
			tmpt->mt.color = e->cur_color;
		tmpt = tmpt->next;
	}
	if (unsel)
		MeshTriSelAll(e, FALSE);
}

U0 MeshRevTris(CMeshFrame *e)
{
	Bool		 unsel;
	CMeshEdTri	*tmpt = e->tri_head.next;

	if (!MeshSelCount(e))
	{
		MeshTriSelAll(e, TRUE);
		unsel = TRUE;
	}
	else
		unsel = FALSE;
	while (tmpt != &e->tri_head)
	{
		if (tmpt->flags & TF_SEL)
			SwapI64(&tmpt->t[1], &tmpt->t[2]);
		tmpt = tmpt->next;
	}
	if (unsel)
		MeshTriSelAll(e, FALSE);
}

U0 MeshRecalcCxCy(CTask *task, CMeshFrame *e)
{
	e->cx = RoundI64(task->pix_width  / 2 - task->horz_scroll.pos, e->cur_snap);
	e->cy = RoundI64(task->pix_height / 2 - task->vert_scroll.pos, e->cur_snap);
}

U0 MeshCurSnap(CMeshFrame *e)
{
	I64 x1, y1, z1, x2, y2, z2;

	if (e->w2s)
	{
		x1 = e->cur_snap << 16;
		y1 = 0;
		z1 = 0;
		Mat4x4MulXYZ(e->w2s, &x1, &y1, &z1);
		x2 = 0;
		y2 = e->cur_snap << 16;
		z2 = 0;
		Mat4x4MulXYZ(e->w2s, &x2, &y2, &z2);
		mouse_grid.x = Max(1, MaxI64(x1, x2) >> 16);
		mouse_grid.y = Max(1, MaxI64(y1, y2) >> 16);
		mouse_grid.z = Min(mouse_grid.x, mouse_grid.y);
	}
}

U0 MeshScaleZoom(CMeshFrame *e, F64 scale)
{
	CTask	*task = Fs;
	I64		 x = mouse.pos.x - task->pix_left - task->scroll_x - task->pix_width  / 2, 
			 y = mouse.pos.y - task->pix_top  - task->scroll_y - task->pix_height / 2;

	task->horz_scroll.pos *= scale;
	task->vert_scroll.pos *= scale;
	task->horz_scroll.pos += scale * x - x;
	task->vert_scroll.pos += scale * y - y;
	e->view_scale *= scale;
	MeshRecalcCxCy(task, e);
	MeshCurSnap(e);
}

U0 MPDrawIt(CMeshFrame *e)
{//Multiprocessing draw it, called by each core.

	//Makes a copy of e->dc so we can change dc->color member and stuff.
	CDC			*dc = DCAlias(e->dc, e->dc->win_task);
	CMeshEdTri	*tmpt = e->tri_head.next;
	I64			 i, *old_r = dc->r;

	//$LK,"DCAlias",A="MN:DCAlias"$() allocs a new identity rotation matrix.
	//We want e->dc's rotation matrix.
	dc->r			= e->dc->r;
	dc->depth_buf	= e->depth_buf;
	MemCopy(&dc->ls, &e->dc->ls, sizeof(CD3I32));

	//... and translation (shift) vals.
	dc->x = e->dc->x;
	dc->y = e->dc->y;
	dc->z = e->dc->z;
	dc->flags |= DCF_TRANSFORMATION;

	if (e->grid_on)
//Draw grid with different cores.
		for (i = -500 + 25 * Gs->num; i <= 500; i += 25 * mp_count)
		{
			if (i)
			{
				dc->color = DKGRAY;
				GrLine3(dc, i, -500, 0, i, 500, 0);
				dc->color = LTGRAY;
				GrLine3(dc, -500, i, 0, 500, i, 0);
			}
		}
	if (!Gs->num)
	{
		dc->color = RED;					//Y-Axis red
		GrLine3(dc, 0, 0, 0, 0, 500, 0);
		dc->color = ROPF_DITHER + RED;		//Y-Axis red
		GrLine3(dc, 0, -500, 0, 0, 0, 0);

		dc->color = YELLOW;					//X-Axis yellow
		GrLine3(dc, 0, 0, 0, 500, 0, 0);
		dc->color = ROPF_DITHER + YELLOW; 	//X-Axis yellow
		GrLine3(dc, -500, 0, 0, 0, 0, 0);

		dc->color = GREEN;					//Z-Axis green
		GrLine3(dc, 0, 0, 0, 0, 0, 500);
		dc->color = ROPF_DITHER + GREEN;		//Z-Axis green
		GrLine3(dc, 0, 0, -500, 0, 0, 0);
	}

	while (tmpt != &e->tri_head)
	{
		if (tmpt->cpu_num == Gs->num)
		{
			if (tmpt->flags & TF_SEL)
			{
				if (Blink)
					dc->color = ROPF_DITHER + WHITE << 16 + RED;
				else
					dc->color = ROPF_DITHER + RED << 16 + WHITE;
				GrFillTri0(dc, &tmpt->t[0]->pt, &tmpt->t[1]->pt, &tmpt->t[2]->pt);
			}
			else
			{
				(*dc->lighting)(dc, &tmpt->t[0]->pt, &tmpt->t[1]->pt, 
									&tmpt->t[2]->pt, tmpt->mt.color);
				GrFillTri0(dc, &tmpt->t[0]->pt, &tmpt->t[1]->pt, &tmpt->t[2]->pt);
			}
		}
		tmpt = tmpt->next;
	}
	dc->r = old_r;

	//e->dc's depth buf was copied but we don't want it freed during $LK,"DCDel",A="MN:DCDel"$().
	dc->depth_buf = NULL;

	DCDel(dc);
	LBtr(&e->mp_not_done_flags, Gs->num);
}

I64 *MeshW2S(CMeshFrame *e, CTask *task)
{//World to screen coordinate transform matrix.
	CCtrl		*c = CtrlFindUnique(task, CTRLT_VIEWING_ANGLES);
	CViewAngles	*s = c->state;
	I64			*r = Mat4x4IdentNew(task);

	Mat4x4Scale(r, e->view_scale);
	Mat4x4RotZ(r, s->az);
	Mat4x4RotY(r, s->ay);
	if (e->flip_y)
		Mat4x4RotX(r, s->ax);
	else
		Mat4x4RotX(r, s->ax + ã);

	return r;
}

I64 *MeshS2W(CMeshFrame *e, CTask *task)
{//Screen to world coordinate transform matrix.
	CCtrl		*c = CtrlFindUnique(task, CTRLT_VIEWING_ANGLES);
	CViewAngles	*s = c->state;
	I64			*r = Mat4x4IdentNew(task);

	if (e->flip_y)
		Mat4x4RotX(r, -s->ax);
	else
		Mat4x4RotX(r, -(s->ax + ã));
	Mat4x4RotY(r, -s->ay);
	Mat4x4RotZ(r, -s->az);
	Mat4x4Scale(r, 1 / e->view_scale);

	return r;
}

I64 *MeshSetW2S(CMeshFrame *e, CTask *task)
{
	Free(e->w2s);
	e->w2s = MeshW2S(e, task);
	Free(e->s2w);
	e->s2w = MeshS2W(e, task);
//returned matrix is assigned to dc->r and will be freed by $LK,"DCDel",A="MN:DCDel"$().

	return Mat4x4New(e->w2s, task);
}

U0 MeshCursorW(CMeshFrame *e, CTask *task, I64 *_x, I64 *_y, I64 *_z)
{
	I64	x_shadow, y_shadow, z_shadow, 
		xc = mouse.pos.x - task->pix_left - task->scroll_x - e->cx, 
		yc = mouse.pos.y - task->pix_top  - task->scroll_y - e->cy,
		zc = 0, x = 0, y = 0, z = e->mouse_z, i, x2, y2, z2;

	Mat4x4MulXYZ(e->w2s, &x, &y, &z); //screen of Z vect

	//Converges onto a solution for zc, an unknown.
	for (i = 0; i < 128; i++)
	{
		x_shadow = xc - x; //Shadow of mouse cursor on xy plane
		y_shadow = yc - y;
		z_shadow = zc - z;
		Mat4x4MulXYZ(e->s2w, &x_shadow, &y_shadow, &z_shadow);
		x2 = 0;
		y2 = 0;
		z2 = -z_shadow;
		Mat4x4MulXYZ(e->w2s, &x2, &y2, &z2);
		zc += Round(Sqrt(x2 * x2 + y2 * y2 + z2 * z2)) * SignI64(z2);
	}

	x = xc - x;
	y = yc - y;
	z = zc - z;
	Mat4x4MulXYZ(e->s2w, &x, &y, &z);
	x = RoundI64(x, e->cur_snap);
	y = RoundI64(y, e->cur_snap);
	z = RoundI64(e->mouse_z, e->cur_snap);
	*_x = x;
	*_y = y;
	*_z = z;
}

CMeshEdVertex 	sys_clip_vertex_head;
CMeshEdTri		sys_clip_tri_head;

U0 MeshClipInit()
{
	QueueInit(&sys_clip_vertex_head);
	QueueInit(&sys_clip_tri_head);
}

U0 MeshClipReset()
{
	QueueDel(&sys_clip_vertex_head, TRUE);
	QueueDel(&sys_clip_tri_head, TRUE);
	MeshClipInit;
}

U0 MeshClipCopy(CMeshFrame *e)
{
	CMeshEdVertex	*tmpv = e->vertex_head.next, *tmpv2;
	CMeshEdTri		*tmpt = e->tri_head.next, *tmpt2;

	MeshClipReset;
	while (tmpv != &e->vertex_head)
	{
		if (tmpv->flags & VF_SEL)
		{
			tmpv->copy = tmpv2 = ZCAlloc(sizeof(CMeshEdVertex));
			MemCopy(&tmpv2->p, &tmpv->p, sizeof(CD3I32));
			QueueInsert(tmpv2, sys_clip_vertex_head.last);
			tmpv->flags |= VF_COPIED;
			tmpv->flags &= ~VF_SEL;
		}
		else
		{
			tmpv->copy = NULL;
			tmpv->flags &= ~(VF_COPIED | VF_SEL);
		}
		tmpv = tmpv->next;
	}
	while (tmpt != &e->tri_head)
	{
		if (tmpt->flags & TF_SEL && tmpt->t[0]->copy && tmpt->t[1]->copy && tmpt->t[2]->copy)
		{
			tmpt2 = ZCAlloc(sizeof(CMeshEdTri));
			tmpt2->t[0] = tmpt->t[0]->copy;
			tmpt2->t[1] = tmpt->t[1]->copy;
			tmpt2->t[2] = tmpt->t[2]->copy;
			tmpt2->mt.color = tmpt->mt.color;
			QueueInsert(tmpt2, sys_clip_tri_head.last);
			tmpt->flags |= TF_COPIED;
			tmpt->flags &= ~TF_SEL;
		}
		else
			tmpt->flags &= ~(TF_COPIED|TF_SEL);
		tmpt = tmpt->next;
	}
}

U0 MeshClipCut(CMeshFrame *e)
{
	CMeshEdVertex	*tmpv = e->vertex_head.next, *tmpv1;
	CMeshEdTri		*tmpt = e->tri_head.next, *tmpt1;

	MeshClipCopy(e);
	while (tmpt != &e->tri_head)
	{
		tmpt1 = tmpt->next;
		if (tmpt->flags & TF_COPIED)
			MeshTriDel(e, tmpt);
		tmpt = tmpt1;
	}
	while (tmpv != &e->vertex_head)
	{
		tmpv1 = tmpv->next;
		if (tmpv->flags & VF_COPIED)
			MeshVertexDel(e, tmpv);
		tmpv = tmpv1;
	}
}

U0 MeshClipDel(CMeshFrame *e)
{//Technically not clip
	CMeshEdVertex	*tmpv = e->vertex_head.next, *tmpv1;
	CMeshEdTri		*tmpt = e->tri_head.next, *tmpt1;

	while (tmpt != &e->tri_head)
	{
		tmpt1 = tmpt->next;
		if (tmpt->flags & TF_SEL)
			MeshTriDel(e, tmpt);
		tmpt = tmpt1;
	}
	while (tmpv != &e->vertex_head)
	{
		tmpv1 = tmpv->next;
		if (tmpv->flags & VF_SEL)
			MeshVertexDel(e, tmpv);
		tmpv = tmpv1;
	}
}

U0 MeshClipPaste(CMeshFrame *e)
{
	CMeshEdVertex	*tmpv2 = sys_clip_vertex_head.next, *tmpv;
	CMeshEdTri		*tmpt2 = sys_clip_tri_head.next, *tmpt;

	MeshVertexSelAll(e, FALSE);
	MeshTriSelAll(e, FALSE);
	while (tmpv2 != &sys_clip_vertex_head)
	{
		tmpv2->copy = tmpv = CAlloc(sizeof(CMeshEdVertex));
		MemCopy(&tmpv->p, &tmpv2->p, sizeof(CD3I32));
		QueueInsert(tmpv, e->vertex_head.last);
		tmpv->flags |= VF_SEL;
		tmpv2 = tmpv2->next;
	}

	while (tmpt2 != &sys_clip_tri_head)
	{
		tmpt = MeshTriNew(e, tmpt2->mt.color, tmpt2->t[0]->copy, tmpt2->t[1]->copy, tmpt2->t[2]->copy);
		tmpt->flags |= TF_SEL;
		tmpt2 = tmpt2->next;
	}
}

MeshClipInit;

U0 DrawIt(CTask *task, CDC *dc)
{
	CMeshFrame		*e = FramePtr("CMeshFrame", task);
	CCtrl			*c = CtrlFindUnique(task, CTRLT_VIEWING_ANGLES);
	F64				 d;
	I64				 i, x, y, z;
	CMeshEdVertex	*tmpv;

	task->horz_scroll.min = -(MESH_WORKSPACE_SIZE - task->pix_width)  / 2;
	task->horz_scroll.max =  (MESH_WORKSPACE_SIZE - task->pix_width)  / 2;
	task->vert_scroll.min = -(MESH_WORKSPACE_SIZE - task->pix_height) / 2;
	task->vert_scroll.max =  (MESH_WORKSPACE_SIZE - task->pix_height) / 2;
	TaskDerivedValsUpdate(task);
	MeshRecalcCxCy(task, e);

	dc->flags |= DCF_TRANSFORMATION;

	Free(dc->r); //Set rotmat doesn't free old dc->r matrix.
	DCMat4x4Set(dc, MeshSetW2S(e, task));

	dc->x = e->cx;
	dc->y = e->cy;
//z-vals less than zero are in front of screen and not drawn.
	//we want to shift all Z-vals into a drawable range.
	//GR_Z_ALL is set to half of the Z-range which is an I32.
	dc->z = GR_Z_ALL;

	//Light source set to mouse.
	MeshCursorW(e, task, &x, &y, &z);
	dc->ls.x = x;
	dc->ls.y = y;
	dc->ls.z = z;
	d = 1 << 16 / D3I32Norm(&dc->ls); //Light source normalized to 65536.
	dc->ls.x *= d;
	dc->ls.y *= d;
	dc->ls.z *= d;

	DCDepthBufAlloc(dc);

	tmpv = e->vertex_head.next;
	while (tmpv != &e->vertex_head)
	{
		x = tmpv->p.x;
		y = tmpv->p.y;
		z = tmpv->p.z;
		(*dc->transform)(dc, &x, &y, &z);
		tmpv->pt.x = x;
		tmpv->pt.y = y;
		tmpv->pt.z = z;
		tmpv = tmpv->next;
	}

	e->mp_not_done_flags = 1 << mp_count - 1; //Issue jobs to all cores.
	e->dc				 = dc;
	e->depth_buf		 = dc->depth_buf;
	for (i = 0; i < mp_count; i++)
		JobQueue(&MPDrawIt, e, i);

	tmpv = e->vertex_head.next;
	while (tmpv != &e->vertex_head)
	{
		x = tmpv->pt.x;
		y = tmpv->pt.y;
		z = tmpv->pt.z;
		if (e->vertex_on)
		{
			if (Blink(10)) //This blinks at 10 Hz.
				dc->color = ROPF_DITHER + BLACK << 16 + WHITE;
			else
				dc->color = ROPF_DITHER + WHITE << 16 + BLACK;
			GrLine(dc, x - 3, y - 3, x + 3, y + 3);
			GrLine(dc, x - 3, y + 3, x + 3, y - 3);
		}
		if (tmpv->flags & VF_SEL)
		{
			if (e->ed_mode == 't')
			{
				if (Blink(10)) //This blinks at 10 Hz.
					dc->color = ROPF_DITHER + e->cur_color.c0.color << 16 + e->cur_color.c0.color ^ 8;
				else
					dc->color = ROPF_DITHER + (e->cur_color.c0.color ^ 8) << 16 + e->cur_color.c0.color;
			}
			else
			{
				if (Blink)
					dc->color = ROPF_DITHER + RED << 16 + WHITE;
				else
					dc->color = ROPF_DITHER + WHITE << 16 + RED;
			}
			GrCircle(dc, x, y, 3);
		}
		tmpv = tmpv->next;
	}

	if (CtrlInside(c, mouse.presnap.x, mouse.presnap.y) || winmgr.show_menu)
	{
		GridInit;
		task->win_inhibit = WIF_SELF_DOC;
	}
	else
	{
		MeshCurSnap(e);
		task->win_inhibit = WIG_TASK_DEFAULT |
				WIF_SELF_DOC - WIF_SELF_FOCUS - WIF_SELF_BORDER - WIF_SELF_CTRLS - WIF_FOCUS_TASK_MENU - WIF_SELF_GRAB_SCROLL;
	}

	MeshCursorW(e, task, &x, &y, &z);
	if (z < 0)
		dc->color = ROP_XOR + RED ^ TRANSPARENT;
	else
		dc->color = ROP_XOR + TRANSPARENT;
	GrPrint(dc, 0, 0, "%6.3f%% (%d,%d,%d)", e->view_scale * 100, x, y, z);
	dc->thick = 1;
	dc->color &= 0xF;
	if (Blink(10))
		dc->color ^= 0xF;
	GrLine3(dc, x, y, z, x, y, 0);

	if (e->sel_rect)
	{
		dc->flags &= ~DCF_TRANSFORMATION;
		dc->thick = 1;
		if (Blink)
			dc->color = ROPF_DITHER + RED << 16 + WHITE;
		else
			dc->color = ROPF_DITHER + WHITE << 16 + RED;
		GrBorder(dc, e->x1, e->y1, e->x2, e->y2);
	}
//Wait for all cores to complete.
	while (e->mp_not_done_flags)
		Yield;
}

U0 MeshInit(CMeshFrame *e, Bool flip_y)
{
	MemSet(e, 0, sizeof(CMeshFrame));
	QueueInit(&e->vertex_head);
	QueueInit(&e->tri_head);
	e->ed_mode		= 'v';
	e->grid_on		= TRUE;
	e->vertex_on	= TRUE;
	e->mouse_z		= 0;
	e->thickness	= 25;
	e->closed		= TRUE;
	e->view_scale	= 1.0;
	e->w2s			= NULL;
	e->s2w			= NULL;
	e->cur_color	= RED;
	e->cur_snap		= 5;
	e->flip_y		= flip_y;
	e->sel_rect		= FALSE;
	e->cur_tri		= NULL;
	e->cur_vertex	= NULL;
	e->chain_pred	= NULL;
}

U0 MeshLoad(CMeshFrame *e, U8 *src)
{
	I64				 i, j, x, y, z;
	CColorROPU32	 color;
	CMeshEdVertex	*tmpv, *va[3];

	QueueInit(&e->vertex_head);
	QueueInit(&e->tri_head);

	e->vertex_count = *src(I32 *)++;
	e->tri_count = *src(I32 *)++;
	for (i = 0; i < e->vertex_count; i++)
	{
		x = *src(I32 *)++;
		y = *src(I32 *)++;
		z = *src(I32 *)++;
		tmpv = MeshVertexNew(e, x, y, z);
		tmpv->num = i;
	}
	for (i = 0; i < e->tri_count; i++)
	{
		color = *src(I32 *)++;
		for (j = 0; j < 3; j++)
			va[j] = MeshVertexFindNum(e, *src(I32 *)++);
		MeshTriNew(e, color, va[0], va[1], va[2]);
	}
}

I64 MeshSize(CMeshFrame *e)
{
	I64				 i;
	CMeshEdVertex	*tmpv = e->vertex_head.next;
	CMeshEdTri		*tmpt = e->tri_head.next;

	e->vertex_count = 0;
	while (tmpv != &e->vertex_head)
	{
		tmpv->num = e->vertex_count++;
		tmpv = tmpv->next;
	}

	e->tri_count = 0;
	while (tmpt != &e->tri_head)
	{
		e->tri_count++;
		for (i = 0; i < 3; i++)
			tmpt->mt.nums[i] = tmpt->t[i]->num;
		tmpt = tmpt->next;
	}
	return sizeof(I32) * 2 + (offset(CMeshEdVertex.end) - offset(CMeshEdVertex.start))	* e->vertex_count +
							 (offset(CMeshEdTri.end)	- offset(CMeshEdTri.start))		* e->tri_count;
}

I32 *MeshSave(CMeshFrame *e, I64 *_size=NULL)
{
	I64				 size = MeshSize(e);
	U8				*res = MAlloc(size), *dst = res;
	CMeshEdVertex	*tmpv = e->vertex_head.next;
	CMeshEdTri		*tmpt = e->tri_head.next;

	*dst(I32 *)++ = e->vertex_count;
	*dst(I32 *)++ = e->tri_count;

	e->vertex_count = 0;
	while (tmpv != &e->vertex_head)
	{
		MemCopy(dst, &tmpv->start, offset(CMeshEdVertex.end)
		-offset(CMeshEdVertex.start));
		dst += offset(CMeshEdVertex.end) - offset(CMeshEdVertex.start);
		tmpv = tmpv->next;
	}

	e->tri_count = 0;
	while (tmpt != &e->tri_head)
	{
		MemCopy(dst, &tmpt->start, offset(CMeshEdTri.end) - offset(CMeshEdTri.start));
		dst += offset(CMeshEdTri.end) - offset(CMeshEdTri.start);
		tmpt = tmpt->next;
	}
	if (_size)
		*_size = size;

	return res;
}

U0 MeshCleanUp(CMeshFrame *e)
{
	QueueDel(&e->vertex_head, TRUE);
	QueueDel(&e->tri_head, TRUE);
	Free(e->w2s);
	Free(e->s2w);
}

U0 MeshUpdateMenu(CMeshFrame *e)
{
	CMenuEntry *tmpse;

	if (tmpse = MenuEntryFind(Fs->cur_menu, "View/Grid"))
		tmpse->checked = ToBool(e->grid_on);

	if (tmpse = MenuEntryFind(Fs->cur_menu, "View/Vertex"))
		tmpse->checked = ToBool(e->vertex_on);

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/PlaceVertex"))
		tmpse->checked = ToBool(e->ed_mode == 'v');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/MoveVertex"))
		tmpse->checked = ToBool(e->ed_mode == 'm');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/MoveVertexZ"))
		tmpse->checked = ToBool(e->ed_mode == 'M');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/Triangle"))
		tmpse->checked = ToBool(e->ed_mode == 't');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/Polygon"))
		tmpse->checked = ToBool(e->ed_mode == 'n');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/Fence"))
		tmpse->checked = ToBool(e->ed_mode == 'f');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "Mode/Prism"))
		tmpse->checked = ToBool(e->ed_mode == 'p');

	if (tmpse = MenuEntryFind(Fs->cur_menu, "View/FlipY"))
		tmpse->checked = ToBool(e->flip_y);
}

I32 *SpriteMeshEd(I32 *head=NULL, I64 *_size=NULL, Bool flip_y=FALSE)
{/*Format for mesh:
{
I32 vertex_count;
I32 tri_count;
CD3I32 vertices[];
CMeshTri tris[];
}

If head points to a mesh, it will load it.

Returns a newly malloced mesh or NULL.

See $LK,"::/Demo/Graphics/SpritePlot3D.CC"$.
$WW,0$*/
	CCtrl			*c = CtrlFindUnique(Fs, CTRLT_VIEWING_ANGLES);
	CViewAngles		*s, *old_s;
	I64				 i, message_code, sel_mode, arg1, arg2, make_tri_vertex_num = 0, x, y, z;
	CD3I32			 p0a, p0b;
	CMeshEdVertex	*va[3], *tmpv;
	Bool			 adjusting_z = FALSE, moving, save_and_exit, adjusting_camera_xy = FALSE, adjusting_camera_z = FALSE;

	CMeshFrame e;

	if (c)
	{
		old_s = MAlloc(sizeof(CViewAngles));
		MemCopy(old_s, c->state, sizeof(CViewAngles));
	}
	else
	{
		c = ViewAnglesNew;
		old_s = NULL;
	}

	s = c->state;
	s->sx = 0;
	s->sy = 0;
	s->sz = 0;
	s->cx = YELLOW;
	s->cy = RED;
	s->cz = GREEN;

	MenuPush(
				"File {"
				"  Abort(,CH_SHIFT_ESC);"
				"  Exit(,CH_ESC);"
				"}"
				"Edit {"
				"  Delete(,,SC_DELETE);"
				"  DelLast(,CH_BACKSPACE);"
				"  Cut(,CH_CTRLX);"
				"  Copy(,CH_CTRLC);"
				"  Paste(,CH_CTRLV);"
				"  SelectAll(,'A');"
				"  UnSelectAll(,'U');"
				"  SelectRect(,'a');"
				"  UnSelectRect(,'u');"
				"  OrSelectRect(,'o');"
				"  JumpToZ(,'j');"
				"  ResetColor(,'C');"
				"  ReverseTri(,'r');"
				"}"
				"Mode {"
				"  PlaceVertex(,'v');"
				"  MoveVertex(,'m');"
				"  MoveVertexZ(,'M');"
				"  Triangle(,'t');"
				"  Polygon(,'n');"
				"  Fence(,'f');"
				"  Prism(,'p');"
				"}"
				"Settings {"
				"  Color(,'c');"
				"  Snap(,'s');"
				"}"
				"View {"
				"  ZoomIn(,'z');"
				"  ZoomOut(,'Z');"
				"  NullAngles(,'N');"
				"  FlipY(,'y');"
				"  Grid(,'g');"
				"  Vertex(,'V');"
				"  ToggleBorder(,CH_CTRLB);"
				"}"
				"Transforms {"
				"  Transform(,'T');"
				"  SwapXY(,'1');"
				"  SwapXZ(,'2');"
				"  SwapYZ(,'3');"
				"  InvertX(,'4');"
				"  InvertY(,'5');"
				"  InvertZ(,'6');"
				"  ReverseTri(,'R');"
				"}");

	SettingsPush; //See $LK,"SettingsPush",A="MN:SettingsPush"$
	AutoComplete;
	RegOneTimePopUp(ARf_MESH_ED,
				"$$GREEN$$Right Mouse$$FG$$: Hold and move to shift cursor z\n"
				"$$GREEN$$Shift + Right Mouse$$FG$$: Hold and move to rotate camera XY\n"
				"$$GREEN$$Alt + Right Mouse$$FG$$: Hold and move to rotate camera Z\n"
				"$$GREEN$$'j'$$FG$$: Jump cursor Z to nearest vertex's Z\n"
				"$$GREEN$$'v'$$FG$$: Place Vertex Mode\n"
				"$$GREEN$$'m'$$FG$$: Move Vertex Mode\n"
				"$$GREEN$$'M'$$FG$$: Move Vertex Z\n"
				"$$GREEN$$'t'$$FG$$: Form Triangle Mode\n"
				"$$GREEN$$'n'$$FG$$: Polygon Mode\n"
				"$$GREEN$$'f'$$FG$$: Fence Mode\n"
				"$$GREEN$$'p'$$FG$$: Prism Mode\n"
				"$$GREEN$$'c'$$FG$$: Set color\n"
				"$$GREEN$$'s'$$FG$$: Set snap\n"
				"\nSee menu at top of screen for more.\n");

	Fs->win_inhibit = WIG_TASK_DEFAULT |
			WIF_SELF_DOC - WIF_SELF_FOCUS - WIF_SELF_BORDER - WIF_SELF_CTRLS - WIF_FOCUS_TASK_MENU - WIF_SELF_GRAB_SCROLL;
	Fs->horz_scroll.pos = 0;
	Fs->vert_scroll.pos = 0;
	MeshInit(&e, flip_y);
	if (head)
		MeshLoad(&e, head);
	FramePtrAdd("CMeshFrame", &e);
	Fs->draw_it = &DrawIt;
	MeshCurSnap(&e);
	MeshRecalcCxCy(Fs, &e);

	try
	{//In case of <CTRL-ALT-c>
		while (TRUE)
		{
			MeshUpdateMenu(&e);
			message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_MOVE | 1 << MESSAGE_KEY_DOWN  | 1 << MESSAGE_MS_L_DOWN|
													1 << MESSAGE_MS_L_UP | 1 << MESSAGE_MS_R_DOWN | 1 << MESSAGE_MS_R_UP);
me_restart:
			switch (message_code)
			{
				case MESSAGE_KEY_DOWN:
					switch (arg1)
					{
						case 0:
							switch (arg2.u8[0])
							{
								case SC_DELETE:
									if (arg2 & SCF_SHIFT)
										goto me_clip_cut;
									else
									{
										if (MeshSelCount(&e))
											MeshClipDel(&e);
										else if (e.ed_mode != 't')
											MeshVertexDel(&e, MeshVertexFindScrPt(&e,
																mouse.presnap.x - Fs->pix_left - Fs->scroll_x, 
																mouse.presnap.y - Fs->pix_top  - Fs->scroll_y));
										MeshVertexSelAll(&e, FALSE);
										MeshTriSelAll(&e, FALSE);
										make_tri_vertex_num = 0;
									}
									break;

								case SC_INS:
									if (arg2 & SCF_CTRL)
										goto me_clip_copy;
									else if (arg2 & SCF_SHIFT)
										goto me_clip_paste;
							}
							break;

						case CH_BACKSPACE:
							switch (e.ed_mode)
							{
								case 'n':
								case 'f':
								case 'p':
								case 'v':
									MeshVertexDel(&e, e.cur_vertex);
									break;

								case 't':
									if (make_tri_vertex_num)
									{
										MeshVertexSelAll(&e, FALSE);
										MeshTriSelAll(&e, FALSE);
										make_tri_vertex_num = 0;
									}
									else
										MeshTriDel(&e, e.cur_tri);
									break;
							}
							break;

						case 'f':
						case 'p':
							e.thickness = PopUpI64Get("Thickness (%d):", e.thickness);
						case 'n':
							if (arg1 == 'n' || arg1 == 'p')
								e.closed = TRUE;
							else
								e.closed = PopUpNoYes("Closed?\n");
me_chain:
							e.chain_pred = e.vertex_head.last;
						case 't':
							MeshVertexSelAll(&e, FALSE);
							MeshTriSelAll(&e, FALSE);
						case 'v':
						case 'm':
						case 'M':
							adjusting_z = FALSE;
							moving = FALSE;
							e.ed_mode = arg1;
							make_tri_vertex_num = 0;
							Sound;
							break;

						case 'T':
							MeshTransformSel(&e);
							break;

						case 'A':
							MeshTriSelAll(&e, TRUE);
							if (e.ed_mode != 't')
								MeshVertexSelAll(&e, TRUE);
							else
								MeshVertexSelAll(&e, FALSE);
							make_tri_vertex_num = 0;
							break;

						case 'U':
							MeshTriSelAll(&e, FALSE);
							MeshVertexSelAll(&e, FALSE);
							make_tri_vertex_num = 0;
							break;

						case 'a':
						case 'u':
						case 'o':
							if (arg1 == 'a')
								sel_mode = SEL_MESH_EQU;
							else if (arg1 == 'u')
								sel_mode = SEL_MESH_AND;
							else
								sel_mode = SEL_MESH_OR;
							if ((message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN  |
																		 1 << MESSAGE_MS_L_DOWN |
																		 1 << MESSAGE_MS_L_UP   |
																		 1 << MESSAGE_MS_R_DOWN |
																		 1 << MESSAGE_MS_R_UP)) != MESSAGE_MS_L_DOWN)
							{
								Beep;
								Beep;
								goto me_restart;
							}
							e.x1		= arg1;
							e.y1		= arg2;
							e.x2		= arg1;
							e.y2		= arg2;
							e.sel_rect	= TRUE;
							while (TRUE)
							{
								message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_MOVE	|
																		1 << MESSAGE_KEY_DOWN	|
																		1 << MESSAGE_MS_L_DOWN	|
																		1 << MESSAGE_MS_L_UP	|
																		1 << MESSAGE_MS_R_DOWN	|
																		1 << MESSAGE_MS_R_UP);
								if (message_code == MESSAGE_MS_MOVE)
								{
									e.x2 = arg1;
									e.y2 = arg2;
								}
								else if (message_code == MESSAGE_MS_L_UP)
								{
									e.x2 = arg1;
									e.y2 = arg2;
									break;
								}
								else
								{
									e.sel_rect = FALSE;
									Beep;
									Beep;
									goto me_restart;
								}
							}
							e.sel_rect = FALSE;
							MeshTriSelRect(&e, sel_mode, e.x1, e.x2, e.y1, e.y2);
							if (e.ed_mode != 't')
								MeshVertexSelRect(&e, sel_mode, e.x1, e.x2, e.y1, e.y2);
							else
								MeshVertexSelAll(&e, FALSE);
							make_tri_vertex_num = 0;
							break;

						case CH_CTRLB:
							WinBorder(Bt(&Fs->display_flags, DISPLAYf_NO_BORDER));
							break;

						case CH_CTRLC:
me_clip_copy:
							if (e.ed_mode == 't')
							{
								Beep;
								Beep;
							}
							else
								MeshClipCopy(&e);
							break;

						case CH_CTRLV:
me_clip_paste:
							if (e.ed_mode == 't')
							{
								Beep;
								Beep;
							}
							else
							{
								MeshClipPaste(&e);
								e.ed_mode = 'm';
							}
							break;

						case CH_CTRLX:
me_clip_cut:
							if (e.ed_mode == 't')
							{
								Beep;
								Beep;
							}
							else
								MeshClipCut(&e);
							break;

						case CH_SHIFT_ESC:
							save_and_exit = FALSE;
							goto me_done;

						case CH_ESC:
							save_and_exit = TRUE;
							goto me_done;

						case 'z':
							MeshScaleZoom(&e, 1.5);
							break;

						case 'Z':
							MeshScaleZoom(&e, 1 / 1.5);
							break;

						case 'c':
							e.cur_color = PopUpColorLighting;
							break;

						case 's':
							i = PopUpRangeI64(1, 25, 1, "New Snap\n");
							if (i >= 1)
								e.cur_snap = i;
							MeshCurSnap(&e);
							MeshRecalcCxCy(Fs, &e);
							break;

						case 'g':
							e.grid_on = !e.grid_on;
							break;

						case 'V':
							e.vertex_on = !e.vertex_on;
							break;

						case 'N':
							s->sx = s->sy = s->sz = 0;
							break;

						case 'y':
							e.flip_y = !e.flip_y;
							break;

						case 'j':
							if (moving)
								MeshVertexIgnoreSet(&e, TRUE);
							if (tmpv = MeshVertexFindScrPt(&e,
															mouse.pos.x - Fs->pix_left - Fs->scroll_x, 
															mouse.pos.y - Fs->pix_top  - Fs->scroll_y))
							{
								Noise(25, 86, 110);
								e.mouse_z = RoundI64(tmpv->p.z, e.cur_snap);
							}
							else
							{
								Beep;
								Beep;
								e.mouse_z = 0;
							}
							MeshVertexIgnoreSet(&e, FALSE);
							if (moving)
							{
								MeshCursorW(&e, Fs, &x, &y, &z);
								if (adjusting_z)
									MeshP0Offset(&e, 0, 0, z - p0a.z);
								else
									MeshP0Offset(&e, x - p0a.x, y - p0a.y, z - p0a.z);
								p0a.x = x;
								p0a.y = y;
								p0a.z = z;
								MeshP0Capture(&e);
							}
							break;

						case '1':
							MeshSwapAxes(&e, offset(CD3I32.x), offset(CD3I32.y));
							break;

						case '2':
							MeshSwapAxes(&e, offset(CD3I32.x), offset(CD3I32.z));
							break;

						case '3':
							MeshSwapAxes(&e, offset(CD3I32.y), offset(CD3I32.z));
							break;

						case '4':
							MeshInvertAxis(&e, offset(CD3I32.x));
							break;

						case '5':
							MeshInvertAxis(&e, offset(CD3I32.y));
							break;

						case '6':
							MeshInvertAxis(&e, offset(CD3I32.z));
							break;

						case 'r':
							if (e.cur_tri)
								SwapI64(&e.cur_tri->t[1], &e.cur_tri->t[2]);
							break;

						case 'C':
							MeshColorTris(&e);
							break;

						case 'R':
							MeshRevTris(&e);
							break;
					}
					break;

				case MESSAGE_MS_L_DOWN:
					switch (e.ed_mode)
					{
						case 'm':
							if (!moving)
							{
								if (!MeshSelCount(&e) && (tmpv = MeshVertexFindScrPt(&e, arg1, arg2)))
								{
									tmpv->flags |= VF_SEL;
									e.mouse_z = RoundI64(tmpv->p.z, e.cur_snap);
								}
								if (MeshSelCount(&e))
								{
									MeshCursorW(&e, Fs, &x, &y, &z);
									p0a.x = x;
									p0a.y = y;
									p0a.z = z;
									MeshP0Capture(&e);
									moving = TRUE;
								}
							}
							break;

						case 'M':
							if (!adjusting_z && !moving)
							{
								if (!MeshSelCount(&e) && (tmpv = MeshVertexFindScrPt(&e, arg1, arg2)))
								{
									tmpv->flags |= VF_SEL;
									e.mouse_z = RoundI64(tmpv->p.z, e.cur_snap);
								}
								if (MeshSelCount(&e))
								{
									MeshCursorW(&e, Fs, &x, &y, &z);
									p0a.x = x;
									p0a.y = y;
									p0a.z = z;
									MeshP0Capture(&e);
									moving = TRUE;

									p0b.x = mouse.presnap.x;
									p0b.y = mouse.presnap.y;
									p0b.z = e.mouse_z;
									adjusting_z = TRUE;
									Sound(ClampI64(Freq2Ona(3 * e.mouse_z + 1500), 0, I8_MAX));
								}
							}
							break;
					}

					break;
				case MESSAGE_MS_L_UP:
					switch (e.ed_mode)
					{
						case 'n':
						case 'f':
						case 'p':
						case 'v':
							Noise(25, 86, 110);
							MeshCursorW(&e, Fs, &x, &y, &z);
							e.cur_vertex = MeshVertexNew(&e, x, y, z);
							break;

						case 'm':
						case 'M':
							if (moving)
							{
								if (adjusting_z)
								{
									e.mouse_z = RoundI64(Sign(p0b.y - mouse.presnap.y) *
														 Sqrt(Sqr(p0b.x - mouse.presnap.x) +
															  Sqr(p0b.y - mouse.presnap.y)) + p0b.z, e.cur_snap);
									Sound;
									adjusting_z = FALSE;
									MeshCursorW(&e, Fs, &x, &y, &z);
									MeshP0Offset(&e, 0, 0, z - p0a.z);
								}
								else
								{
									MeshCursorW(&e, Fs, &x, &y, &z);
									MeshP0Offset(&e, x - p0a.x, y - p0a.y, z - p0a.z);
								}
								MeshTriSelAll(&e, FALSE);
								MeshVertexSelAll(&e, FALSE);
								moving = FALSE;
							}
							break;

						case 't':
							if (tmpv = MeshVertexFindScrPt(&e, arg1, arg2))
							{
								for (i = 0; i < make_tri_vertex_num; i++)
									if (va[i] == tmpv)
									{
										Beep;
										Beep;
										break;
									}
								if (i == make_tri_vertex_num)
								{
									Noise(25, 86, 110);
									va[make_tri_vertex_num++] = tmpv;
									tmpv->flags |= VF_SEL;
									if (make_tri_vertex_num == 3)
									{
										e.cur_tri = MeshTriNew(&e, e.cur_color, va[0], va[1], va[2]);
										for (i = 0; i < make_tri_vertex_num; i++)
											va[i]->flags &= ~VF_SEL;
										make_tri_vertex_num = 0;
									}
								}
							}
							break;
					}
					break;

				case MESSAGE_MS_R_DOWN:
					if (Bt(kbd.down_bitmap, SC_SHIFT))	adjusting_camera_xy = TRUE;
					if (Bt(kbd.down_bitmap, SC_ALT))	adjusting_camera_z	= TRUE;

					if (!adjusting_z && e.ed_mode != 'M' &&
						(e.chain_pred == e.vertex_head.last || e.ed_mode != 'n' && e.ed_mode != 'f' && e.ed_mode != 'p') &&
						!adjusting_camera_xy && !adjusting_camera_z)
					{
						if (moving)
						{
							MeshCursorW(&e, Fs, &x, &y, &z);
							MeshP0Offset(&e, x - p0a.x, y - p0a.y, z - p0a.z);
							p0a.x = x;
							p0a.y = y;
							p0a.z = z;
							MeshP0Capture(&e);
						}
						p0b.x = mouse.presnap.x;
						p0b.y = mouse.presnap.y;
						p0b.z = e.mouse_z;
						adjusting_z = TRUE;
						Sound(ClampI64(Freq2Ona(3 * e.mouse_z + 1500), 0, I8_MAX));
					}
					break;

				case MESSAGE_MS_R_UP:
					adjusting_camera_xy = FALSE;
					adjusting_camera_z  = FALSE;
					if (adjusting_z)
					{
						e.mouse_z = RoundI64(Sign(p0b.y - mouse.presnap.y) *
											 Sqrt(Sqr(p0b.x - mouse.presnap.x) +
												  Sqr(p0b.y - mouse.presnap.y)) + p0b.z, e.cur_snap);
						Sound;
						adjusting_z = FALSE;
						if (moving)
						{
							MeshCursorW(&e, Fs, &x, &y, &z);
							MeshP0Offset(&e, 0, 0, z - p0a.z);
							p0a.x = x;
							p0a.y = y;
							p0a.z = z;
							MeshP0Capture(&e);
						}
					}
					else if (e.ed_mode == 'n')
					{
						if (e.chain_pred && e.chain_pred != e.vertex_head.last)
							MeshPolygon(&e, e.chain_pred->next, e.vertex_head.last, FALSE);
						arg1 = e.ed_mode;
						goto me_chain;
					}
					else if (e.ed_mode == 'f')
					{
						if (e.chain_pred && e.chain_pred != e.vertex_head.last)
							MeshFence(&e);
						arg1 = e.ed_mode;
						goto me_chain;
					}
					else if (e.ed_mode == 'p')
					{
						if (e.chain_pred && e.chain_pred != e.vertex_head.last)
							MeshPrism(&e);
						arg1 = e.ed_mode;
						goto me_chain;
					}
					break;

				case MESSAGE_MS_MOVE:
					if (adjusting_camera_xy)
					{
						// In the editor, X and Y of mouse rotate around Y and X 3D axes.

						s->sx += mouse.presnap.y - mouse_last.presnap.y;
						if (s->sx < 0)
							s->sx = 360; // VIEWANGLES_RANGE
						else if (s->sx > 360)
							s->sx = 0;
						s->sy -= mouse.presnap.x - mouse_last.presnap.x;
						if (s->sy < 0)
							s->sy = 360; // VIEWANGLES_RANGE
						else if (s->sy > 360)
							s->sy = 0;
					}
					if (adjusting_camera_z)
					{
						// In the editor, X and Y of mouse rotates around Z 3D axis.

						s->sz += mouse.presnap.x - mouse_last.presnap.x;
						s->sz += mouse.presnap.y - mouse_last.presnap.y;
						if (s->sz < 0)
							s->sz = 360; // VIEWANGLES_RANGE
						else if (s->sz > 360)
							s->sz = 0;
					}

					if (adjusting_camera_xy || adjusting_camera_z)
						break;

					if (adjusting_z)
					{
						e.mouse_z = RoundI64(Sign(p0b.y - mouse.presnap.y) *
											 Sqrt(Sqr(p0b.x - mouse.presnap.x) +
												  Sqr(p0b.y - mouse.presnap.y)) + p0b.z, e.cur_snap);
						Sound(ClampI64(Freq2Ona(3 * e.mouse_z + 1500), 0, I8_MAX));
					}
					if (moving)
					{
						MeshCursorW(&e, Fs, &x, &y, &z);
						if (adjusting_z)
							MeshP0Offset(&e, 0, 0, z - p0a.z);
						else
							MeshP0Offset(&e, x - p0a.x, y - p0a.y, z - p0a.z);
						p0a.x = x;
						p0a.y = y;
						p0a.z = z;
						MeshP0Capture(&e);
					}
					break;
			}
		}
me_done:
	}
	catch
		Fs->catch_except = TRUE;

	SettingsPop;
	MenuPop;
	if (save_and_exit)
		head = MeshSave(&e, _size);
	else
		head = NULL;
	MeshCleanUp(&e);
	FramePtrDel("CMeshFrame");
	if (old_s)
	{
		MemCopy(c->state, old_s, sizeof(CViewAngles));
		Free(old_s);
	}
	else
		ViewAnglesDel;

	return head;
}
