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
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;
Procedures for Storage and Playback of Image Sequences from Files
bitsPer75thSecond: INT ~ 314573; -- roughly what bitBlt can move in one LF field time
labelsEnabled: BOOLEAN ← FALSE;
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: NAT ← IF context.antiAliasing THEN 1 ELSE 0;
bitsPerPixel: NAT ← IF 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: NAT ← IF 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:
BOOL ←
FALSE ]
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.STREAM ← FS.StreamOpen[ fileName: fileOfNames ];
numFiles: NAT ← IO.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:
BOOL ←
FALSE]
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:
BOOL ←
FALSE ]
RETURNS[
REF FrameSequence] ~ {
Action:
PROC ~ {
frameFailed, firstFrameFound: BOOLEAN ← FALSE;
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:
BOOLEAN ←
FALSE, startNum:
NAT ← 0] ~ {
vt: Terminal.Virtual ← context.terminal;
waitCount, transferTime: NAT;
repetitions: INT ← MAX[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: ROPE ← NARROW[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:
ROPE ←
NIL ] ~ {
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:
BOOLEAN ←
TRUE,
framesPerRev:
NAT ← 16, startAt:
NAT ← 0, endAt:
NAT ← 32767,
filename:
ROPE ←
NIL ] ~ {
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:
ROPE ←
NIL] ~ {
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:
ROPE ←
NIL, closed:
BOOLEAN ←
TRUE ] ~ {
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 ANY ← NIL ] 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: INTEGER ← NARROW[ 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[LORA ← LIST[ begin, end, NEW[ NAT ← frames] ] ]
];
renderData.class.doBeforeFrame ← CONS[LerpShape, renderData.class.doBeforeFrame];
};
LerpShape: G3dRender.ShapeProc ~ {
PROC[context: Context, shape: REF Shape, data: REF ANY ← NIL] RETURNS[REF Shape]
Translates texture coordinates over sequence of frames
frameNumber: NAT ← context.frameNumber;
renderData: REF RenderData ← G3dRender.RenderDataFrom[shape];
list: LORA ← NARROW[GetProp[renderData.shadingProps, $ShapeLerp] ];
begin: REF Shape ← NARROW[list.first];
end: REF Shape ← NARROW[list.rest.first];
lastFrame: NAT ← NARROW[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 ];
};