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: BOOL ← FALSE;
squareThresh: REAL ← 1.25;
useXOR: BOOL ← TRUE;
histPerf: BOOL ← FALSE;
pickRandomNs: BOOL ← FALSE;
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: BOOL ← TRUE;
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: BOOL ← FALSE;
going: BOOL ← FALSE;
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: BOOL ← FALSE;
viewerMouse: Imager.VEC;
PaintSFV:
PROC [self: Viewer, context: Imager.Context, whatChanged:
REF
ANY, clear:
BOOL]
RETURNS [quit:
BOOL ←
FALSE]
--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 ANY ← NIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOL ← FALSE] --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: REAL ← IF 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: REAL ← IF (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].r
pMax
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:
BOOL ←
FALSE] =
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.rp ← MAX[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: BOOL ← FALSE;
DrawDeltaByXOR:
PROC [context: Imager.Context, pm: PixelMap] = {
shouldUp: BOOL ← (T >= upText) AND (T < downText);
max: NAT ← MAX[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: BOOL ← TRUE;
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: BOOL ← TRUE;
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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 [r
pMax:
REAL] = {
r: REAL ← rpMax*0.99;
n: INT ← Real.RoundLI[r*2];
o: REAL ← IF (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.STREAM ← FS.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 =
d
i+1 m
i (p - c
i - v
i+1)
= di+1p'i - mi+1vi+1
m
i+1v
i+1 =
d
i+1m
i(v
i + a
i+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+