G3dAnimationSupportImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, August 8, 1989 1:09:13 pm PDT
Bloomenthal, September 14, 1988 1:05:33 pm PDT
DIRECTORY Atom, CedarProcess, Convert, FS, G3dAnimationSupport, G3dBasic, G3dColorDisplaySupport, G3dMatrix, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, G3dSpline, G3dVector, ImagerBackdoor, ImagerSample, IO, Real, Rope, Terminal;
G3dAnimationSupportImpl: CEDAR MONITOR
IMPORTS Atom, CedarProcess, Convert, FS, G3dColorDisplaySupport, G3dMatrix, G3dRender, G3dRenderWithPixels, G3dSortandDisplay, G3dSpline, G3dVector, ImagerSample, IO, Real, Rope, Terminal
EXPORTS G3dAnimationSupport
~ BEGIN
Basic Types
ROPE:    TYPE ~ Rope.ROPE;
RopeSequence:  TYPE ~ RECORD[ SEQUENCE length: NAT OF Rope.ROPE ];
Context:    TYPE ~ G3dRender.Context;
ContextClass:  TYPE ~ G3dRender.ContextClass;
Pair:     TYPE ~ G3dRender.Pair;
PairSequence: TYPE ~ G3dRender.PairSequence;
Triple:   TYPE ~ G3dRender.Triple;       -- RECORD [ x, y, z: REAL];
TripleSequence: TYPE ~ G3dRender.TripleSequence;
TripleSequenceRep: TYPE ~ G3dBasic.TripleSequenceRep;
Rectangle:   TYPE ~ G3dRender.Rectangle;
Shape:   TYPE ~ G3dRender.Shape;
RenderData:  TYPE ~ G3dRender.RenderData;
ImagerProc:  TYPE ~ G3dRender.ImagerProc;
ImagerProcRec: TYPE ~ G3dRender.ImagerProcRec;
ViewProc:  TYPE ~ G3dAnimationSupport.ViewProc;
FrameSequence: TYPE ~ G3dAnimationSupport.FrameSequence;
SampleMap:   TYPE ~ ImagerSample.SampleMap;
LORA:    TYPE ~ LIST OF REF ANY;
Renamed Procedures
GetProp: PROC [propList: Atom.PropList, prop: REF ANY] RETURNS [REF ANY] ~
                     Atom.GetPropFromList;
PutProp: PROC [propList: Atom.PropList, prop: REF ANY, val: REF ANY]
   RETURNS [Atom.PropList] ~ Atom.PutPropOnList;
Procedures for Storage and Playback of Image Sequences from Files
bitsPer75thSecond: INT ~ 314573;  -- roughly what bitBlt can move in one LF field time
labelsEnabled: BOOLEANFALSE;
labelPosn: Pair ← [0.02, 0.02];
Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ {
result ← Real.Round[number];
IF result < number THEN result ← result + 1;
};
GetWaitCount: PROC[context: Context, framesPerSec: NAT]
     RETURNS[waitCount: NAT, transferTime: NAT] ~ {
width: INT ← Ceiling[context.viewPort.w];
height: INT ← Ceiling[context.viewPort.h];
addOn: NATIF context.antiAliasing THEN 1 ELSE 0;
bitsPerPixel: NATIF context.class.displayType = $FullColor THEN 24 ELSE 8;
transferTime ← width * height * bitsPerPixel / bitsPer75thSecond;
IF framesPerSec = 0
THEN waitCount ← 150
ELSE waitCount ← 75 / framesPerSec;  -- LF display VerticalRetrace comes 75/sec.
IF transferTime > waitCount   -- take bitBlt overhead into account
THEN waitCount ← 0
ELSE waitCount ← waitCount - transferTime;
waitCount ← MAX[waitCount, 1];
};
ShowAISCacheFrame: PUBLIC PROC[ context: Context,
           frames: REF FrameSequence ] ~ {
IF frames.currentFrame < 0 OR frames.currentFrame >= frames.length
THEN SIGNAL G3dRender.Error[$MisMatch, "Frame number out of range"];
IF frames[frames.currentFrame] # NIL
THEN IF context.viewer # NIL
THEN {
context.class.updateViewer[context];
context.pixels ← frames[frames.currentFrame];
context.class.drawInViewer[
context, NEW[ImagerProcRec ← [G3dColorDisplaySupport.StuffBuf, context]]
];
}
ELSE {
samplesPerColor: NATIF context.class.displayType = $FullColor THEN 3 ELSE 1;
FOR i: NAT IN [0..samplesPerColor) DO
ImagerSample.Transfer[dst: context.pixels[i], src: frames[frames.currentFrame][i]];
ENDLOOP;
};
};
PlayBackNumberedAISFiles: PUBLIC PROC[context: Context,
             fileRoot : Rope.ROPE,
         startNum, numFiles, framesPerSec, secondsPlayingTime : NAT] ~ {
frames: REF FrameSequence ← CacheNumberedAISFiles[context, fileRoot, numFiles, startNum];
PlayBackAISCache[context, frames, framesPerSec, secondsPlayingTime];
};
CacheAISFiles: PUBLIC PROC[ context: Context, fileOfNames: Rope.ROPE,
         labeled: BOOLFALSE ]
     RETURNS[REF FrameSequence] ~ {
pictureNames: REF RopeSequence;
GetName: PROC[number: NAT] RETURNS[Rope.ROPE] ~ {
RETURN[ pictureNames[number] ];
};
Extend: PROC[names: REF RopeSequence] ~ {
newNames: REF RopeSequence ← NEW[ RopeSequence[names.length*2] ];
FOR i: NAT IN [0..names.length) DO newNames[i] ← names[i]; ENDLOOP;
names ← newNames;
};
input: IO.STREAMFS.StreamOpen[ fileName: fileOfNames ];
numFiles: NATIO.GetInt[input];    -- first thing in file is number of pictures
pictureNames ← NEW[ RopeSequence[numFiles] ];
numFiles ← 0;
WHILE NOT IO.EndOf[input] DO   -- read robustly in case number is wrong
IF numFiles >= pictureNames.length THEN Extend[pictureNames];
pictureNames[numFiles] ← IO.GetRopeLiteral[input ! IO.EndOfStream => LOOP];
IF pictureNames[numFiles] # NIL THEN numFiles ← numFiles + 1;
ENDLOOP;
RETURN[ CacheFiles[context, numFiles, 0, GetName] ];
};
CacheNumberedAISFiles: PUBLIC PROC[context: Context, fileRoot: Rope.ROPE,
            numFiles: NAT, start: NAT ← 0,
            labeled: BOOLFALSE]
        RETURNS[REF FrameSequence] ~ {
GetName: PROC[number: NAT] RETURNS[Rope.ROPE] ~ {
RETURN[ G3dRender.PasteInSequenceNo[
G3dRender.PrependWorkingDirectory[context, fileRoot],
number
] ];
};
RETURN[ CacheFiles[context, numFiles, start, GetName] ];
};
CacheFiles: PROC[context: Context, numFiles, start: NAT ← 0,
      getname: PROC[number: NAT] RETURNS[Rope.ROPE], labeled: BOOLFALSE ]
    RETURNS[REF FrameSequence] ~ {
Action: PROC ~ {
frameFailed, firstFrameFound: BOOLEANFALSE;
firstFrame, i: NAT ← start;
tmpSamples: ARRAY[0..5) OF SampleMap ← ALL[NIL];
WHILE TRUE DO
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];      -- respond to Stop button
G3dRenderWithPixels.AllocatePixelMemory[ context ];-- get new set of pixels each frame
[] ← G3dColorDisplaySupport.GetAIS[
context, getname[i]
! G3dRender.Error => IF code = $NotFound
THEN { frameFailed ← TRUE; CONTINUE; }
];
IF NOT frameFailed
THEN firstFrameFound ← TRUE   -- found frame, legit sequence started
ELSE IF NOT firstFrameFound  -- frame failed, has a sequence been started?
THEN IF i < start + 16      -- try for 16 consecutive frames, just in case
THEN { firstFrame ← i ← i + 1; frameFailed ← FALSE; LOOP; }
ELSE EXIT      -- frame not found after 16 tries, assume bad name
ELSE EXIT;  -- must be end of sequence, earlier frames found but not this one
IF frames.maxLength <= i - firstFrame THEN { -- extend FrameSequence if necessary
newFrames: REF FrameSequence ← NEW[ FrameSequence[frames.maxLength+16] ];
FOR j: NAT IN [0..frames.maxLength) DO newFrames[j] ← frames[j]; ENDLOOP;
frames ← newFrames;
};
Get pixels left in proplist by GetAIS
frames[i - firstFrame] ← NARROW[ Atom.GetPropFromList[context.props, $TempPixels] ];
Copy pixels from viewer to stored frame to get label in stored frame 
IF frames.labels AND context.viewer # NIL THEN
G3dColorDisplaySupport.GetPixelsFromViewer[context, frames[i - firstFrame]];
context.props ← Atom.RemPropFromList[context.props, $TempPixels]; -- save VM
frames.length ← i - firstFrame + 1;
i ← i + 1;
IF numFiles > 0 AND i - start >= numFiles THEN EXIT; -- found requested no. of files
ENDLOOP;
IF NOT firstFrameFound THEN {
frames ← NIL;      -- no frames found, prevent further mistakes
SIGNAL G3dRender.Error[$NotFound, "File not found by that name"];
};
};
frames: REF FrameSequence ← NEW[ FrameSequence[numFiles] ];
IF labeled THEN frames.labels ← TRUE;
CedarProcess.DoWithPriority[background, Action];
IF context.stopMe^ THEN RETURN[NIL] ELSE RETURN[frames];
};
PlayBackAISCache: PUBLIC PROC[context: Context,
           frames: REF FrameSequence,
           framesPerSec, secondsPlayingTime: NAT,
           bothWays: BOOLEANFALSE, startNum: NAT ← 0] ~ {
vt: Terminal.Virtual ← context.terminal;
waitCount, transferTime: NAT;
repetitions: INTMAX[1,
       INT[secondsPlayingTime] * framesPerSec / (frames.length-startNum)];
IF frames = NIL THEN {
SIGNAL G3dRender.Error[$MisMatch, "NoCachedFrames"]; RETURN;
};
context.stopMe^ ← FALSE;       -- get stop flag unstuck, if necessary
context.imageReady ← FALSE;    -- discourage use of bits by other processes
context.class.validateDisplay[context];     -- update viewport, etc.
[waitCount, transferTime] ← GetWaitCount[context, framesPerSec];
IF waitCount = 0
THEN repetitions ← (secondsPlayingTime * 75 / transferTime) / (frames.length - startNum);
IF bothWays THEN repetitions ← repetitions/2;
FOR i: INT IN [0..repetitions) DO        -- play back
FOR j: NAT IN [startNum..frames.length) DO
FOR k: NAT IN [0..waitCount) DO vt.WaitForBWVerticalRetrace[]; ENDLOOP;
frames.currentFrame ← j;
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ShowAISCacheFrame[context, frames];
ENDLOOP;
IF bothWays THEN FOR j: NAT DECREASING IN [startNum..frames.length) DO
FOR k: NAT IN [0..waitCount) DO vt.WaitForBWVerticalRetrace[]; ENDLOOP;
frames.currentFrame ← j;
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ShowAISCacheFrame[context, frames];
ENDLOOP;
ENDLOOP;
context.imageReady ← TRUE;    -- encourage use of bits by other processes
};
ShowNextAISCacheFrame: PUBLIC PROC[context: Context,
           frames: REF FrameSequence, framesPerSec: INTEGER] ~ {
waitCount: NAT;
vt: Terminal.Virtual ← Terminal.Current[];
IF framesPerSec < 0
THEN frames.currentFrame ← (frames.currentFrame + frames.length-1) MOD frames.length
ELSE frames.currentFrame ← (frames.currentFrame + 1) MOD frames.length;
[waitCount: waitCount] ← GetWaitCount[context, ABS[framesPerSec]];
FOR k: NAT IN [0..waitCount) DO vt.WaitForBWVerticalRetrace[]; ENDLOOP;
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ShowAISCacheFrame[context, frames];
};
Procedures for Making Simple Animations
NewFrame: ENTRY ViewProc ~ {
PROC[context: Context, lookingFrom, lookingAt: Triple, frameNo: NAT]
ENABLE UNWIND => NULL;
filename: ROPENARROW[Atom.GetPropFromList[context.props, $OutputFile]];
G3dRender.SetView[ context, lookingFrom, lookingAt, context.fieldOfView,
       context.rollAngle, context.upDirection,
       context.hitherLimit, context.yonLimit ];
context.class.render[context];  -- make frame
IF context.stopMe^ THEN RETURN[];
CedarProcess.CheckAbort[];   -- check for external abort request before proceeding
IF filename # NIL THEN     -- if storing frames
IF context.class.displayType = $FullColor AND NotRGB[filename]
THEN {          -- dither to 8 bits per pixel for storage savings
ditherContext: Context ← NARROW[
Atom.GetPropFromList[context.props, $DitherContext]
];
G3dColorDisplaySupport.DitherImage[ditherContext, context];
G3dRender.StoreImage[ditherContext, filename, frameNo];
}
ELSE G3dRender.StoreImage[context, filename, frameNo];
};
NotRGB: PROC[fileRoot: Rope.ROPE] RETURNS[BOOLEAN] ~ { -- not an RGB file name?
cp: FS.ComponentPositions; fullFName, ext: Rope.ROPE;
[fullFName, cp, ] ← FS.ExpandName[fileRoot];  
ext ← Rope.Substr[ fullFName, cp.ext.start, cp.ext.length];
IF Rope.Equal[ext, "rgb", FALSE] THEN RETURN[FALSE] ELSE RETURN[TRUE];
};
SetUpAnimation: PROC[ context: Context, filename: ROPENIL ] ~ {
context.stopMe^ ← FALSE;            -- clear stop flag
IF context.preferredRenderMode = $Imager OR filename # NIL -- using imager / writing files
THEN G3dRenderWithPixels.BufferRendering[context, FALSE];  -- then draw on screen
IF filename # NIL THEN {
context.props ← Atom.PutPropOnList[context.props, $OutputFile, filename];
IF context.class.displayType = $FullColor
THEN IF NotRGB[filename]
THEN {
ditherContext: Context ← G3dRender.Create[];
ditherContext.class ← NEW[
ContextClass ← G3dRender.GetDisplayClass[$PseudoColor]
];
G3dSortandDisplay.ValidateContext[context];   -- get everything straight
ditherContext.viewPort ← context.viewPort;
G3dRenderWithPixels.AllocatePixelMemory[ditherContext];
context.props ← Atom.PutPropOnList[context.props, $DitherContext, ditherContext];
}
ELSE {     -- file being prepared for Abekas
context.viewPort ← NEW[Rectangle ← [ 0.0, 0.0, 720.0, 486.0 ]];
G3dRenderWithPixels.AllocatePixelMemory[context]; -- get buffer memory of right size
G3dRender.SetViewPort[ context, [0.0, 0.0, 720.0, 486.0] ];
G3dRender.SetWindow[context, [x: -1.0, y: -0.75, w: 2.0, h: 1.5] ];
context.pixelAspectRatio ← (4.0/3.0) / (720.0/486.0);  -- pixel width/height
};
};
};
FinishAnimation: PROC[ context: Context ] ~ {
context.props ← Atom.RemPropFromList[context.props, $OutputFile];
context.props ← Atom.RemPropFromList[context.props, $DitherContext];
};
Orbit: PUBLIC PROC[ context: Context, lookingFrom, lookingAt, axis, base: Triple,
      moveEPNotCI: BOOLEANTRUE,
      framesPerRev: NAT ← 16, startAt: NAT ← 0, endAt: NAT ← 32767,
      filename: ROPENIL ] ~ {
ENABLE UNWIND => NULL;
SetUpAnimation[context, filename];
IF moveEPNotCI
THEN MoveEyePointInOrbit[
context, lookingFrom, lookingAt, axis, base,
NewFrame, framesPerRev, startAt, endAt
]
ELSE MoveCtrOfInterestInOrbit[
context, lookingFrom, lookingAt, axis, base,
NewFrame, framesPerRev, startAt, endAt
];
FinishAnimation[context];
};
MakeFramesFromTo: PUBLIC PROC[context: Context,
        lookingFrom, lookingAt, toLookingFrom, toLookingAt: Triple,
        framesOnLine: NAT, startAt, endAt: NAT ← 0,
        filename: ROPENIL] ~ {
ENABLE UNWIND => NULL;
SetUpAnimation[context, filename];
IF endAt = 0 THEN endAt ← framesOnLine;   -- default is full number of frames
MoveOnLine[ context,
        lookingFrom, lookingAt, toLookingFrom, toLookingAt,
        NewFrame, framesOnLine, startAt, endAt ];
FinishAnimation[context];
};
TripletoRope: PROC[ r: Triple] RETURNS[ROPE] ~ {
rope: ROPE;
rope ← Rope.Cat[ " ", Convert.RopeFromReal[r.x], " ", Convert.RopeFromReal[r.y] ];
rope ← Rope.Cat[ rope, " ", Convert.RopeFromReal[r.z], " " ];
RETURN[ rope ];
};
MakeFramesOnPath: PUBLIC PROC[ context: Context,
           lookingFrom, lookingAt: LIST OF Triple,
           framesOnPath: NAT, startAt, endAt: NAT ← 0,
           filename: ROPENIL, closed: BOOLEANTRUE ] ~ {
Animate along curved path of typed in points
ENABLE UNWIND => NULL;
ExpandSequence: PROC[seq: TripleSequence] ~ {
newSeq: TripleSequence ← NEW[TripleSequenceRep[seq.length + 64]];
FOR i: NAT IN [0..seq.length) DO newSeq[i] ← seq[i]; ENDLOOP;
seq ← newSeq;
};
lookingFroms: TripleSequence ← NEW[TripleSequenceRep[16]];
lookingAts: TripleSequence ← NEW[TripleSequenceRep[16]];
i: NAT ← 0;
SetUpAnimation[context, filename];
FOR list: LIST OF Triple ← lookingFrom, list.rest UNTIL list = NIL DO
lookingFroms[i] ← list.first; i ← i + 1;
IF i >= lookingFroms.maxLength THEN ExpandSequence[lookingFroms];
ENDLOOP;
lookingFroms.length ← i;
i ← 0;
FOR list: LIST OF Triple ← lookingAt, list.rest UNTIL list = NIL DO
lookingAts[i] ← list.first; i ← i + 1;
IF i >= lookingAts.maxLength THEN ExpandSequence[lookingAts];
ENDLOOP;
lookingAts.length ← i;
IF endAt = 0 THEN endAt ← framesOnPath;
IF closed
THEN MoveOnClosedCurve[ context, lookingFroms, lookingAts,
            NewFrame, framesOnPath, startAt, endAt ]
ELSE MoveOnOpenCurve[ context, lookingFroms, lookingAts,
            NewFrame, framesOnPath, startAt, endAt ];
FinishAnimation[context];
};
Procedures for Eyepoint-Ctr of Interest Trajectories
MoveEyePointInOrbit: PUBLIC PROC[ context: Context,
            eyePt, lookingAt, axis, base: Triple,
            displayProc: ViewProc,
             framesPerRev: NAT, startAt, endAt: NAT ← 0 ] ~ {
theta: REAL ← 360. / framesPerRev;
currentEyePt: Triple;
FOR i: NAT IN [startAt .. endAt] DO
IF context.stopMe^ THEN RETURN[];
currentEyePt ← G3dMatrix.Transform[
eyePt,
G3dMatrix.MakeRotateAbout[
axis: axis,
theta: theta * i,
base: base
]
];
Call display procedure
context.frameNumber ← i;
displayProc[ context, currentEyePt, lookingAt, context.frameNumber ];
ENDLOOP;
context.frameNumber ← 0;
};
MoveCtrOfInterestInOrbit: PUBLIC PROC[ context: Context,
             eyePt, lookingAt, axis, base: Triple,
             displayProc: ViewProc,
              framesPerRev: NAT,
              startAt, endAt: NAT ← 0 ] ~ {
theta: REAL ← 360. / framesPerRev;
currentLookingAt: Triple;
FOR i: NAT IN [startAt .. endAt] DO
IF context.stopMe^ THEN RETURN[];
currentLookingAt ← G3dMatrix.Transform[
lookingAt,
G3dMatrix.MakeRotateAbout[
axis: axis,
theta: theta * i,
base: base
]
];
Call display procedure
context.frameNumber ← i;
displayProc[ context, eyePt, currentLookingAt, context.frameNumber ];
ENDLOOP;
context.frameNumber ← 0;
};
MoveOnLine: PUBLIC PROC[ context: Context,
         eyePt, lookingAt, toEyePt, toLookingAt: Triple,
         displayProc: ViewProc,
         framesOnLine: NAT, startAt, endAt: NAT ← 0 ] ~ {
currentEyePt, currentLookingAt: Triple;
FOR i: NAT IN [startAt .. endAt] DO
IF context.stopMe^ THEN RETURN[];
currentEyePt.x ← eyePt.x + (toEyePt.x - eyePt.x) * i / (framesOnLine-1);
currentEyePt.y ← eyePt.y + (toEyePt.y - eyePt.y) * i / (framesOnLine-1);
currentEyePt.z ← eyePt.z + (toEyePt.z - eyePt.z) * i / (framesOnLine-1);
currentLookingAt.x ← lookingAt.x + (toLookingAt.x - lookingAt.x) * i / (framesOnLine-1);
currentLookingAt.y ← lookingAt.y + (toLookingAt.y - lookingAt.y) * i /(framesOnLine-1);
currentLookingAt.z ← lookingAt.z + (toLookingAt.z - lookingAt.z) * i / (framesOnLine-1);
context.frameNumber ← i;
displayProc[ context, currentEyePt, currentLookingAt, context.frameNumber ];
ENDLOOP;
context.frameNumber ← 0;
};
MoveOnOpenCurve: PUBLIC PROC[ context: Context,
           eyePts, lookingAts: TripleSequence,
           displayProc: ViewProc,
            framesOnCurve: NAT, startAt, endAt: NAT ← 0 ] ~ {
currentEyePt, currentLookingAt: TripleSequence;
epCoeffs: G3dSpline.SplineSequence ← G3dSpline.Interpolate[eyePts];
ciCoeffs: G3dSpline.SplineSequence ← G3dSpline.Interpolate[lookingAts];
currentEyePt ← ExpandCurve[epCoeffs, framesOnCurve];
currentLookingAt ← ExpandCurve[ciCoeffs, framesOnCurve];
IF context.stopMe^ THEN RETURN[];
FOR i: NAT IN [startAt .. endAt] DO
j: NAT ← i MOD framesOnCurve;
context.frameNumber ← i;
displayProc[ context, currentEyePt[j], currentLookingAt[j], context.frameNumber ];
ENDLOOP;
context.frameNumber ← 0;
};
MoveOnClosedCurve: PUBLIC PROC[ context: Context,
           eyePts, lookingAts: TripleSequence,
           displayProc: ViewProc,
            framesOnCurve: NAT, startAt, endAt: NAT ← 0 ] ~ {
currentEyePt, currentLookingAt: TripleSequence;
epCoeffs: G3dSpline.SplineSequence ← G3dSpline.InterpolateCyclic[eyePts];
ciCoeffs: G3dSpline.SplineSequence ← G3dSpline.InterpolateCyclic[lookingAts];
currentEyePt ← ExpandCurve[epCoeffs, framesOnCurve];
currentLookingAt ← ExpandCurve[ciCoeffs, framesOnCurve];
FOR i: NAT IN [startAt .. endAt] DO
j: NAT ← i MOD framesOnCurve;
IF context.stopMe^ THEN RETURN[];
context.frameNumber ← i;
displayProc[ context, currentEyePt[j], currentLookingAt[j], context.frameNumber ];
ENDLOOP;
context.frameNumber ← 0;
};
ExpandCurve: PROC[coeffs: G3dSpline.SplineSequence, numFrames: NAT]
      RETURNS[TripleSequence] ~ {
points: TripleSequence ← NEW[TripleSequenceRep[numFrames]];
framesPerSeg: REAL ← Real.Float[numFrames - 1] / (coeffs.length);
FOR i: NAT IN [0..numFrames - 1) DO
t: REAL ← i / framesPerSeg;     -- distance, in segments, along curve
seg: NAT ← Real.Fix[t];       -- segment
t ← t - seg;          -- position in segment
points[i] ← G3dSpline.Position[coeffs[seg], t];
ENDLOOP;
points[numFrames - 1] ← G3dSpline.Position[coeffs[coeffs.length - 1], 1.0];
points.length ← numFrames;
RETURN[points];
};
Procedures for Viewing Trajectories
ShowOpenCurve: PUBLIC PROC[ context: Context, eyePts, lookingAts: TripleSequence,
        numFrames: NAT, startAt: NAT ← 0 ] ~ {
SIGNAL G3dRender.Error[$Unimplemented, "Sorry, ask Frank for an implementation"];
};
ShowClosedCurve: PUBLIC PROC[ context: Context, eyePts, lookingAts: TripleSequence,
        numFrames: NAT, startAt: NAT ← 0 ] ~ {
SIGNAL G3dRender.Error[$Unimplemented, "Sorry, ask Frank for an implementation"];
};
ShowOrbit: PUBLIC PROC[ context: Context, startPt, axis, base: Triple, framesPerRev: NAT ] ~ {
Adds description of path to data for current scene, will be displayed just like any other shape. "Trajectory" is name given to resulting Shape.
SIGNAL G3dRender.Error[$Unimplemented, "Sorry, ask Frank for an implementation"];
};
Procedures for Texture Animation
SetTxtrTranslation: PUBLIC PROC[ context: Context, shapeName: Rope.ROPE,
           translation: Pair, frames: NAT ] ~{
Sets up translation by [translation.x, translation.y] over "frames" frames
shape: Shape ← G3dRender.FindShape[context, shapeName];
translation.x ← translation.x / frames;
translation.y ← translation.y / frames;
shape.shadingProps ← PutProp[
shape.shadingProps, $TxtrTranslation, NEW[Pair ← translation]
];
shape.shadingClass.loadVtxAux ← LoadTranslatedTxtrCoords;
};
LoadTranslatedTxtrCoords: G3dRender.CtlPtInfoProc ~ { -- load aux field in vtx
PROC[ context: Context, vtx: REF CtlPtInfo, data: REF ANYNIL ] RETURNS[REF CtlPtInfo];
frameNumber: NAT ← context.frameNumber;
shape: Shape ← NARROW[ Atom.GetPropFromList[vtx.props, $Shape] ];
txtrIncr: REF Pair ← NARROW[GetProp[shape.shadingProps, $TxtrTranslation] ];
vtxAux: REF Pair ← NEW[Pair];
WITH data SELECT FROM
input: LORA => {
auxInfo: PairSequence ← NARROW[ input.first ];
index: INTEGERNARROW[ input.rest.first, REF INTEGER ]^;
vtxAux.x ← auxInfo[index].x + txtrIncr.x * frameNumber;    -- texture coords
vtxAux.y ← auxInfo[index].y + txtrIncr.y * frameNumber;
};
txtr: REF Pair => {
vtxAux.x ← txtr.x + txtrIncr.x * frameNumber;
vtxAux.y ← txtr.y + txtrIncr.y * frameNumber;
};
ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unrecognized type"];
vtx.aux ← vtxAux;
RETURN[ vtx ];
};
Procedures for Shape Interpolation
SetShapeInterpolation: PUBLIC PROC[ context: Context, shapeName: Rope.ROPE,
            begin, end: REF Shape, frames: NAT ] ~{
Sets up translation by [translation.x, translation.y] over "frames" frames
shape: Shape ← G3dRender.FindShape[context, shapeName];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
renderData.shadingProps ← PutProp[
renderData.shadingProps, $ShapeLerp,
NEW[LORALIST[ begin, end, NEW[ NAT ← frames] ] ]
];
renderData.class.doBeforeFrame ← CONS[LerpShape, renderData.class.doBeforeFrame];
};
LerpShape: G3dRender.ShapeProc ~ {
PROC[context: Context, shape: REF Shape, data: REF ANYNIL] RETURNS[REF Shape]
Translates texture coordinates over sequence of frames
frameNumber: NAT ← context.frameNumber;
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
list: LORANARROW[GetProp[renderData.shadingProps, $ShapeLerp] ];
begin: REF Shape ← NARROW[list.first];
end: REF Shape ← NARROW[list.rest.first];
lastFrame: NATNARROW[list.rest.rest.first, REF NAT]^;
alpha: REAL ← 1.0 * frameNumber / lastFrame;
FOR i: NAT IN [0..shape.vertices.length) DO
shape.vertices[i].point ← G3dVector.Add[
begin.vertices[i].point,
G3dVector.Mul[ G3dVector.Sub[end.vertices[i].point, begin.vertices[i].point], alpha ]
];
shape.vertices[i].normal ← G3dVector.Add[
begin.vertices[i].normal,
G3dVector.Mul[ G3dVector.Sub[end.vertices[i].normal, begin.vertices[i].normal], alpha ]
];
ENDLOOP;
renderData.patch ← NIL;    -- must rebuild patches
shape.renderValid ← FALSE;
RETURN[shape];
};
Procedure for Registering Shape Manipulation Procedures for Animation
SetShapeManipulation: PUBLIC PROC[ context: Context, shapeName: Rope.ROPE,
            prop, propVal: REF ANY,
            proc: G3dRender.ShapeProc ] ~ {
Way of registering procedure to be called to modify shape before each frame
shape: Shape ← G3dRender.FindShape[context, shapeName];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
renderData.shadingProps ← PutProp[ renderData.shadingProps, prop, propVal ];
renderData.class.doBeforeFrame ← CONS[ proc, NIL ];
};
AddShapeManipulation: PUBLIC PROC[ context: Context, shapeName: Rope.ROPE,
            prop, propVal: REF ANY,
            proc: G3dRender.ShapeProc ] ~ {
Way of registering procedure to be called to modify shape before each frame
shape: Shape ← G3dRender.FindShape[context, shapeName];
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
renderData.shadingProps ← PutProp[ renderData.shadingProps, prop, propVal ];
renderData.class.doBeforeFrame ← CONS[ proc, renderData.class.doBeforeFrame ];
};
END.