-- CGOutlinesImpl.mesa
--Written by Doug Wyatt,  Nov-81
-- Last changed by J Warnock June 28, 1982 12:24 pm

DIRECTORY
  ParametricMap,
  CGCubic USING [Bezier],
  GraphicsBasic USING [Vec],
  CGVector USING [Add,Mul,Sub,Dot],
  CGOutlines USING [Data, DataRep, ErrorType, NodeRep, Rep],
  CGStorage USING [pZone, qZone],
  RealFns USING [SqRt];

CGOutlinesImpl: PROGRAM
IMPORTS CGStorage,CGVector,ParametricMap,RealFns
EXPORTS CGOutlines = {
OPEN CGOutlines, GraphicsBasic, CGVector, CGCubic;

Ref: TYPE = REF Rep;

Error: PUBLIC ERROR[type: ErrorType] = CODE;

dataZone: ZONE = CGStorage.pZone;
nodeZone: ZONE = CGStorage.qZone;
boxZone: ZONE = CGStorage.qZone;
repZone: ZONE = CGStorage.qZone;


New: PUBLIC PROC[size: NAT] RETURNS[Ref] = {
  self: Ref ← repZone.NEW[Rep ← [size: 0, data: NIL, fpi: 0, fp: [0,0], lp: [0,0], sum:0]];
  [] ← GetData[self,size];
  RETURN[self];
  };

GetData: PROC[self: Ref, size: NAT, bump: NAT ← 0] RETURNS[Data] = INLINE {
  data: Data ← self.data;
  space: NAT ← IF data=NIL THEN 0 ELSE data.space;
  IF space<size THEN data ← Grow[self,MAX[size,space+bump]];
  RETURN[data];
  };

Grow: PROC[self: Ref, size: NAT] RETURNS[Data] = {
  old: Data ← self.data;
  space: NAT ← IF old=NIL THEN 0 ELSE old.space;
  new: Data ← dataZone.NEW[DataRep[size]];
  FOR i: NAT IN[0..space) DO new[i] ← old[i] ENDLOOP;
  FOR i: NAT IN[space..size) DO new[i] ← nodeZone.NEW[NodeRep ← nullNode] ENDLOOP;
  self.data ← new;
  RETURN[new];
  };

nullNode:NodeRep=NodeRep[TRUE,0,0,0,0,Bezier[[0,0],[0,0],[0,0],[0,0]]];

MoveTo: PUBLIC PROC[self: Ref, p: Vec] = {
  i: NAT ← self.size;
  self.fpi←i;
  self.fp ← p;
  self.lp ← p;
  };


LineTo: PUBLIC PROC[self: Ref, p: Vec] = {
  l:REAL; -- length of vec
  v:Vec;
  size: NAT;
  data: Data;
  i: NAT ← self.size;
  IF p = self.lp THEN RETURN; --ignore duplicated points.
  size ← i + 1;
  data ← GetData[self,size,MAX[8,MIN[size/2,1024]]];
  v←Sub[p,self.lp];
  l←Mag[v];
  IF i=0 
  	THEN {data[i]↑←NodeRep[TRUE,0,0,0,l,
 							Bezier[self.lp,Add[self.lp,Mul[v,1.0/3.0]],Add[self.lp,Mul[v,2.0/3.0]],p]];}
  	ELSE  { s1,s2:REAL;
  			  v1,v2,v3:Vec;
  			  data[i]↑←NodeRep[TRUE,0,0,self.sum,self.sum+l,
  			  				Bezier[self.lp,Add[self.lp,Mul[v,1.0/3.0]],Add[self.lp,Mul[v,2.0/3.0]],p]];
  			  v1←Norm[Sub[data[i-1].bz.b3,data[i-1].bz.b2]];
  			  v2←Norm[v];
  			  v3←Norm[Add[v1,v2]];
  			  s1←Dot[Vec[v1.y,-v1.x],v3];
  			  s2←Dot[Vec[v2.y,-v2.x],v3];
  			  data[i-1].a2←s1/RealFns.SqRt[1-s1*s1]; --compute horz vec for angle between v1 & v3
  			  data[i].a1←s2/RealFns.SqRt[1-s2*s2]; --compute horz vec for angle between v2 & v3
  			  };
  self.sum←self.sum+l;
  self.lp ← p;
  self.size ← size;
  };


CurveTo: PUBLIC PROC[self: Ref, b1,b2,b3: Vec] = {
    IF self.lp = b1 
  	THEN 
  	{IF b3 = b2
  		THEN
  		{IF b2 = b1 
  			THEN RETURN  --ignore degenerate cubic.
  			ELSE LineTo[self,b3];
  		}
  		ELSE
  		{IF b2 = b1 
  			THEN LineTo[self,b3]  
  			ELSE FourPoint[self,self.lp,b1,b2,b3];
  		};
  	}
 	ELSE
 	{IF b3 = b2
  		THEN
  		{IF b2 = b1 THEN LineTo[self,b3];}  
  		ELSE FourPoint[self,self.lp,b1,b2,b3];
  	 };
};

Close: PUBLIC PROC[self: Ref] = {
  l:REAL; -- length of vec
  p,p1,v:Vec;
  i: NAT;
  p1←self.fp;
  p←self.data[self.fpi].bz.b1;
  LineTo[self,p1];
  v←Sub[p,p1];
  i←self.size;
  l←Mag[v];
  IF i=0 
  	THEN {ERROR}
  	ELSE  {s1,s2:REAL;
  			  v1,v2,v3:Vec;
  			  v1←Norm[Sub[self.data[i-1].bz.b3,self.data[i-1].bz.b2]];
  			  v2←Norm[v];
  			  v3←Norm[Add[v1,v2]];
  			  s1←Dot[Vec[v1.y,-v1.x],v3];
  			  s2←Dot[Vec[v2.y,-v2.x],v3];
  			  self.data[i-1].a2←s1/RealFns.SqRt[1-s1*s1]; --compute horz vec for angle 
  			  self.data[self.fpi].a1←s2/RealFns.SqRt[1-s2*s2]; --compute horz vec for angle
			  };
   };

FourPoint:PROC[self:Ref,vec1,vec2,vec3,vec4:Vec]=
  	{data: Data;
    l:REAL;
  	v:Vec;
  	i,size:NAT;
  	i←self.size;
  	size ← i + 1;
  	data ← GetData[self,size,MAX[8,MIN[size/2,1024]]];
  	v←Sub[vec2,vec1];
  	l←GetLength[vec1,vec2,vec3,vec4];
  	IF i=0 
  		THEN {data[i]↑ ← NodeRep[FALSE,0,0,0,l,Bezier[vec1,vec2,vec3,vec4]];}
  		ELSE  {s1,s2:REAL;
  			  v1,v2,v3:Vec;
  			  data[i]↑ ← [FALSE,0,0,self.sum,self.sum+l,Bezier[vec1,vec2,vec3,vec4]];
  			  v1←Norm[Sub[data[i-1].bz.b3,data[i-1].bz.b2]];
  			  v2←Norm[v];
  			  v3←Norm[Add[v1,v2]];
  			  s1←Dot[Vec[v1.y,-v1.x],v3]; --compute sin of angle
  			  s2←Dot[Vec[v2.y,-v2.x],v3]; --compute sin of angle
  			  self.data[i-1].a2←s1/RealFns.SqRt[1-s1*s1]; --compute horz vec for angle  
  			  data[i].a1←s2/RealFns.SqRt[1-s2*s2]; --compute horz vec for angle
  			  };
  	self.sum←self.sum+l;
    self.lp ← vec4;
  	self.size ← size;};

GetMappedVec: PUBLIC PROC[self:Ref,v:Vec] RETURNS [w:Vec]=
  {a1,a2,x,t:REAL;
  pos,dc:Vec;
  bz:Bezier;
  i:NAT;
  i←0;
  IF v.x > self.sum THEN v.x←self.sum;
  UNTIL v.x IN [self.data[i].l1..self.data[i].l2] DO
    i←i+1;
  ENDLOOP;
  bz←self.data[i].bz;
  x←v.x-self.data[i].l1;
  IF self.data[i].line 
  	THEN
  	{t←x/(self.data[i].l2-self.data[i].l1)}
  	ELSE
  	{[,t]←ParametricMap.GetTforArc[@bz,x];};
  [pos,dc]←ParametricMap.GetDirCos[t,@bz];
  a1←self.data[i].a1;
  a2←self.data[i].a2;
  w←Add[pos,Mul[Add[Vec[-dc.y,dc.x],Mul[dc,a1+(a2-a1)*t]],v.y]];
  };
  
  
GetLinkCount:PUBLIC PROC[self:Ref] RETURNS [c:NAT]=
  {RETURN[self.size];};
  
GetLinkLength:PUBLIC PROC[self:Ref,i:NAT] RETURNS [l:REAL]=
  {RETURN[self.data[i].l2-self.data[i].l1];};
  

Copy: PUBLIC PROC[self: Ref] RETURNS[Ref] = {
  size: NAT ← self.size;
  old: Data ← self.data;
  copy: Ref ← New[size];
  new: Data ← copy.data;
  FOR i: NAT IN[0..size) DO new[i]↑ ← old[i]↑ ENDLOOP;
  copy.size ← size;
  copy.fpi ← self.fpi;
  copy.fp ← self.fp;
  copy.lp ← self.lp;
  RETURN[copy];
  };

Assign: PUBLIC PROC[self: Ref, copy: Ref] = {
  size: NAT ← copy.size;
  new: Data ← copy.data;
  old: Data ← GetData[self,size];
  FOR i: NAT IN[0..size) DO old[i]↑ ← new[i]↑ ENDLOOP;
  self.size ← size;
  self.fpi ← copy.fpi;
  self.fp ← copy.fp;
  self.lp ← copy.lp;
};

Mag:PROC[v:Vec] RETURNS [REAL]=INLINE
{RETURN[RealFns.SqRt[v.x*v.x+v.y*v.y]];};

Norm:PROC[v:Vec] RETURNS [Vec]=INLINE
{m:REAL←Mag[v];
IF m = 0 THEN RETURN [v] ELSE RETURN [Mul[v,1.0/m]];};

GetLength:PROC[v1,v2,v3,v4:Vec] RETURNS [REAL]=
{bz:CGCubic.Bezier;
bz←CGCubic.Bezier[v1,v2,v3,v4];
RETURN[ParametricMap.ArcLength[@bz]];};

}.