#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.  (Scrn)
  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 ms_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; //Scrn-to-world and world-to-scrn transform matrices.
  I64 vertex_cnt,tri_cnt; //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;
  QueIns(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_cnt;
  tmpt->mt.color=color;
  tmpt->t[0]=v1;
  tmpt->t[1]=v2;
  tmpt->t[2]=v3;
  QueIns(tmpt,e->tri_head.last);
  return tmpt;
}

CMeshEdVertex *MeshVertexFindScrPt(CMeshFrame *e,I64 x,I64 y)
{//Scrn 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->ms_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;
    QueRem(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;
    QueRem(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) {
    MemCpy(&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 MeshSelCnt(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 (!MeshSelCnt(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 (!MeshSelCnt(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 (!MeshSelCnt(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 (!MeshSelCnt(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 (!MeshSelCnt(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);
    ms_grid.x=Max(1,MaxI64(x1,x2)>>16);
    ms_grid.y=Max(1,MaxI64(y1,y2)>>16);
    ms_grid.z=Min(ms_grid.x,ms_grid.y);
  }
}

U0 MeshScaleZoom(CMeshFrame *e,F64 scale)
{
  CTask *task=Fs;
  I64   x=ms.pos.x-task->pix_left-task->scroll_x-task->pix_width/2,
        y=ms.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;

  //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;
  MemCpy(&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_cnt) {
      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 DCDel().
  dc->depth_buf=NULL;

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

I64 *MeshW2S(CMeshFrame *e,CTask *task)
{//World to scrn 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+pi);
  return r;
}

I64 *MeshS2W(CMeshFrame *e,CTask *task)
{//Scrn 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+pi));
  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 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=ms.pos.x-task->pix_left-task->scroll_x-e->cx,
        yc=ms.pos.y-task->pix_top-task->scroll_y-e->cy,zc=0,
        x=0,y=0,z=e->ms_z,
        i,x2,y2,z2;
  Mat4x4MulXYZ(e->w2s,&x,&y,&z); //scrn 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->ms_z,e->cur_snap);
  *_x=x; *_y=y; *_z=z;
}

CMeshEdVertex   sys_clip_vertex_head;
CMeshEdTri      sys_clip_tri_head;

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

U0 MeshClipRst()
{
  QueDel(&sys_clip_vertex_head,TRUE);
  QueDel(&sys_clip_tri_head,TRUE);
  MeshClipInit;
}

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

  MeshClipRst;
  while (tmpv!=&e->vertex_head) {
    if (tmpv->flags&VF_SEL) {
      tmpv->copy=tmpv2=ACAlloc(sizeof(CMeshEdVertex));
      MemCpy(&tmpv2->p,&tmpv->p,sizeof(CD3I32));
      QueIns(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=ACAlloc(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;
      QueIns(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));
    MemCpy(&tmpv->p,&tmpv2->p,sizeof(CD3I32));
    QueIns(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 scrn 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_cnt-1; //Issue jobs to all cores.
  e->dc=dc;
  e->depth_buf=dc->depth_buf;
  for (i=0;i<mp_cnt;i++)
    JobQue(&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,ms.presnap.x,ms.presnap.y)||winmgr.show_menu) {
    GridInit;
    task->win_inhibit=WIF_SELF_DOC;
  } else {
    MeshCurSnap(e);
    task->win_inhibit=WIG_TASK_DFT|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));
  QueInit(&e->vertex_head);
  QueInit(&e->tri_head);
  e->ed_mode='v';
  e->grid_on=TRUE;
  e->vertex_on=TRUE;
  e->ms_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];

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

  e->vertex_cnt  =*src(I32 *)++;
  e->tri_cnt=*src(I32 *)++;
  for (i=0;i<e->vertex_cnt;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_cnt;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_cnt=0;
  while (tmpv!=&e->vertex_head) {
    tmpv->num=e->vertex_cnt++;
    tmpv=tmpv->next;
  }

  e->tri_cnt=0;
  while (tmpt!=&e->tri_head) {
    e->tri_cnt++;
    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_cnt+
        (offset(CMeshEdTri.end)-offset(CMeshEdTri.start))*e->tri_cnt;
}

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_cnt;
  *dst(I32 *)++=e->tri_cnt;

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

  e->tri_cnt=0;
  while (tmpt!=&e->tri_head) {
    MemCpy(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)
{
  QueDel(&e->vertex_head,TRUE);
  QueDel(&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)
{/*Fmt for mesh:
{
I32 vertex_cnt;
I32 tri_cnt;
CD3I32 vertices[];
CMeshTri tris[];
}

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

Returns a newly malloced mesh or NULL.

See ::/Demo/Graphics/SpritePlot3D.HC.
*/
  CCtrl *c=CtrlFindUnique(Fs,CTRLT_VIEWING_ANGLES);
  CViewAngles *s,*old_s;
  I64 i,msg_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;
  CMeshFrame e;

  if (c) {
    old_s=MAlloc(sizeof(CViewAngles));
    MemCpy(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 SettingsPush
  AutoComplete;
  RegOneTimePopUp(ARf_MESH_ED,
        "$GREEN$Right Mouse$FG$: Hold and move to shift cursor 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 scrn for more.\n");

  Fs->win_inhibit=WIG_TASK_DFT|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);
      msg_code=GetMsg(&arg1,&arg2,
            1<<MSG_MS_MOVE|1<<MSG_KEY_DOWN|1<<MSG_MS_L_DOWN|
            1<<MSG_MS_L_UP|1<<MSG_MS_R_DOWN|1<<MSG_MS_R_UP);
me_restart:
      switch (msg_code) {
        case MSG_KEY_DOWN:
          switch (arg1) {
            case 0:
              switch (arg2.u8[0]) {
                case SC_DELETE:
                  if (arg2&SCF_SHIFT)
                    goto me_clip_cut;
                  else {
                    if (MeshSelCnt(&e))
                      MeshClipDel(&e);
                    else if (e.ed_mode!='t')
                      MeshVertexDel(&e,MeshVertexFindScrPt(&e,
                            ms.presnap.x-Fs->pix_left-Fs->scroll_x,
                            ms.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=PopUpGetI64("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;
              Snd;
              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 ((msg_code=GetMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|1<<MSG_MS_L_DOWN|
                    1<<MSG_MS_L_UP|1<<MSG_MS_R_DOWN|1<<MSG_MS_R_UP))
                    !=MSG_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) {
                msg_code=GetMsg(&arg1,&arg2,1<<MSG_MS_MOVE|1<<MSG_KEY_DOWN|
                      1<<MSG_MS_L_DOWN|1<<MSG_MS_L_UP|1<<MSG_MS_R_DOWN|
                      1<<MSG_MS_R_UP);
                if (msg_code==MSG_MS_MOVE) {
                  e.x2=arg1; e.y2=arg2;
                } else if (msg_code==MSG_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,
                    ms.pos.x-Fs->pix_left-Fs->scroll_x,
                    ms.pos.y-Fs->pix_top-Fs->scroll_y)) {
                Noise(25,86,110);
                e.ms_z=RoundI64(tmpv->p.z,e.cur_snap);
              } else {
                Beep; Beep;
                e.ms_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 MSG_MS_L_DOWN:
          switch (e.ed_mode) {
            case 'm':
              if (!moving) {
                if (!MeshSelCnt(&e) &&
                      (tmpv=MeshVertexFindScrPt(&e,arg1,arg2))) {
                  tmpv->flags|=VF_SEL;
                  e.ms_z=RoundI64(tmpv->p.z,e.cur_snap);
                }
                if (MeshSelCnt(&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 (!MeshSelCnt(&e) &&
                      (tmpv=MeshVertexFindScrPt(&e,arg1,arg2))) {
                  tmpv->flags|=VF_SEL;
                  e.ms_z=RoundI64(tmpv->p.z,e.cur_snap);
                }
                if (MeshSelCnt(&e)) {
                  MeshCursorW(&e,Fs,&x,&y,&z);
                  p0a.x=x;
                  p0a.y=y;
                  p0a.z=z;
                  MeshP0Capture(&e);
                  moving=TRUE;

                  p0b.x=ms.presnap.x;
                  p0b.y=ms.presnap.y;
                  p0b.z=e.ms_z;
                  adjusting_z=TRUE;
                  Snd(ClampI64(Freq2Ona(3*e.ms_z+1500),0,I8_MAX));
                }
              }
              break;
          }
          break;
        case MSG_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.ms_z=RoundI64(Sign(p0b.y-ms.presnap.y)
                  *Sqrt(Sqr(p0b.x-ms.presnap.x)+Sqr(p0b.y-ms.presnap.y))
                  +p0b.z,e.cur_snap);
                  Snd;
                  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 MSG_MS_R_DOWN:
          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')) {
            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=ms.presnap.x;
            p0b.y=ms.presnap.y;
            p0b.z=e.ms_z;
            adjusting_z=TRUE;
            Snd(ClampI64(Freq2Ona(3*e.ms_z+1500),0,I8_MAX));
          }
          break;
        case MSG_MS_R_UP:
          if (adjusting_z) {
            e.ms_z=RoundI64(Sign(p0b.y-ms.presnap.y)
            *Sqrt(Sqr(p0b.x-ms.presnap.x)+Sqr(p0b.y-ms.presnap.y))
            +p0b.z,e.cur_snap);
            Snd;
            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 MSG_MS_MOVE:
          if (adjusting_z) {
            e.ms_z=RoundI64(Sign(p0b.y-ms.presnap.y)
            *Sqrt(Sqr(p0b.x-ms.presnap.x)+Sqr(p0b.y-ms.presnap.y))
            +p0b.z,e.cur_snap);
            Snd(ClampI64(Freq2Ona(3*e.ms_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) {
    MemCpy(c->state,old_s,sizeof(CViewAngles));
    Free(old_s);
  } else
    ViewAnglesDel;
  return head;
}