StarField.Mesa
Copyright 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Spreitzer, May 13, 1986 2:43:46 pm PDT
Mike Spreitzer September 27, 1986 6:18:26 pm PDT
DIRECTORY Basics, BasicTime, CedarProcess, DebuggerSwap, FS, Histograms, IdleBackdoor, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerPixelArray, ImagerPixelMap, ImagerPrivate, ImagerTerminal, ImagerTransformation, IO, Menus, PrincOps, Process, Random, Real, RealFns, Rope, RuntimeError, Terminal, TerminalDefs, TerminalFace, ThisMachine, TIPUser, Vector2, ViewerClasses, ViewerOps;
StarField: CEDAR PROGRAM
IMPORTS BasicTime, CedarProcess, DebuggerSwap, FS, Histograms, IdleBackdoor, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPixelMap, ImagerTerminal, ImagerTransformation, IO, Process, Random, Real, RealFns, Rope, RuntimeError, Terminal, TerminalFace, ThisMachine, TIPUser, ViewerOps
=
{
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
PixelMap: TYPE = ImagerPixelMap.PixelMap;
PixelArray: TYPE = ImagerPixelArray.PixelArray;
Font: TYPE = ImagerFont.Font;
Transformation: TYPE = ImagerTransformation.Transformation;
Index: TYPE = NAT;
StarData: TYPE = REF StarDataSeq;
StarDataSeq: TYPE = RECORD [stars: SEQUENCE size: NAT OF Star];
Star: TYPE = RECORD [xw, yw, zw, xp, yp, rp: REAL, ci: NAT ← 0];
StarPtr: TYPE = LONG POINTER TO Star;
stars: StarData ← NIL;
Mat: TYPE = ARRAY Dim OF Vec;
Vec: TYPE = ARRAY Dim OF REAL;
Dim: TYPE = {X, Y, Z};
idMat: Mat = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
Face: TYPE = {Left, Right, Bottom, Top, Back};
ImageCache: TYPE = REF ImageCacheRep;
ImageCacheRep: TYPE = RECORD [
length: NAT ← 0,
images: SEQUENCE size: NAT OF CachedImage];
[ci+1].rpMax > [ci].rpMax
CachedImage: TYPE = RECORD [
rpMax, d: REAL,
di: INTEGER,
image: PixelMap];
TextData: TYPE = RECORD [
texts: TextList,
numTexts: NAT,
totalProbability: REAL];
TextList: TYPE = LIST OF Text;
Text: TYPE = RECORD [
text: ROPE,
bounds: Imager.Box,
cumProb: REAL
];
Request: TYPE = REF RequestRep;
RequestRep: TYPE = RECORD [
proc: PROC [data: REF ANY, context: Imager.Context],
data: REF ANY];
smallReal: REAL = Real.SmallestNormalizedNumber * 16;
td: TextData;
imageCache: ImageCache ← NIL;
font: Font ← NIL;
toUnit: Transformation;
rs: Random.RandomStream ← Random.Create[seed: -1];
Milliseconds: TYPE = INT;
OneSecond: Milliseconds = 1000;
maxStars: NAT ← 200;
minStars: NAT ← 5;
histDtMin: Milliseconds ← 10;
histDtMax: Milliseconds ← 70;
goalDtOffsetLow: Milliseconds;
goalDtOffsetHigh: Milliseconds;
goalNDtProduct: INT;
FOVX: REAL ← 45;
edgeDegrees: REAL ← 65;
hither: REAL ← 0.01;
border: REAL ← 1.0;
viewerStartRp: REAL ← 0.7;
vtStartRp: REAL ← 0.7;
viewerRpMin: REAL ← 1.0;
vtRpMin: REAL ← 1.0;
rw: REAL ← 1;
initialDzw: REAL ← 5;
dv: REAL ← 0.05;
upTextMin: Milliseconds ← 10*OneSecond;
upTextMax: Milliseconds ← 60*OneSecond;
downTextMin: Milliseconds ← 1*OneSecond;
downTextMax: Milliseconds ← 6*OneSecond;
blankTimeoutMS: Milliseconds ← 60*OneSecond;
usePeriod: BOOLFALSE;
squareThresh: REAL ← 1.25;
useXOR: BOOLTRUE;
histPerf: BOOLFALSE;
pickRandomNs: BOOLFALSE;
pickPeriod: NAT ← 10;
runningPriority: CedarProcess.Priority ← background;
nStars: NAT ← minStars;
v: Vec;
avgDt: REAL--Milliseconds-- ← OneSecond;
decay: REAL ← 0.1;
hold: REAL ← 1.0 - decay;
fullAngle: REAL--degrees per step at left/right edge-- ← 1.0;
rollRate: REAL--degrees per step-- ← 1.0;
yaw, pitch, roll: REAL--degrees-- ← 0;
rotMat: Mat ← idMat;
msTillBlank: Milliseconds ← 0;
blank: BOOLTRUE;
pausePeriod: Process.Ticks ← 0;
retraces: NAT ← 1;
machineName: ROPE ← ThisMachine.Name[];
sfvcFlavor: ATOM ← $StarFieldViewerClass;
sfvc: ViewerClasses.ViewerClass ← NEW [ViewerClasses.ViewerClassRec ← [
flavor: sfvcFlavor,
notify: NotifySFV,
paint: PaintSFV,
tipTable: TIPUser.InstantiateNewTIPTable["StarFielder.TIP"]
]];
sfv: Viewer;
hists: ARRAY BOOL--useXOR-- OF Histograms.Histogram ← ALL[NIL];
okToGo: BOOLFALSE;
going: BOOLFALSE;
SetGoal: PROC [n1, dt1, n2, dt2, dd: REAL] = {
dt1 = o + p/n1
dt2 = o + p/n2
o: REAL;
goalNDtProduct ← Real.RoundLI[(dt1 - dt2)/(1.0/n1 - 1.0/n2)];
dt1 * n1 = o * n1 + p
dt2 * n2 = o * n2 + p
o ← (dt1*n1 - dt2*n2)/(n1 - n2);
goalDtOffsetLow ← Real.RoundLI[o - dd/2];
goalDtOffsetHigh ← Real.RoundLI[o + dd/2];
};
NotifySFV: PROC [self: Viewer, input: LIST OF REF ANY] = {
FOR input ← input, input.rest WHILE input # NIL DO
WITH input.first SELECT FROM
a: ATOM => SELECT a FROM
$Start => {okToGo ← TRUE; IF NOT going THEN TRUSTED {Process.Detach[FORK Viewit[]]}};
$Stop => okToGo ← FALSE;
$StartStick => stickViewers ← TRUE;
$StopStick => stickViewers ← FALSE;
ENDCASE => ERROR;
z: TIPUser.TIPScreenCoords => viewerMouse ← [z.mouseX, z.mouseY];
ENDCASE => ERROR;
ENDLOOP;
};
stickViewers: BOOLFALSE;
viewerMouse: Imager.VEC;
PaintSFV: PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOLFALSE] --ViewerClasses.PaintProc-- = {
IF whatChanged = NIL
THEN sfvPM ← PixelMapFromViewer[self]
ELSE WITH whatChanged SELECT FROM
r: Request => r.proc[r.data, context];
ENDCASE => ERROR;
};
sfvPM: PixelMap;
icConsumer: PROC [context: Imager.Context, pm: PixelMap];
Satisfy: PROC [data: REF ANY, context: Imager.Context] = {
icConsumer[context, sfvPM];
};
Viewit: PROC = {
r: Request;
GiveContext: PROC [to: PROC [context: Imager.Context, pm: PixelMap]] = {
TRUSTED {icConsumer ← to};
ViewerOps.PaintViewer[viewer: sfv, hint: client, clearClient: FALSE, whatChanged: r];
};
TRUSTED {
CedarProcess.SetPriority[runningPriority];
r ← NEW [RequestRep ← [Satisfy, NIL]]};
going ← TRUE;
sfvPM ← PixelMapFromViewer[sfv];
Dewit[giveContext: GiveContext, xp0: 0, yp0: 0, xp1: sfv.cw, yp1: sfv.ch, FOVX: FOVX, edgeDegrees: edgeDegrees, hither: hither, border: border, startRp: viewerStartRp, rpMin: viewerRpMin, Stop: StopViewing, background: Imager.black, vt: Terminal.Current[], inViewers: TRUE];
going ← FALSE;
};
StopViewing: PROC RETURNS [BOOL] =
{RETURN [NOT okToGo]};
Staridle: PROC [parent: REF ANY, clientData: REF ANYNIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOLFALSE] --Buttons.ButtonProc-- = {Sleepit[NOT control]};
Sleepit: PROC [logout: BOOL] = {
ENABLE RuntimeError.UNCAUGHT => IF logout THEN DebuggerSwap.WorryCallDebugger["Uncaught SIGNAL or ERROR while Idle"];
[] ← IdleBackdoor.UseAlternateVT[vtProc: DoForVT, logout: logout];
};
vtContext: Imager.Context;
vtPM: PixelMap;
GiveVTContext: PROC [to: PROC [context: Imager.Context, pm: PixelMap]] =
{to[vtContext, vtPM]};
quitFilter: IdleBackdoor.KeyFilter ← [first: Q, last: Q, pass: ALL[TRUE]];
QuitKey: PROC RETURNS [stop: BOOL] = {
stop ← IdleBackdoor.KeyTyped[quitFilter]};
xCursor: Terminal.BWCursorBitmap = [
8001H, 4002H, 2004H, 1008H,
0810H, 0420H, 0240H, 0180H,
0180H, 0240H, 0420H, 0810H,
1008H, 2004H, 4002H, 8001H];
bullseyeCursor: Terminal.BWCursorBitmap = [
001700B, 007760B, 014630B, 030614B,
060606B, 040602B, 140603B, 177177B,
177177B, 140603B, 040602B, 060606B,
030614B, 014630B, 007760B, 001700B];
blankCursor: Terminal.BWCursorBitmap = ALL[0];
DoForVT: PROC [vt: Terminal.Virtual] = {
Doit: PROC = {
Dewit[giveContext: GiveVTContext, xp0: 0, yp0: 0, xp1: vt.bwWidth, yp1: vt.bwHeight, FOVX: FOVX, edgeDegrees: edgeDegrees, hither: hither, border: border, startRp: vtStartRp, rpMin: vtRpMin, inViewers: FALSE, Stop: QuitKey, background: Imager.white, vt: vt];
};
vt.Select[];
[] ← vt.SetBWBitmapState[allocated];
[vtContext, vtPM] ← ContextAndPMFromVT[vt];
[] ← vt.SetBWBitmapState[displayed];
{fb: Terminal.FrameBuffer = vt.GetBWFrameBuffer[];
vt.SetMousePosition[[fb.width/2, fb.height/2]];
vt.SetBWCursorPosition[[fb.width/2, fb.height/2]];
vt.SetBWCursorPattern[blankCursor];
};
CedarProcess.DoWithPriority[runningPriority, Doit];
[] ← vt.SetBWBitmapState[none];
};
Draw: PROC [context: Imager.Context, pm: PixelMap, index: Index] = {
xc: REAL ← stars[index].xp;
yc: REAL ← stars[index].yp;
r: REAL ← stars[index].rp;
IF usePeriod THEN {
res: REALIF r < 1.5 THEN 0.25 ELSE IF r < 3 THEN 0.5 ELSE 1.0;
n: INT ← Real.RoundLI[r/res];
rr: REAL ← n*res;
nHalves: INT ← Real.RoundLI[r*2];
d: REALIF (nHalves MOD 2) = 0 THEN 0.0 ELSE 0.5;
size: Transformation ← ImagerTransformation.Concat[toUnit, ImagerTransformation.Scale[rr]];
Doit: PROC = {
context.SetXY[[xc, yc]];
context.ConcatT[size];
context.SetFont[font];
context.ShowChar['.];
};
xc ← Real.RoundLI[xc-d]+d;
yc ← Real.RoundLI[yc-d]+d;
context.DoSave[Doit];
}
ELSE IF imageCache # NIL AND imageCache.length # 0 AND r <= imageCache[imageCache.length-1].rpMax THEN {
ci: NAT ← stars[index].ci;
xlated: PixelMap;
x: INTEGER ← Real.RoundI[xc];
y: INTEGER ← Real.RoundI[yc];
Establish ic[ci].rpMax >= r > ic[ci-1].rpMax
WHILE ci > 0 AND imageCache[ci-1 ].rpMax >= r DO ci ← ci - 1 ENDLOOP;
WHILE imageCache[ci].rpMax < r DO ci ← ci + 1 ENDLOOP;
stars[index].ci ← ci;
xlated ← imageCache[ci].image.ShiftMap[pm.sSize - y + imageCache[ci].di - imageCache[ci].image.sSize, x - imageCache[ci].di];
pm.Transfer[source: xlated, function: [xor, null]];
}
ELSE {
d: REAL;
n: INT;
path: ImagerPath.PathProc = {
moveTo[[xc-r, yc]];
arcTo[[xc+r, yc], [xc-r, yc]];
};
n ← Real.RoundLI[r*2];
d ← IF (n MOD 2) = 0 THEN 0.0 ELSE 0.5;
xc ← Real.RoundLI[xc-d]+d;
yc ← Real.RoundLI[yc-d]+d;
IF r < squareThresh
THEN Imager.MaskRectangle[context, [xc-r, yc-r, r*2, r*2]]
ELSE Imager.MaskFill[context, path];
};
};
upText, downText, T: Milliseconds ← 0;
curText: Text;
cto: Vector2.VEC;
cts: REAL;
PMContext: PROC [pm: PixelMap] RETURNS [context: Imager.Context] = {
bm: ImagerBackdoor.Bitmap ← NEW [ImagerBackdoor.BitmapRep ← [
ref: pm.refRep.ref,
base: pm.refRep.pointer,
wordsPerLine: pm.refRep.rast,
width: pm.refRep.rast*Basics.bitsPerWord,
height: pm.refRep.lines]];
IF pm.refRep.lgBitsPerPixel # 0 OR pm.sMin # 0 OR pm.fMin # 0 THEN ERROR;
context ← ImagerBackdoor.BitmapContext[bm];
Imager.ConcatT[context, ImagerTransformation.Invert[ImagerBackdoor.GetT[context]]];
};
Dewit: PROC
[
giveContext: PROC [to: PROC [context: Imager.Context, pm: PixelMap]],
xp0, yp0, xp1, yp1, FOVX, edgeDegrees, hither, border, startRp, rpMin: REAL,
inViewers: BOOL, Stop: PROC RETURNS [BOOL], background: Imager.Color, vt: Terminal.Virtual] =
{
halfDxp: REAL = (xp1 - xp0)/2;
halfDyp: REAL = (yp1 - yp0)/2;
halfFOVX: REAL = FOVX/2;
sinHalfFOVX: REAL = RealFns.SinDeg[halfFOVX];
cosHalfFOVX: REAL = RealFns.CosDeg[halfFOVX];
tanHalfFOVX: REAL = RealFns.TanDeg[halfFOVX];
tanHalfFOVY: REAL = (halfDyp / halfDxp) * tanHalfFOVX;
halfFOVY: REAL = RealFns.ArcTanDeg[tanHalfFOVY, 1.0];
sinHalfFOVY: REAL = RealFns.SinDeg[halfFOVY];
cosHalfFOVY: REAL = RealFns.CosDeg[halfFOVY];
scale: REAL = halfDxp / tanHalfFOVX;
angleScale: REAL--degrees per pixel-- = fullAngle / halfDxp;
xo: REAL = xp0 + halfDxp;
yo: REAL = yp0 + halfDyp;
depth: REAL = scale*rw/startRp;
width: REAL = depth * tanHalfFOVX * 2.0;
height: REAL = depth * tanHalfFOVY * 2.0;
yon: REAL = depth * 1.3;
PickText: PROC [T: Milliseconds] = {
p: REAL ← Choose[0, td.totalProbability*0.999];
tl: TextList;
FOR tl ← td.texts, tl.rest WHILE p > tl.first.cumProb DO NULL ENDLOOP;
curText ← tl.first;
upText ← T + rs.ChooseInt[upTextMin, upTextMax];
downText ← upText + rs.ChooseInt[downTextMin, downTextMax];
cts ← (xp1 - xp0)/(curText.bounds.xmax - curText.bounds.xmin)/2;
cto ← [
x: Choose[
xp0 - cts*curText.bounds.xmin,
xp1 - cts*curText.bounds.xmax],
y: Choose[
yp0 - cts*curText.bounds.ymin,
yp1 - cts*curText.bounds.ymax]];
};
DrawText: PROC [context: Imager.Context, pm: PixelMap] = {
InnerDoit: PROC = {
context.SetXY[cto];
context.TranslateT[cto];
context.ScaleT[cts];
context.SetFont[font];
context.ShowRope[curText.text];
};
Imager.DoSave[context, InnerDoit];
};
PickNew: PROC [index: Index, pickLast: BOOLFALSE] = TRUSTED {
dp: StarPtr = @stars[index];
dp.ci ← 0;
IF pickLast THEN {
prepareToDie ← FALSE;
dying ← TRUE;
dp.yw ← dp.xw ← 0;
dp.zw ← depth;
}
ELSE {
faceSel: REAL = rs.ChooseInt[1, 100]/100.0 * faceWeights[Back];
SELECT faceSel FROM
> faceWeights[Top] => {
halfDxw, halfDyw: REAL;
halfDxw ← depth * tanHalfFOVX * border;
halfDyw ← depth * tanHalfFOVY * border;
dp.xw ← Choose[-halfDxw, halfDxw];
dp.yw ← Choose[-halfDyw, halfDyw];
dp.zw ← depth;
};
> faceWeights[Bottom] => {
radius: REAL = (1.0 - rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]/1E6)*depth;
angle: REAL = Choose[-halfFOVX, halfFOVX];
yz: REAL = radius*RealFns.CosDeg[angle];
dp.xw ← radius*RealFns.SinDeg[angle];
dp.yw ← yz * sinHalfFOVY;
dp.zw ← yz * cosHalfFOVY + hither;
};
> faceWeights[Right] => {
radius: REAL = (1.0 - rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]/1E6)*depth;
angle: REAL = Choose[-halfFOVX, halfFOVX];
yz: REAL = radius*RealFns.CosDeg[angle];
dp.xw ← radius*RealFns.SinDeg[angle];
dp.yw ←-yz * sinHalfFOVY;
dp.zw ← yz * cosHalfFOVY + hither;
};
> faceWeights[Left] => {
radius: REAL = (1.0 - rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]/1E6)*depth;
angle: REAL = Choose[-halfFOVY, halfFOVY];
xz: REAL = radius*RealFns.CosDeg[angle];
dp.yw ← radius*RealFns.SinDeg[angle];
dp.xw ← xz * sinHalfFOVX;
dp.zw ← xz * cosHalfFOVX + hither;
};
ENDCASE => {
radius: REAL = (1.0 - rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]*rs.ChooseInt[0, 100]/1E6)*depth;
angle: REAL = Choose[-halfFOVY, halfFOVY];
xz: REAL = radius*RealFns.CosDeg[angle];
dp.yw ← radius*RealFns.SinDeg[angle];
dp.xw ←-xz * sinHalfFOVX;
dp.zw ← xz * cosHalfFOVX + hither;
};
};
ToPort[dp];
};
ToPort: PROC [dp: StarPtr] = TRUSTED {
dp.xp ← (dp.xw/dp.zw)*scale + xo;
dp.yp ← (dp.yw/dp.zw)*scale + yo;
dp.rpMAX[rpMin, (rw/dp.zw)*scale];
};
DrawInit: PROC [context: Imager.Context, pm: PixelMap] = {
Imager.SetColor[context, background];
Imager.MaskRectangle[context, [xp0, yp0, xp1 - xp0, yp1 - yp0]];
Imager.SetColor[context, ImagerBackdoor.invert];
FOR index: Index IN [0 .. nStars) DO
PickNew[index];
Draw[context, pm, index];
ENDLOOP;
};
Update: PROC [index: Index] RETURNS [clip: BOOL] = TRUSTED {
dp: StarPtr = @stars[index];
xw: REAL = dp.xw;
yw: REAL = dp.yw;
zw: REAL = dp.zw;
dp.xw ← rotMat[X][X]*xw + rotMat[X][Y]*yw + rotMat[X][Z]*zw - v[X];
dp.yw ← rotMat[Y][X]*xw + rotMat[Y][Y]*yw + rotMat[Y][Z]*zw - v[Y];
dp.zw ← rotMat[Z][X]*xw + rotMat[Z][Y]*yw + rotMat[Z][Z]*zw - v[Z];
clip ← FALSE;
IF dp.zw < hither THEN clip ← TRUE
ELSE IF dp.zw > yon THEN clip ← TRUE
ELSE {
ToPort[dp];
IF
dp.xp - dp.rp > xp1 OR
dp.xp + dp.rp < xp0 OR
dp.yp - dp.rp > yp1 OR
dp.yp + dp.rp < yp0
THEN clip ← TRUE;
};
};
DrawDelta: PROC [context: Imager.Context, pm: PixelMap] ← IF useXOR THEN DrawDeltaByXOR ELSE DrawDeltaBuffered;
prevUp: BOOLFALSE;
DrawDeltaByXOR: PROC [context: Imager.Context, pm: PixelMap] = {
shouldUp: BOOL ← (T >= upText) AND (T < downText);
max: NATMAX[nStars, newN];
index: Index ← 0;
Imager.SetColor[context, ImagerBackdoor.invert];
IF blank # (msTillBlank <= 0) THEN {
blank ← NOT blank;
context.MaskVector[[xo, yp0], [xo, yp1]];
context.MaskVector[[xp0, yo], [xp1, yo]];
};
FOR index ← 0, index + 1 WHILE index < nStars DO
need: BOOLTRUE;
WHILE need DO
Draw[context, pm, index];
need ← FALSE;
IF Update[index].clip THEN {
IF nStars > newN THEN {
IF index < nStars - 1 THEN {
stars[index] ← stars[nStars-1];
need ← TRUE;
};
nStars ← nStars - 1;
}
ELSE PickNew[index];
};
ENDLOOP;
IF index < nStars THEN Draw[context, pm, index];
ENDLOOP;
FOR index ← nStars, index+1 WHILE index < newN DO
nStars ← index + 1;
PickNew[index, prepareToDie];
IF index < nStars THEN Draw[context, pm, index];
ENDLOOP;
IF shouldUp # prevUp THEN DrawText[context, pm];
prevUp ← shouldUp;
IF T >= downText THEN PickText[T];
};
DrawDeltaBuffered: PROC [context: Imager.Context, pm: PixelMap] = {
index: Index ← 0;
Imager.SetColor[bufferContext, background];
Imager.MaskRectangle[bufferContext, [0, 0, pm.fSize, pm.sSize]];
Imager.SetColor[bufferContext, ImagerBackdoor.invert];
FOR index ← 0, index + 1 WHILE index < nStars DO
need: BOOLTRUE;
WHILE need DO
need ← FALSE;
IF Update[index].clip THEN {
IF nStars > newN THEN {
IF index < nStars - 1 THEN {
stars[index] ← stars[nStars-1];
need ← TRUE;
};
nStars ← nStars - 1;
}
ELSE PickNew[index];
};
ENDLOOP;
IF index < nStars THEN Draw[bufferContext, bufferPM, index];
ENDLOOP;
FOR index ← nStars, index+1 WHILE index < newN DO
nStars ← index + 1;
PickNew[index, prepareToDie];
IF index < nStars THEN Draw[bufferContext, bufferPM, index];
ENDLOOP;
IF (T >= upText) AND (T < downText) THEN DrawText[bufferContext, bufferPM];
pm.Transfer[bufferPM];
IF T >= downText THEN PickText[T];
};
newN: NAT ← nStars ← 1;
prepareToDie, dying: BOOLFALSE;
bufferPM: PixelMap;
bufferContext: Imager.Context;
oldP: BasicTime.Pulses;
choice: NAT ← 0;
oldMousePos: Terminal.Position ← vt.GetMousePosition[];
stars ← NEW [StarDataSeq[maxStars]];
v ← [0, 0, initialDzw];
IF NOT useXOR THEN [bufferContext, bufferPM] ← MakeBuffer[xp0, yp0, xp1, yp1];
PickText[T ← 0];
giveContext[DrawInit];
oldP ← BasicTime.GetClockPulses[];
yaw ← pitch ← roll ← 0;
rotMat ← idMat;
faceWeights ← [0, 0, 0, 0, 1];
blank ← TRUE;
msTillBlank ← 0;
FOR i: INT ← 0, i+1 DO
newP: BasicTime.Pulses;
oldYaw: REAL = yaw;
oldPitch: REAL = pitch;
oldRoll: REAL = roll;
oldV: Vec = v;
doStick: BOOLFALSE;
stickPos: Imager.VEC;
IF NOT inViewers THEN {
mousePos: Terminal.Position = vt.GetMousePosition[];
{moved: BOOL = mousePos # oldMousePos;
IF moved THEN {
vt.SetBWCursorPosition[[mousePos.x-8, mousePos.y-8]];
oldMousePos ← mousePos;
};
IF (doStick ← KeyDown[Yellow]) THEN {
stickPos ← [mousePos.x, yp1 - mousePos.y];
};
IF KeyDown[Red] THEN roll ← rollRate
ELSE IF KeyDown[Blue] THEN roll ← -rollRate
ELSE roll ← 0.0;
IF doStick OR moved THEN {
IF msTillBlank <= 0 THEN {
vt.SetBWCursorPattern[bullseyeCursor];
};
msTillBlank ← blankTimeoutMS;
};
}}
ELSE {
IF (doStick ← stickViewers) THEN {
stickPos ← viewerMouse;
};
};
IF doStick THEN {
yaw ← (stickPos.x - xo) * angleScale;
pitch ← (stickPos.y - yo) * angleScale;
};
IF yaw # oldYaw OR pitch # oldPitch OR roll # oldRoll THEN {
rotMat ← RotateMat[X, Y, Z, roll, RotateMat[Y, Z, X, pitch, RotateMat[X, Z, Y, yaw, idMat]]];
};
If we were doing a simple, physical simulation, we'd have to rotate the velocity vector also, like this:
IF yaw # 0 OR pitch # 0 THEN {
vx: REAL = v[X]; vy: REAL = v[Y];
v[X] ← rotMat[X][X]*vx + rotMat[X][Y]*vy + rotMat[X][Z]*v[Z];
v[Y] ← rotMat[Y][X]*vx + rotMat[Y][Y]*vy + rotMat[Y][Z]*v[Z];
v[Z] ← rotMat[Z][X]*vx + rotMat[Z][Y]*vy + rotMat[Z][Z]*v[Z];
{avx: REAL = ABS[v[X]]; avy: REAL = ABS[v[Y]]; avz: REAL = ABS[v[Z]];
small: REAL = (avx+avy+avz)*1E-5;
IF avx < small THEN v[X] ← 0;
IF avy < small THEN v[Y] ← 0;
IF avz < small THEN v[Z] ← 0}};
But we're not; we suppose that turning includes the right accelerations to keep the ship moving in the same direction we face.
IF KeyDown[A] THEN v[Z] ← v[Z] + dv;
IF KeyDown[D] THEN v[Z] ← v[Z] - dv;
IF yaw # oldYaw OR pitch # oldPitch OR v # oldV THEN {
depth2: REAL = depth * depth;
yawWgt: REAL = height * depth2 * RealFns.SinDeg[ABS[yaw]] / 3.0;
pitchWgt: REAL = width * depth2 * RealFns.SinDeg[ABS[pitch]] / 3.0;
yf: Face = IF yaw < 0 THEN Left ELSE Right;
pf: Face = IF pitch < 0 THEN Bottom ELSE Top;
full: REAL = width*height*v[Z];
wh: REAL = width*height/4;
hd: REAL = height*depth/2;
dw: REAL = depth*width/2;
--since only the Z component of v can be non-zero, we optimize like this: --faceWeights[Left] ← faceWeights[Right] ← faceWeights[Bottom] ← faceWeights[Top] ← full*-0.25;
faceWeights[Left] ← -wh*v[Z] - hd*v[X];
faceWeights[Left] ← STP[
[-width/2, -height/2, depth],
[-width/2, height/2, depth], v]/2;
faceWeights[Right] ← hd*v[X] - wh*v[Z];
faceWeights[Right] ← STP[
[width/2, height/2, depth],
[width/2, -height/2, depth], v]/2;
faceWeights[Bottom] ← -wh*v[Z] - dw*v[Y];
faceWeights[Bottom] ← STP[
[width/2, -height/2, depth],
[-width/2, -height/2, depth], v]/2;
faceWeights[Top] ← dw*v[Y] - wh*v[Z];
faceWeights[Top] ← STP[
[-width/2, height/2, depth],
[width/2, height/2, depth], v]/2;
faceWeights[Back] ← full;
faceWeights[Back] ← STP[[width, 0, 0], [0, height, 0], v];
faceWeights[yf] ← faceWeights[yf] + yawWgt;
faceWeights[pf] ← faceWeights[pf] + pitchWgt;
faceWeights[Left] ← MAX[0.0, faceWeights[Left]];
FOR f: Face IN (Face.FIRST .. Face.LAST] DO
faceWeights[f] ← faceWeights[f.PRED] + MAX[0.0, faceWeights[f]];
ENDLOOP;
IF faceWeights[Back] = 0.0 THEN faceWeights[Back] ← 1.0;
};
IF dying THEN NULL
ELSE IF NOT prepareToDie THEN prepareToDie ← Stop[];
giveContext[DrawDelta];
IF nStars = 0 THEN EXIT;
IF pausePeriod # 0 THEN Process.Pause[pausePeriod];
FOR i: NAT IN [0 .. retraces) DO
Terminal.WaitForBWVerticalRetrace[vt];
ENDLOOP;
newP ← BasicTime.GetClockPulses[];
IF newP > oldP THEN {
Dt: Milliseconds ← BasicTime.PulsesToMicroseconds[newP - oldP]/1000;
q: Milliseconds ← goalNDtProduct/nStars;
goalDtLow: Milliseconds ← q + goalDtOffsetLow;
goalDtHigh: Milliseconds ← q + goalDtOffsetHigh;
IF histPerf THEN hists[useXOR].ChangeTransformed[x: nStars, y: MIN[histDtMax, MAX[histDtMin, Dt]]];
avgDt ← hold*avgDt + decay*Dt;
IF pickRandomNs
THEN {IF (choice ← choice + 1) MOD pickPeriod = 1 THEN newN ← rs.ChooseInt[minStars, maxStars] ELSE newN ← nStars}
ELSE newN ←
IF prepareToDie THEN nStars + 1 ELSE
IF dying THEN 0 ELSE
IF avgDt < goalDtLow THEN MIN[maxStars, nStars + 1] ELSE
IF avgDt > goalDtHigh THEN MAX[minStars, nStars - 1] ELSE nStars;
IF (NOT inViewers) AND msTillBlank > 0 THEN {
msTillBlank ← msTillBlank - Dt;
IF msTillBlank <= 0 THEN {
vt.SetBWCursorPattern[blankCursor];
};
};
T ← T + Dt};
oldP ← newP;
ENDLOOP;
};
faceWeights: ARRAY Face OF REAL--cumulative volume estimates-- ← [0, 0, 0, 0, 1];
MakeBuffer: PROC [xmin, ymin, xmax, ymax: REAL] RETURNS [context: Imager.Context, pm: PixelMap] = {
ixmin, iymin, ixmax, iymax: INT;
ixmin ← Floor[xmin];
iymin ← Floor[ymin];
ixmax ← Ceiling[xmax];
iymax ← Ceiling[ymax];
pm ← ImagerPixelMap.Create[lgBitsPerPixel: 0, bounds: [sMin: 0, fMin: 0, sSize: iymax - iymin, fSize: ixmax - ixmin]];
context ← PMContext[pm];
};
ContextAndPMFromVT: PROC [vt: Terminal.Virtual] RETURNS [context: Imager.Context, pm: PixelMap] = {
pm ← PixelMapFromVT[vt];
context ← ImagerTerminal.BWContext[vt, TRUE];
};
PixelMapFromVT: PROC [vt: Terminal.Virtual] RETURNS [pm: PixelMap] = {
fb: Terminal.FrameBuffer ← vt.GetBWFrameBuffer[];
IF fb.bitsPerPixel # 1 THEN ERROR;
pm ← [
sOrigin: 0, fOrigin: 0,
sMin: 0, fMin: 0,
sSize: fb.height, fSize: fb.width,
refRep: NEW [ImagerPixelMap.PixelMapRep ← [
ref: fb.vm,
pointer: fb.base,
words: fb.vm.words,
lgBitsPerPixel: 0,
rast: fb.wordsPerLine,
lines: fb.height]]
];
};
PixelMapFromViewer: PROC [v: Viewer] RETURNS [pm: PixelMap] = {
vx1, vx2, vy1, vy2: INTEGER;
height: NAT;
pm ← PixelMapFromVT[Terminal.Current[]];
height ← pm.sSize;
[vx1, vy1] ← ViewerOps.UserToScreenCoords[v, 0, 0];
[vx2, vy2] ← ViewerOps.UserToScreenCoords[v, v.cw, v.ch];
vy1 ← height - vy1;
vy2 ← height - vy2;
IF vy1 > vy2 THEN {y: INTEGER ← vy1; vy1 ← vy2; vy2 ← y};
pm ← pm.Clip[[sMin: vy1, fMin: vx1, sSize: vy2-vy1, fSize: vx2 - vx1]];
pm ← pm.ShiftMap[s: -vy1, f: -vx1];
};
CacheImages: PROC = {
Do: PROC [rpMax: REAL] = {
r: REAL ← rpMax*0.99;
n: INT ← Real.RoundLI[r*2];
o: REALIF (n MOD 2) = 0 THEN 0.0 ELSE 0.5;
l: INTEGER ← Floor[o-r];
size: NAT ← Ceiling[o+r] - l;
pm: PixelMap ← ImagerPixelMap.Create[0, [0, 0, size, size]];
context: Imager.Context ← PMContext[pm];
path: ImagerPath.PathProc = {
moveTo[[o-r, o]];
arcTo[[o+r, o], [o-r, o]];
};
context.TranslateT[[-l, -l]];
context.SetColor[Imager.white];
context.MaskRectangle[[-l, -l, size, size]];
context.SetColor[ImagerBackdoor.invert];
context.MaskFill[path];
imageCache[imageCache.length] ← [rpMax: rpMax, d: o-l, di: -l, image: pm];
imageCache.length ← imageCache.length + 1;
};
imageCache ← NEW [ImageCacheRep[6+8+9+9]];
imageCache.length ← 0;
Do[0.75];
Do[1.25];
Do[RealFns.SqRt[2]];
Do[1.75];
Do[2.25];
Do[2.75];
FOR i: NAT IN [3 .. 10] DO Do[i+0.5] ENDLOOP;
FOR i: NAT IN [1 .. 9] DO Do[11+2*i] ENDLOOP;
FOR i: NAT IN [1 .. 9] DO Do[32.5+5*i] ENDLOOP;
};
Floor: PROC [r: REAL] RETURNS [i: INT] = {
d: INT ← 1 - Real.Fix[r];
i ← Real.Fix[r+d]-d};
Ceiling: PROC [r: REAL] RETURNS [i: INT] = {
d: INT ← 1 + Real.Fix[r];
i ← Real.Fix[r-d]+d};
ShowCache: PROC = {
space: INT ← Real.RoundLI[2.5*imageCache[imageCache.length-1].rpMax];
s, f: INT ← space;
FOR i: NAT IN [0 .. imageCache.length) DO
xlated: PixelMap ← imageCache[i].image.ShiftMap[s, f];
sfvPM.Transfer[xlated];
f ← f + space;
IF f + space > sfvPM.fSize THEN {f ← space; s ← s + space};
ENDLOOP;
};
SizePeriod: PROC = {
bounds: ImagerFont.Extents;
aspectRatio: REAL;
bounds ← ImagerFont.BoundingBox[font, [0, ORD['.]]];
toUnit ← ImagerTransformation.Concat[
ImagerTransformation.Translate[
[-(bounds.leftExtent+bounds.rightExtent)/2, -(bounds.descent+bounds.ascent)/2]],
ImagerTransformation.Scale2[
[2/(bounds.rightExtent-bounds.leftExtent), 2/(bounds.ascent-bounds.descent)]]];
aspectRatio ← (bounds.ascent-bounds.descent) / (bounds.rightExtent-bounds.leftExtent);
IF aspectRatio < 0.999 OR aspectRatio > 1.001 THEN ERROR;
};
ReadTextData: PROC [fileName: ROPE] RETURNS [td: TextData] = {
from: IO.STREAMFS.StreamOpen[fileName];
last: TextList ← NIL;
td ← [
texts: NIL,
numTexts: 0,
totalProbability: 0];
DO
prob: REAL;
text: ROPE;
this: TextList;
[] ← from.SkipWhitespace[];
IF from.EndOf[] THEN EXIT;
prob ← from.GetReal[];
text ← from.GetRopeLiteral[];
text ← Replace[text, "<machine name>", machineName];
td.numTexts ← td.numTexts + 1;
this ← LIST[ [
text: text,
bounds: ImagerBox.BoxFromExtents[ImagerFont.RopeBoundingBox[font, text]],
cumProb: td.totalProbability ← td.totalProbability + prob] ];
IF last = NIL THEN td.texts ← this ELSE last.rest ← this;
last ← this;
ENDLOOP;
from.Close[];
};
KeyDown: PROC [key: TerminalDefs.KeyName] RETURNS [down: BOOL]
= TRUSTED INLINE {down ← TerminalFace.keyboard[key] = down};
Choose: PROC [min, max: REAL] RETURNS [r: REAL] =
{r ← min + (rs.ChooseInt[0, 10000]/1.0E4) * (max-min)};
Replace: PROC [in, what, with: ROPE] RETURNS [new: ROPE] = {
start, len: INT;
ousLen: INT ← what.Length[];
new ← in;
WHILE (start ← new.Index[s2: what]) < (len ← new.Length[]) DO
new ← new.Substr[len: start].Cat[with, new.Substr[start: start+ousLen, len: len - (start+ousLen)]];
ENDLOOP;
};
Limit: PROC [x, lim: REAL] RETURNS [lx: REAL] = {
IF ABS[x] <= slowRate THEN RETURN [x];
IF ABS[x] <= slowRegion THEN RETURN [IF x > 0 THEN slowRate ELSE -slowRate];
lx ← IF x > 0 THEN lim ELSE -lim;
};
restoreRate: REAL--degrees per step-- ← 1.0;
slowRate: REAL ← 0.2;
slowRegion: REAL--degrees yet to slew-- ← 10;
RotateMat: PROC [d1, d2, d3: Dim, degrees: REAL, m: Mat] RETURNS [rm: Mat] = {
c: REAL = RealFns.CosDeg[degrees];
s: REAL = RealFns.SinDeg[degrees];
rm[d1][d1] ← c*m[d1][d1] - s*m[d2][d1];
rm[d1][d2] ← c*m[d1][d2] - s*m[d2][d2];
rm[d1][d3] ← c*m[d1][d3] - s*m[d2][d3];
rm[d2][d1] ← s*m[d1][d1] + c*m[d2][d1];
rm[d2][d2] ← s*m[d1][d2] + c*m[d2][d2];
rm[d2][d3] ← s*m[d1][d3] + c*m[d2][d3];
rm[d3] ← m[d3];
};
STP: PROC [a, b, c: Vec] RETURNS [stp: REAL] = {
stp ← a[X]*b[Y]*c[Z] + a[Y]*b[Z]*c[X] + a[Z]*b[X]*c[Y] - a[Z]*b[Y]*c[X] - a[Y]*b[X]*c[Z] - a[X]*b[Z]*c[Y];
};
CreateViewer: PROC = {
sfv ← ViewerOps.CreateViewer[flavor: sfvcFlavor, info: [name: "StarField"]]};
NewHistograms: PROC = {
hists[FALSE] ← Histograms.Create2D[iMin: minStars, iMax: maxStars, jMin: histDtMin, jMax: histDtMax];
[] ← hists[FALSE].Show[[name: "Dt vs n (buffered)"]];
hists[TRUE] ← Histograms.Create2D[iMin: minStars, iMax: maxStars, jMin: histDtMin, jMax: histDtMax];
[] ← hists[TRUE].Show[[name: "Dt vs n (XOR)"]];
};
Start: PROC = {
SetGoal[n1: 15, dt1: OneSecond/20, n2: 50, dt2: OneSecond/40, dd: OneSecond/200];
IF histPerf THEN NewHistograms[];
ViewerOps.RegisterViewerClass[flavor: sfvcFlavor, class: sfvc];
CacheImages[];
font ← ImagerFont.Find["Xerox/PressFonts/TimesRoman-MRR"];
td ← ReadTextData["Starfield.texts"];
SizePeriod[];
};
Start[];
}.
p'i = mi (p - ci)
mi+1 = di+1mi
ci+1 = ci + vi+1
vi+1 = vi + ai+1
p'i+1 = di+1 mi (p - ci - vi+1)
= di+1p'i - mi+1vi+1
mi+1vi+1 = di+1mi(vi + ai+1)
= di+1mivi + mi+1ai+1
At edge of viewing cone: xw = zw tan a/2
xp = (Dxp/2)(1 + xw/(zw tan a/2)) + xp0
rp = (Dxp/2)(rw/(zw tan a/2))
zw = (Dxp/2)(rw/(rp tan a/2))
{ &Test: PROC [xc, yc, r: REAL] = {Imager.MaskFill[StarField.bridgeContext, Imager.ArcTo[Imager.MoveTo[[xc-r, yc]], [xc+r, yc], [xc-r, yc]]]}; &Test[0, 0, 0]}
abcdefghijklmnopqrstuvwxyz1234567890-=\[]←',./
ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%~&*()—+|{}^:"<>?
蝩C%gfh). "`z{bP +
![ ]H*({ '`˧de