--compiler localspline
--m.stone September 26, 1980 5:09 PM
-- Last Edited by: Stone, March 15, 1983 3:27 pm
DIRECTORY
 SplineDefs: FROM "SplineDefs";

LocalSpline: PROGRAM EXPORTS SplineDefs =
BEGIN OPEN SplineDefs;

--here are some REAL constants used for assigning real constants.
ONE: REAL ← 1; TWO: REAL ← 2;
E1: REAL ← 10; E2: REAL ← 100; E3: REAL ← 1000; E4: REAL ← 10000;
TooFewKnots: PUBLIC SIGNAL[numknots: INTEGER] = CODE;
UnknownSpline: PUBLIC SIGNAL[splineType: SplineType] = CODE;

RABS: PROCEDURE[r: REAL] RETURNS[REAL] = INLINE
BEGIN RETURN[IF r<0 THEN -r ELSE r] END;

FourKnots: TYPE=ARRAY [0..3] OF FPCoords;
MakeSpline: PUBLIC PROCEDURE
 [knots: KnotSequence, splineType: SplineType] RETURNS [CoeffsSequence] =

BEGIN
allCoeffs: CoeffsSequence;
numknots: INTEGER ← knots.length;
IF numknots <= 0 THEN SIGNAL TooFewKnots[numknots];
--special case for 1 and 2 points
IF numknots < 3 THEN BEGIN
 allCoeffs ← NEW[CoeffsSequenceRec[1]];
 allCoeffs[0].t0 ← knots[0];
 allCoeffs[0].t1 ← [0,0];
 allCoeffs[0].t2 ← [0,0];
 allCoeffs[0].t3 ← [0,0];
IF numknots=2 THEN BEGIN
  allCoeffs[0].t1[1] ← knots[1][1]-knots[0][1];
  allCoeffs[0].t1[2] ← knots[1][2]-knots[0][2];
  END;
RETURN[allCoeffs];
END; 
SELECT splineType FROM

 bsplineInterp => BEGIN
  interpKnots: KnotSequence ← KnotsToCPoints[knots];
  allCoeffs ← MakeLocalSpline[interpKnots,bspline];
  END;

 bezier =>
  BEGIN
  kindex,cindex,i: INTEGER;
  fourKnots: FourKnots;
--allocate for allCoeffs
  IF numknots>=4 THEN i ← (numknots-1)/3 ELSE i 𡤀 
  IF (numknots-1) MOD 3#0 THEN i ← i+1; --corresponds to knots hanging over
  allCoeffs ← NEW[CoeffsSequenceRec[i]];
  cindex ← 0;
--set up first 4 knots, duplicating if necessary
  FOR kindex IN [0..3] DO
   IF kindex >numknots-1 THEN EXIT;
   fourKnots[kindex] ← knots[kindex];
   ENDLOOP;
--only case where duplication is necessary since check above for numknots<3
  IF numknots=3 THEN fourKnots[3] ← fourKnots[2];
  kindex ← 4;
--need to do this at least once
  DO
   allCoeffs[cindex] ← Bezier[@fourKnots];
   cindex ← cindex + 1;
--Take 4th knot + next 3 knots. Special case if less than 3 left
   fourKnots[0] ← fourKnots[3];
   SELECT numknots-kindex FROM
    <=0 => EXIT;

    =1 =>
     BEGIN
     fourKnots[1] ← fourKnots[0];
     fourKnots[2] ← knots[kindex];
     kindex ← kindex+1;
     fourKnots[3] ← fourKnots[2];
     END;
    =2 =>
     BEGIN
     FOR i IN [1..2] DO
      fourKnots[i] ← knots[kindex];
      kindex ← kindex+1;
      ENDLOOP;
     fourKnots[3] ← fourKnots[2];
     END;

    >=3 =>
     FOR i IN [1..3] DO
      fourKnots[i] ← knots[kindex];
      kindex ← kindex+1;
      ENDLOOP;

    ENDCASE;

   ENDLOOP;
  END;

IN [bspline..crspline] => allCoeffs ← MakeLocalSpline[knots,splineType];

ENDCASE => SIGNAL UnknownSpline[splineType];

RETURN[allCoeffs];
END;

--take knots and Spline procedure name and draw the curve
MakeLocalSpline: PROCEDURE
 [knots: KnotSequence, splineType: SplineType]
RETURNS [CoeffsSequence] =
BEGIN
fourKnots: FourKnots;
kindex,cindex,i: INTEGER;
numknots: INTEGER ← knots.length;
lastknot: INTEGER ← numknots-1;
allCoeffs: CoeffsSequence;

IF numknots >= 4 THEN i ← numknots-3 ELSE i ← 1;
allCoeffs ← NEW[CoeffsSequenceRec[i]];
cindex ← 0;

--set up first 4 knots
FOR kindex IN [0..3] DO
IF kindex >lastknot THEN EXIT;
 fourKnots[kindex] ← knots[kindex];
ENDLOOP;
IF numknots=3 THEN fourKnots[3] ← fourKnots[2];
kindex ← 4;

--need to do this at least once
DO
 allCoeffs[cindex] ← SELECT splineType FROM
  bspline => BSpline[@fourKnots],
  crspline => CRSpline[@fourKnots],
  ENDCASE => ZeroSpline[@fourKnots];
 cindex ← cindex+1;
IF kindex > lastknot THEN EXIT;

--shift the segment over
 fourKnots[0] ← fourKnots[1];
 fourKnots[1] ← fourKnots[2];
 fourKnots[2] ← fourKnots[3];
 fourKnots[3] ← knots[kindex];
 kindex ← kindex+1;
ENDLOOP;

RETURN[allCoeffs];
END;
RealSequence: TYPE = REF RealSequenceRec;
RealSequenceRec: TYPE = RECORD[element: SEQUENCE length:NAT OF REAL];

--Preform Inversion routine to get control points from drawn points
--From Yamaguci's paper
KnotsToCPoints: PROCEDURE[fpKnots: KnotSequence] RETURNS[fpCPoints: KnotSequence] =
BEGIN
delta: RealSequence;
deltaMax,tolerence: REAL;
xyzw,i,loopCount: INTEGER; numknots: INTEGER ← fpKnots.length;
fpCPoints ← NEW[KnotSequenceRec[numknots+2]];
delta ← NEW[RealSequenceRec[numknots]];
--inits
FOR i IN [1..numknots] DO fpCPoints[i] ← fpKnots[i-1]; ENDLOOP;
--set the first and last control points, and the tolerence
 fpCPoints[0] ← fpCPoints[1];
 fpCPoints[numknots+1] ← fpCPoints[numknots];
 tolerence ← ONE/3;

--Main Loop
loopCount ← 0;
DO
 deltaMax ← 0;
--iterate and collect maximum delta
FOR xyzw IN [1..NDIM] DO
  FOR i IN [1..numknots] DO
   delta[i-1] ← fpKnots[i-1][xyzw] -fpCPoints[i][xyzw]
     +fpKnots[i-1][xyzw]/2-(fpCPoints[i-1][xyzw]+fpCPoints[i+1][xyzw])/4;
   IF RABS[delta[i-1]] >= deltaMax THEN deltaMax ← RABS[delta[i-1]];
   ENDLOOP;
  FOR i IN [1..numknots] DO
   fpCPoints[i][xyzw] ← delta[i-1]+fpCPoints[i][xyzw]; ENDLOOP;
  ENDLOOP;
--set the first and last control points
 fpCPoints[0] ← fpCPoints[1];
 fpCPoints[numknots+1] ← fpCPoints[numknots];
--End Check
IF RABS[deltaMax] <= tolerence THEN EXIT;
ENDLOOP;
END;

--take 4 knots and make the BSpline Coeficient matrix
BSpline: PROCEDURE[fpKnots4: POINTER TO FourKnots] RETURNS[coeffs: Coeffs] =
BEGIN
xyzw: INTEGER;
FOR xyzw IN [1..NDIM] DO
 coeffs.t3[xyzw] ← (-fpKnots4[0][xyzw] +3*fpKnots4[1][xyzw] -3*fpKnots4[2][xyzw] +fpKnots4[3][xyzw])/6;
 coeffs.t2[xyzw] ← (3*fpKnots4[0][xyzw]-6*fpKnots4[1][xyzw]+3*fpKnots4[2][xyzw])/6;
 coeffs.t1[xyzw] ← (-3*fpKnots4[0][xyzw]+3*fpKnots4[2][xyzw])/6;
 coeffs.t0[xyzw] ← (fpKnots4[0][xyzw]+4*fpKnots4[1][xyzw]+fpKnots4[2][xyzw])/6;
ENDLOOP;
END;

--take 4 knots and make the Catmul-Rom Coeficient matrix
--tan = a(P2 - P1) where a is chosen to be .5
CRSpline: PROCEDURE[fpKnots4: POINTER TO FourKnots] RETURNS[coeffs: Coeffs] =
BEGIN
xyzw: INTEGER;
a: REAL 𡤅/E1;
FOR xyzw IN [1..NDIM] DO
 coeffs.t3[xyzw] ← (-a*fpKnots4[0][xyzw] +(2-a)*fpKnots4[1][xyzw] +(a-2)*fpKnots4[2][xyzw] +a*fpKnots4[3][xyzw]);
 coeffs.t2[xyzw] ← (2*a*fpKnots4[0][xyzw]+(a-3)*fpKnots4[1][xyzw]+(3-2*a)*fpKnots4[2][xyzw]-a*fpKnots4[3][xyzw]);
 coeffs.t1[xyzw] ← (-a*fpKnots4[0][xyzw]+a*fpKnots4[2][xyzw]);
 coeffs.t0[xyzw] ← fpKnots4[1][xyzw];
ENDLOOP;
END;

--take 4 knots and make the Bezier Coeficient matrix
Bezier: PROCEDURE[fpKnots4: POINTER TO FourKnots] RETURNS[coeffs: Coeffs] =
BEGIN
xyzw: INTEGER;
FOR xyzw IN [1..NDIM] DO
 coeffs.t3[xyzw] ← -fpKnots4[0][xyzw] +3*fpKnots4[1][xyzw] -3*fpKnots4[2][xyzw] +fpKnots4[3][xyzw];
 coeffs.t2[xyzw] ← 3*fpKnots4[0][xyzw]-6*fpKnots4[1][xyzw]+3*fpKnots4[2][xyzw];
 coeffs.t1[xyzw] ← -3*fpKnots4[0][xyzw]+3*fpKnots4[1][xyzw];
 coeffs.t0[xyzw] ← fpKnots4[0][xyzw];
ENDLOOP;
END;

--ZeroSpline fills the coeff matrix with zeros. Mostly for a pesky SELECT expression, but
--might be generally useful
ZeroSpline: PROCEDURE[fpKnots4: POINTER TO FourKnots] RETURNS[coeffs: Coeffs] =
BEGIN
xyzw: INTEGER;
FOR xyzw IN [1..NDIM] DO
 coeffs.t3[xyzw] ← 0;
 coeffs.t2[xyzw] ← 0;
 coeffs.t1[xyzw] ← 0;
 coeffs.t0[xyzw] ← 0;
ENDLOOP;
END;

END.