AISAnimationImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, May 14, 1986 2:50:31 pm PDT
DIRECTORY
CedarProcess    USING [CheckAbort, DoWithPriority],
Rope      USING [ROPE, Substr, Cat],
Convert     USING [RopeFromInt],
Real      USING [RoundI, RoundC],
FS       USING [ExpandName, ComponentPositions, FileInfo, Error,
         StreamOpen],
IO       USING [STREAM, GetRopeLiteral, GetInt, EndOf, EndOfStream],
Terminal     USING [Virtual, Current, WaitForBWVerticalRetrace,
         ModifyColorFrame],
MessageWindow   USING [Append],
AIS      USING [BRef, FRef, WRef, OpenWindow, OpenFile,
         GetWindowParams, CloseFile, Raster, RasterPart, CreateFile,
         Buffer, UnsafeWriteLine, UnsafeReadLine],
SampleMapOps   USING [Create, Get, Put, Transfer, SampleMap],
Pixels      USING [PixelBuffer, XfmMapPlace, SampleSet, GetSampleSet,
         Transfer, TerminalFromBuffer],
ThreeDScenes   USING [Context, Create, FillInBackGround],
ThreeDMisc    USING [PrependWorkingDirectory],
AISAnimation   USING [FrameSequence];
AISAnimationImpl: CEDAR PROGRAM
IMPORTS FS, IO, Rope, Convert, Terminal, CedarProcess, Real, AIS, Pixels, SampleMapOps, ThreeDMisc, ThreeDScenes, MessageWindow
EXPORTS AISAnimation
~ BEGIN
Internal Declarations
AISAnimationError: PUBLIC SIGNAL [reason: ATOM] ~ CODE;
RopeSequence: TYPE ~ RECORD[ SEQUENCE length: NAT OF Rope.ROPE ];
FrameSequence: TYPE ~ AISAnimation.FrameSequence;
bitsPer75thSecond: INT ~ 314573;  -- roughly what bitBlt can move in one LF field time
stopMe: BOOLEANTRUE;
Procedures for Animation
PlayBackNumberedAISFiles: PUBLIC PROC[context: REF ThreeDScenes.Context,
             fileRoot : Rope.ROPE,
         startNum, numFiles, framesPerSec, secondsPlayingTime : NAT] ~ {
frames: REF FrameSequence ← CacheNumberedAISFiles[context, fileRoot, numFiles, startNum];
PlayBackAISCache[context, frames, framesPerSec, secondsPlayingTime];
};
StoreFiles: PUBLIC PROC[context: REF ThreeDScenes.Context, fileRoot: Rope.ROPE, number: NAT] ~ {
PutAIS[ context, PasteInSequenceNo[fileRoot, number] ];
};
CacheAISFiles: PUBLIC PROC[context: REF ThreeDScenes.Context, fileOfNames: Rope.ROPE,
         frames: REF FrameSequence ← NIL]
     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[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, frames] ];
};
CacheNumberedAISFiles: PUBLIC PROC[context: REF ThreeDScenes.Context,
            fileRoot: Rope.ROPE, numFiles: NAT, start: NAT ← 0]
        RETURNS[REF FrameSequence] ~ {
GetName: PROC[number: NAT] RETURNS[Rope.ROPE] ~ {
RETURN[ PasteInSequenceNo[fileRoot, number] ];
};
RETURN[ CacheFiles[context, numFiles, start, GetName, NIL, TRUE] ];
};
CacheFiles: PROC[context: REF ThreeDScenes.Context, numFiles, start: NAT,
      getname: PROC[number: NAT] RETURNS[Rope.ROPE],
      frames: REF FrameSequence ← NIL, doVisibly: BOOLEANFALSE ]
    RETURNS[REF FrameSequence] ~ {
left: INT ← Real.RoundC[context.viewPort.x];
bottom: INT ← Real.RoundC[context.viewPort.y];
width: INT ← Real.RoundC[context.viewPort.w];
height: INT ← Real.RoundC[context.viewPort.h];
Action: PROC ~ {
FOR i: NAT IN [start .. start+numFiles) DO
bufContext, sinkContext: REF ThreeDScenes.Context;
vt: Terminal.Virtual ← Pixels.TerminalFromBuffer[context.display];
InnerAction: PROC ~ {
[] ← GetAIS[ sinkContext, getname[i] ];
};
bufContext ← ThreeDScenes.Create[
width,        -- viewport dimensions
height,
context.renderMode,
FALSE         -- no alpha buffer, no depth buffer
];
sinkContext ← IF doVisibly THEN context ELSE bufContext;
IF stopMe THEN HoldEverything[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ThreeDScenes.FillInBackGround[sinkContext]; -- clear random garbage from memory
IF doVisibly OR i = start
THEN Terminal.ModifyColorFrame[vt, InnerAction]    -- protect screen
ELSE InnerAction[];    
IF frames = NIL THEN frames ← NEW[ FrameSequence[numFiles] ];
IF frames.maxLength <= i - start THEN {   -- extend FrameSequence if necessary
newFrames: REF FrameSequence ← NEW[ FrameSequence[2*frames.maxLength] ];
FOR j: NAT IN [0..frames.maxLength) DO newFrames[i] ← frames[i]; ENDLOOP;
frames ← newFrames;
};
IF doVisibly
THEN {
Pixels.Transfer[ bufContext.display, sinkContext.display];
frames[i - start] ← bufContext.display;
}
ELSE frames[i - start] ← bufContext.display;
frames.length ← i - start + 1;
ENDLOOP;
};
stopMe ← FALSE;
CedarProcess.DoWithPriority[background, Action];    -- load 'em up quietly
RETURN[frames];
};
PlayBackAISCache: PUBLIC PROC[context: REF ThreeDScenes.Context,
           frames: REF FrameSequence,
           framesPerSec, secondsPlayingTime: NAT,
           bothWays: BOOLEANFALSE, startNum: NAT ← 0] ~ {
vt: Terminal.VirtualTerminal.Current[];
waitCount, transferTime: NAT;
repetitions: INTMAX[1,
       INT[secondsPlayingTime] * framesPerSec / (frames.length-startNum)];
IF frames = NIL THEN { SIGNAL AISAnimationError[$NoCachedFrames]; RETURN; };
[waitCount, transferTime] ← GetWaitCount[context, framesPerSec];
IF waitCount = 0
THEN repetitions ← (secondsPlayingTime * 75 / transferTime) / (frames.length - startNum);
IF bothWays THEN repetitions ← repetitions/2;
stopMe ← FALSE;
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;
ShowAISCacheFrame[context, frames];
IF stopMe THEN HoldEverything[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
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;
ShowAISCacheFrame[context, frames];
IF stopMe THEN HoldEverything[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ENDLOOP;
ENDLOOP;
stopMe ← TRUE;
};
ShowNextAISCacheFrame: PUBLIC PROC[context: REF ThreeDScenes.Context,
           frames: REF FrameSequence, framesPerSec: INTEGER] ~ {
waitCount: NAT;
vt: Terminal.VirtualTerminal.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;
ShowAISCacheFrame[context, frames];
};
Support Procedures
Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ {
result ← Real.RoundI[number];
IF result < number THEN result ← result + 1;
};
HoldEverything: PUBLIC PROCEDURE [] ~ {
This shuts down the current process
ERROR ABORTED;
};
StopDisplay: PUBLIC PROC [] ~ { stopMe ← TRUE; };
PasteInSequenceNo: PUBLIC PROC[fileRoot: Rope.ROPE, number: NAT] RETURNS[Rope.ROPE] ~ {
cp: FS.ComponentPositions; fullFName, fName, ext: Rope.ROPE;
[fullFName, cp, ] ← FS.ExpandName[fileRoot];
fName ← Rope.Substr[ fullFName, 0, cp.ext.start-1];   -- get name for filling in numbers
ext ← Rope.Substr[ fullFName, cp.ext.start, cp.ext.length];
RETURN[ Rope.Cat[fName, Convert.RopeFromInt[number, 10, FALSE], ".", ext] ];
};
PasteInLabel: PROC[fileRoot: Rope.ROPE, label: Rope.ROPE] RETURNS[Rope.ROPE] ~ {
cp: FS.ComponentPositions; fullFName, fName, ext: Rope.ROPE;
[fullFName, cp, ] ← FS.ExpandName[fileRoot];
fName ← Rope.Substr[ fullFName, 0, cp.ext.start-1];   -- get name for filling in numbers
ext ← Rope.Substr[ fullFName, cp.ext.start, cp.ext.length];
RETURN[ Rope.Cat[fName, label, ".", ext] ];
};
GetWaitCount: PROC[context: REF ThreeDScenes.Context, framesPerSec: NAT]
     RETURNS[waitCount: NAT, transferTime: NAT] ~ {
width: INT ← Real.RoundC[context.viewPort.w];
height: INT ← Real.RoundC[context.viewPort.h];
addOn: NATIF context.alphaBuffer THEN 1 ELSE 0;
bitsPerPixel: NAT ← context.display.pixels[addOn].subMap.sampleMap.bitsPerSample;
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;
};
ShowAISCacheFrame: PUBLIC PROC[ context: REF ThreeDScenes.Context,
           frames: REF FrameSequence ] ~ {
IF frames.currentFrame < 0 OR frames.currentFrame >= frames.length
THEN SIGNAL AISAnimationError[$FrameNumBoundsError];
IF frames[frames.currentFrame].pixels # NIL
THEN Pixels.Transfer[context.display, frames[frames.currentFrame]];
};
standardNames: ARRAY[0..5) OF RECORD[ pref, alt, other: Rope.ROPE ] ← [
["-grey", "-gray", NIL],
["-red", "-r", NIL],
["-grn", "-green", "-g"],
["-blu", "-blue", "-b"],
["-alpha", "-alph", "-a"]
];
FindFile: PROC [fileRoot: Rope.ROPE, names: RECORD[ pref, alt, other: Rope.ROPE ] ]
RETURNS
[name: Rope.ROPE] ~ {
ok: BOOLEANTRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.pref]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.alt]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.other]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ]
ELSE {
MessageWindow.Append[
Rope.Cat[" Can't find ", fileRoot, " with extensions: ",
   Rope.Cat[names.pref, " ", names.alt, " ", names.other]
] ];
RETURN[ NIL ];
};
};
GetAIS: PUBLIC PROC[context: REF ThreeDScenes.Context, fileRoot: Rope.ROPE ← "Temp.ais",
      xOffset, yOffset: INTEGER ← 0, center: BOOLEANTRUE ]
     RETURNS
[ xSize, ySize: INTEGER] ~ {
numFiles: NAT;
names: ARRAY[0..4) OF RECORD[ pref, alt, other: Rope.ROPE ];
fileRoot ← ThreeDMisc.PrependWorkingDirectory[context, fileRoot];
SELECT context.renderMode FROM
$Dithered, $PseudoClr, $Grey => {
names[0] ← standardNames[0];
numFiles ← 1;
};
$FullClr, $Dorado24  => {
names[0] ← standardNames[1];
names[1] ← standardNames[2];
names[2] ← standardNames[3];
numFiles ← 3;
};
ENDCASE => SIGNAL AISAnimationError[$BadRenderMode];
IF context.alphaBuffer
THEN { names[numFiles] ← standardNames[4]; numFiles ← numFiles + 1; };
FOR i: NAT IN [0..numFiles) DO   -- get a file for each pixel entry
fileName: Rope.ROPE ← FindFile[fileRoot, names[i]];
fileInfo: AIS.FRef;
wndw: AIS.WRef;
wbufr: AIS.BRef;
samples: Pixels.SampleSet;
scanLine: SampleMapOps.SampleMap;
buffer: AIS.Buffer;
bufXOffset, bufYOffset: INTEGER;    -- start position in pixel buffer
xBeg, yBeg: INTEGER;       -- start position in AIS file
firstScan, lastScan, firstPixel, lastPixel, height, width: INTEGER;
IF fileName = NIL THEN LOOP;     -- skip if no file found
fileInfo ← AIS.OpenFile[ FindFile[fileRoot, names[i]] ];   -- get file
wndw ← AIS.OpenWindow[fileInfo];
wbufr ← wndw.bref;
[firstScan, lastScan, firstPixel, lastPixel] ← AIS.GetWindowParams[wndw];
IF center              -- automatic centering
THEN {
bufXOffset ← xOffset + (Real.RoundI[context.viewPort.w] - (lastPixel-firstPixel+1)) /2;
bufYOffset ← yOffset + (Real.RoundI[context.viewPort.h] - (lastScan-firstScan+1)) / 2;
}
ELSE { bufXOffset ← xOffset; bufYOffset ← yOffset; };
IF bufXOffset >= 0       -- set lower left corner in AIS and PixelBuffer
THEN { xBeg ← 0; }
ELSE { xBeg ← -bufXOffset; bufXOffset ← 0; };
IF bufYOffset >= 0
THEN { yBeg ← 0; }
ELSE { yBeg ← -bufYOffset; bufYOffset ← 0; };
height ← MIN[ Real.RoundI[context.viewPort.h]-bufYOffset, (lastScan-firstScan+1) - yBeg];
width ← MIN[ Real.RoundI[context.viewPort.w]-bufXOffset, (lastPixel-firstPixel+1) - xBeg ];
xSize ← MAX[width, lastPixel-firstPixel+1];
ySize ← MAX[height, lastScan-firstScan+1];    -- grab height and width to return
yBeg ← lastScan - (yBeg + height-1);  -- flip y-values
[bufXOffset, bufYOffset] ← Pixels.XfmMapPlace[ context.display.pixels[i],
              bufXOffset, bufYOffset+height-1 ];
samples ← Pixels.GetSampleSet[lastPixel-firstPixel+1];
scanLine ← SampleMapOps.Create[
fSize: (lastPixel-firstPixel+1),
sSize: 1,
bitsPerSample: context.display.pixels[i].subMap.sampleMap.bitsPerSample
];
TRUSTED { buffer ← [length: wndw.wordsPerLine, addr: scanLine.base.word]; };
FOR y: INTEGER IN [ 0 .. height ) DO
IF y+yBeg >= 0 AND y+yBeg <= lastScan THEN {
TRUSTED { AIS.UnsafeReadLine[ wndw, buffer, y+yBeg ]; };
IF context.display.pixels[i].df > 1
THEN {
SampleMapOps.Get[ buffer: samples, start: 0, count: lastPixel-firstPixel+1,
sampleMap: scanLine ];
SampleMapOps.Put[ buffer: samples, start: xBeg, count: width,
sampleMap: context.display.pixels[i].subMap.sampleMap,
f: bufXOffset,
s: y+bufYOffset,
df: context.display.pixels[i].df
];
}
ELSE SampleMapOps.Transfer[
dest: context.display.pixels[i].subMap.sampleMap,
destStart: [f: bufXOffset, s: y+bufYOffset],
source: [scanLine, [f: xBeg, s: 0], [f: width, s: 1] ]
];
};
ENDLOOP;
AIS.CloseFile[fileInfo];
ENDLOOP;
};
PutAIS: PUBLIC PROC[context: REF ThreeDScenes.Context, fileRoot: Rope.ROPE ← "Temp.ais" ] ~{
addOn: NATIF context.alphaBuffer THEN 1 ELSE 0;
numFiles: NAT;
bitsPerPixel: NAT ← context.display.pixels[addOn].subMap.sampleMap.bitsPerSample;
raster: AIS.Raster ← NEW[AIS.RasterPart];
samples: Pixels.SampleSet;
scanLine: SampleMapOps.SampleMap;
height, width: INTEGER;
names: ARRAY[0..4) OF Rope.ROPEALL[NIL];
fileRoot ← ThreeDMisc.PrependWorkingDirectory[context, fileRoot];
raster.scanCount ← height ← Real.RoundC[context.viewPort.h];
raster.scanLength ← width ← Real.RoundC[context.viewPort.w];
raster.scanMode ← rd;
raster.bitsPerPixel ← bitsPerPixel;
raster.linesPerBlock ← -1;
raster.paddingPerBlock ← 0;
SELECT context.renderMode FROM
$Dithered, $PseudoClr => { names[0] ← NIL; numFiles ← 1; };
$Grey => { names[0] ← "-grey"; numFiles ← 1; };
$FullClr, $Dorado24  => {
names[0] ← "-red"; names[1] ← "-grn"; names[2] ← "-blu"; numFiles ← 3;
};
ENDCASE => SIGNAL AISAnimationError[$BadRenderMode];
IF context.alphaBuffer
AND (context.extentCovered.right > context.extentCovered.left)
AND (context.extentCovered.top > context.extentCovered.bottom)
THEN { names[numFiles] ← "-alpha"; numFiles ← numFiles + 1; };
FOR i: NAT IN [0..numFiles) DO   -- write a file for each pixel entry
fileInfo: AIS.FRef ← AIS.CreateFile[
name: PasteInLabel[fileRoot, names[i]],
raster: raster
];
window: AIS.WRef ← AIS.OpenWindow[fileInfo];
buffer: AIS.Buffer;
bufXOffset, bufYOffset: INTEGER;
[bufXOffset, bufYOffset] ← Pixels.XfmMapPlace[context.display.pixels[i], 0, height-1];
samples ← Pixels.GetSampleSet[ width ];
scanLine ← SampleMapOps.Create[
fSize: width,
sSize: 1,
bitsPerSample: context.display.pixels[i].subMap.sampleMap.bitsPerSample
];
TRUSTED{buffer ← [length: width, addr: scanLine.base.word];};
FOR y: NAT IN [0..height) DO
IF context.display.pixels[i].df > 1
THEN {
SampleMapOps.Get[ buffer: samples, count: width,
sampleMap: context.display.pixels[i].subMap.sampleMap,
f: bufXOffset,
s: y+bufYOffset,
df: context.display.pixels[i].df
];
SampleMapOps.Put[ buffer: samples, count: width, sampleMap: scanLine ];
}
ELSE SampleMapOps.Transfer[
dest: scanLine,
destStart: [f: 0, s: 0],
source: [
sampleMap: context.display.pixels[i].subMap.sampleMap,
start: [f: bufXOffset, s: y+bufYOffset],
size: [f: width, s: 1]
]
];
TRUSTED { AIS.UnsafeWriteLine[window, buffer, y]; };
ENDLOOP;
AIS.CloseFile[fileInfo];
ENDLOOP;
};
END.