<> <> <> DIRECTORY Basics, BasicTime, FS, Histograms, IdleBackdoor, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerPixelArray, ImagerPixelMap, ImagerPrivate, ImagerTransformation, IO, Menus, PrincOps, Process, Random, Real, RealFns, Rope, Terminal, ThisMachine, TIPUser, Vector2, ViewerClasses, ViewerOps; StarField: CEDAR PROGRAM IMPORTS BasicTime, FS, Histograms, IdleBackdoor, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPixelMap, ImagerTransformation, IO, Process, Random, Real, RealFns, Rope, Terminal, 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; Data: TYPE = REF DataSeq; DataSeq: TYPE = RECORD [data: SEQUENCE size: NAT OF Datum]; Datum: TYPE = RECORD [xw, yw, zw, xp, yp, rp: REAL, ci: NAT _ 0]; data: Data _ NIL; ImageCache: TYPE = REF ImageCacheRep; ImageCacheRep: TYPE = RECORD [ length: NAT _ 0, images: SEQUENCE size: NAT OF CachedImage]; 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; maxData: NAT _ 200; minData: NAT _ 5; hist hist goal goal goalN FOVX: REAL _ 45; edgeDegrees: REAL _ 65; hither: REAL _ 0.01; border: REAL _ 1.0; viewerStartRp: REAL _ 0.35; vtStartRp: REAL _ 2.5; viewerRpMin: REAL _ 0.5; vtRpMin: REAL _ 1.0; rw: REAL _ 1; initial slovershoot: REAL _ 1.5; upTextMin: Milliseconds _ 10*OneSecond; upTextMax: Milliseconds _ 60*OneSecond; downTextMin: Milliseconds _ 1*OneSecond; downTextMax: Milliseconds _ 6*OneSecond; usePeriod: BOOL _ FALSE; squareThresh: REAL _ 1.25; useXOR: BOOL _ TRUE; histPerf: BOOL _ FALSE; pickRandomNs: BOOL _ FALSE; pickPeriod: NAT _ 10; nData: NAT _ minData; slowFactor: REAL _ 0.95; avg decay: REAL _ 0.1; hold: REAL _ 1.0 - decay; 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] = { <> <> o: REAL; goalN <> <> o _ (dt1*n1 - dt2*n2)/(n1 - n2); goal goal }; NotifySFV: PROC [self: Viewer, input: LIST OF REF ANY] = { a: ATOM _ NARROW[input.first]; SELECT a FROM $Start => {okToGo _ TRUE; IF NOT going THEN TRUSTED {Process.Detach[FORK Viewit[]]}}; $Stop => okToGo _ FALSE; ENDCASE => ERROR; }; 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 { Process.SetPriority[Process.priorityNormal]; 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[]]; 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] = { [] _ IdleBackdoor.UseAlternateVT[vtProc: DoForVT, logout: logout]; }; vtContext: Imager.Context; vtPM: PixelMap; GiveVTContext: PROC [to: PROC [context: Imager.Context, pm: PixelMap]] = {to[vtContext, vtPM]}; KeyTyped: PROC RETURNS [stop: BOOL] = { stop _ IdleBackdoor.KeyTyped[IdleBackdoor.defaultKeyFilter]}; DoForVT: PROC [vt: Terminal.Virtual] = { vt.Select[]; [] _ vt.SetBWBitmapState[allocated]; [vtContext, vtPM] _ ContextAndPMFromVT[vt]; [] _ vt.SetBWBitmapState[displayed]; 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, Stop: KeyTyped, background: Imager.white, vt: vt]; [] _ vt.SetBWBitmapState[none]; }; Draw: PROC [context: Imager.Context, pm: PixelMap, index: Index] = { xc: REAL _ data[index].xp; yc: REAL _ data[index].yp; r: REAL _ data[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].rpMax THEN { ci: NAT _ data[index].ci; xlated: PixelMap; x: INTEGER _ Real.RoundI[xc]; y: INTEGER _ Real.RoundI[yc]; IF ci > 0 AND imageCache[ci-1].rpMax >= r THEN ERROR; WHILE imageCache[ci].rpMax < r DO ci _ ci + 1 ENDLOOP; data[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, Stop: PROC RETURNS [BOOL], background: Imager.Color, vt: Terminal.Virtual] = { half 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] = { data[index].ci _ 0; data[index].zw _ scale*rw/startRp; IF pickLast THEN { prepareToDie _ FALSE; dying _ TRUE; data[index].yw _ data[index].xw _ 0; } ELSE { half half half data[index].yw _ RealFns.TanDeg[Choose[-edgeDegrees, edgeDegrees]] * half data[index].xw _ RealFns.TanDeg[Choose[-edgeDegrees, edgeDegrees]] * half }; ToPort[index]; }; ToPort: PROC [index: Index] = { data[index].xp _ (data[index].xw/data[index].zw)*scale + xo; data[index].yp _ (data[index].yw/data[index].zw)*scale + yo; data[index].rp _ MAX[rpMin, (rw/data[index].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 .. nData) DO PickNew[index]; Draw[context, pm, index]; ENDLOOP; }; Update: PROC [index: Index] RETURNS [clip: BOOL] = { clip _ FALSE; data[index].zw _ data[index].zw - IF data[index].zw < hither THEN clip _ TRUE ELSE {ToPort[index]; IF data[index].xp - data[index].rp > xp1 OR data[index].xp + data[index].rp < xp0 OR data[index].yp - data[index].rp > yp1 OR data[index].yp + data[index].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[nData, newN]; index: Index _ 0; Imager.SetColor[context, ImagerBackdoor.invert]; FOR index _ 0, index + 1 WHILE index < nData DO need: BOOL _ TRUE; WHILE need DO Draw[context, pm, index]; need _ FALSE; IF Update[index].clip THEN { IF nData > newN THEN { IF index < nData - 1 THEN { data[index] _ data[nData-1]; need _ TRUE; }; nData _ nData - 1; } ELSE PickNew[index]; }; ENDLOOP; IF index < nData THEN Draw[context, pm, index]; ENDLOOP; FOR index _ nData, index+1 WHILE index < newN DO nData _ index + 1; PickNew[index, prepareToDie]; IF index < nData 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 < nData DO need: BOOL _ TRUE; WHILE need DO need _ FALSE; IF Update[index].clip THEN { IF nData > newN THEN { IF index < nData - 1 THEN { data[index] _ data[nData-1]; need _ TRUE; }; nData _ nData - 1; } ELSE PickNew[index]; }; ENDLOOP; IF index < nData THEN Draw[bufferContext, bufferPM, index]; ENDLOOP; FOR index _ nData, index+1 WHILE index < newN DO nData _ index + 1; PickNew[index, prepareToDie]; IF index < nData 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 _ nData _ 1; prepareToDie, dying: BOOL _ FALSE; bufferPM: PixelMap; bufferContext: Imager.Context; oldP: BasicTime.Pulses; choice: NAT _ 0; data _ NEW [DataSeq[maxData]]; half half TanEdgeDegrees _ RealFns.TanDeg[edgeDegrees]; TanHalfFOVX _ RealFns.TanDeg[FOVX/2]; TanHalfFOVY _ (half scale _ half xo _ xp0 + half yo _ yp0 + half slowFactor _ 1 - ( IF NOT useXOR THEN [bufferContext, bufferPM] _ MakeBuffer[xp0, yp0, xp1, yp1]; PickText[T _ 0]; giveContext[DrawInit]; oldP _ BasicTime.GetClockPulses[]; FOR i: INT _ 0, i+1 DO newP: BasicTime.Pulses; IF dying THEN ELSE IF NOT prepareToDie THEN prepareToDie _ Stop[]; giveContext[DrawDelta]; IF nData = 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 { q: Milliseconds _ goalN goal goal IF histPerf THEN hists[useXOR].ChangeTransformed[x: nData, y: MIN[hist avg IF pickRandomNs THEN {IF (choice _ choice + 1) MOD pickPeriod = 1 THEN newN _ rs.ChooseInt[minData, maxData] ELSE newN _ nData} ELSE newN _ IF prepareToDie THEN nData + 1 ELSE IF dying THEN 0 ELSE IF avg IF avg T _ T + oldP _ newP; ENDLOOP; }; 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 _ PMContext[pm]; }; 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: 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, "", 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; }; CreateViewer: PROC = { sfv _ ViewerOps.CreateViewer[flavor: sfvcFlavor, info: [name: "StarField"]]}; NewHistograms: PROC = { hists[FALSE] _ Histograms.Create2D[iMin: minData, iMax: maxData, jMin: hist [] _ hists[FALSE].Show[[name: "Dt vs n (buffered)"]]; hists[TRUE] _ Histograms.Create2D[iMin: minData, iMax: maxData, jMin: hist [] _ 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"]; <> }; Start[]; }. At edge of viewing cone: xw = zw tan xp = ( rp = ( zw = ( { &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]}