--Stand alone contouring stuff. Goes in config with XMemory and AISObjImpl
--major use it to write contour files for use in JaMGraphics.
--Written by M.Stone
--Last changed by M.Stone April 7, 1981 4:39 PM

DIRECTORY
InlineDefs,
MiscDefs,
Graphics,
JaMGraphicsDefs,
ImageObj,
Cubic,
ContourDefs,
XMemory,
Vector USING[Vec,Sub,Gr,Mul],
AISObj,
Real,
StringDefs,
StreamDefs,
IODefs,
JaMFnsDefs;

ContourMain: PROGRAM IMPORTS Vector, XMemory, MiscDefs, AISObj, ImageObj, JaMFnsDefs, Graphics, JaMGraphicsDefs, Real, StringDefs, StreamDefs, IODefs EXPORTS ContourDefs =
BEGIN OPEN JaMFnsDefs, Vector, ContourDefs;

cZone: PUBLIC UNCOUNTED ZONE ← XMemory.NewZone["contour"];

dc: Graphics.DisplayContext ← JaMGraphicsDefs.GetDC[];
aisXLen,aisYLen: CARDINAL;
ais: ImageObj.Handle ← NIL;
linksList: LineLinkHandle ← cZone.NEW[line Link];
lastLink: LineLinkHandle ← linksList;
contourList: ContourHandle ← cZone.NEW[Contour];
pathList: PathHandle ← NIL;
lastPath: PathHandle ← pathList;
ty,by,rx,lx: REAL ← 0;

JOpenAIS: PROCEDURE = {ENABLE StreamDefs.FileNameError =>{
IODefs.WriteString[name];
IODefs.WriteLine[" file not found"];
CONTINUE};
s:STRING←[64];
PopString[s];
IF ais#NIL THEN JFree[];
ais←AISObj.NewAISObj[s];
rx ← aisXLen ← ImageObj.GetScanLength[ais];
by ← aisYLen ← ImageObj.GetScanCount[ais];
ty ← lx ← 0;
};

SetTValue: PROCEDURE = { tValue ← PopReal[]};

SetNPoints: PROCEDURE = { nPoints ← PopInteger[]};

KludgeOn: PROCEDURE = { kludge ← TRUE};
KludgeOff: PROCEDURE = { kludge ← FALSE};
OnePoly: PROCEDURE = { onePoly ← ~onePoly};

DrawAIS:PROCEDURE= {OPEN Graphics;
IF ais=NIL THEN RETURN;
StartAreaPath[dc];
EnterPoint[dc,[0,0]];
EnterPoint[dc,[2000,0]];
EnterPoint[dc,[2000,2000]];
EnterPoint[dc,[0,2000]];
DrawImage[dc,ais]
};

DrawSubAIS:PROCEDURE= {OPEN Graphics;
IF ais=NIL THEN RETURN;
PushContext[dc];
Translate[dc,[-lx,-ty]];
StartAreaPath[dc];
EnterPoint[dc,[lx,ty]];
EnterPoint[dc,[rx,ty]];
EnterPoint[dc,[rx,by]];
EnterPoint[dc,[lx,by]];
DrawImage[dc,ais];
PopContext[dc];
};

SetAISBox:PROCEDURE= {
temp: REAL ← 0;
by ← GetReal[];
rx ← GetReal[];
ty ← GetReal[];
lx ← GetReal[];
IF by < ty THEN {temp ← by; by ← ty; ty ← by};
IF rx < lx THEN {temp ← rx; rx ← lx; lx ← rx};
};

JFitContour: PROCEDURE = {
count: INTEGER ← 0;
p:ContourHandle;
cn: INTEGER ← PopInteger[];
FOR p ← contourList, p.next UNTIL p=NIL OR count=cn
DO count ← count+1; ENDLOOP;
IF p#NIL THEN FitContour[p.links];
};

AnalyzePath: PROCEDURE = {
count: INTEGER ← 0;
p:ContourHandle;
cn: INTEGER ← PopInteger[];
FOR p ← contourList, p.next UNTIL p=NIL OR count=cn
DO count ← count+1; ENDLOOP;
--
IF p#NIL THEN ContourDefs.Analyze[p.links];
};

GetSDChar: PROCEDURE = {
s: STRING ← [10];
PopString[s];
InitContours[];
--
contourList ← ContourDefs.GetSDContours[s[0]];
};

JFree: PROCEDURE = {
IF ais=NIL THEN RETURN;
ImageObj.Free[@ais];
ais ← NIL;
};

LineCount: PROCEDURE = {
PushInteger[lineCount];
};

ContourCount: PROCEDURE = {
PushInteger[contourCount];
};

BuildLinks: PROCEDURE = {
buildLinks ← ~buildLinks;
};

JMakeContour: PROCEDURE = {
MakeContour[];
PruneContours[];
DrawContour[];
};

JDrawLinks: PROCEDURE = {
DrawLinks[linksList];
};

FitContour: PROCEDURE[links: LinkHandle] = {
path: PathHandle;
--
path ← ContourDefs.Fit[links];
IF pathList=NIL THEN pathList ← path
ELSE lastPath.next ← path;
lastPath ← path;
};

DrawPaths: PROCEDURE = {
path: PathHandle ← pathList;
UNTIL path=NIL DO
DrawLinks[path.links];
path ← path.next;
ENDLOOP;
};

FillPaths: PROCEDURE = {
path: PathHandle ← pathList;
Graphics.StartAreaPath[dc];
UNTIL path=NIL DO
Graphics.NewBoundary[dc];
EnterLinks[path.links];
path ← path.next;
ENDLOOP;
Graphics.DrawArea[dc];
};

FreePathList: PROCEDURE = {
p: PathHandle;
path: PathHandle ← pathList;
UNTIL path=NIL DO
p ← path.next;
--
ContourDefs.FreePath[path];
path ← p;
ENDLOOP;
pathList ← NIL;
lastPath ← pathList;
};

lineCount: INTEGER ← 0;
contourCount: INTEGER ← 0;
Outline: PROC = {
aisX, aisY: REAL ← 0;
v1,v2,v3,v4: CARDINAL;
i,j,ity,iby,ilx,irx: INTEGER;
IF ais=NIL THEN RETURN;
lineCount ← 0;
contourCount ← 0;
InitContours[];
ity ← Real.FixI[ty];
iby ← Real.FixI[by];
ilx ← Real.FixI[lx];
irx ← Real.FixI[rx];
--top line, start with top left corner
v3 ← ImageObj.ReadSample[ais,ilx,ity];
ContourCell[255,255,v3,255,ilx-1,ity-1];
FOR j IN [ilx..irx-1) DO
aisX ← j;
v3 ← ImageObj.ReadSample[ais,aisX+1,ity];
v4 ← ImageObj.ReadSample[ais,aisX,ity];
ContourCell[255,255,v3,v4,aisX,aisY];
ENDLOOP;
--top right corner
v4 ← ImageObj.ReadSample[ais,irx,ity];
ContourCell[255,255,255,v4,irx-1,ity-1];
--main part of image
FOR i IN [ity..iby-1) DO
aisY ← i;
IF JaMFnsDefs.GetJaMBreak[] THEN EXIT;
--left edge
v2 ← ImageObj.ReadSample[ais,ilx,aisY];
v3 ← ImageObj.ReadSample[ais,ilx,aisY+1];
ContourCell[255,v2,v3,255,ilx-1,aisY];
FOR j IN [ilx..irx-1) DO
aisX ← j;
v1 ← ImageObj.ReadSample[ais,aisX,aisY];
v2 ← ImageObj.ReadSample[ais,aisX+1,aisY];
v3 ← ImageObj.ReadSample[ais,aisX+1,aisY+1];
v4 ← ImageObj.ReadSample[ais,aisX,aisY+1];
ContourCell[v1,v2,v3,v4,aisX,aisY];
ENDLOOP;
--right edge
v1 ← ImageObj.ReadSample[ais,irx-1,aisY];
v4 ← ImageObj.ReadSample[ais,irx-1,aisY+1];
ContourCell[v1,255,255,v4,irx-1,aisY];
ENDLOOP;
--bottom line, start with bottom left corner
v2 ← ImageObj.ReadSample[ais,ilx,iby-1];
ContourCell[255,v2,255,255,ilx-1,iby-1];
FOR j IN [ilx..irx-1) DO
aisX ← j;
v1 ← ImageObj.ReadSample[ais,aisX,iby-1];
v2 ← ImageObj.ReadSample[ais,aisX+1,iby-1];
ContourCell[v1,v2,255,255,aisX,iby-1];
ENDLOOP;
--bottom right corner
v1 ← ImageObj.ReadSample[ais,irx-1,iby-1];
ContourCell[v1,255,255,255,irx-1,iby-1];
};

Trans: TYPE = RECORD[x,y,d: REAL, dir:{nt,dl,ld}];
ContourCell: PROC[v1,v2,v3,v4:CARDINAL, aisX,aisY:REAL] = { OPEN Graphics;
edgePts: ARRAY [0..3] OF Trans;
i,ldCount,dlCount,index,move: CARDINAL;
baseX,baseY: REAL;
FindLD: PROC [start: CARDINAL] RETURNS[CARDINAL] = {
FOR k: CARDINAL IN [start..3] DO
IF edgePts[k].dir=ld THEN RETURN[k];
ENDLOOP;
ERROR;
};
FindDL: PROC [start: CARDINAL] RETURNS[CARDINAL] = {
FOR k: CARDINAL IN [start..3] DO
IF edgePts[k].dir=dl THEN RETURN[k]; ENDLOOP;
FOR k: CARDINAL IN [0..start) DO
IF edgePts[k].dir=dl THEN RETURN[k]; ENDLOOP;
ERROR;
};

baseX ← aisX-lx;
baseY ← aisY-ty;
--
v1 v2clockwise
--
v4 v3
edgePts[0] ← ContourEdge[v1,v2];
edgePts[0].x ← baseX+edgePts[0].d; edgePts[0].y ← baseY;
edgePts[1] ← ContourEdge[v2,v3];
edgePts[1].x ← baseX+1; edgePts[1].y ← baseY+edgePts[1].d;
--switch the order here for numerical reasons. Must then invert the dir
edgePts[2] ← ContourEdge[v4,v3];
edgePts[2].x ← baseX+edgePts[2].d; edgePts[2].y ← baseY+1;
edgePts[2].dir ←
(SELECT edgePts[2].dir FROM ld => dl, dl => ld, ENDCASE => nt);
edgePts[3] ← ContourEdge[v1,v4];
edgePts[3].x ← baseX; edgePts[3].y ← baseY+edgePts[3].d;
edgePts[3].dir ←
(SELECT edgePts[3].dir FROM ld => dl, dl => ld, ENDCASE => nt);
ldCount ← dlCount ← 0;
FOR i IN [0..3] DO
SELECT edgePts[i].dir FROM
dl => dlCount ← dlCount+1;
ld => ldCount ← ldCount+1;
ENDCASE;
ENDLOOP;
IF dlCount#ldCount THEN MiscDefs.CallDebugger["odd transitions"];
IF dlCount>2 THEN MiscDefs.CallDebugger["too many transitions"];
IF dlCount=0 THEN RETURN;--no transitions
--ok, now have transitions. Need to connect ld to dl
--this prefers solid dark areas
move ← index ← FindLD[0];
MoveTo[dc,[edgePts[index].x,edgePts[index].y]];
index ← FindDL[index];
DrawTo[dc,[edgePts[index].x,edgePts[index].y]];
lineCount ← lineCount+1;
AddLink[edgePts[move].x,edgePts[move].y,edgePts[index].x,edgePts[index].y];
IF dlCount=2 THEN BEGIN
move ← index ← FindLD[index];
MoveTo[dc,[edgePts[index].x,edgePts[index].y]];
index ← FindDL[index];
DrawTo[dc,[edgePts[index].x,edgePts[index].y]];
AddLink[edgePts[move].x,edgePts[move].y,
edgePts[index].x,edgePts[index].y];
lineCount ← lineCount+1;
END;

};

tValue: REAL ← 128.5;
nPoints: INTEGER ← 4;
ContourEdge: PROC[v0,v1: REAL] RETURNS[trans: Trans] = {
trans ← [x:0, y:0, d:0, dir:nt];
IF v0<tValue AND v1<tValue THEN RETURN;
IF v0>tValue AND v1>tValue THEN RETURN;
trans.d ← (v0-tValue)/(v0-v1);
IF v0<tValue THEN trans.dir ← ld ELSE trans.dir ← dl;
};

DeleteLinks: PROC [links: LinkHandle] = {
p: LinkHandle;
UNTIL links=NIL DO
p ← links.next;
cZone.FREE[@links];
links ← p;
ENDLOOP;
};

InitContours: PROC = {
r,s: ContourHandle;
DeleteLinks[linksList.next];
DeleteLinks[contourList.links];
r ← contourList.next;
UNTIL r=NIL DO
s ← r.next;
DeleteLinks[r.links];
cZone.FREE[@r];
r ← s;
ENDLOOP;
linksList↑ ← [NIL,[0,0],line[[0,0]]];
lastLink ← linksList;
contourList↑ ← [NIL,NIL,NIL];
};

buildLinks: BOOLEAN ← TRUE;
AddLink: PROC[x0,y0,x1,y1: REAL] = {OPEN Real;
newLink: LineLinkHandle;
IF ~buildLinks THEN RETURN;
newLink ← cZone.NEW[line Link];
newLink↑ ← [NIL,[x0,y0],line[[x1,y1]]];
lastLink↑.next ← newLink;
lastLink ← newLink;
};

LineProc: TYPE = PROC[l: line Link];
CubicProc: TYPE = PROC[c: cubic Link];
NullCP: CubicProc = {};
FirstLinkProc: TYPE = PROC[p: Vec];
NullFLP: FirstLinkProc = {};

ForAllLinks: PROC [list: LinkHandle, line: LineProc, cubic: CubicProc ← NullCP, first: FirstLinkProc ← NullFLP] = {
IF list=NIL THEN RETURN;
first[list.p0];
FOR link: LinkHandle ← list,link.next UNTIL link=NIL DO
WITH l: link SELECT FROM
line => line[l];
cubic => cubic[l];
ENDCASE;
ENDLOOP;
};

--draw at given scale
DrawLinks: PROC [list: LinkHandle,scale: REAL ← 1] = {
line: LineProc = {
--we don’t know that the links are in a contour
Graphics.MoveTo[dc,Mul[l.p0,scale]];
Graphics.DrawTo[dc,Mul[l.p1,scale]]
};
cubic: CubicProc = {
cc: Cubic.Coeffs ← c.coeffs;
cc.c0 ← Mul[cc.c0,scale];
cc.c1 ← Mul[cc.c1,scale];
cc.c2 ← Mul[cc.c2,scale];
cc.c3 ← Mul[cc.c3,scale];
};
Graphics.SetPaint[dc,replace];
ForAllLinks[list,line,cubic];
};

EnterLinks: PROC [list: LinkHandle] = {
first: FirstLinkProc = {Graphics.MoveTo[dc,list.p0]};
line: LineProc = {Graphics.EnterPoint[dc,l.p1]};
cubic: CubicProc = {
cc: Cubic.Coeffs ← c.coeffs;
Graphics.EnterCubic[dc,@cc]
};
ForAllLinks[list,line,cubic,first];
};

WriteLinks: PROC [stream: StreamDefs.DiskHandle, list: LinkHandle] = {
line: LineProc = {WritePoint[stream,l.p1]};
cubic: CubicProc = {
cc: Cubic.Coeffs ← c.coeffs;
WriteCubic[stream,cc]
};
ForAllLinks[list,line,cubic];
};

kludge: BOOLEAN ← TRUE;
onePoly: BOOLEAN ← FALSE;
XFormPt: PROC [p: Vec] RETURNS[Vec] = {
p ← [p.x-minX,maxY-p.y];
--scale up to avoid scientific notation problem
IF kludge THEN {
p ← Mul[p,1000.0];
p ← [Real.RoundLI[p.x],Real.RoundLI[p.y]];
p ← Mul[p,0.01]
};
RETURN[p];
};

WritePoint: PROC [stream: StreamDefs.DiskHandle, p: Vec] = {
p ← XFormPt[p];
WriteReal[stream,p.x];
WriteReal[stream,p.y];
WriteString[stream, ".enterpoint
"];
};


WriteBB: PROC [stream: StreamDefs.DiskHandle, ll,ur: Vec] = {
ll ← XFormPt[ll];
WriteReal[stream,ll.x];
WriteReal[stream,ll.y];
ur ← XFormPt[ur];
WriteReal[stream,ur.x];
WriteReal[stream,ur.y];
WriteString[stream, "bb "];
};

WriteReal: PROC [stream: StreamDefs.DiskHandle, r: REAL] = {
put: PROC[c: CHARACTER] = {stream.put[stream,c];};
Real.WriteReal[put,r];
stream.put[stream,’ ];
};

WriteCubic: PROC [stream: StreamDefs.DiskHandle, c: Cubic.Coeffs] = {
c.c0 ← XFormPt[c.c0];
WriteReal[stream,c.c0.x];
WriteReal[stream,c.c0.y];
WriteReal[stream,c.c1.x];
WriteReal[stream,c.c1.y];
WriteReal[stream,c.c2.x];
WriteReal[stream,c.c2.y];
WriteReal[stream,c.c3.x];
WriteReal[stream,c.c3.y];
WriteString[stream, ".entercubic
"];
};

WriteString: PROC [stream: StreamDefs.DiskHandle, s: STRING] = {
FOR i: CARDINAL IN [0..s.length) DO stream.put[stream,s[i]]; ENDLOOP;
};

DrawContour: PROC = {
r: ContourHandle;
contourCount ← 0;
FOR r ← contourList, r.next UNTIL r=NIL DO
contourCount ← contourCount+1;
DrawLinks[r.links]
ENDLOOP;
};

minX,maxX,minY,maxY: REAL ← 0;
SetMinMax: PROC = {
minx,miny,maxx,maxy: REAL ← 0;
r: ContourHandle;
minX ← rx; minY ← by;
maxX ← lx; maxY ← ty;
FOR r ← contourList, r.next UNTIL r=NIL DO
[minx,miny,maxx,maxy] ← MinMax[r.links];
IF maxx>maxX THEN maxX ← maxx;
IF maxy>maxY THEN maxY ← maxy;
IF minx<minX THEN minX ← minx;
IF miny<minY THEN minY ← miny;
ENDLOOP;
Graphics.MoveTo[dc,[minX,minY]];
Graphics.DrawTo[dc,[maxX,minY]];
Graphics.DrawTo[dc,[maxX,maxY]];
Graphics.DrawTo[dc,[minX,maxY]];
Graphics.DrawTo[dc,[minX,minY]];
};

MinMax: PROC[list: LinkHandle] RETURNS [minx,miny,maxx,maxy: REAL] = {
line: LineProc = {
IF l.p1.x>maxx THEN maxx ← l.p1.x;
IF l.p1.y>maxy THEN maxy ← l.p1.y;
IF l.p1.x<minx THEN minx ← l.p1.x;
IF l.p1.y<miny THEN miny ← l.p1.y;
};
minx ← rx; miny ← by;
maxx ← lx; maxy ← ty;
ForAllLinks[list,line];
Graphics.MoveTo[dc,[minx,miny]];
Graphics.DrawTo[dc,[maxx,miny]];
Graphics.DrawTo[dc,[maxx,maxy]];
Graphics.DrawTo[dc,[minx,maxy]];
Graphics.DrawTo[dc,[minx,miny]];
};

NewPoly: PROC[stream: StreamDefs.DiskHandle,list: LinkHandle] = {
l: LinkHandle ← NIL;
minx,miny,maxx,maxy: REAL ← 0;
black: BOOLEAN ← TRUE;
WriteString[stream,".drawarea

"];
[minx,miny,maxx,maxy] ← MinMax[list];
--not a rigorous test. IF ymax were after xmin and xmax in the list,
--it would fail. However, links start near ymin, so this should be ok
FOR l ← list, l.next UNTIL l=NIL OR l.p0.y=maxy DO ENDLOOP;
UNTIL l=NIL DO
IF l.p0.x=minx THEN {black ← TRUE; EXIT};
IF l.p0.x=maxx THEN {black ← FALSE; EXIT};
l ← l.next;
ENDLOOP;
WriteBB[stream,[minx,maxy],[maxx,miny]];
WriteString[stream,(IF black THEN "black " ELSE "white ")];
WriteString[stream,".startpath
"];
};

WriteContour: PROC = {
r: ContourHandle;
s: STRING ← [64];
sf: STRING ← [64];
first: BOOLEAN ← TRUE;
minx,miny,maxx,maxy: REAL ← 0;
stream: StreamDefs.DiskHandle;
PopString[s];
StringDefs.AppendString[sf,s];
StringDefs.AppendString[sf,".jam"];
stream ← StreamDefs.NewByteStream[sf,StreamDefs.ReadWriteAppend];
SetMinMax[];
WriteString[stream,"("];
WriteString[stream,s];
WriteString[stream,")("];
WriteBB[stream,[minX,maxY],[maxX,minY]];
WriteString[stream,"black .startpath
"];
FOR r ← contourList, r.next UNTIL r=NIL DO
IF first THEN first ← FALSE
ELSE IF onePoly
THEN WriteString[stream,"
.newboundary "]
ELSE NewPoly[stream,r.links];
WriteLinks[stream,r.links]
ENDLOOP;
WriteString[stream,"
.drawarea).cvx .def
"];
StreamDefs.TruncateDiskStream[stream];
};

EnterContour: PROC = {
r: ContourHandle;
contourCount ← 0;
FOR r ← contourList, r.next UNTIL r=NIL DO
contourCount ← contourCount+1;
EnterLinks[r.links];
Graphics.NewBoundary[dc];
ENDLOOP;
};

DeleteContour: PROC = {
--call with a .touch
tol: REAL ← 1;
contour,prev,r: ContourHandle ← NIL;
y: REAL ← GetReal[];
x: REAL ← GetReal[];
found: BOOLEAN ← FALSE;
pt: Vec ← [x,y];
p0: Vec ← [0,0];
line: LineProc = {
p0 ← Sub[pt,l.p0];
IF p0.x IN [-tol..tol] AND p0.y IN [-tol..tol] THEN found ← TRUE;
};
FOR r ← contourList, r.next UNTIL r=NIL DO
contour ← r;
ForAllLinks[contour.links,line];
IF found THEN EXIT;
prev ← contour;
ENDLOOP;
IF ~found THEN RETURN;
--a contour was found. Remove it from the list and delete it
IF prev = NIL THEN contourList ← contourList.next
ELSE prev.next ← contour.next;
Graphics.PushContext[dc];
Graphics.SetTexture[dc,Graphics.white];
DrawLinks[contour.links];
Graphics.PopContext[dc];
DeleteLinks[contour.links];
cZone.FREE[@contour];
};

PruneContours: PROC = {
r,s: ContourHandle;
contourCount ← 0;
r ← contourList;
IF r.links=NIL THEN RETURN;
UNTIL r=NIL DO
IF r.links.p0=r.lastLink.p1 AND r.links#r.lastLink
THEN {contourCount ← contourCount+1; s ← r; r ← r.next}
ELSE {DeleteLinks[r.links];
IF r=contourList THEN
{contourList ← contourList.next; cZone.FREE[@r]; r ← contourList}
ELSE {s.next ← r.next; cZone.FREE[@r]; r ← s.next}};
ENDLOOP;
};

MakeContour: PROC = {
contour: ContourHandle ← contourList;
link: LineLinkHandle;
--put first link into first contour
AddLinkToContour[contour,NIL];
--search the links list for adjoining segments to add to contour list
UNTIL linksList.next=NIL DO
link ← LOOPHOLE[FindLink[contour.lastLink.p1]];
--if no more links in current contour, start a new contour
IF link=NIL THEN {contour.next ← cZone.NEW[Contour];
contour ← contour.next; contourCount ← contourCount+1};
--AddLinkToContour will delete a link from LinksList
AddLinkToContour[contour,link]
ENDLOOP;
};

--know that all Links in linksList are line Links
AddLinkToContour: PROC[contour: ContourHandle, link: LineLinkHandle] = {
l: LineLinkHandle ← linksList;
IF link=NIL THEN link ← LOOPHOLE[linksList.next];
IF contour.links=NIL THEN contour.links ← link
ELSE contour.lastLink↑.next ← link;
contour.lastLink ← link;
FOR l ← linksList, l ← LOOPHOLE[l.next] UNTIL l.next=link DO ENDLOOP;
l.next ← link.next;
link.next ← NIL;
};

FindLink: PROC[pt: Vec] RETURNS[LinkHandle] = {
p0: Vec ← [0,0];
minP0: Vec ← [10000,10000];
FOR links: LinkHandle ← linksList.next,links.next UNTIL links=NIL DO
p0 ← Sub[pt,links.p0];
p0 ← [ABS[p0.x],ABS[p0.y]];
IF Gr[minP0,p0] THEN minP0 ← p0;
IF pt=links.p0 THEN RETURN[links];
ENDLOOP;
RETURN[NIL];
};

--XMemory STARTed with call to NewZone
--ProgFFT will be STARTed with call to FFT. No module code in ProgFFT
Register[".setupais"L,JOpenAIS];
Register[".freeais"L,JFree];
Register[".tvalue"L,SetTValue];
Register[".npoints"L,SetNPoints];
Register[".outline"L,Outline];
Register[".drawmyais"L,DrawAIS];
Register[".drawsub"L,DrawSubAIS];
Register[".setbox"L,SetAISBox];
Register[".linecount"L,LineCount];
Register[".contourcount"L,ContourCount];
Register[".drawlinks"L,JDrawLinks];
Register[".makecontours"L,JMakeContour];
Register[".drawcontours"L,DrawContour];
Register[".writecontours"L,WriteContour];
Register[".prunecontours"L,PruneContours];
Register[".entercontours"L,EnterContour];
Register[".buildlinks"L,BuildLinks];
Register[".fitcontour"L,JFitContour];
Register[".drawpaths"L,DrawPaths];
Register[".fillpaths"L,FillPaths];
Register[".freepaths"L,FreePathList];
Register[".analyze"L,AnalyzePath];
Register[".getsd"L,GetSDChar];
Register[".kludgeon"L,KludgeOn];
Register[".kludgeoff"L,KludgeOff];
Register[".onepoly"L,OnePoly];
Register[".deletecontour"L,DeleteContour];

END.