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 22, 1986 8:35:30 pm PDT
DIRECTORY Basics, BasicTime, CedarProcess, FS, Histograms, IdleBackdoor, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerPixelArray, ImagerPixelMap, ImagerPrivate, ImagerTerminal, ImagerTransformation, IO, Menus, PrincOps, Process, Random, Real, RealFns, Rope, Terminal, TerminalFace, ThisMachine, TIPUser, Vector2, ViewerClasses, ViewerOps;
StarField: CEDAR PROGRAM
IMPORTS BasicTime, CedarProcess, FS, Histograms, IdleBackdoor, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPixelMap, ImagerTerminal, ImagerTransformation, IO, Process, Random, Real, RealFns, Rope, 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 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]];
Kind: 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];
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;
dDzw: REAL ← 0.1;
slovershoot: REAL ← 1.5;
upTextMin: Milliseconds ← 10*OneSecond;
upTextMax: Milliseconds ← 60*OneSecond;
downTextMin: Milliseconds ← 1*OneSecond;
downTextMax: Milliseconds ← 6*OneSecond;
blankTimeoutMS: Milliseconds ← 15*OneSecond;
usePeriod: BOOLFALSE;
squareThresh: REAL ← 1.25;
useXOR: BOOLTRUE;
histPerf: BOOLFALSE;
pickRandomNs: BOOLFALSE;
pickPeriod: NAT ← 10;
runningPriority: CedarProcess.Priority ← background;
nStars: NAT ← minStars;
Dzw: REAL;
slowFactor: REAL ← 0.95;
avgDt: REAL--Milliseconds-- ← OneSecond;
decay: REAL ← 0.1;
hold: REAL ← 1.0 - decay;
fullAngle: REAL--degrees per step at left/right edge-- ← 1.0;
yaw, pitch: REAL--degrees-- ← 0;
rotMat: Mat ← idMat;
msTillBlank: Milliseconds ← 0;
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] = {
[] ← 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]];
};
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 {
kindSel: REAL = rs.ChooseInt[1, 100]/100.0 * kindWeights[Back];
SELECT kindSel FROM
> kindWeights[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;
};
> kindWeights[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;
};
> kindWeights[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;
};
> kindWeights[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;
};
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;
};
};
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;
dp.yw ← rotMat[Y][X]*xw + rotMat[Y][Y]*yw + rotMat[Y][Z]*zw;
dp.zw ← rotMat[Z][X]*xw + rotMat[Z][Y]*yw + rotMat[Z][Z]*zw - Dzw;
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];
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]];
Dzw ← initialDzw;
slowFactor ← 1 - (Dzw)/(slovershoot * scale*rw/startRp);
IF NOT useXOR THEN [bufferContext, bufferPM] ← MakeBuffer[xp0, yp0, xp1, yp1];
PickText[T ← 0];
giveContext[DrawInit];
oldP ← BasicTime.GetClockPulses[];
yaw ← pitch ← 0;
rotMat ← idMat;
kindWeights ← [0, 0, 0, 0, 1];
FOR i: INT ← 0, i+1 DO
newP: BasicTime.Pulses;
oldYaw: REAL = yaw;
oldPitch: REAL = pitch;
oldSpeed: REAL = Dzw;
doStick: BOOLFALSE;
stickPos: Imager.VEC;
IF NOT inViewers THEN TRUSTED {
mousePos: Terminal.Position = vt.GetMousePosition[];
{moved: BOOL = mousePos # oldMousePos;
IF moved THEN {
vt.SetBWCursorPosition[[mousePos.x-8, mousePos.y-8]];
oldMousePos ← mousePos;
};
IF (doStick ← TerminalFace.keyboard[Yellow] = down) THEN {
stickPos ← [mousePos.x, yp1 - mousePos.y];
};
IF doStick OR moved THEN {
IF msTillBlank <= 0 THEN {
vt.SetBWCursorPattern[bullseyeCursor];
};
msTillBlank ← blankTimeoutMS;
};
}}
ELSE {
IF (doStick ← stickViewers) THEN {
stickPos ← viewerMouse;
};
};
IF TerminalFace.keyboard[A] = down THEN DzwDzw + dDzw;
IF TerminalFace.keyboard[D] = down THEN DzwDzw - dDzw;
IF (doStick ← TerminalFace.keyboard[Yellow] = down) THEN {
yaw ← (stickPos.x - xo) * angleScale;
pitch ← (stickPos.y - yo) * angleScale;
IF yaw # oldYaw OR pitch # oldPitch THEN {
rotMat ← RotateMat[Y, Z, X, pitch, RotateMat[X, Z, Y, yaw, idMat]];
};
};
IF yaw # oldYaw OR pitch # oldPitch OR Dzw # oldSpeed 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;
lrWgt: REAL = height * depth * tanHalfFOVX * MAX[0.0, -Dzw];
tbWgt: REAL = width * depth * tanHalfFOVY * MAX[0.0, -Dzw];
kindWeights[Left] ← lrWgt;
IF yaw < 0 THEN kindWeights[Left] ← kindWeights[Left] + yawWgt;
kindWeights[Right] ← kindWeights[Left] + lrWgt;
IF yaw > 0 THEN kindWeights[Right] ← kindWeights[Right] + yawWgt;
kindWeights[Bottom] ← kindWeights[Right] + tbWgt;
IF pitch < 0 THEN kindWeights[Bottom] ← kindWeights[Bottom] + pitchWgt;
kindWeights[Top] ← kindWeights[Bottom] + tbWgt;
IF pitch > 0 THEN kindWeights[Top] ← kindWeights[Top] + pitchWgt;
kindWeights[Back] ← kindWeights[Top] + width * height * MAX[0.0, Dzw];
IF kindWeights[Back] = 0.0 THEN kindWeights[Back] ← 1.0;
};
IF dying THEN DzwDzw * slowFactor
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;
};
kindWeights: ARRAY Kind 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[];
};
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;
};
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];
};
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[];
}.
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