<> <> <> DIRECTORY Atom USING [ PutPropOnList, RemPropFromList ], CedarProcess USING [CheckAbort, DoWithPriority], Rope USING [Cat, ROPE, Substr], Convert USING [RopeFromInt], Real USING [FixC, RoundI], FS USING [ExpandName, ComponentPositions, FileInfo, Error, StreamOpen], IO USING [STREAM, GetRopeLiteral, GetInt, EndOf, EndOfStream], Terminal USING [Virtual, Current, WaitForBWVerticalRetrace], 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], QuickViewer USING [DrawInViewer], Imager USING [Context], ThreeDScenes USING [Context, Create, DisplayFromVM, Error, FillInBackGround], ThreeDMisc USING [PrependWorkingDirectory], AISAnimation USING [FrameSequence]; AISAnimationImpl: CEDAR PROGRAM IMPORTS Atom, FS, IO, Rope, Convert, Terminal, CedarProcess, QuickViewer, Real, AIS, Pixels, SampleMapOps, ThreeDMisc, ThreeDScenes EXPORTS AISAnimation ~ BEGIN <> 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 <> 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[ThreeDMisc.PrependWorkingDirectory[context, 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[ 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, frames, TRUE] ]; }; 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[ ThreeDMisc.PrependWorkingDirectory[context, 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: BOOLEAN _ FALSE ] RETURNS[REF FrameSequence] ~ { left: INT _ Real.FixC[context.viewPort.x]; bottom: INT _ Real.FixC[context.viewPort.y]; width: INT _ Ceiling[context.viewPort.w]; height: INT _ Ceiling[context.viewPort.h]; Action: PROC ~ { FOR i: NAT IN [start .. start+numFiles) DO bufContext: REF ThreeDScenes.Context; vt: Terminal.Virtual _ Pixels.TerminalFromBuffer[context.display]; bufContext _ ThreeDScenes.Create[]; ThreeDScenes.DisplayFromVM[bufContext, width, height, context.renderMode]; context.props _ Atom.PutPropOnList[context.props, $BufferContext, bufContext]; IF NOT doVisibly THEN ThreeDScenes.FillInBackGround[bufContext]; -- clear memory [] _ GetAIS[ bufContext, getname[i] ]; IF context.stopMe THEN HoldEverything[]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button 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; }; frames[i - start] _ bufContext.display; frames.length _ i - start + 1; IF doVisibly THEN Pixels.Transfer[ context.display, bufContext.display]; -- to display ENDLOOP; context.props _ Atom.RemPropFromList[context.props, $BufferContext]; }; CedarProcess.DoWithPriority[background, Action]; -- load 'em up quietly RETURN[frames]; }; PlayBackAISCache: PUBLIC PROC[context: REF ThreeDScenes.Context, frames: REF FrameSequence, framesPerSec, secondsPlayingTime: NAT, bothWays: BOOLEAN _ FALSE, startNum: NAT _ 0] ~ { vt: Terminal.Virtual _ Terminal.Current[]; waitCount, transferTime: NAT; repetitions: INT _ MAX[1, INT[secondsPlayingTime] * framesPerSec / (frames.length-startNum)]; IF frames = NIL THEN { SIGNAL ThreeDScenes.Error[[$MisMatch, "NoCachedFrames"]]; RETURN; }; [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; ShowAISCacheFrame[context, frames]; IF context.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 context.stopMe THEN HoldEverything[]; -- shut down if stop signal received CedarProcess.CheckAbort[]; -- respond to Stop button ENDLOOP; ENDLOOP; }; ShowNextAISCacheFrame: PUBLIC PROC[context: REF ThreeDScenes.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; ShowAISCacheFrame[context, frames]; }; <> Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ { result _ Real.RoundI[number]; IF result < number THEN result _ result + 1; }; HoldEverything: PUBLIC PROCEDURE [] ~ { <> ERROR ABORTED; }; PasteInSequenceNo: PUBLIC PROC[fileRoot: Rope.ROPE, number: NAT] RETURNS[Rope.ROPE] ~ { RETURN[ PasteInLabel[fileRoot, Convert.RopeFromInt[number, 10, FALSE] ] ]; }; PasteInLabel: PROC[fileRoot: Rope.ROPE, label: Rope.ROPE] RETURNS[Rope.ROPE] ~ { cp: FS.ComponentPositions; fullFName, fName, ext: Rope.ROPE; [fullFName, cp, ] _ FS.ExpandName[fileRoot]; IF cp.ext.length = 0 THEN { fName _ Rope.Substr[ fullFName, 0, cp.ext.start]; -- name for filling in numbers ext _ "ais"; } ELSE { fName _ Rope.Substr[ fullFName, 0, cp.ext.start-1]; -- drop "." before ext 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 _ Ceiling[context.viewPort.w]; height: INT _ Ceiling[context.viewPort.h]; addOn: NAT _ IF 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; waitCount _ MAX[waitCount, 1]; }; ShowAISCacheFrame: PUBLIC PROC[ context: REF ThreeDScenes.Context, frames: REF FrameSequence ] ~ { DoTransfer: PROCEDURE [imagerCtx: Imager.Context] ~ { Pixels.Transfer[context.display, frames[frames.currentFrame]]; }; IF frames.currentFrame < 0 OR frames.currentFrame >= frames.length THEN SIGNAL ThreeDScenes.Error[[$MisMatch, "Frame number out of range"]]; IF frames[frames.currentFrame].pixels # NIL THEN IF context.viewer # NIL THEN QuickViewer.DrawInViewer[NARROW[context.viewer], DoTransfer] ELSE DoTransfer[NIL]; }; 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: BOOLEAN _ TRUE; [] _ 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 { SIGNAL ThreeDScenes.Error[[ $MisMatch, 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: BOOLEAN _ TRUE ] 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, $PseudoColor, $Grey => { names[0] _ standardNames[0]; numFiles _ 1; }; $FullColor, $Dorado24 => { names[0] _ standardNames[1]; names[1] _ standardNames[2]; names[2] _ standardNames[3]; numFiles _ 3; }; ENDCASE => SIGNAL ThreeDScenes.Error[[$MisMatch, "Bad RenderMode"]]; 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 + (Ceiling[context.viewPort.w] - (lastPixel-firstPixel+1)) /2; bufYOffset _ yOffset + (Ceiling[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[ Ceiling[context.viewPort.h]-bufYOffset, (lastScan-firstScan+1) - yBeg]; width _ MIN[ Ceiling[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: NAT _ IF 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.ROPE _ ALL[NIL]; fileRoot _ ThreeDMisc.PrependWorkingDirectory[context, fileRoot]; raster.scanCount _ height _ Ceiling[context.viewPort.h]; raster.scanLength _ width _ Ceiling[context.viewPort.w]; raster.scanMode _ rd; raster.bitsPerPixel _ bitsPerPixel; raster.linesPerBlock _ -1; raster.paddingPerBlock _ 0; SELECT context.renderMode FROM $Dithered, $PseudoColor => { names[0] _ NIL; numFiles _ 1; }; $Grey => { names[0] _ "-grey"; numFiles _ 1; }; $FullColor, $Dorado24 => { names[0] _ "-red"; names[1] _ "-grn"; names[2] _ "-blu"; numFiles _ 3; }; ENDCASE => SIGNAL ThreeDScenes.Error[[$MisMatch, "Bad RenderMode"]]; 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.