ThreeDDemoImpl.mesa
Last Edited by: Crow, December 16, 1986 5:22:56 pm PST
DIRECTORY
Atom     USING [ GetPropFromList, RemPropFromList, PutPropOnList ],
List     USING [ Memb ],
CedarProcess   USING [ Abort, CheckAbort, Fork, ForkableProc, Process],
Process    USING [ Pause, SecondsToTicks ],
Terminal    USING [ Virtual, Current ],
Rope     USING [ ROPE ],
Real     USING [ FixI, Float ],
Imager    USING [ Context, Rectangle ],
ImagerColor   USING [ RGB ],
ImagerColorMap  USING [SetStandardColorMap],
QuickViewer   USING [ BuildViewer, DrawInViewer, QuickView ],
ViewerOps   USING [ ChangeColumn, OpenIcon ],
Vector3d    USING [ Triple ],
Pixels     USING [ PixelBuffer ],
ThreeDScenes  USING [ AddAlphaBuffer, AddDepthBuffer, Context, Create,
         DisplayFromImagerContext, DisplayFromVM, FillInBackGround,
         FindShape, GetShading, PutShading, SetLight, SetView,
         SetViewPort, SetWindow, ShapeSequence, WindowFromViewPort
         ],
ThreeDMisc   USING [ AddShapeAt, CloseLog, CopyContextData, LoadColorRamp,
         LoadStd8BitClrMap, MakeFrame, OrbitEye, PutPixel,
         SetBackgroundColor, SetFacetedColor, SetShininess, SetSmoothColor,
         SetTransmittance, ShowRope, ShowShapes, StartLog,
         UpdateDisplay ],
AISAnimation  USING [ FrameSequence, ShowNextAISCacheFrame,
         CacheAISFiles, CacheNumberedAISFiles ];
ThreeDDemoImpl: CEDAR MONITOR
IMPORTS Atom, List, AISAnimation, CedarProcess, ImagerColorMap, Process, QuickViewer, Real, Terminal, ThreeDMisc, ThreeDScenes, ViewerOps
~ BEGIN
Types
ThreeDDemoError: PUBLIC SIGNAL [reason: ATOM] = CODE;
Context: TYPE ~ ThreeDScenes.Context;
Triple: TYPE ~ Vector3d.Triple;
RGB: TYPE ~ ImagerColor.RGB;
Global Variables
window3d: REF QuickViewer.QuickView;
context3d, offScreenCtx, onScreenCtx: REF Context;
currentScene, scene1, scene2, scene3: REF ThreeDScenes.ShapeSequence ← NIL;
frames: REF AISAnimation.FrameSequence;
xNow, yNow, zNow: REAL ← 3.0;
xOrigin, yOrigin: REAL ← 100.0;
animation, gallery: BOOLEANFALSE;
movieProcess: CedarProcess.Process ← NIL;
cachedViewPort: Imager.Rectangle;
Context and Interactive Control
Init: PROC[] ~ {
context3d ← ThreeDScenes.Create[];
context3d.preferredRenderMode ← $PseudoColor;
context3d.viewer ← window3d ← QuickViewer.BuildViewer[
"ThreeDDemo",
LIST["Help", "NewScene", "Lines", "Facets", "Smooth", "Shiny", "Orbit", "STOP!", "Movie", "Gallery", "Jaggy", "NoJaggy", "Reset"],
ReDraw,
MenuHit,
ShutDown
];
ThreeDMisc.SetBackgroundColor[ context3d, "Darkish Blue" ];  -- set background color
[] ← ThreeDScenes.SetLight[context3d, "Initial", [-100., -200., 50.] ];
ThreeDScenes.SetView[ context3d, [2.0, -10.0, 3.0], [0.0, 0.0, 0.0] ];
SetScene1[];
IF window3d.outer.column # color THEN ViewerOps.ChangeColumn[window3d.outer, color];
IF window3d.outer.iconic THEN ViewerOps.OpenIcon[icon: window3d.outer];
};
This resets the clipper, transformation, scene, etc. to the unused state
Reset: PROCEDURE [] ~ { 
DoReDraw: PROCEDURE [imagerCtx: Imager.Context] ~ { ReDraw[imagerCtx, NIL]; };
context3d.stopMe ← FALSE;       -- get stop flag unstuck, if necessary
ThreeDScenes.SetWindow[context3d,ThreeDScenes.WindowFromViewPort[context3d.viewPort]];
context3d.shapes ← context3d.lights;   -- delete all the objects
ThreeDScenes.SetView[ context3d, [2.0, -10.0, 3.0], [0.0, 0.0, 0.0] ];
gallery ← FALSE;
currentScene ← NIL;
frames ← NIL;
QuickViewer.DrawInViewer[window3d, DoReDraw];   -- gets a new imager context
};
This will be called when the viewer is changed, grabs Imager context and rebuilds context3d.display
ReDraw: PROCEDURE [imagerCtx: Imager.Context, toDo: REF ANY] ~ { 
ENABLE UNWIND => NULL;
ThreeDScenes.DisplayFromImagerContext[context3d, imagerCtx];
IF context3d.depthBuffer THEN ThreeDScenes.AddDepthBuffer[context3d];
IF context3d.alphaBuffer THEN ThreeDScenes.AddAlphaBuffer[context3d];
};
Notify: ENTRY PROCEDURE [] ~ {
NOTIFY context3d.halted;
};
This will be called when the viewer is destroyed
ShutDown: PROCEDURE [] ~ {
buf: Pixels.PixelBuffer;  -- zeroed buffer
context3d.display ← buf;
IF offScreenCtx # NIL THEN offScreenCtx.display ← buf;
frames ← NIL;   -- free frame storage for garbage colection
pictures ← NIL;   -- free image storage for garbage collection
ThreeDMisc.CloseLog[context3d];
};
GetOffScreenCtx: PROC[context: REF Context, width, height: NAT] ~ {
offScreenCtx ← ThreeDScenes.Create[];
ThreeDScenes.DisplayFromVM[offScreenCtx, width, height, context.renderMode];
IF context.alphaBuffer THEN ThreeDScenes.AddAlphaBuffer[offScreenCtx];
ThreeDMisc.CopyContextData[dstCtx: offScreenCtx, srcCtx: context];
offScreenCtx.viewPort ← [ x: 0.0, y: 0.0, w: Real.Float[width], h: Real.Float[height] ];
[] ← ThreeDMisc.StartLog[offScreenCtx];
FOR i: NAT IN [0..offScreenCtx.shapes.length) DO
offScreenCtx.shapes[i].vtcesInValid ← TRUE;
ENDLOOP;
};
GetOnScreenCtx: PROC[context: REF Context, width, height: NAT] ~ {
Makes a new view of the supplied context, useful for using two portions of the screen differently, etc.
GetDisplay: PROC[ imagerCtx: Imager.Context] ~ {
ThreeDScenes.DisplayFromImagerContext[ onScreenCtx, imagerCtx ];
IF context.depthBuffer THEN ThreeDScenes.AddDepthBuffer[context3d];
IF context.alphaBuffer THEN ThreeDScenes.AddAlphaBuffer[context3d];
};
onScreenCtx ← ThreeDScenes.Create[];
QuickViewer.DrawInViewer[ NARROW[context.viewer], GetDisplay ];
IF context.alphaBuffer THEN ThreeDScenes.AddAlphaBuffer[onScreenCtx];
IF context.depthBuffer THEN ThreeDScenes.AddDepthBuffer[onScreenCtx];
ThreeDMisc.CopyContextData[dstCtx: onScreenCtx, srcCtx: context];
onScreenCtx.viewPort ← [ x: 0.0, y: 0.0, w: Real.Float[width], h: Real.Float[height] ];
FOR i: NAT IN [0..onScreenCtx.shapes.length) DO
onScreenCtx.shapes[i].vtcesInValid ← TRUE;
ENDLOOP;
};
StopEverything: PROCEDURE[] ~ {         -- panic stop
bufContext: REF Context ← NARROW[
Atom.GetPropFromList[context3d.props, $BufferContext]
];
context3d.stopMe ← TRUE;
IF bufContext # NIL THEN {
bufContext.stopMe ← TRUE;
context3d.props ← Atom.RemPropFromList[context3d.props, $BufferContext];
};
IF gallery THEN ClearGallery[];
IF movieProcess # NIL THEN {
CedarProcess.Abort[movieProcess];
movieProcess ← NIL;
SetViewPort[ cachedViewPort ];
};
};
MenuHit: PROCEDURE[bttn, choice: ATOM, x, y: REAL] ~ { 
This will be called whenever the mouse is moved or a button is pushed.
x ← x - context3d.display.width/2;    -- center mouse coords
y ← y - context3d.display.height/2;
SELECT bttn FROM
$Help    => HelpMessage;
$NewScene  => IF currentScene = NIL THEN SetScene1[]
      ELSE IF currentScene = scene1 THEN SetScene2[]
      ELSE IF currentScene = scene2 THEN SetScene3[] ELSE SetScene1[]; 
$Lines   => LinesDemo[];
$Facets   => FacetedDemo[];
$Smooth   => SmoothDemo[];
$Shiny   => ShinyDemo[];
$Orbit   => Orbit[];
$STOP   => StopEverything[];
$Movie   => movieProcess ← CedarProcess.Fork[PlayBackbyFrame];
$Gallery   => [] ← CedarProcess.Fork[LoadGallery];
$Jaggy   => SetAliased[];
$NoJaggy   => SetAntiAliased[];
$Reset   => { StopEverything[]; Reset[]; };
$LeftButton, $LeftHeld => IF movieProcess # NIL
THEN frameRate ← Real.FixI[x / 10.]
ELSE IF gallery THEN ShowNextPicture[]
ELSE {
xNow ← x / 15.0;
yNow ← y / 15.0;
ThreeDScenes.SetView[ context3d, [xNow, yNow, zNow], context3d.ptOfInterest ];
MakeFrame[];
};
$MiddleButton, $MiddleHeld => IF NOT context3d.lineDrawing THEN {
xLight: REAL ← x * 1.0;
yLight: REAL ← y * 1.0;
[] ← ThreeDScenes.SetLight[ context3d, "Initial", [xLight, yLight, 50.0] ];
MakeFrame[];
};
$RightButton, $RightHeld => 
IF gallery THEN ShowLastPicture[]
ELSE {
zNow ← y / 15.0;
ThreeDScenes.SetView[ context3d, [xNow, yNow, zNow], context3d.ptOfInterest ];
MakeFrame[];
};
ENDCASE;
};
HelpMessage: PROCEDURE [] ~ {
ThreeDMisc.ShowRope[context3d, 60.0, 360.0,
       "Scene1 - Select Scene with Polyhedra on Checkerboard." ];
ThreeDMisc.ShowRope[context3d, 60.0, 330.0,
       "Scene2 - Select Scene with Egg, banana, and Glass." ];
ThreeDMisc.ShowRope[context3d, 60.0, 300.0,
       "Lines, Facets, Smooth, Shiny - Make image as specified." ];
ThreeDMisc.ShowRope[context3d, 60.0, 270.0,
       "Orbit - Make images following elliptical path around scene." ];
ThreeDMisc.ShowRope[context3d, 60.0, 240.0,
       "Movie - Play back a precalculated sequence."];
ThreeDMisc.ShowRope[context3d, 60.0, 210.0,
       "Jaggy/NoJaggy - turn antialiasing off/on (NoJaggy is slow!)." ];
ThreeDMisc.ShowRope[context3d, 60.0, 180.0,
       "Buttoning in the image makes a new image with:" ];
ThreeDMisc.ShowRope[context3d, 60.0, 150.0,
       " Left Button determining eyepoint position in x-y plane," ];
ThreeDMisc.ShowRope[context3d, 60.0, 120.0,
       " Right Button determining eyepoint height," ];
ThreeDMisc.ShowRope[context3d, 60.0, 90.0,
       " Middle Button determining light source x-y." ];
ThreeDMisc.ShowRope[context3d, 60.0, 60.0,
       "STOP - stops long operations, Reset - recovers from trouble."];
};
SetViewPort: PROC [ size: Imager.Rectangle ] ~ {
ThreeDScenes.SetViewPort[ context3d, size ];   -- set clippers etc.
! ThreeDScenes.ThreeDScenesError => IF reason = $NullClipper THEN RESUME
FOR i: NAT IN [0..context3d.shapes.length) DO
context3d.shapes[i].vtcesInValid ← TRUE;
context3d.shapes[i].shadingInValid ← TRUE;
ENDLOOP;
ThreeDScenes.SetView[      -- get new screen dimensions into transformations
context:  context3d,
eyePoint:  context3d.eyePoint,
ptOfInterest: context3d.ptOfInterest,
fieldOfView: context3d.fieldOfView,
rollAngle: context3d.rollAngle,
upDirection: context3d.upDirection,
hitherLimit: context3d.hitherLimit,
yonLimit:  context3d.yonLimit
];
};
ChangeRenderMode: PROC [ mode: ATOM ] ~ {  -- for {$Dithered, $PseudoColor, $Grey}
SELECT mode FROM
$Dithered =>  ImagerColorMap.SetStandardColorMap[ Terminal.Current[] ];
$PseudoColor => ThreeDMisc.LoadStd8BitClrMap[ Terminal.Current[] ];
$Grey   =>
ThreeDMisc.LoadColorRamp[ Terminal.Current[], [0.,0.,0.], [1.,1.,1.], [.43, .43, .43] ];
ENDCASE  => SIGNAL ThreeDDemoError[$CantHackThatMode];
context3d.renderMode ← mode;
context3d.preferredRenderMode ← mode;
context3d.display.props ← Atom.PutPropOnList[ context3d.display.props, $RenderMode, mode ];
};
Demos
SetDepthBuffer: PROCEDURE [] ~ {
IF context3d.depthBuffer = TRUE THEN RETURN[];
ThreeDScenes.AddDepthBuffer[context3d];
};
NoDepthBuffer: PROCEDURE [] ~ {
GetDisplay: PROC[ imagerCtx: Imager.Context] ~ {
ThreeDScenes.DisplayFromImagerContext[ context3d, imagerCtx ];
};
IF context3d.depthBuffer = FALSE THEN RETURN[];
QuickViewer.DrawInViewer[ NARROW[context3d.viewer], GetDisplay ];
IF context3d.alphaBuffer THEN ThreeDScenes.AddAlphaBuffer[context3d];
context3d.depthBuffer ← FALSE;
};
SetAliased: PROCEDURE [] ~ {
GetDisplay: PROC[ imagerCtx: Imager.Context] ~ {
ThreeDScenes.DisplayFromImagerContext[ context3d, imagerCtx ];
};
IF context3d.alphaBuffer = FALSE THEN RETURN[];
QuickViewer.DrawInViewer[ NARROW[context3d.viewer], GetDisplay ];
IF context3d.depthBuffer THEN ThreeDScenes.AddDepthBuffer[context3d];
context3d.alphaBuffer ← FALSE;
};
SetAntiAliased: PROCEDURE [] ~ {
IF context3d.alphaBuffer = TRUE THEN RETURN[];
ThreeDScenes.AddAlphaBuffer[context3d];
};
DemoErrorMessages: PROCEDURE [] RETURNS[ error: BOOLEANFALSE] ~ {
NoSurfaces: PROCEDURE [shapes: REF ThreeDScenes.ShapeSequence] RETURNS[BOOLEAN]~{
FOR i: NAT IN [0..shapes.length) DO
IF shapes[i].surface # NIL THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
IF context3d.shapes = NIL OR NoSurfaces[context3d.shapes] THEN {
ThreeDMisc.ShowRope[context3d, 60.0, 340.0, "No objects, nothing to display"];
error ← TRUE;
};
IF context3d.alphaBuffer
AND NOT List.Memb[ context3d.renderMode, LIST[$Grey, $FullColor, $Dorado24] ]
THEN {
ThreeDMisc.ShowRope[context3d, 60.0, 300.0, "NoJaggy needs GreyScale or 24-bit Color"];
error ← TRUE;
};
};
LinesDemo: PROCEDURE [] ~ {
context3d.lineDrawing ← TRUE;
IF DemoErrorMessages[] THEN RETURN;
FOR i: NAT IN [0..context3d.shapes.length) DO
IF context3d.shapes[i].surface # NIL
THEN ThreeDScenes.PutShading[context3d.shapes[i], $Type, $Lines ];
ENDLOOP;
MakeFrame[];
};
FacetedDemo: PROCEDURE [] ~ {
context3d.lineDrawing ← FALSE;
IF DemoErrorMessages[] THEN RETURN;
FOR i: NAT IN [0..context3d.shapes.length) DO IF context3d.shapes[i].surface # NIL THEN {
context3d.shapes[i].shadingProps ← Atom.RemPropFromList[  -- no highlights
context3d.shapes[i].shadingProps,
$Shininess
];
ThreeDScenes.PutShading[context3d.shapes[i], $Type, $Faceted ];
IF ThreeDScenes.GetShading[context3d.shapes[i], $PatchColors ] = NIL THEN {
color: REF ← ThreeDScenes.GetShading[context3d.shapes[i], $Color];
rgb: RGBIF color # NIL THEN NARROW[color, REF RGB ]^ ELSE [0.7, 0.7, 0.7];
ThreeDMisc.SetFacetedColor[context3d, context3d.shapes[i].name, rgb];
};
};
ENDLOOP;
MakeFrame[];
};
SmoothDemo: PROCEDURE [] ~ {
context3d.lineDrawing ← FALSE;
IF DemoErrorMessages[] THEN RETURN;
FOR i: NAT IN [0..context3d.shapes.length) DO IF context3d.shapes[i].surface # NIL THEN {
context3d.shapes[i].shadingProps ← Atom.RemPropFromList[  -- no highlights
context3d.shapes[i].shadingProps,
$Shininess
];
ThreeDScenes.PutShading[context3d.shapes[i], $Type, $Smooth ];
};
ENDLOOP;
MakeFrame[];
};
ShinyDemo: PROCEDURE [] ~ {
context3d.lineDrawing ← FALSE;
IF DemoErrorMessages[] THEN RETURN;
FOR i: NAT IN [0..context3d.shapes.length) DO IF context3d.shapes[i].surface # NIL THEN {
shininess: REF REALNARROW[
ThreeDScenes.GetShading[context3d.shapes[i], $Shininess]
];
IF shininess = NIL THEN {
shininess ← NEW[REAL ← 50.0];
ThreeDMisc.SetShininess[ context3d, context3d.shapes[i].name, shininess^ ];
};
IF NARROW[ ThreeDScenes.GetShading[context3d.shapes[i], $Type], ATOM ] = $Lines
THEN ThreeDScenes.PutShading[context3d.shapes[i], $Type, $Smooth ];
};
ENDLOOP;
MakeFrame[];
};
Orbit: PROCEDURE [] ~ {
IF DemoErrorMessages[] THEN RETURN;
context3d.stopMe ← FALSE;       -- get stop flag unstuck, if necessary
context3d.suspendMe ← FALSE;
animation ← TRUE;
ThreeDMisc.OrbitEye[
context: context3d,
lookingFrom: context3d.eyePoint,
lookingAt: context3d.ptOfInterest,
axis: [0.3, 0.0, 1.0],
framesPerRev: 53
! UNWIND => animation ← FALSE
];
animation ← FALSE;
};
SetScene1: PROCEDURE [] ~ {
IF scene1 = NIL THEN {
context3d.shapes ← context3d.lights;   -- don't change the lighting
ThreeDMisc.AddShapeAt[ context3d,
"CheckerBoard",        -- internal name
"CheckerBoard.shape",       -- file name
[-8.0, -8.0, -2.0]        -- position
];
ThreeDMisc.AddShapeAt[ context3d, "Icosahedron", "Icosahedron.shape", [2.0, 0.0, 0.0] ];
ThreeDMisc.AddShapeAt[ context3d, "SoccerBall", "SoccerBall.shape", [-2.0, 0.0, 0.0] ];
ThreeDMisc.AddShapeAt[ context3d, "CutCube", "CutCube.shape", [0.0, 3.0, 0.0] ];
ThreeDMisc.SetFacetedColor[ context3d, "CheckerBoard", [.8, .4, .2] ]; -- add shading
ThreeDMisc.SetSmoothColor[ context3d, "CheckerBoard", [.8, .4, .2] ];
ThreeDMisc.SetFacetedColor[ context3d, "CutCube", [.9, .3, .1] ];
scene1 ← NEW[ThreeDScenes.ShapeSequence[context3d.shapes.length] ← context3d.shapes^];
context3d.lineDrawing ← TRUE;
}
ELSE context3d.shapes ← scene1;
NoDepthBuffer[];
currentScene ← scene1;
};
SetScene2: PROCEDURE [] ~ {
IF scene2 = NIL THEN {
context3d.shapes ← context3d.lights;   -- don't change the lighting;
ThreeDMisc.AddShapeAt[context3d, "ChampagneGlass", "ChampagneGlass.shape",
        [2.0, 0.0, 0.0] ];
ThreeDMisc.AddShapeAt[context3d, "UtahEgg", "UtahEgg.shape", [-2.0, .0, .0] ];
ThreeDMisc.AddShapeAt[context3d, "Banana", "Banana.shape", [0.0, .0, .0] ];
ThreeDScenes.FindShape[ context3d.shapes, "UtahEgg" ].orientation ← [-1., 1., 0.];
ThreeDScenes.FindShape[ context3d.shapes, "UtahEgg" ].rotation ← 90.0;
ThreeDScenes.FindShape[ context3d.shapes, "ChampagneGlass" ].orientation ← [0., 0., 1.];
ThreeDScenes.FindShape[ context3d.shapes, "ChampagneGlass" ].rotation ← 45.0;
ThreeDMisc.SetFacetedColor[context3d, "ChampagneGlass", [1.,0.,1.] ];
ThreeDMisc.SetSmoothColor[context3d, "ChampagneGlass", [1.,0.,1.] ];
ThreeDMisc.SetTransmittance[context3d, "ChampagneGlass", .8 ];
ThreeDMisc.SetFacetedColor[context3d, "UtahEgg", [1.0, .8, .5] ];
ThreeDMisc.SetSmoothColor[context3d, "UtahEgg", [1.0, .8, .5] ];
ThreeDMisc.SetFacetedColor[context3d, "Banana", [1.0, .9, .1] ];
ThreeDMisc.SetSmoothColor[context3d, "Banana", [1.0, .9, .1] ];
scene2 ← NEW[ThreeDScenes.ShapeSequence[context3d.shapes.length] ← context3d.shapes^];
}
ELSE context3d.shapes ← scene2;
NoDepthBuffer[];
currentScene ← scene2;
};
SetScene3: PROCEDURE [] ~ {
IF scene3 = NIL THEN {
context3d.shapes ← context3d.lights;   -- don't change the lighting;
ThreeDMisc.AddShapeAt[context3d, "TeaPot", "TeaPotWithBot.shape", [0.0, 0.0, -1.25] ];
scene3 ← NEW[ThreeDScenes.ShapeSequence[context3d.shapes.length] ← context3d.shapes^];
ThreeDScenes.SetView[context3d, [3.0, -10.0, 3.0], [0.0, 0.0, 0.0] ];
}
ELSE context3d.shapes ← scene3;
SetDepthBuffer[];
currentScene ← scene3;
};
PutPixel: PROCEDURE[x, y: NAT, clr: RGB] ~ {
ThreeDMisc.PutPixel[context3d, x, y, clr];
};
Frame Generation and Animation
MakeFrame: PROC ~ {
context3d.stopMe ← FALSE;       -- get stop flag unstuck, if necessary
ThreeDMisc.MakeFrame[context3d ];      -- clear and make new frame
};
ShowShapes: PROC ~ {
context3d.stopMe ← FALSE;       -- get stop flag unstuck, if necessary
ThreeDMisc.ShowShapes[context3d ];     -- show shapes without clearing
};
movieFileRoot: Rope.ROPE ← "/Pixel/Crow/Animation/StillCloser.ais";
movieViewPort: Imager.Rectangle ← [120.0, 90.0, 400.0, 300.0];
movieNumFiles: NAT ← 40;
movieStart: NAT ← 1;
SetUpMovie: PROC[fileRoot: Rope.ROPE, viewPort: Imager.Rectangle, numFiles, start: NAT] ~ {
ChangeRenderMode[$PseudoColor];
movieFileRoot ← fileRoot;
movieViewPort ← viewPort;
movieNumFiles ← numFiles;
movieStart ← start;
frames ← NIL;
};
frameRate: INTEGER ← 6;  -- playback rate in frames/second, negative plays backwards
PlayBackbyFrame: ENTRY CedarProcess.ForkableProc ~ {
ENABLE UNWIND => NULL;
context3d.stopMe ← FALSE;       -- get stop flag unstuck, if necessary
cachedViewPort ← context3d.viewPort;
IF context3d.renderMode # $PseudoColor THEN {
ThreeDMisc.ShowRope[context3d, 140.0, 50.0, "Change to 8-bit pseudocolor, please!"];
RETURN[];
};
ThreeDScenes.FillInBackGround[context3d];
SetViewPort[ movieViewPort ];
IF frames = NIL THEN {
pictures ← NIL;        -- avoid using up all the VM
frames ← AISAnimation.CacheNumberedAISFiles[
context3d, movieFileRoot, movieNumFiles, movieStart
];
};
ThreeDMisc.ShowRope[context3d, 140.0, 50.0, "Control frame rate using left button"];
WHILE TRUE DO
ENABLE UNWIND => SetViewPort[ cachedViewPort ];
changed: BOOLEAN ← ThreeDMisc.UpdateDisplay[context3d];   
IF changed THEN {
Process.Pause[Process.SecondsToTicks[2]]; -- hang on moment to allow things to settle
SetViewPort[ movieViewPort ];
};
AISAnimation.ShowNextAISCacheFrame[context3d, frames, frameRate];
CedarProcess.CheckAbort[];
ENDLOOP;
};
pictureFile: Rope.ROPE ← "/Cyan/AIS/Crow/PrettyPictures.txt";
pictures: REF AISAnimation.FrameSequence; 
LoadGallery: CedarProcess.ForkableProc ~ {
IF context3d.renderMode # $Dorado24 THEN {
ThreeDMisc.ShowRope[context3d, 140.0, 50.0, "Change to 24-bit color, please!"];
RETURN[];
};
context3d.stopMe ← FALSE;       -- get stop flag unstuck, if necessary
ThreeDScenes.FillInBackGround[context3d];
ThreeDMisc.ShowRope[context3d, 140.0, 50.0, "Pictures will appear as they are read in"];
ThreeDMisc.ShowRope[context3d, 140.0, 20.0, "Left button, ahead - Right button, back"];
gallery ← TRUE;
SetAliased[];
IF pictures = NIL THEN pictures ← NEW[AISAnimation.FrameSequence[16]];
pictures ← AISAnimation.CacheAISFiles[context3d, pictureFile, pictures];
};
ShowNextPicture: PROC ~ {   -- display next picture in sequence
IF pictures # NIL AND pictures.length > 0 -- make sure frame ready and no wraparound
AND ((pictures.currentFrame + 1) MOD pictures.length) >= pictures.currentFrame
THEN AISAnimation.ShowNextAISCacheFrame[context3d, pictures, 10];
};
ShowLastPicture: PROC ~ {   -- display previous picture in sequence
IF pictures # NIL AND pictures.currentFrame > 0
THEN AISAnimation.ShowNextAISCacheFrame[context3d, pictures, -10];
};
ClearGallery: PROC ~ {
IF pictures # NIL
THEN FOR i: NAT IN [0..pictures.length) DO pictures[i].pixels ← NIL; ENDLOOP;
gallery ← FALSE;
};
Init[];
END.