-- ParametricMapImpl.mesa
-- Last changed by John Warnock
-- Last changed by Maureen Stone November 28, 1982 4:37 pm
-- Last changed by Rick Beach, November 30, 1982 2:24 am

DIRECTORY
    ParametricMap,
    GraphicsOps USING[UserToDevice],
    CGVector USING [Add,Min,Max,Sub,In],
    CGCubic USING[Bezier,Split,Flat],
    RealFns USING [SqRt],
    Graphics,
    GraphicsBasic USING [Vec];

ParametricMapImpl: PROGRAM IMPORTS GraphicsOps,RealFns,CGVector,CGCubic EXPORTS ParametricMap= 
{OPEN ParametricMap,GraphicsBasic,CGVector,CGCubic,Graphics,GraphicsOps;

dc:Context;
cpx,cpy:REAL;
map:PROC[u:Vec] RETURNS [t:Vec];

SetMap:PUBLIC PROC[f:PROC[u:Vec] RETURNS [t:Vec]]=
{CacheInit[];
map←f;};

MapMoveTo:PUBLIC PROC[self: Graphics.Context, p:Vec] RETURNS[tp:Vec]={
dc ← self;
cpx←p.x;
cpy←p.y;
RETURN[map[p]];
};

MapCurveTo:PUBLIC PROC[p1,p2,p3:Vec,point:PROC[v:Vec]]={
bz:Bezier;
bz←Bezier[Vec[cpx,cpy],p1,p2,p3];
MapCSplit[bz,point];
cpx←p3.x;
cpy←p3.y;
RETURN;
};

MapLineTo: PUBLIC PROC[p:Vec,point:PROC[v:Vec]]={
MapLSplit[Vec[cpx,cpy],p,point];
cpx←p.x;
cpy←p.y;
RETURN;
};

MapCSplit:PROC [bz:Bezier,point:PROC[v:Vec]]=
{mbz:Bezier;
v0,v1,v2,v3,v3p:Vec;
v0←UToD[map[bz.b0]];
v1←UToD[map[bz.b1]];
v2←UToD[map[bz.b2]];
v3p←map[bz.b3];
v3←UToD[v3p];
mbz←Bezier[v0,v1,v2,v3];
IF CGCubic.Flat[mbz,1.0] -- was 0.5 originally
   THEN {point[v3p]; RETURN;}
    ELSE {bz1,bz2:Bezier;
            [bz1,bz2]←Split[bz];
            MapCSplit[bz1,point]; 
            MapCSplit[bz2,point]; 
            RETURN;};
 };

MapLSplit:PROC [p1,p2:Vec,point:PROC[v:Vec]]=
{v1,v2,v2p,v3,v3p,pi:Vec;
pi←Vec[(p1.x+p2.x)*.5,(p1.y+p2.y)*.5];
v1←UToD[map[p1]];
v2p←map[pi];
v2←UToD[v2p];
v3p←map[p2];
v3←UToD[v3p];
IF Flat[v1,v2,v3,1.0] -- was 0.5 originally
    THEN {point[v2p]; point[v3p]; RETURN;}
    ELSE {MapLSplit[p1,pi,point]; MapLSplit[pi,p2,point];RETURN;};
 };

UToD:PROC[v:Vec]RETURNS[tv:Vec]=
{x,y:REAL;
[x,y]←UserToDevice[dc,v.x,v.y,FALSE];
RETURN[Vec[x,y]];
};

GetTforArc:PUBLIC PROC [bz:LONG POINTER TO Bezier,arc:REAL] RETURNS [success:BOOLEAN,t:REAL]=
{exact:BOOLEAN;
bzs:Bezier;
t0,t1,arcl:REAL;
IF ~validCache THEN InitCache[bz];
[exact,t0,t1,arcl]←FindCSec[arc];
IF exact THEN RETURN [TRUE,t0];
bzs←CubicPiece[bz,t0,t1];
[success,t,]←ArcSplit[@bzs,0.0,0.0,arc-arcl,1,0];
t←t0+t*(t1-t0);
RETURN[success,t];
};

ArcLength:PUBLIC PROC [bz:LONG POINTER TO Bezier] RETURNS [arc:REAL]=
{IF ~validCache THEN InitCache[bz];
RETURN[vcache[0]];
};

FindCSec:PROC[arc:REAL] RETURNS [exact:BOOLEAN,t0,t1,tarc:REAL]=
 	{cnt,level:CARDINAL;
 	t:REAL;
 	IF arc =vcache[0] THEN RETURN[TRUE,1,1,arc];
 	tarc←0;
 	t0←0;
 	t1←1.0;
 	level←0;
	cnt←1;
 	UNTIL level = maxlevel DO
 	 	t←(t0+t1)/2;
 	 	SELECT vcache[cnt] FROM
  			< arc =>{t0←t0+Exp2[level];
  					  tarc←vcache[cnt];
  				      cnt←cnt*2+1;};
  			= arc =>RETURN[TRUE,t,t,vcache[cnt]];
  			> arc =>{t1←t1-Exp2[level];
  				      cnt←cnt*2;};
	ENDCASE;
 	level←level+1;
  	ENDLOOP;
 	RETURN[FALSE,t0,t1,tarc];
 	};
 

InitCache:PUBLIC PROC [bz:LONG POINTER TO Bezier]=
{vcache[0]←ArcAdd[bz,0.0,1,0];
validCache ← TRUE;
};

ArcAdd:PUBLIC PROC [bz:LONG POINTER TO Bezier,sum:REAL,cnt,level:CARDINAL] RETURNS [arc:REAL]=
{IF CGCubic.Flat[bz↑,0.1] AND level >= maxlevel
	THEN {tarc:REAL;
			tarc←Mag[Sub[bz.b3,bz.b0]]+sum;
			RETURN[tarc];}
	ELSE {tarc:REAL;
    		bz1,bz2:Bezier;
            [bz1,bz2]←Split[bz↑];
            tarc←ArcAdd[@bz1,sum,cnt*2,level+1]; 
            IF level < maxlevel THEN vcache[cnt]←tarc;
			tarc←ArcAdd[@bz2,tarc,cnt*2+1,level+1]; 
            RETURN[tarc];};
 };


ArcSplit:PROC [bz:LONG POINTER TO Bezier,t,sum,arc:REAL,cnt,level:CARDINAL] RETURNS [success:BOOLEAN,dt,arcl:REAL]=
{ IF CGCubic.Flat[bz↑,0.1]
	THEN {tt:REAL;
			da:REAL;
 			darc:REAL←Mag[Sub[bz.b3,bz.b0]];
			IF (da←darc+sum) < arc 
   				THEN {RETURN[FALSE,t+Exp2[level],da];}
				ELSE {tt←PSolve[bz,t,sum,arc,darc,level];
						RETURN[TRUE,tt,arc];};}
	ELSE {sccs:BOOLEAN;
    		tt,tarc:REAL;
    		bz1,bz2:Bezier;
            [bz1,bz2]←Split[bz↑];
            [sccs,tt,tarc]←ArcSplit[@bz1,t,sum,arc,cnt*2,level+1]; 
            IF sccs THEN {RETURN [sccs,tt,tarc]; }
            ELSE {[sccs,tt,tarc]←ArcSplit[@bz2,t+Exp2[level],tarc,arc,cnt*2+1,level+1]; 
            RETURN[sccs,tt,tarc];};};
 };


PSolve:PROC[bz:LONG POINTER TO Bezier,t,sum,arc,darc:REAL,level:CARDINAL] RETURNS[dt:REAL]=
{HalfBezier:TYPE=RECORD[c0,c1,c2,c3:REAL];
lcnt:CARDINAL;
eps,fd,fchange,fdel,fcmax,qt,dx,dy,tarc,delarc,fnew,fold:REAL;
dz,cz:HalfBezier;
eps←0.0001;
dx←bz.b3.x-bz.b0.x;
dy←bz.b3.y-bz.b0.y;
delarc←(arc-sum);
IF ABS[dy] < ABS[dx] 
	THEN {tarc←delarc*dx/darc;
			cz←HalfBezier[0,bz.b1.x-bz.b0.x,bz.b2.x-bz.b0.x,bz.b3.x-bz.b0.x]}
	ELSE  {tarc←delarc*dy/darc;
			cz←HalfBezier[0,bz.b1.y-bz.b0.y,bz.b2.y-bz.b0.y,bz.b3.y-bz.b0.y];};
  -- d0 ← c0;
  -- d1 ← 3(c1-c0);
  -- d2 ← 3(c0-2c1+c2); [ = 3(c2-c1) - d1 ]
  -- d3 ← -c0 +3(c1-c2) + c3; [ = c3 - (c0 + 3(c2-c1)) ]
 qt←3*(cz.c2-cz.c1);
 dz.c0 ← cz.c0;
 dz.c1 ← 3*(cz.c1-cz.c0);
 dz.c2 ← qt-dz.c1;
 dz.c3 ← cz.c3-(cz.c0+qt);
fnew←1;
fold←0;
lcnt←0;
fcmax←1.0;
UNTIL ABS[fnew-fold] < eps OR lcnt = 10 DO
fold←fnew;
fd←(3*dz.c3*fold+2*dz.c2)*fold+dz.c1;
IF fd = 0 THEN EXIT;
fchange←-(((dz.c3*fold+dz.c2)*fold+dz.c1)*fold+dz.c0-tarc)/fd;
fdel←IF ABS[fchange] > fcmax 
	THEN 
	IF fchange < 0 THEN -fcmax ELSE fcmax 
	ELSE fchange;
fnew←fold+fdel;
fcmax←fcmax/2;
lcnt←lcnt+1;
ENDLOOP;
dt←2*Exp2[level]*fnew+t;};
	
Flat:PROC[v1,v2,v3:Vec,epsilon:REAL] RETURNS [b:BOOLEAN]=
{OPEN CGVector;
  dx,dy: REAL;
  d1,d,bl,bh: Vec;
  oh: Vec=[0.5,0.5];
  bh ← Add[Max[v1,v3],oh];
  bl ← Sub[Min[v1,v3],oh];
  IF ~In[v2,bl,bh] THEN RETURN[FALSE];
  d1 ← Sub[v2,v1];
  d ← Sub[v3,v1];
  dx ← ABS[d.x]; dy ← ABS[d.y];
  IF dx+dy < 1 THEN RETURN[TRUE];
  IF dy < dx THEN {
    dydx: REAL ← d.y/d.x;
    RETURN[ABS[d1.y-d1.x*dydx]<epsilon] }
  ELSE {
    dxdy: REAL ← d.x/d.y;
    RETURN[ABS[d1.x-d1.y*dxdy]<epsilon] };
};

--CubicPiece computes the cubic section between t0 and t1 for the cubic:bz

CubicPiece:PUBLIC PROC [bz:LONG POINTER TO Bezier,t0,t1:REAL]RETURNS[pbz:Bezier]=
{nt:REAL;
 tbz:Bezier;
 a,b,c,ab,bc,p: Vec;
  TMid: PROC[u,v:Vec,t:REAL] RETURNS[Vec] = INLINE {
  RETURN[[u.x+(v.x-u.x)*t, u.y+(v.y-u.y)*t]]
  };
  a ← TMid[bz.b0,bz.b1,t0];
  b ← TMid[bz.b1,bz.b2,t0];
  c ← TMid[bz.b2,bz.b3,t0];
  ab ← TMid[a,b,t0];
  bc ← TMid[b,c,t0];
  p ← TMid[ab,bc,t0];
 tbz←Bezier[p,bc,c,bz.b3];
 nt←(t1-t0)/(1.0-t0);
 a ← TMid[tbz.b0,tbz.b1,nt];
  b ← TMid[tbz.b1,tbz.b2,nt];
  c ← TMid[tbz.b2,tbz.b3,nt];
  ab ← TMid[a,b,nt];
  bc ← TMid[b,c,nt];
  p ← TMid[ab,bc,nt];
 RETURN[Bezier[tbz.b0,a,ab,p]];
};

Mag:PROC[v:Vec]RETURNS [REAL]=INLINE
	{RETURN[RealFns.SqRt[v.x*v.x+v.y*v.y]];};
	
GetDirCos: PUBLIC PROC[t:REAL,bz: LONG POINTER TO Bezier] RETURNS[pos,dc:Vec] = {
  a,b,c,ab,bc,p: Vec;
  TMid: PROC[u,v: Vec] RETURNS[Vec] = INLINE {
  RETURN[[u.x+(v.x-u.x)*t, u.y+(v.y-u.y)*t]]
  };
  a ← TMid[bz.b0,bz.b1];
  b ← TMid[bz.b1,bz.b2];
  c ← TMid[bz.b2,bz.b3];
  ab ← TMid[a,b];
  bc ← TMid[b,c];
  p ← TMid[ab,bc];
  RETURN[p,Norm[Sub[bc,ab]]];
  };

Norm:PROC[v:Vec]RETURNS [Vec]=INLINE
	{m:REAL←RealFns.SqRt[v.x*v.x+v.y*v.y];
	RETURN[Vec[v.x/m,v.y/m]];};

Exp2:PROC[e:CARDINAL]RETURNS [REAL]=
{RETURN[Etab[e]];};

CacheInit:PROC=
 {i:CARDINAL;
 FOR i IN [0..maxcnt] DO
 vcache[i]←-1.0;
 ENDLOOP;
 validCache ←FALSE};
 
Etab:ARRAY [0..16] OF REAL=
[.5,1/4.0,1/8.0,1/16.0,1/32.0,1/64.0,1/128.0,1/256.0,1/512.0,1/1024.0,1/2048.0,1/4096.0,1/(4096.0*2),1/(4096.0*4),1/(4096.0*8),1/(4096.0*16),1/(4096.0*32)];

maxlevel:CARDINAL=7;
maxcnt:CARDINAL=128;
validCache: BOOLEAN ← FALSE;
vcache:ARRAY[0..maxcnt] OF REAL;
CacheInit[];

}.