-- CurveImpl.mesa
-- Last Edited by July 17, 1982 5:05 pm
-- Last Edited by Michael Plass December 7, 1982 10:25 am
-- Last Edited by: Stone, November 21, 1983 11:33 am

DIRECTORY
Cubic,
Curve,
Complex,
ConvertUnsafe,
IO,
FileIO,
TJaMGraphics USING [Painter],
JaMFnsDefs,
JaMBasic,
JaMOps,
Graphics,
Real,
Rope,
Seq,
Vector;

CurveImpl: PROGRAM
IMPORTS Complex, ConvertUnsafe, TJaMGraphics, JaMFnsDefs, JaMOps, IO, FileIO, Graphics, Real, Rope, Vector
EXPORTS Curve =
BEGIN OPEN JaMFnsDefs, Curve;

SampleHandle: TYPE = REF Sample;
SListHandle: TYPE = REF SList;
LinkHandle: TYPE = REF Link;
TrajHandle: TYPE = REF Traj;

Sample: TYPE = RECORD
[next: SampleHandle ← NIL,
prev: SampleHandle ← NIL,
xy: Complex.Vec ← [0,0],
isNode: BOOLEANTRUE,
isCusp: BOOLEANFALSE,
tangent: Complex.Vec ← [0,0],
tanOut: Complex.Vec ← [0,0]
];

SList: TYPE = RECORD
[header: SampleHandle,
  selectedSample: SampleHandle,
  first: SampleHandle ← NIL,
  last: SampleHandle ← NIL
];

Link: TYPE = RECORD
[next: LinkHandle ← NIL,
prev: LinkHandle ← NIL,
cubic: Cubic.Bezier
];

Traj: TYPE = RECORD
[next: TrajHandle ← NIL,
prev: TrajHandle ← NIL,
links: LinkHandle ← NIL,
lastLink: LinkHandle ← NIL
];


stream: IO.Handle ← FileIO.Open["Curve.log", append];
OpenLogFile: PROC = {
 ls: LONG STRING ← [80];
 rope: Rope.ROPE;
 PopString[ls];
 rope ← ConvertUnsafe.ToRope[ls];
IF stream # NIL THEN stream.Close[];
 stream ← NIL;
IF rope.Length[] > 0 THEN stream ← FileIO.Open[rope, append];
 };

CloseLogFile: PROC = {
IF stream # NIL THEN stream.Close[];
 stream ← NIL;
 typescript ← FALSE;
 };

Handle: PUBLIC TYPE = REF Rec;
Rec: PUBLIC TYPE = RECORD [
 traj: TrajHandle,
 slist: SListHandle,
 visible: BOOLEAN,
 otherContours: LIST OF TrajAndSlist
 ];

TrajAndSlist: TYPE = RECORD [
 traj: TrajHandle,
 slist: SListHandle
 ];

Create: PUBLIC PROC RETURNS [handle: Handle] = {
 handle ← NEW[Rec];
 handle.traj ← NEW[Traj];
 handle.slist ← NewSList[];
 handle.visible ← FALSE;
 handle.otherContours ← NIL;
 };

Visible: PUBLIC PROC [handle: Handle, visible: BOOLEANTRUE]
RETURNS [was: BOOLEAN] = {
 was ← handle.visible;
 handle.visible ← visible;
 };

TTY: PUBLIC PROC RETURNS[IO.Handle] = {RETURN[stream]};

ResetSa: PROC = {ResetSamples[defaultHandle]};

ResetSamples: PUBLIC PROC [handle: Handle] = {OPEN handle^;
IF slist.header#NIL THEN slist.header.next ← slist.header.prev ← NIL;
 slist ← NewSList[]};

NewSList: PROC RETURNS [s: SListHandle] = {
 s ← NEW[SList];
 s.header ← NEW[Sample];
 s.selectedSample ← s.header.next ← s.header.prev ← s.header;
 s.first ← NIL;
 s.last ← NIL;
 };

InsertBefore: PROC[a: Complex.Vec, s: SampleHandle] = {
 new: SampleHandle ← NEW[Sample];
 new.xy ← a;
 new.prev ← s.prev;
 new.next ← s;
 s.prev ← new;
 new.prev.next ← new;
 };

StartSa: PROC = {y: REAL ← GetReal[]; x: REAL ← GetReal[]; StartSamples[defaultHandle, x,y]};

StartSamples: PUBLIC PROC[handle: Handle, x,y: REAL] = {ResetSamples[handle]; InsertBefore[[x,y], handle.slist.header]};

AddSa: PROC = {y: REAL ← GetReal[]; x: REAL ← GetReal[]; AddSample[defaultHandle, x,y]};

AddSample: PUBLIC PROC[handle: Handle, x,y: REAL] = {OPEN handle^;
Paint: PROC[dc: Graphics.Context] = {
  IF Vector.Mag[Vector.Sub[[x,y],slist.header.prev.xy]]>sLen THEN {
   IF slist.header.next#slist.header THEN
    {MoveTo[dc,slist.header.prev.xy]; DrawTo[dc,[x,y]]};
   InsertBefore[[x,y],slist.header];
   };
  };
IF visible THEN TJaMGraphics.Painter[Paint]
ELSE {
  IF Vector.Mag[Vector.Sub[[x,y],slist.header.prev.xy]]>sLen THEN {
   InsertBefore[[x,y],slist.header];
   };
  };
 };

CountSa: PROC = {OPEN defaultHandle^;
s: SampleHandle;
n: NAT ← 0;
FOR s ← slist.header.next, s.next UNTIL s=slist.header DO
n ← n+1;
ENDLOOP;
PushInteger[n];
};

ResetLinks: PUBLIC PROC[handle: Handle] = {
handle.traj.links ← NIL;
handle.traj.lastLink ← NIL;
};

AddLink: PUBLIC PROC[handle: Handle, b: Cubic.Bezier] = {OPEN handle^;
Paint: PROC[dc: Graphics.Context] = {
DrawCubic[dc,b];
};
 new: LinkHandle ← NEW[Link];
 new.cubic ← b;
IF traj.links=NIL THEN {
  traj.links ← new;
  traj.lastLink ← new;
  }
ELSE {
  traj.lastLink.next ← new;
  new.prev ← traj.lastLink;
  traj.lastLink ← new;
  };
IF visible THEN TJaMGraphics.Painter[Paint];
};

ForAllLinks: PROC = {
 body: JaMBasic.Object ← JaMOps.Pop[JaMOps.defaultFrame.opstk];
FOR l: LinkHandle ← defaultHandle.traj.links, l.next UNTIL l=NIL DO
PushReal[l.cubic.b0.x];
PushReal[l.cubic.b0.y];
PushReal[l.cubic.b1.x];
PushReal[l.cubic.b1.y];
PushReal[l.cubic.b2.x];
PushReal[l.cubic.b2.y];
PushReal[l.cubic.b3.x];
PushReal[l.cubic.b3.y];
JaMOps.Execute[JaMOps.defaultFrame, body];
ENDLOOP;
};

ResetCon: PUBLIC PROC[] = {ResetContours[defaultHandle]};
ResetContours: PUBLIC PROC[handle: Handle] = {
FOR p: LIST OF TrajAndSlist ← handle.otherContours, p.rest UNTIL p=NIL DO
  IF p.first.slist.header # NIL THEN p.first.slist.header.next ← p.first.slist.header.prev ← NIL;
  ENDLOOP;
 handle.otherContours ← NIL;
 ResetSamples[handle]
 };

CountCon: PUBLIC PROC[] = {PushInteger[CountContours[defaultHandle]]};
CountContours: PUBLIC PROC[handle: Handle] RETURNS [INT] = {
 i: INT ← 1;
FOR p: LIST OF TrajAndSlist ← handle.otherContours, p.rest UNTIL p=NIL DO i←i+1 ENDLOOP;
RETURN[i]
 };

AddCon: PUBLIC PROC[] = {AddContour[defaultHandle]};
AddContour: PUBLIC PROC[handle: Handle] = {
IF handle.otherContours = NIL THEN handle.otherContours ← LIST[[handle.traj, handle.slist]]
ELSE {
  p: LIST OF TrajAndSlist ← handle.otherContours;
  UNTIL p.rest = NIL DO p ← p.rest ENDLOOP;
  p.rest ← LIST[[handle.traj, handle.slist]];
  };
 handle.slist ← NewSList[];
 handle.traj ← NEW[Traj];
 };

NextCon: PUBLIC PROC[] = {NextContour[defaultHandle]};
NextContour: PUBLIC PROC[handle: Handle] = {
IF handle.otherContours # NIL THEN {
  p: LIST OF TrajAndSlist ← handle.otherContours;
  ts: TrajAndSlist ← handle.otherContours.first;
  handle.otherContours.first ← [handle.traj, handle.slist];
  [handle.traj, handle.slist] ← ts;
  UNTIL p.rest = NIL DO p ← p.rest ENDLOOP;
  p.rest ← handle.otherContours; -- make it circular for a few microseconds
  p ← p.rest;
  handle.otherContours ← p.rest;
  p.rest ← NIL;
  }
 };

CurrentSamples: PUBLIC PROC[handle: Handle] RETURNS [z: Seq.ComplexSequence] = {OPEN handle^;
s: SampleHandle;
i: NAT;
n: NAT ← 0;
first: SampleHandle ← IF slist.first=NIL THEN slist.header.next ELSE slist.first;
last: SampleHandle ← IF slist.last=NIL THEN slist.header ELSE slist.last.next;
FOR s ← first, s.next UNTIL s=last DO
n ← n+1;
ENDLOOP;
z ← NEW[Seq.ComplexSequenceRec[n]];
i ← 0;
FOR s𡤏irst, s.next UNTIL s=last DO
z[i] ← s.xy;
i←i+1;
ENDLOOP;
};

ResetDefaultNodes: PROC = {ResetNodes[defaultHandle]};
ResetNodes: PUBLIC PROC[handle: Handle] ={OPEN handle^;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
s.isNode ← s.isCusp ← FALSE;
s.tangent ← [0,0]
ENDLOOP;
};

AddNode: PUBLIC PROC[handle: Handle, index: NAT, tan: Complex.Vec ← [0,0]] = {OPEN handle^;
i:NAT ← 0;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
IF i=index THEN {s.isNode ← TRUE; s.tangent ← tan; RETURN};
i←i+1;
ENDLOOP;
};

DeleteNode: PUBLIC PROC[handle: Handle, index: NAT] = {OPEN handle^;
i:NAT ← 0;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
IF i=index THEN {s.isNode ← s.isCusp ← FALSE; s.tangent ← [0,0]; RETURN};
i←i+1;
ENDLOOP;
};

CurrentNodes: PUBLIC PROC [handle: Handle]
RETURNS[nodes: Seq.NatSequence, tangents: Seq.ComplexSequence] = {OPEN handle^;
i,n:NAT ← 0;
first: SampleHandle ← IF slist.first=NIL THEN slist.header.next ELSE slist.first;
last: SampleHandle ← IF slist.last=NIL THEN slist.header ELSE slist.last.next;
FOR s: SampleHandle ← first, s.next UNTIL s=last DO
IF s.isNode THEN n←n+1;
ENDLOOP;
nodes ← NEW[Seq.NatSequenceRec[n]];
tangents ← NEW[Seq.ComplexSequenceRec[n]];
i ← n ← 0;
FOR s: SampleHandle ← first, s.next UNTIL s=last DO
IF s.isNode THEN {nodes[n] ← i; tangents[n] ← s.tangent; n←n+1};
i←i+1;
ENDLOOP;
};

ResetDefaultCusps: PROC = {ResetCusps[defaultHandle]};
ResetCusps: PUBLIC PROC [handle: Handle] = {OPEN handle^;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
IF s.isCusp THEN {s.isCusp ← FALSE; s.tangent ← [0,0]; s.tanOut ← [0,0]};
ENDLOOP;
};

AddCusp: PUBLIC PROC[handle: Handle, index: NAT, tanIn,tanOut: Complex.Vec ← [0,0]] = {OPEN handle^;
i:NAT ← 0;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
IF i=index THEN {
 s.isCusp ← s.isNode ← TRUE;
 s.tangent ← tanIn;
 s.tanOut ← tanOut;
RETURN};
i←i+1;
ENDLOOP;
};

DeleteCusp: PUBLIC PROC[handle: Handle, index: NAT] = {OPEN handle^;
i:NAT ← 0;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
IF i=index THEN {
 s.isCusp ← s.isNode ← FALSE;
 s.tangent ← s.tanOut ← [0,0];
RETURN};
i←i+1;
ENDLOOP;
};

CurrentCusps: PUBLIC PROC[handle: Handle] RETURNS[cusps: Seq.NatSequence, tangents: Seq.ComplexSequence] = {OPEN handle^;
i,n:NAT ← 0;
first: SampleHandle ← IF slist.first=NIL THEN slist.header.next ELSE slist.first;
last: SampleHandle ← IF slist.last=NIL THEN slist.header ELSE slist.last.next;
FOR s: SampleHandle ← first, s.next UNTIL s=last DO
IF s.isCusp THEN n←n+1;
ENDLOOP;
cusps ← NEW[Seq.NatSequenceRec[n]];
tangents ← NEW[Seq.ComplexSequenceRec[2*n]];
i ← n ← 0;
FOR s: SampleHandle ← first, s.next UNTIL s=last DO
IF s.isCusp THEN {
 cusps[n] ← i;
 tangents[2*n] ← s.tangent;
 tangents[2*n+1] ← s.tanOut;
 n←n+1};
i←i+1;
ENDLOOP;
};

DrawSamples: PROC= {OPEN defaultHandle^;
Paint: PROC[dc: Graphics.Context] = {
s: SampleHandle ← slist.header.next;
  MoveTo[dc,s.xy];
  s ← s.next;
  WHILE s#slist.header DO
   DrawTo[dc,s.xy];
   s ← s.next;
   ENDLOOP;
IF typescript THEN PrintLine["\n"];
};
TJaMGraphics.Painter[Paint];
};

MarkSamples: PROC= {OPEN defaultHandle^;
Paint: PROC[dc: Graphics.Context] = {
  oldFat: BOOLEAN ← Graphics.SetFat[dc,TRUE];
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
   MarkPoint[dc,s.xy];
  ENDLOOP;
  [] ← Graphics.SetFat[dc,oldFat];
  IF typescript THEN PrintLine["\n"];
  };
 TJaMGraphics.Painter[Paint];
 };

MarkLinks: PROC = {OPEN defaultHandle^;
Paint: PROC[dc: Graphics.Context] = {
Mark[dc,traj.links.cubic.b0];
FOR l: LinkHandle ← traj.links, l.next UNTIL l=NIL DO
Mark[dc,[l.cubic.b3.x,l.cubic.b3.y]];
ENDLOOP;
IF typescript THEN PrintLine["\n"];
};
IF traj.links # NIL THEN TJaMGraphics.Painter[Paint];
};

MarkNodes: PROC = {OPEN defaultHandle^;
Paint: PROC[dc: Graphics.Context] = {
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
   IF s.isNode THEN MarkNode[dc,s];
  ENDLOOP;
  IF typescript THEN PrintLine["\n"];
  };
TJaMGraphics.Painter[Paint];
};

MarkNode: PROC [dc: Graphics.Context, s: SampleHandle] = {
 d:REAL ← markSize/scale;
IF s.isCusp THEN d ← 1.5*d;
IF s.tangent = [0,0] THEN
  BEGIN OPEN s.xy;
  MoveTo[dc,[x+d,y+d]];
  DrawTo[dc,[x-d,y-d]];
  MoveTo[dc,[x-d,y+d]];
  DrawTo[dc,[x+d,y-d]];
  END
ELSE
  BEGIN
  t: Complex.Vec ← Complex.Mul[Vector.Unit[s.tangent],[0,2*d]];
  MoveTo[dc,Complex.Sub[s.xy,t]];
  DrawTo[dc,Complex.Add[s.xy,t]];
  IF s.isCusp THEN {
   t ← Complex.Mul[Vector.Unit[s.tanOut],[0,2*d]];
   MoveTo[dc,Complex.Sub[s.xy,t]];
   DrawTo[dc,Complex.Add[s.xy,t]];
   };
  END
 };

DrawLinks: PROC = {OPEN defaultHandle^;
Paint: PROC[dc: Graphics.Context] = {
MoveTo[dc,traj.links.cubic.b0];
FOR l: LinkHandle ← traj.links, l.next UNTIL l=NIL DO
CurveTo[dc,l.cubic.b1,l.cubic.b2,l.cubic.b3];
ENDLOOP;
IF fill THEN DrawArea[dc] ELSE DrawPath[dc,0];
IF typescript THEN PrintLine["\n"];
};
IF traj.links # NIL THEN TJaMGraphics.Painter[Paint];
};

markSize: REAL ← 2;
markPath: Graphics.Path ← Graphics.NewPath[4];
SetMarkSize: PROC = {markSize ← GetReal[]; };
Mark: PROC[dc: Graphics.Context, pt: Complex.Vec] = {
pt ← XForm[pt];
Graphics.MoveTo[markPath, 0, 0];
Graphics.Rectangle[markPath, pt.x-markSize,pt.y-markSize,pt.x+markSize,pt.y+markSize];
Graphics.DrawArea[dc, markPath];
IF typescript THEN {PrintPoint[pt]; PrintString["mark "]};
};

MarkPoint: PROC[dc: Graphics.Context, pt: Complex.Vec] = {
pt ← XForm[pt];
Graphics.SetCP[dc,pt.x,pt.y];
  Graphics.DrawTo[dc,pt.x,pt.y];
IF typescript THEN {PrintPoint[pt]; PrintString["mark "]};
};

SelectSa: PROC = {OPEN defaultHandle^;
 closest: SampleHandle;
 z: Complex.Vec;
 z.y ← GetReal[];
 z.x ← GetReal[];
 [closest,] ← FindSa[z];
IF closest#NIL THEN slist.selectedSample ← closest;
 };

FindSa: PROC [z: Complex.Vec] RETURNS[found: SampleHandle, index: NAT] = {OPEN defaultHandle^;
 closest,d: REAL ← 10.0E+30;
 i: NAT ← 0;
 found ← NIL;
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
  p: Complex.Vec ← XForm[s.xy];
  IF ABS[p.x-z.x]<closest AND ABS[p.y-z.y]<closest
  AND (d ← Vector.Mag[Vector.Sub[z,p]]) < closest THEN
   {closest ← d; found ← s; index ← i};
  i ← i+1;
  ENDLOOP;
};

Subrange: PROC = {OPEN defaultHandle^;
 first,last: SampleHandle;
 p0,p1: Complex.Vec;
 i0,i1: NAT;
 p1.y ← GetReal[];
 p1.x ← GetReal[];
 [last,i1] ← FindSa[p1];
 p0.y ← GetReal[];
 p0.x ← GetReal[];
 [first,i0] ← FindSa[p0];
IF i0<i1 THEN {slist.first ← first; slist.last ← last} ELSE {slist.first ← last; slist.last ← first};
 };

NoSubrange: PROC = {OPEN defaultHandle^; slist.first ← slist.last ← NIL};

DeleteSa: PROC = {OPEN defaultHandle^;
IF slist.selectedSample # slist.header THEN {
  p,q: SampleHandle;
  p ← slist.selectedSample.prev;
  q ← slist.selectedSample.next;
  p.next ← q;
  q.prev ← p;
  slist.selectedSample ← q
  };
 };

InsertSa: PROC = {OPEN defaultHandle^;
 z: Complex.Vec;
 samp: SampleHandle ← slist.selectedSample;
 z.y ← GetReal[];
 z.x ← GetReal[];
 InsertBefore[z, slist.selectedSample];
 };

InsertBetween: PROC = {OPEN defaultHandle^;
 z: Complex.Vec;
 insertBefore: BOOLEAN;
 samp: SampleHandle ← slist.selectedSample;
 z.y ← GetReal[];
 z.x ← GetReal[];
 insertBefore ←
  (Vector.Mag[Vector.Sub[z, samp.prev.xy]] < Vector.Mag[Vector.Sub[z, samp.next.xy]]);
IF insertBefore THEN InsertBefore[z, samp]
ELSE InsertBefore[z, samp.next];
 };

TheSa: PROC = {OPEN defaultHandle^; PushReal[slist.selectedSample.xy.x]; PushReal[slist.selectedSample.xy.y]};

TheTan: PROC = {OPEN defaultHandle.slist.selectedSample;
 PushReal[tangent.x]; PushReal[tangent.y]; PushBoolean[isNode]};

HomeSa: PROC = {OPEN defaultHandle^; slist.selectedSample ← slist.header};

MakeFirstSa: PROC = {OPEN defaultHandle^;
IF slist.selectedSample # NIL AND slist.selectedSample # slist.header AND slist.selectedSample # slist.header.next THEN {
 slist.header.next.prev ← slist.header.prev;
 slist.header.prev.next ← slist.header.next;
 slist.header.next ← slist.selectedSample;
 slist.header.prev ← slist.selectedSample.prev;
 slist.header.next.prev ← slist.header;
 slist.header.prev.next ← slist.header}
};

NextSa: PROC = {OPEN defaultHandle^;
 slist.selectedSample ← slist.selectedSample.next;
IF slist.selectedSample = slist.header THEN
  slist.selectedSample ← slist.selectedSample.next
 };

PrevSa: PROC = {OPEN defaultHandle^;
 slist.selectedSample ← slist.selectedSample.prev;
IF slist.selectedSample = slist.header THEN
  slist.selectedSample ← slist.selectedSample.prev
 };

NodeSa: PROC = {OPEN defaultHandle^;
 slist.selectedSample.isNode ← PopBoolean[];
IF ~slist.selectedSample.isNode THEN slist.selectedSample.isCusp ← FALSE;
 };

CuspSa: PROC = {OPEN defaultHandle^; slist.selectedSample.isCusp ← slist.selectedSample.isNode ← PopBoolean[]};

CuspNode: PROC = {OPEN defaultHandle^; IF slist.selectedSample.isNode THEN slist.selectedSample.isCusp ← PopBoolean[]};

GetVec: PROC RETURNS [z:Complex.Vec] = {z.y ← GetReal[]; z.x ← GetReal[]};

TanSa: PROC = {defaultHandle.slist.selectedSample.tangent ← GetVec[]};
TanOutSa: PROC = {defaultHandle.slist.selectedSample.tanOut ← GetVec[]};

ScaleSa: PROC = {OPEN defaultHandle^;
 z: Complex.Vec ← GetVec[];
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
  p:Complex.Vec ← s.xy;
  s.xy ← Complex.Mul[p,z];
  p ← s.tangent;
  s.tangent ← Complex.Mul[p,z];
  ENDLOOP;
 };

TranSa: PROC = {OPEN defaultHandle^;
 z: Complex.Vec ← GetVec[];
FOR s: SampleHandle ← slist.header.next, s.next UNTIL s=slist.header DO
  s.xy ← Complex.Add[s.xy,z];
  ENDLOOP;
 };

InterpolateSa: PROC = {OPEN defaultHandle^;
 mindelta: REALMAX[GetReal[], 0.00001];
 s: SampleHandle ← slist.header.next;
UNTIL s=slist.header DO
  nexts: SampleHandle ← s.next;
  nextxy: Complex.Vec ← (IF s.next = slist.header THEN slist.header.next ELSE s.next).xy;
  delta: Complex.Vec ← Complex.Sub[nextxy, s.xy];
  k: REAL ← Real.RoundLI[Complex.Abs[delta]/mindelta];
  FOR i: REAL ← 1, i+1 UNTIL i>k DO
   InsertBefore[Complex.Add[s.xy, Vector.Mul[delta, i/(k+1)]], nexts];
   ENDLOOP;
  s ← nexts;
  ENDLOOP;
 };

PrintReal: PROC[r: REAL] = {OPEN IO; Put[stream,real[r],char[SP]]};
PrintString: PROC[s: LONG STRING] = {OPEN IO; Put[stream,string[s]]};
PrintLine: PROC[s: LONG STRING] = {OPEN IO; Put[stream,string[s],char[CR]]};
PrintPoint: PROC[pt: Complex.Vec] = {PrintReal[pt.x]; PrintReal[pt.y]};

cp: Complex.Vec ← [0,0];

thePath: Graphics.Path ← Graphics.NewPath[300];

MoveTo: PUBLIC PROC[dc: Graphics.Context, p: Complex.Vec] = {
Graphics.SetColor[dc,Graphics.black];
[] ← Graphics.SetFat[dc,TRUE];
p ← XForm[p];
Graphics.MoveTo[thePath,p.x,p.y];
Graphics.SetCP[dc, p.x,p.y];
IF typescript THEN {
 PrintLine["\nbeginoutline"];
 PrintPoint[p];
IF fill THEN PrintLine["moveto"] ELSE PrintLine["setcp"];
 };
cp ← p;
};

DrawTo: PUBLIC PROC[dc: Graphics.Context, p: Complex.Vec] = {
p ← XForm[p];
Graphics.DrawTo[dc, p.x,p.y];
IF typescript THEN {PrintPoint[p]; PrintString["drawto "]};
cp ← p;
};

LineTo: PROC[dc: Graphics.Context, p: Complex.Vec] = {
p ← XForm[p];
Graphics.LineTo[thePath, p.x,p.y];
Graphics.SetCP[dc, p.x,p.y];
IF typescript THEN {
IF fill THEN {PrintPoint[p]; PrintString["lineto "]}
ELSE {PrintPoint[p]; PrintString["drawto "]};
};
cp ← p;
};

DrawLine: PUBLIC PROC[from, to: Complex.Vec] = {
Paint: PROC[dc: Graphics.Context] = {
MoveTo[dc,from];
DrawTo[dc,to];
};
TJaMGraphics.Painter[Paint];
};

DrawCubic: PROC[dc: Graphics.Context, b: Cubic.Bezier] = {
oldfill: BOOLEAN ← fill;
fill ← FALSE;
MoveTo[dc,b.b0];
CurveTo[dc,b.b1,b.b2,b.b3];
DrawPath[dc,0];
fill ← oldfill
};

CurveTo: PROC[dc: Graphics.Context, p1,p2,p3: Complex.Vec] = {
p1 ← XForm[p1];
p2 ← XForm[p2];
p3 ← XForm[p3];
Graphics.CurveTo[thePath,p1.x,p1.y,p2.x,p2.y,p3.x,p3.y];
Graphics.SetCP[dc, p3.x,p3.y];
IF typescript THEN {
IF fill THEN {
PrintPoint[p1];
PrintPoint[p2];
PrintPoint[p3];
PrintLine["curveto"];
}
ELSE {
PrintPoint[cp];
PrintPoint[p1];
PrintPoint[p2];
PrintPoint[p3];
PrintLine["bezier"];
};
};
cp ← p3;
};

DrawPath: PROC[dc: Graphics.Context, width: REAL] = {
Graphics.DrawStroke[dc,thePath,width];
};

DrawArea: PROC[dc: Graphics.Context, parityFill: BOOLEANFALSE] = {
Graphics.DrawArea[dc,thePath,parityFill];
IF typescript AND fill THEN PrintLine["endoutline"];
};

sLen: REAL ← 0;
SetSLen: PROC = {sLen ← GetReal[]};

fill: BOOLEANFALSE;
SetFill: PROC = {fill ← PopBoolean[]};

scale: REAL ← 1;
SetScale: PROC = {scale ← GetReal[]};

offset: Complex.Vec ← [0,0];
SetOffset: PROC = {offset.y ← GetReal[]; offset.x ← GetReal[]};

XForm: PROC[v: Complex.Vec] RETURNS[vt: Complex.Vec] =
{vt.x ← v.x*scale+offset.x; vt.y ← v.y*scale+offset.y};
IXForm: PROC[v: Complex.Vec] RETURNS[vt: Complex.Vec] =
{vt.x ← (v.x-offset.x)/scale; vt.y ← (v.y+offset.y)/scale};

typescript: BOOLEANFALSE;
SetTypescript: PROC = {
 typescript ← PopBoolean[];
IF typescript AND stream=NIL THEN {
  typescript ← FALSE;
  JaMFnsDefs.JaMExec["(no log file) .print"];
  };
 };

Note: PROC = {
c: LONG STRING ← [80];
PopString[c];
IF typescript THEN PrintString[c];
};

Notes: PROC = {
c: LONG STRING ← [80];
PopString[c];
IF typescript THEN {PrintString[c]; PrintString[" "]};
};

Noter: PROC = {
c: LONG STRING ← [80];
PopString[c];
IF typescript THEN {PrintLine[c]; stream.Flush[]};
};
Time: PROC = {
IF typescript THEN {IO.Put[stream,IO.time[]]; stream.Flush[]};
};


defaultHandle: PUBLIC Handle ← Create[];
[] ← Visible[defaultHandle];

JaMFnsDefs.Register[".startsa", StartSa];
JaMFnsDefs.Register[".resetsa", ResetSa];
JaMFnsDefs.Register[".addsa", AddSa];
JaMFnsDefs.Register[".countsa", CountSa];
JaMFnsDefs.Register[".foralllinks", ForAllLinks];
JaMFnsDefs.Register[".nextcon", NextCon];
JaMFnsDefs.Register[".resetcon", ResetCon];
JaMFnsDefs.Register[".addcon", AddCon];
JaMFnsDefs.Register[".countcon", CountCon];
JaMFnsDefs.Register[".interpolatesa", InterpolateSa]; -- d => . Interpolates samples to make deltas no larger than about d
JaMFnsDefs.Register[".drawsa", DrawSamples];
JaMFnsDefs.Register[".marksa", MarkSamples];
JaMFnsDefs.Register[".drawli", DrawLinks];
JaMFnsDefs.Register[".markli", MarkLinks];
JaMFnsDefs.Register[".marknodes", MarkNodes];
JaMFnsDefs.Register[".setslen", SetSLen];
JaMFnsDefs.Register[".setfill", SetFill];
JaMFnsDefs.Register[".setlog", SetTypescript];
JaMFnsDefs.Register[".setscale", SetScale];
JaMFnsDefs.Register[".setoffset", SetOffset];
JaMFnsDefs.Register[".setmarksize", SetMarkSize];
JaMFnsDefs.Register[".note", Note];
JaMFnsDefs.Register[".notes", Notes];
JaMFnsDefs.Register[".noter", Noter];
JaMFnsDefs.Register[".time", Time];

-- Sample Editing Commands
JaMFnsDefs.Register[".selectsa", SelectSa]; -- x y => . Selects a sample point for editing
JaMFnsDefs.Register[".thesa", TheSa]; -- => x y . Returns the current sample
JaMFnsDefs.Register[".thetan", TheTan]; -- => x y boolean . Returns the tangent of the current sample, and whether it is a node
JaMFnsDefs.Register[".deletesa", DeleteSa]; -- => . Deletes the current sample
JaMFnsDefs.Register[".insertsa", InsertSa]; -- x y => . Inserts before the current sample
JaMFnsDefs.Register[".insertbetween", InsertBetween]; -- x y => . Inserts between the current sample and the neighbor nearest the new point
JaMFnsDefs.Register[".nodesa", NodeSa]; -- boolean => . Makes or unmakes a node
JaMFnsDefs.Register[".tansa", TanSa]; -- deltax deltay => . Sets the tangent at a sample
JaMFnsDefs.Register[".cuspsa", CuspSa]; -- boolean => . Makes or unmakes a cusp
JaMFnsDefs.Register[".cuspnode", CuspNode]; -- boolean => . Makes or unmakes a cusp ONLY on a node
JaMFnsDefs.Register[".tanoutsa", TanOutSa]; -- deltax deltay => . Sets the outgoing tangent
JaMFnsDefs.Register[".homesa", HomeSa]; -- => . Selects the header
JaMFnsDefs.Register[".makefirstsa", MakeFirstSa]; -- => . Selects the header
JaMFnsDefs.Register[".nextsa", NextSa]; -- => x y . Moves selection to the next sample
JaMFnsDefs.Register[".prevsa", PrevSa]; -- => x y . Moves selection to the previous sample
JaMFnsDefs.Register[".scalesa", ScaleSa]; -- x y => . Scales/rotates all samples by multiplying by x+iy
JaMFnsDefs.Register[".transa", TranSa]; -- x y => . Translates all samples by adding x+iy
JaMFnsDefs.Register[".subrange", Subrange]; -- change CurrentSamples so it returns a subrange
JaMFnsDefs.Register[".allsa", NoSubrange]; -- remove the subrange
JaMFnsDefs.Register[".resetnodes", ResetDefaultNodes]; -- remove the nodes
JaMFnsDefs.Register[".resetcusps", ResetDefaultCusps]; -- remove the cusps
JaMFnsDefs.Register[".openlogfile", OpenLogFile]; -- filename => . Switches the log file.
JaMFnsDefs.Register[".closelogfile", CloseLogFile]; -- => . Closes the log file.

END.

Michael Plass August 13, 1982 9:48 am: Added multiple sample sets, InsertBetween.
Michael Plass August 13, 1982 10:29 am: Added CornerSa.
Michael Plass August 23, 1982 9:59 am: Added ForAllLinks.
Michael Plass August 23, 1982 2:31 pm: Sent Curve.Log to file.

Michael Plass August 23, 1982 2:34 pm: CornerSa moved out because of Storage overflow in Pass 3.
CornerSa: PROC = {OPEN Vector;
 samp: SampleHandle ← IF defaultHandle.slist.selectedSample = defaultHandle.slist.header THEN defaultHandle.slist.selectedSample.next ELSE defaultHandle.slist.selectedSample;
 p: SampleHandle ← IF samp.prev = defaultHandle.slist.header THEN samp.prev.prev ELSE samp.prev;
 pp: SampleHandle ← IF p.prev = defaultHandle.slist.header THEN p.prev.prev ELSE p.prev;
 n: SampleHandle ← IF samp.next = defaultHandle.slist.header THEN samp.next.next ELSE samp.next;
 nn: SampleHandle ← IF n.next = defaultHandle.slist.header THEN n.next.next ELSE n.next;
 a: Vec ← pp.xy;
 b: Vec ← p.xy;
 c: Vec ← nn.xy;
 d: Vec ← n.xy;
 coeffMat: Matrix ← [a.y-b.y, b.x-a.x, c.y-d.y, d.x-c.x];
 r: Vec ← [Det[[b.x, b.y, a.x, a.y]], Det[[d.x, d.y, c.x, c.y]]];
 denom: REAL ← Det[coeffMat];
 xTimesDenom: REAL ← Det[[r.x, coeffMat.a12, r.y, coeffMat.a22]];
 yTimesDenom: REAL ← Det[[coeffMat.a11, r.x, coeffMat.a21, r.y]];
IF denom=0 THEN RETURN;
 samp.xy.x ← xTimesDenom/denom;
 samp.xy.y ← yTimesDenom/denom;
 };
JaMFnsDefs.Register[".cornersa", CornerSa]; -- => . moves the selected sample so it is colinear with its two left neightbors and with its two right neighbors

Michael Plass August 24, 1982 9:48 am: Removed Curve.Log typescript.
Michael Plass August 27, 1982 7:21 pm: Bulletproofed against empty links.
Michael Plass August 30, 1982 1:33 pm: Added OpenLogFile.
Michael Plass September 28, 1982 12:26 pm: Added CloseLogFile.
Michael Plass December 7, 1982 10:25 am: Took out Transaction import for 3.5.