<> <> <> <> <> DIRECTORY Atom, Basics, BasicTime, CedarProcess, Convert, FS, G3dMatrix, G3dRender, G3dScanConvert, G3dShade, G3dClipXfmShade, G3dShape, G3dSortandDisplay, G3dVector, ImagerPixel, ImagerSample, IO, Process, Real, RealFns, Rope; G3dSortandDisplayImpl: CEDAR MONITOR IMPORTS Atom, Basics, BasicTime, CedarProcess, Convert, FS, G3dMatrix, G3dRender, G3dShade, G3dClipXfmShade, G3dShape, G3dVector, ImagerPixel, ImagerSample, IO, Process, Real, RealFns, Rope EXPORTS G3dSortandDisplay ~ BEGIN <> ROPE: TYPE ~ Rope.ROPE; Context: TYPE ~ G3dRender.Context; ContextProc: TYPE ~ G3dRender.ContextProc; RGB: TYPE ~ G3dRender.RGB; -- [ r, g, b: REAL]; Pixel: TYPE ~ G3dRender.Pixel; IntegerPair: TYPE ~ G3dRender.IntegerPair; Pair: TYPE ~ G3dRender.Pair; Box: TYPE ~ G3dRender.Box; Rectangle: TYPE ~ G3dRender.Rectangle; SixSides: TYPE ~ G3dRender.SixSides; OutCode: TYPE ~ G3dRender.OutCode; Triple: TYPE ~ G3dRender.Triple; NatSequence: TYPE ~ G3dRender.NatSequence; NatSequenceRep: TYPE ~ G3dRender.NatSequenceRep; Shape: TYPE ~ G3dShape.Shape; ShapeRep: TYPE ~ G3dShape.ShapeRep; ShapeSequence: TYPE ~ G3dShape.ShapeSequence; ShapeSequenceRep: TYPE ~ G3dShape.ShapeSequenceRep; FaceRep: TYPE ~ G3dShape.FaceRep; FaceSequenceRep: TYPE ~ G3dShape.FaceSequenceRep; FacingDir: TYPE ~ G3dRender.FacingDir; Patch: TYPE ~ G3dRender.Patch; PatchProc: TYPE ~ G3dRender.PatchProc; PatchSequence: TYPE ~ G3dRender.PatchSequence; PatchSequenceRep: TYPE ~ G3dRender.PatchSequenceRep; SortRecord: TYPE ~ G3dSortandDisplay.SortRecord; SortSequence: TYPE ~ G3dSortandDisplay.SortSequence; SortSequenceRep: TYPE ~ G3dSortandDisplay.SortSequenceRep; CtlPoint: TYPE ~ G3dRender.CtlPoint; CtlPointSequence: TYPE ~ G3dRender.CtlPointSequence; CtlPtInfo: TYPE ~ G3dRender.CtlPtInfo; CtlPtInfoProc: TYPE ~ G3dRender.CtlPtInfoProc; CtlPtInfoSequence: TYPE ~ G3dRender.CtlPtInfoSequence; Shading: TYPE ~ G3dRender.Shading; ShadingSequence: TYPE ~ G3dRender.ShadingSequence; Matrix: TYPE ~ G3dRender.Matrix; Order: TYPE ~ {inFront, behind, coincident, intersects}; Bounds: TYPE ~ RECORD[left, right, bottom, top, near, far: REAL]; BoundsSequence: TYPE ~ RECORD[SEQUENCE length: NAT OF Bounds]; RenderStyle: TYPE ~ G3dRender.RenderStyle; RenderData: TYPE ~ G3dRender.RenderData; ShadingClass: TYPE ~ G3dRender.ShadingClass; ShapeClass: TYPE ~ G3dRender.ShapeClass; ShapeProc: TYPE ~ G3dRender.ShapeProc; -- PROC[ Context, Shape ] NoneOut: OutCode ~ G3dRender.NoneOut; AllOut: OutCode ~ G3dRender.AllOut; debug: BOOL _ FALSE; <> GetProp: PROC [propList: Atom.PropList, prop: REF ANY] RETURNS [REF ANY] ~ Atom.GetPropFromList; PutProp: PROC [propList: Atom.PropList, prop: REF ANY, val: REF ANY] RETURNS [Atom.PropList] ~ Atom.PutPropOnList; <> nullPtr: INT ~ 0; -- handy name for zero in sort sequences progressReportsPerFrame: INT _ 0; -- set to non zero for progress reports on long frames <> Sqr: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { RETURN[number * number]; }; RectangleFromBox: PUBLIC PROC[box: ImagerSample.Box] RETURNS[ Rectangle] ~ { RETURN [[ x: Real.Float[box.min.f], y: Real.Float[box.min.s], w: Real.Float[box.max.f - box.min.f], h: Real.Float[box.max.s - box.min.s] ]]; }; BoxFromRectangle: PUBLIC PROC[ rect: Rectangle ] RETURNS[ ImagerSample.Box ] ~ { RETURN [[ min: [ f: Real.Fix[rect.x], s: Real.Fix[rect.y] ], max: [ f: Real.Fix[rect.x + rect.w], s: Real.Fix[rect.y + rect.h] ] ]]; }; ElapsedTime: PROC[startTime: REAL] RETURNS[ROPE] ~ { timeX100: REAL _ 100.0 * (CurrentTime[] - startTime); RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[timeX100] / 100.0 ], "s," ] ]; }; CurrentTime: PROC[] RETURNS[REAL] ~ { RETURN[ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] ]; }; OpenLogCarefully: PROC[fileName: ROPE] RETURNS [IO.STREAM] ~ { <> conflict: BOOLEAN _ FALSE; report: IO.STREAM; DO -- keep trying until others release file report _ FS.StreamOpen[ "FrameProgress.log", $append ! FS.Error => IF error.group = lock -- filed locked elsewhere? THEN {conflict _ TRUE; CONTINUE} ]; IF NOT conflict THEN EXIT; Process.PauseMsec[5000]; -- wait 5 sec. before trying again ENDLOOP; RETURN[report]; }; <> ValidateContext: PUBLIC PROC[ context: Context ] ~ { j: CARDINAL _ 0; IF context.viewer = NIL AND context.pixels = NIL AND context.viewPort = NIL THEN SIGNAL G3dRender.Error[$Fatal, "Image Area Undefined"]; IF context.class = NIL THEN Process.PauseMsec[3000]; -- wait 3s. (maybe viewers settling) IF context.class = NIL THEN G3dRender.Error[$Fatal, "No Class for Context"]; IF context.viewer # NIL AND context.viewPort = NIL AND (context.pixels = NIL OR context.pixels.samplesPerPixel = 1) THEN context.class.updateViewer[context]; IF context.viewPort = NIL AND context.pixels # NIL THEN { ValidateView[context]; ValidateDisplay[context]; } ELSE { ValidateDisplay[context]; ValidateView[context]; }; IF context.lightSources = NIL THEN G3dRender.Error[$Fatal, "No Light Sources"]; IF context.changed THEN { SetEyeSpace[context]; -- compute eyespace matrix FOR n: NAT IN [0..context.lightSources.length) DO -- update lightSource eyepositions context.lightSources[n].eyePosition _ G3dMatrix.Transform[ context.lightSources[n].position, context.eyeSpaceXfm ]; ENDLOOP; }; <<>> << Validate all shapes and get visible shapes >> IF context.shapes # NIL THEN { j _ 0; IF context.visibleShapes = NIL OR context.visibleShapes.length < context.shapes.length THEN context.visibleShapes _ NEW[ ShapeSequenceRep[context.shapes.length] ]; FOR i: NAT IN [0..context.shapes.length) DO shape: Shape _ ValidateShape[context, context.shapes[i]]; IF shape # NIL THEN { context.visibleShapes[j] _ shape; j _ j + 1; }; ENDLOOP; context.visibleShapes.length _ j; }; context.changed _ FALSE; }; ValidateDisplay: PUBLIC PROC[ context: Context ] ~ { <> IF context.displayInValid THEN { context.class.validateDisplay[context]; context.changed _ TRUE; G3dRender.SetView[ -- get new screen dimensions into transformations context: context, eyePoint: context.eyePoint, lookAt: context.lookAt, fieldOfView: context.fieldOfView, rollAngle: context.rollAngle, upDirection: context.upDirection, hitherLimit: context.hitherLimit, yonLimit: context.yonLimit ]; IF context.window = NIL THEN context.window _ WindowFromViewPort[context]; -- fit to viewprt }; context.screenExtent _ [[0,0], [0,0]]; -- clear coverage (new frame, alpha buffer, or display) context.displayInValid _ FALSE; }; ValidateView: PUBLIC PROC[ context: Context ] ~ { IF context.viewer = NIL OR context.viewPort = NIL THEN { s: ARRAY [0..5) OF ImagerSample.SampleMap _ ALL[NIL]; pixels: ImagerPixel.PixelMap _ NARROW[ Atom.GetPropFromList[ context.displayProps, $FullDisplayMemory ] ]; IF pixels # NIL THEN context.pixels _ pixels ELSE IF context.pixels = NIL THEN SIGNAL G3dRender.Error[$MisMatch, "no pixel memory"]; IF context.viewPort = NIL THEN context.viewPort _ NEW[ Rectangle _ RectangleFromBox[ ImagerSample.GetBox[context.pixels[0]] ] ]; context.viewPort^ _ G3dRender.IntersectRectangles[ context.viewPort^, context.preferredViewPort -- clip viewPort to preferredViewPort ]; context.ndcToPixels _ [ -- going to VM so render with origin at top left context.viewPort.w-1.0, -(context.viewPort.h-1.0), REAL[LAST[NAT]], 0.0, context.viewPort.h-1.0, 0.0 -- last line scales, this line adds ]; FOR i: NAT IN [0..context.pixels.samplesPerPixel) DO -- re-address pixelmap s[i] _ ImagerSample.Clip[ context.pixels[i], BoxFromRectangle[context.viewPort^] ]; ENDLOOP; IF context.pixels.samplesPerPixel > 0 THEN context.pixels _ ImagerPixel.MakePixelMap[s[0], s[1], s[2], s[3], s[4]]; }; IF context.window = NIL THEN { context.window _ WindowFromViewPort[context]; context.changed _ TRUE; }; IF context.changed THEN G3dRender.SetView[ context, context.eyePoint, context.lookAt, context.fieldOfView, context.rollAngle, context.upDirection, context.hitherLimit, context.yonLimit ]; -- check for mismatched viewPort and pixel storage IF context.pixels # NIL AND context.pixels.samplesPerPixel > 0 THEN IF context.pixels.box.max.f < context.viewPort.w OR context.pixels.box.max.s < context.viewPort.h THEN SIGNAL G3dRender.Error[$MisMatch, "pixel memory smaller than viewport"]; }; ValidateShape: PUBLIC ShapeProc ~ { <> <> render: REF RenderData _ G3dRender.RenderDataFrom[shape]; shapeClass: REF ShapeClass _ render.class; shadingClass: REF ShadingClass _ render.shadingClass; xfm: Matrix; IF context.stopMe^ THEN RETURN[shape]; IF context.changed OR NOT shape.renderValid THEN { xfm _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; <> IF shapeClass = NIL THEN -- load class { G3dRender.LoadShapeClass[shape, shape.type]; shapeClass _ render.class }; IF shadingClass = NIL THEN -- load default shading { G3dShade.LoadShadingClass[shape]; shadingClass _ render.shadingClass }; IF NOT shape.visible THEN RETURN[NIL]; -- invisible <> IF shape.sphereExtent.radius = 0.0 -- validate bounding Sphere THEN shape.sphereExtent _ G3dShape.BoundingSphere[ shape ]; shape.clipState _ G3dClipXfmShade.ClipBoundingSphere[context, shape, xfm]; -- clip state <> IF shape.clipState # out THEN [] _ shapeClass.validate[context, shape, data]; }; IF shape.clipState # out AND shape.surfaces # NIL -- clipstate can be refined in validate THEN RETURN[shape] ELSE RETURN[NIL]; }; ValidatePolyhedron: PUBLIC ShapeProc ~ { <> <> <> ValidateScreenCoords: PROC[] ~ { FOR i: NAT IN [0..shape.vertices.length) DO eVtx: Triple _ G3dMatrix.Transform[ shape.vertices[i].point, xfm]; eVtx _ G3dClipXfmShade.XfmPtToDisplay[ context, eVtx ]; shape.vertices[i].screen.x _ eVtx.x; shape.vertices[i].screen.y _ eVtx.y; ENDLOOP; shape.screenValid _ TRUE; }; render: REF RenderData _ G3dRender.RenderDataFrom[shape]; shapeClass: REF ShapeClass _ render.class; shadingClass: REF ShadingClass _ render.shadingClass; renderStyle: RenderStyle; xfm: Matrix _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; allPolysIn, allPolysOut: BOOLEAN _ TRUE; WITH shadingClass.renderMethod SELECT FROM style: REF RenderStyle => renderStyle _ style^; ENDCASE => renderStyle _ smooth; -- default to smooth to get normals and shading <> IF renderStyle # faceted THEN -- get vertex normals IF NOT shape.vertices.valid.normal THEN G3dClipXfmShade.GetVtxNmls[context, shape]; IF shape.faces = NIL THEN { -- always get face normals shape.faces _ NEW[FaceSequenceRep[shape.surfaces.length]]; shape.faces.length _ shape.surfaces.length; FOR i: NAT IN [0..shape.faces.length) DO shape.faces[i] _ NEW[FaceRep]; ENDLOOP; }; IF NOT shape.faces.valid.normal THEN G3dClipXfmShade.GetPolyNmls[context, shape]; IF NOT shape.visible THEN RETURN[shape]; -- don't bother, not visible <> IF render.patch = NIL THEN { <> render.patch _ NEW[PatchSequenceRep[shape.surfaces.length]]; render.patch.length _ shape.surfaces.length; FOR i: NAT IN [0..render.patch.length) DO render.patch[i] _ NEW[Patch[shape.surfaces[i].vertices.length]]; FOR j: NAT IN [0..shape.surfaces[i].vertices.length) DO vtx: NAT _ shape.surfaces[i].vertices[j]; render.patch[i][j].coord.x _ shape.vertices[vtx].point.x; render.patch[i][j].coord.y _ shape.vertices[vtx].point.y; render.patch[i][j].coord.z _ shape.vertices[vtx].point.z; render.patch[i][j].shade.xn _ shape.vertices[vtx].normal.x; render.patch[i][j].shade.yn _ shape.vertices[vtx].normal.y; render.patch[i][j].shade.zn _ shape.vertices[vtx].normal.z; render.patch[i][j].shade.txtrX _ shape.vertices[vtx].texture.x; render.patch[i][j].shade.txtrY _ shape.vertices[vtx].texture.y; render.patch[i][j].vtxPtr _ vtx; ENDLOOP; render.patch[i].nVtces _ shape.surfaces[i].vertices.length; render.patch[i].type _ shape.type; render.patch[i].oneSided _ NOT shape.showBackfaces; render.patch[i].renderData _ render; -- store REF to shading information <> render.patch[i].props _ PutProp[render.patch[i].props, $Shape, shape]; render.patch[i].props _ PutProp[render.patch[i].props, $PatchNo, NEW[NAT _ i]]; ENDLOOP; }; <> <> IF renderStyle = lines AND shape.clipState = in AND render.class.display # NIL THEN { ValidateScreenCoords[]; RETURN [ shape ]; }; shape.screenExtent _ [[32768, 32768], [0, 0]]; -- initialize for computing screenExtent FOR i: NAT IN [0..render.patch.length) DO <> andOfCodes: OutCode _ AllOut; -- test for trivially out orOfCodes: OutCode _ NoneOut; -- test for trivially in FOR j: NAT IN [0..render.patch[i].nVtces) DO OPEN render.patch[i][j].coord; -- transform control points to eyespace [ [ex, ey, ez] ] _ G3dMatrix.Transform[ [x, y, z] , xfm]; IF shape.clipState = clipped THEN { clip _ G3dClipXfmShade.GetClipCodeForPt[ context, [ex, ey, ez] ]; orOfCodes _ LOOPHOLE[ Basics.BITOR[LOOPHOLE[orOfCodes], LOOPHOLE[ clip] ], OutCode]; andOfCodes _ LOOPHOLE[ Basics.BITAND[ LOOPHOLE[andOfCodes], LOOPHOLE[ clip] ], OutCode]; } ELSE clip _ NoneOut; <> IF renderStyle = smooth OR renderStyle = shadedLines THEN { OPEN render.patch[i][j].shade; tmpShininess: REAL; vtx: NAT _ render.patch[i][j].vtxPtr; [xn, yn, zn] _ shape.vertices[vtx].normal; [r, g, b] _ shape.vertices[vtx].color; t _ shape.vertices[vtx].transmittance; IF shape.faces # NIL THEN IF shape.faces.valid.color THEN { -- scale by face color clr: Triple _ shape.faces[i].color; r _ clr.x * r; g _ clr.y * g; b _ clr.z * b; }; [[exn, eyn, ezn]] _ G3dMatrix.TransformVec[ [xn, yn, zn] , xfm]; <> tmpShininess _ shadingClass.shininess; shadingClass.shininess _ 0.0; render.patch[i][j] _ shadingClass.shadeVtx[ context, render.patch[i][j], shadingClass ]; shadingClass.shininess _ tmpShininess; }; <> IF renderStyle = lines THEN { OPEN render.patch[i][j].shade; [er, eg, eb] _ shadingClass.color; }; ENDLOOP; <> IF orOfCodes = NoneOut THEN render.patch[i].clipState _ in -- default case ELSE IF andOfCodes # NoneOut THEN render.patch[i].clipState _ out ELSE render.patch[i].clipState _ clipped; IF shape.clipState = clipped THEN { allPolysIn _ allPolysIn AND (render.patch[i].clipState = in); allPolysOut _ allPolysOut AND (render.patch[i].clipState = out); }; IF render.patch[i].clipState # out THEN { <> eyeNorm: Triple _ G3dMatrix.TransformVec[ shape.faces[i].normal, xfm]; awayness: REAL _ G3dVector.Dot[ -- normal front or back facing? G3dVector.Unit[[render.patch[i][0].coord.ex, render.patch[i][0].coord.ey, render.patch[i][0].coord.ez]], G3dVector.Unit[eyeNorm] ]; IF render.patch[i].type = $ConvexPolygon THEN { IF awayness > 0.0 THEN render.patch[i].dir _ back ELSE IF awayness < 0.0 THEN render.patch[i].dir _ front ELSE render.patch[i].dir _ unknown; IF (render.patch[i].oneSided AND render.patch[i].dir = back) THEN LOOP; }; <<>> <> IF renderStyle = faceted THEN { OPEN render.patch[i][0].shade; [xn, yn, zn] _ shape.faces[i].normal; [r, g, b] _ shape.faces[i].color; t _ shape.faces[i].transmittance; [[exn, eyn, ezn]] _ G3dMatrix.TransformVec[ shape.faces[i].normal, xfm]; render.patch[i][0] _ shadingClass.shadeVtx[context, render.patch[i][0], shadingClass]; FOR j: NAT IN [1..render.patch[i].nVtces) DO render.patch[i][j].shade.exn _ render.patch[i][0].shade.exn; render.patch[i][j].shade.eyn _ render.patch[i][0].shade.eyn; render.patch[i][j].shade.ezn _ render.patch[i][0].shade.ezn; render.patch[i][j].shade.r _ render.patch[i][0].shade.r; render.patch[i][j].shade.g _ render.patch[i][0].shade.g; render.patch[i][j].shade.b _ render.patch[i][0].shade.b; render.patch[i][j].shade.t _ render.patch[i][0].shade.t; render.patch[i][j].shade.er _ render.patch[i][0].shade.er; render.patch[i][j].shade.eg _ render.patch[i][0].shade.eg; render.patch[i][j].shade.eb _ render.patch[i][0].shade.eb; render.patch[i][j].shade.et _ render.patch[i][0].shade.et; ENDLOOP; }; <> FOR j: NAT IN [0..render.patch[i].nVtces) DO OPEN render.patch[i][j].coord; -- xfm to display, update shape screen extent [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; ENDLOOP; }; ENDLOOP; <> IF shape.clipState = clipped THEN -- refine shape clipstate based on polygon states IF allPolysIn THEN shape.clipState _ in ELSE IF allPolysOut THEN shape.clipState _ out; render.patchesValid _ TRUE; IF shape.clipState = in AND renderStyle = lines AND render.class.display # NIL THEN ValidateScreenCoords[]; -- prepare for unclipped line drawings shape.renderValid _ TRUE; RETURN[shape]; }; DummyValidate: PUBLIC ShapeProc ~ { <> <> render: REF RenderData _ G3dRender.RenderDataFrom[shape]; render.patchesValid _ TRUE; RETURN[shape]; }; SetEyeSpace: PUBLIC PROC[ context: Context ] ~ { in, right, up, normal: Triple; mtx: Matrix; wndw: Rectangle; viewSize, xSize, ySize, aspectRatio: REAL; IF context.viewPort.h <= 0 OR context.viewPort.w <= 0 THEN RETURN[]; <> in _ G3dVector.Unit[G3dVector.Sub[context.lookAt, context.eyePoint]]; IF G3dVector.Null[in] THEN SIGNAL G3dRender.Error[$MisMatch, "Eye and Pt of Interest identical"]; right _ G3dVector.Unit[G3dVector.Cross[in, context.upDirection]]; IF G3dVector.Null[right] THEN right _ [1.0, 0.0, 0.0]; -- looking straight down up _ G3dVector.Unit[G3dVector.Cross[right, in]]; <> context.eyeSpaceXfm _ G3dMatrix.Identity[]; context.eyeSpaceXfm[0][0] _ right.x; context.eyeSpaceXfm[1][0] _ right.y; context.eyeSpaceXfm[2][0] _ right.z; context.eyeSpaceXfm[3][0] _ -G3dVector.Dot[right, context.eyePoint]; context.eyeSpaceXfm[0][1] _ up.x; context.eyeSpaceXfm[1][1] _ up.y; context.eyeSpaceXfm[2][1] _ up.z; context.eyeSpaceXfm[3][1] _ -G3dVector.Dot[up, context.eyePoint]; context.eyeSpaceXfm[0][2] _ in.x; context.eyeSpaceXfm[1][2] _ in.y; context.eyeSpaceXfm[2][2] _ in.z; context.eyeSpaceXfm[3][2] _ -G3dVector.Dot[in, context.eyePoint]; mtx _ G3dMatrix.Identity[]; -- Roll about z-axis, clockwise mtx[0][0] _ RealFns.CosDeg[context.rollAngle]; mtx[0][1] _ -RealFns.SinDeg[context.rollAngle]; mtx[1][0] _ RealFns.SinDeg[context.rollAngle]; mtx[1][1] _ RealFns.CosDeg[context.rollAngle]; context.eyeSpaceXfm _ G3dMatrix.Mul[context.eyeSpaceXfm, mtx]; <> IF NOT (context.window.w > 0.0 AND context.window.h > 0.0) THEN RETURN[]; aspectRatio _ context.pixelAspectRatio * (context.viewPort.w/context.viewPort.h); xSize _ IF aspectRatio > 1.0 THEN 1.0 ELSE aspectRatio; ySize _ IF aspectRatio < 1.0 THEN 1.0 ELSE 1.0 / aspectRatio; wndw.x _ MIN[xSize, MAX[-xSize, context.window.x] ]; wndw.w _ MIN[xSize-wndw.x, context.window.w ]; wndw.y _ MIN[ySize, MAX[-ySize, context.window.y] ]; wndw.h _ MIN[ySize-wndw.y, context.window.h ]; viewSize _ RealFns.TanDeg[context.fieldOfView/2.0]; -- scale factor for angle of view <> context.clippingPlanes[Near] _ [0., 0., 1., -context.hitherLimit]; context.clippingPlanes[Far] _ [0., 0., -1., context.yonLimit]; <> normal _ G3dVector.Unit[[1., 0., -(wndw.x * viewSize)]]; context.clippingPlanes[Left] _ [normal.x, 0., normal.z, 0.]; normal _ G3dVector.Unit[[-1., 0., (wndw.x + wndw.w) * viewSize]]; context.clippingPlanes[Right] _ [normal.x, 0., normal.z, 0.]; normal _ G3dVector.Unit[[0., 1., -(wndw.y * viewSize)]]; context.clippingPlanes[Bottom] _ [0., normal.y, normal.z, 0.]; normal _ G3dVector.Unit[[0., -1., (wndw.y + wndw.h) * viewSize]]; context.clippingPlanes[Top] _ [0., normal.y, normal.z, 0.]; <> context.eyeToNdc.scaleX _ (2.0 / wndw.w) * (1.0 / viewSize) / 2.0; context.eyeToNdc.addX _ -(wndw.x / wndw.w); -- center after perspective divide context.eyeToNdc.scaleY _ (2.0 / wndw.h) * (1.0 / viewSize) / 2.0; context.eyeToNdc.addY _ -(wndw.y / wndw.h); -- center after perspective divide context.eyeToNdc.scaleZ _ context.yonLimit / (context.yonLimit - context.hitherLimit); context.eyeToNdc.addZ _ -(context.hitherLimit*context.yonLimit) / (context.yonLimit - context.hitherLimit); }; WindowFromViewPort: PUBLIC PROC[context: Context] RETURNS[REF Rectangle] ~ { <> window: REF Rectangle _ NEW[Rectangle]; vp: Rectangle; IF context.viewPort = NIL OR context.viewPort.h <= 0.0 OR context.viewPort.w <= 0.0 THEN RETURN[ NIL ]; vp _ [ context.viewPort.x * context.pixelAspectRatio, context.viewPort.y, context.viewPort.w * context.pixelAspectRatio, context.viewPort.h ]; IF vp.w > vp.h THEN { window.x _ -1.0; window.w _ 2.0; window.y _ -vp.h / vp.w; window.h _ 2.0 * vp.h / vp.w; } ELSE { window.y _ -1.0; window.h _ 2.0; window.x _ -vp.w / vp.h; window.w _ 2.0 * vp.w / vp.h; }; RETURN[ window ]; }; <> LoadDepthSequence: PUBLIC PROC[ context: Context, sortOrder: LIST OF REF ANY _ NIL ] RETURNS[LIST OF REF ANY, CARDINAL] ~ { <> sortKey: NatSequence; -- 1st element of 'sortOrder', gives sort bucket index from depth buckets: SortSequence; -- 2nd element of 'sortOrder', sort buckets with linked lists of patches shape: ShapeSequence _ context.visibleShapes; patchCount, displayedPatchCount: CARDINAL _ 0; bucketListPtr: INT _ 1; -- start bucket count at 1 to allow use of zero as null minDepth: REAL _ context.yonLimit; maxDepth: REAL _ context.hitherLimit; zScale: REAL _ 1.; NewSortOrder: PROC[] ~ { buckets _ NEW[SortSequenceRep[patchCount+1]]; sortKey _ NEW[NatSequenceRep[context.depthResolution]]; sortKey.length _ context.depthResolution; }; FOR i: NAT IN [0.. shape.length) DO -- get minimum and maximum depth and patch count render: REF RenderData _ G3dRender.RenderDataFrom[shape[i]]; xfm: Matrix _ G3dMatrix.Mul[shape[i].matrix, context.eyeSpaceXfm]; radius: REAL _ shape[i].sphereExtent.radius; center: Triple _ G3dMatrix.Transform[ shape[i].sphereExtent.center, xfm]; IF center.z - radius < minDepth THEN minDepth _ center.z - radius; IF center.z + radius > maxDepth THEN maxDepth _ center.z + radius; patchCount _ patchCount + shape[i].surfaces.length; ENDLOOP; minDepth _ MAX[minDepth, 0]; -- nothing allowed behind the eyepoint IF (maxDepth - minDepth) > 0. THEN zScale _ (context.depthResolution - 1) / (maxDepth - minDepth); IF sortOrder # NIL THEN { sortKey _ NARROW[sortOrder.first]; buckets _ NARROW[sortOrder.rest.first]; } ELSE NewSortOrder[]; IF buckets.length < patchCount OR sortKey.length < context.depthResolution THEN NewSortOrder[]; -- get new storage if sizes have expanded FOR i: NAT IN [0..sortKey.length) DO sortKey[i] _ nullPtr; ENDLOOP; -- clear sort structure FOR s: NAT IN [0.. shape.length) DO -- Enter patches (by nearest z) in bucket lists patch: PatchSequence _ G3dRender.PatchesFrom[shape[s]]; FOR i: NAT IN [0..patch.length) DO IF context.stopMe^ THEN RETURN[NIL, 0]; -- shut down if stop signal received IF patch[i] # NIL AND patch[i].clipState # out THEN { -- can't be proven not visible neg, pos: BOOL _ FALSE; zNear: REAL _ maxDepth; iz: INT; FOR j: NAT IN [0..patch[i].nVtces) DO -- get minimum z-coordinate zNear _ MIN[zNear, patch[i][j].coord.ez]; ENDLOOP; iz _ INTEGER[Real.Fix[zScale *(zNear - minDepth)]]; -- get matching bucket addr. IF iz < 0 THEN iz _ 0; -- clip at zero IF NOT (patch[i].oneSided AND patch[i].dir = back) THEN { -- will be displayed bckPtr, nxtPtr: INT _ nullPtr; nxtPtr _ sortKey[iz]; WHILE nxtPtr # nullPtr AND buckets[nxtPtr].dir = front DO bckPtr _ nxtPtr; -- find right place in ordered chain, front-facing first nxtPtr _ buckets[nxtPtr].next; ENDLOOP; <> buckets[bucketListPtr] _ NEW[SortRecord]; buckets[bucketListPtr].patch _ patch[i]; buckets[bucketListPtr].next _ buckets[bucketListPtr].prev _ nullPtr; -- clear IF nxtPtr # nullPtr THEN { -- not at tail of list buckets[bucketListPtr].prev _ buckets[nxtPtr].prev; buckets[bucketListPtr].next _ nxtPtr; buckets[nxtPtr].prev _ bucketListPtr; }; IF bckPtr # nullPtr THEN { -- not at head of list buckets[bucketListPtr].next _ buckets[bckPtr].next; buckets[bucketListPtr].prev _ bckPtr; buckets[bckPtr].next _ bucketListPtr; }; IF bckPtr = nullPtr THEN { -- at head of list sortKey[iz] _ bucketListPtr; -- reset list header }; IF nxtPtr = nullPtr THEN -- at tail of list buckets[sortKey[iz]].prev _ bucketListPtr; -- reset tail ptr at head buckets[bucketListPtr].dir _ patch[i].dir; -- front or backfacing or unknown bucketListPtr _ bucketListPtr + 1; displayedPatchCount _ displayedPatchCount + 1; }; }; ENDLOOP; ENDLOOP; sortKey.length _ context.depthResolution; RETURN[ LIST[sortKey, buckets], displayedPatchCount ]; }; <> GetDepths: PROC[ context: Context] ~ { <> shape: ShapeSequence _ context.visibleShapes; minDepth: REAL _ context.yonLimit; maxDepth: REAL _ context.hitherLimit; FOR i: NAT IN [0.. shape.length) DO -- get minimum and maximum depth xfm: Matrix _ G3dMatrix.Mul[shape[i].matrix, context.eyeSpaceXfm]; radius: REAL _ shape[i].sphereExtent.radius; center: Triple _ shape[i].sphereExtent.center; ctrLessRadius: Triple _ [center.x, center.y - radius, center.z]; shapeClass: REF ShapeClass _ G3dRender.ShapeClassFrom[shape[i]]; <> center _ G3dMatrix.Transform[ center, xfm]; ctrLessRadius _ G3dMatrix.Transform[ctrLessRadius, xfm]; radius _ G3dVector.Length[G3dVector.Sub[center, ctrLessRadius]]; IF shapeClass.type # $ConvexPolygon AND shapeClass.type # $Poly THEN radius _ 1.1 * radius; -- fudge for patches IF center.z - radius < minDepth THEN minDepth _ center.z - radius; IF center.z + radius > maxDepth THEN maxDepth _ center.z + radius; ENDLOOP; minDepth _ MAX[minDepth, context.hitherLimit]; -- nothing allowed behind the eyepoint <> context.eyeToNdc.scaleZ _ maxDepth / (maxDepth - minDepth); context.eyeToNdc.addZ _ -minDepth*maxDepth / (maxDepth - minDepth); FOR s: NAT IN [0.. shape.length) DO render: REF RenderData _ G3dRender.RenderDataFrom[shape[s]]; FOR i: NAT IN [0.. render.patch.length) DO IF render.patch[i].clipState # out THEN FOR j: NAT IN [0..render.patch[i].nVtces) DO OPEN render.patch[i][j].coord; -- xfm to display, update shape screen extent [[sx, sy, sz]] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape[s] ]; ENDLOOP; ENDLOOP; ENDLOOP; }; DoBackToFront: PUBLIC PROC[ context: Context, sortInfo: LIST OF REF ANY, action: PROC[REF Patch] ] ~ { sortKey: NatSequence _ NARROW[sortInfo.first]; buckets: SortSequence _ NARROW[sortInfo.rest.first]; FOR i: NAT DECREASING IN [0..sortKey.length) DO j: NAT _ sortKey[i]; WHILE j # nullPtr DO j _ buckets[j].prev; -- jump to tail of list and work back action[buckets[j].patch]; -- call back with next polygon in order IF j = sortKey[i] THEN j _ nullPtr; -- exit if head of list reached IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received ENDLOOP; ENDLOOP; CombineBoxes[context]; -- get combined bounding box on scene }; DoFrontToBack: PUBLIC PROC[ context: Context, sortInfo: LIST OF REF ANY, action: PROC[REF Patch] ] ~ { sortKey: NatSequence _ NARROW[sortInfo.first]; buckets: SortSequence _ NARROW[sortInfo.rest.first]; FOR i: NAT IN [0..sortKey.length) DO j: NAT _ sortKey[i]; WHILE j # nullPtr DO action[buckets[j].patch]; -- call back with next polygon in order j _ buckets[j].next; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received ENDLOOP; ENDLOOP; CombineBoxes[context]; -- get combined bounding box on scene }; <<>> DoForPatches: PUBLIC PROC[ context: Context, set: ShapeSequence, patchAction: PROC[REF Patch], shapeAction: PROC[Shape] _ NIL ] ~ { FOR s: NAT IN [0..set.length) DO IF set[s].clipState = in AND shapeAction # NIL THEN shapeAction[set[s]] ELSE { patch: PatchSequence _ G3dRender.PatchesFrom[set[s]]; FOR i: NAT IN [0..patch.length) DO IF patch[i] # NIL THEN { IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received IF patch[i].clipState # out THEN patchAction[ patch[i] ]; }; ENDLOOP; }; ENDLOOP; }; CombineBoxes: PUBLIC PROC[context: Context] ~ { -- get combined bounding box context.screenExtent _ [ [LAST[NAT], LAST[NAT]], [FIRST[NAT], FIRST[NAT]] ]; FOR i: NAT IN [0..context.visibleShapes.length) DO IF context.screenExtent.min.x > context.visibleShapes[i].screenExtent.min.x THEN context.screenExtent.min.x _ context.visibleShapes[i].screenExtent.min.x; IF context.screenExtent.max.x < context.visibleShapes[i].screenExtent.max.x THEN context.screenExtent.max.x _ context.visibleShapes[i].screenExtent.max.x; IF context.screenExtent.min.y > context.visibleShapes[i].screenExtent.min.y THEN context.screenExtent.min.y _ context.visibleShapes[i].screenExtent.min.y; IF context.screenExtent.max.y < context.visibleShapes[i].screenExtent.max.y THEN context.screenExtent.max.y _ context.visibleShapes[i].screenExtent.max.y; ENDLOOP; }; ShowOnePatch: PROC[ context: Context, shape: Shape, i: NAT] ~ { patch: PatchSequence _ G3dRender.PatchesFrom[shape]; IF patch[i].clipState # out THEN [] _ G3dRender.ShapeClassFrom[shape].displayPatch[ context, patch[i] ]; }; ShowOneVtx: PROC[ context: Context, shape: Shape, j: NAT] ~ { patch: PatchSequence _ G3dRender.PatchesFrom[shape]; FOR i: NAT IN [0..patch.length) DO containsVtx: BOOL _ FALSE; FOR k: NAT IN [0..patch[i].nVtces) DO IF patch[i][k].vtxPtr = j THEN { containsVtx _ TRUE; EXIT; }; ENDLOOP; IF containsVtx THEN IF patch[i].clipState # out THEN [] _ G3dRender.ShapeClassFrom[shape].displayPatch[ context, patch[i] ]; ENDLOOP; }; ShowObjects: PUBLIC PROC[ context: Context, frontToBack: BOOL _ FALSE ] ~ { ShowPatch: PROC[p: REF Patch] ~{ [] _ p.renderData.class.displayPatch[ context, p ]; patchCount _ patchCount + 1; IF log # NIL AND context.antiAliasing AND progressReportsPerFrame > 0 AND patchCount >= nextReport THEN { report: IO.STREAM _ OpenLogCarefully["FrameProgress.log"]; message: ROPE _ Rope.Cat[ Rope.Cat[ "\n ", Convert.RopeFromInt[patchCount], " of " ], Rope.Cat[ Convert.RopeFromInt[patchTotal], " done in ", ElapsedTime[time] ], Rope.Cat[ " at ", Convert.RopeFromTime[ from: BasicTime.Now[], start: hours, includeZone: FALSE ] ], Rope.Cat[ " est. done at ", Convert.RopeFromTime[ BasicTime.Update[ BasicTime.Now[], Real.Fix[(CurrentTime[] - time) * (patchTotal - patchCount) / patchCount] ] ] ] ]; log.PutRope[ message ]; IO.Flush[log]; report.PutRope[ message ]; IO.Close[ report]; -- make readable from elsewhere nextReport _ MIN[patchCount + patchTotal/progressReportsPerFrame, patchTotal-1]; }; }; patchCount, patchTotal, nextReport: INT _ 0; time: REAL _ CurrentTime[]; log: IO.STREAM _ NARROW[ Atom.GetPropFromList[context.props, $Log] ]; timing: ROPE; justOne: REF IntegerPair _ NARROW[ Atom.GetPropFromList[context.props, $SinglePatch] ]; oneVtx: REF IntegerPair _ NARROW[ Atom.GetPropFromList[context.props, $SingleVtx] ]; shape: ShapeSequence _ context.visibleShapes; IF shape = NIL THEN RETURN[]; IF context.depthBuffering THEN GetDepths[context]; IF justOne # NIL THEN { ShowOnePatch[context, shape[justOne.x], justOne.y]; RETURN[]; }; IF oneVtx # NIL THEN { ShowOneVtx[context, shape[oneVtx.x], oneVtx.y]; RETURN[]; }; IF context.depthBuffering AND NOT context.antiAliasing THEN DoForPatches[context, shape, ShowPatch] ELSE { IF GetProp[context.props, $SortToPriority] # NIL THEN SIGNAL G3dRender.Error[$Unimplemented, "Priority Sort under repair"] <<[context.sortSequence, patchTotal] _ LoadPrioritySequence[context, NARROW[context.sortSequence] ]>> ELSE [context.sortSequence, patchTotal] _ LoadDepthSequence[context, NARROW[context.sortSequence] ]; timing _ Rope.Cat[ timing, " Sort: ", ElapsedTime[time] ]; time _ CurrentTime[]; IF context.delayClear THEN context.class.loadBackground[context]; -- load background IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received IF frontToBack THEN DoFrontToBack[context, NARROW[context.sortSequence], ShowPatch] ELSE DoBackToFront[context, NARROW[context.sortSequence], ShowPatch]; }; IF log # NIL THEN log.PutRope[ Rope.Cat[ timing, " Scan: ", ElapsedTime[time] ] ]; }; ShowWireFrameObjects: PUBLIC PROC[context: Context ] ~ { ShowPatch: PROC[p: REF Patch] ~{ [] _ p.renderData.class.displayPatch[ context, p ]; }; ShowShape: PROC[s: Shape] ~{ render: REF RenderData _ G3dRender.RenderDataFrom[s]; IF (render.class.display # NIL AND NOT context.antiAliasing) THEN [] _ render.class.display[ context, s ] ELSE OutputShape[context, s]; }; shape: G3dRender.ShapeSequence _ context.visibleShapes; justOne: REF IntegerPair _ NARROW[ Atom.GetPropFromList[context.props, $SinglePatch] ]; oneVtx: REF IntegerPair _ NARROW[ Atom.GetPropFromList[context.props, $SingleVtx] ]; IF shape = NIL THEN RETURN[]; IF context.delayClear THEN context.class.loadBackground[context]; -- load background IF justOne # NIL THEN { ShowOnePatch[context, shape[justOne.x], justOne.y]; RETURN[]; }; IF oneVtx # NIL THEN { ShowOneVtx[context, shape[oneVtx.x], oneVtx.y]; RETURN[]; }; DoForPatches[context, shape, ShowPatch, ShowShape]; CombineBoxes[context]; -- get combined bounding box on scene }; OutputShape: PROC[context: Context, shape: Shape] ~ { <> patch: PatchSequence _ G3dRender.PatchesFrom[shape]; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received FOR i: NAT IN [0..patch.length) DO [] _ G3dRender.ShapeClassFrom[shape].displayPatch[ context, patch[i] ]; ENDLOOP; }; EdgesToPolygons: PatchProc ~ { <> <> MakeEdge: PROC[pt1, pt2: CtlPtInfo, edge: REF Patch] ~ { dirX, dirY, mag, offSetX1, offSetY1, offSetX2, offSetY2: REAL; dirX _ pt2.coord.ex - pt1.coord.ex; -- get unit vector in edge direction dirY _ pt2.coord.ey - pt1.coord.ey; mag _ RealFns.SqRt[ Sqr[dirX] + Sqr[dirY] ]; IF mag < G3dScanConvert.justNoticeable THEN RETURN[]; -- quit if too short to be seen dirX _ dirX / mag; dirY _ dirY / mag; offSetX1 _ dirX * baseRadius; offSetY1 _ dirY * baseRadius; offSetX2 _ dirX * baseRadius; offSetY2 _ dirY * baseRadius; FOR i: NAT IN [0..3) DO edge[i] _ pt1; ENDLOOP; FOR i: NAT IN [3..6) DO edge[i] _ pt2; ENDLOOP; edge[0].coord.ex _ edge[0].coord.ex+offSetY1; edge[0].coord.ey _ edge[0].coord.ey-offSetX1; edge[1].coord.ex _ edge[1].coord.ex-offSetX1; edge[1].coord.ey _ edge[1].coord.ey-offSetY1; edge[2].coord.ex _ edge[2].coord.ex-offSetY1; edge[2].coord.ey _ edge[2].coord.ey+offSetX1; edge[3].coord.ex _ edge[3].coord.ex-offSetY2; edge[3].coord.ey _ edge[3].coord.ey+offSetX2; edge[4].coord.ex _ edge[4].coord.ex+offSetX2; edge[4].coord.ey _ edge[4].coord.ey+offSetY2; edge[5].coord.ex _ edge[5].coord.ex+offSetY2; edge[5].coord.ey _ edge[5].coord.ey-offSetX2; IF patch.dir = back THEN FOR i: NAT IN [0..3) DO -- reverse order to make backfacing temp: CtlPtInfo _ edge[i]; edge[i] _ edge[5-i]; edge[5-i] _ temp; ENDLOOP; IF edge.clipState = in THEN FOR i: NAT IN [0..6) DO OPEN edge[i].coord; [[sx, sy, sz]] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez] ]; ENDLOOP; [] _ DoOutputPolygon[context, edge]; }; baseRadius: REAL _ .003; limit: NAT _ IF patch.type = $PolyLine THEN patch.nVtces - 1 ELSE patch.nVtces; { IF patch.dir = unknown THEN patch.dir _ G3dClipXfmShade.BackFacing[ context, patch ! G3dRender.Error => IF code = $Condition THEN GO TO GiveUp ]; IF patch.dir = front OR NOT patch.oneSided THEN FOR i: NAT IN [0..limit) DO edge: REF Patch _ G3dClipXfmShade.GetPatch[6]; edge.type _ $ConvexPolygon; edge.nVtces _ 6; edge.oneSided _ patch.oneSided; edge.dir _ patch.dir; edge.clipState _ patch.clipState; edge.renderData _ patch.renderData; MakeEdge[ patch[i], patch[(i+1) MOD patch.nVtces], edge ]; G3dClipXfmShade.ReleasePatch[edge]; -- end of the line for this patch ENDLOOP; EXITS GiveUp => NULL }; RETURN[patch]; }; OutputPolygon: PUBLIC PatchProc ~ { <> shadingClass: REF ShadingClass _ patch.renderData.shadingClass; renderStyle: RenderStyle _ NARROW[shadingClass.renderMethod, REF RenderStyle]^; inputPatch: REF Patch _ patch; IF patch = NIL OR patch.clipState = out THEN RETURN[patch]; -- reject if outside frame IF patch.type # $ConvexPolygon AND patch.type # $Poly AND patch.type # $PolyLine THEN { SIGNAL G3dRender.Error[$MisMatch, "Not a Polygon or PolyLine"]; RETURN[patch]; -- catches unexpanded patches, etc. }; IF context.antiAliasing AND -- antialiased lines ( patch.type = $PolyLine OR renderStyle = lines OR renderStyle = shadedLines ) THEN patch _ EdgesToPolygons[context, patch] ELSE patch _ DoOutputPolygon[ context, patch ]; RETURN[patch]; }; DoOutputPolygon: PROC[ context: Context, patch: REF Patch ] RETURNS[REF Patch] ~ { <> CopyPatch: PROC[inPatch: REF Patch] RETURNS[outPatch: REF Patch] ~ { outPatch _ G3dClipXfmShade.GetPatch[inPatch.nVtces]; outPatch.type _ inPatch.type; outPatch.oneSided _ inPatch.oneSided; outPatch.nVtces _ inPatch.nVtces; outPatch.clipState _ inPatch.clipState; outPatch.dir _ inPatch.dir; outPatch.renderData _ inPatch.renderData; outPatch.props _ inPatch.props; FOR i: NAT IN [0..inPatch.nVtces) DO outPatch[i] _ inPatch[i]; ENDLOOP; }; shadingClass: REF ShadingClass _ patch.renderData.shadingClass; renderStyle: RenderStyle _ NARROW[shadingClass.renderMethod, REF RenderStyle]^; inputPatch: REF Patch _ patch; IF patch.clipState = clipped THEN { patch _ CopyPatch[patch]; patch _ G3dClipXfmShade.ClipPoly[context, patch]; IF patch.nVtces > 2 OR ( patch.type = $PolyLine AND patch.nVtces > 1 ) THEN FOR i: NAT IN [0..patch.nVtces) DO OPEN patch[i].coord; [[sx, sy, sz]] _ G3dClipXfmShade.XfmPtToDisplay[context, [ex, ey, ez]]; ENDLOOP; }; IF patch.nVtces > 2 OR ( patch.type = $PolyLine AND patch.nVtces > 1 ) THEN { IF patch.type = $PolyLine OR renderStyle = lines THEN { [] _ context.class.displayPolygon[context, patch]; GO TO GiveUp; }; IF patch.dir = unknown THEN patch.dir _ G3dClipXfmShade.BackFacing[ context, patch ! G3dRender.Error => IF code = $Condition THEN GO TO GiveUp ]; IF patch.dir = front THEN [] _ context.class.displayPolygon[context, patch] ELSE IF patch.oneSided -- backfacing!! THEN GO TO GiveUp -- Reject if closed surface ELSE { -- make reversed copy of patch for display IF inputPatch = patch THEN patch _ CopyPatch[patch]; FOR i: NAT IN [0..patch.nVtces/2) DO -- reorder to keep clockwise on display tempVtx: CtlPtInfo _ patch[i]; patch[i] _ patch[patch.nVtces-1 - i]; patch[patch.nVtces-1 - i] _ tempVtx; ENDLOOP; IF renderStyle # hiddenLines AND renderStyle # linesWnormals THEN FOR i: NAT IN [0..patch.nVtces) DO -- recalculate shading for back side patch[i].shade.exn _ -patch[i].shade.exn; -- reverse normals patch[i].shade.eyn _ -patch[i].shade.eyn; patch[i].shade.ezn _ -patch[i].shade.ezn; patch[i] _ shadingClass.shadeVtx[context, patch[i], shadingClass]; ENDLOOP; [] _ context.class.displayPolygon[context, patch]; }; EXITS GiveUp => NULL }; IF inputPatch # patch THEN G3dClipXfmShade.ReleasePatch[patch]; RETURN[inputPatch]; }; RopeDisplay: PUBLIC PatchProc ~ { renderData: REF RenderData _ patch.renderData; shadingClass: REF ShadingClass _ patch.renderData.shadingClass; msg: ROPE _ NARROW[ GetProp[renderData.props, $RopeMessage ] ]; font: ROPE _ NARROW[ GetProp[renderData.props, $RopeFont ] ]; color: Pixel _ [ Real.Fix[shadingClass.color.R*255.0], Real.Fix[shadingClass.color.G*255.0], Real.Fix[shadingClass.color.B*255.0], 0, 0 ]; position: Triple _ [patch[0].coord.sx, patch[0].coord.sy, 0.0]; position2: Triple _ [patch[1].coord.sx, patch[1].coord.sy, 0.0]; size: REAL _ G3dVector.Length[ G3dVector.Sub[position2, position] ]; <> <> context.class.draw2DRope[ context, msg, [ position.x/context.ndcToPixels.scaleX, 1.0 - position.y/ABS[context.ndcToPixels.scaleY] ], color, size, font ]; RETURN[patch]; }; <> MakeFrame: PUBLIC ContextProc ~ { IF Atom.GetPropFromList[context.props, $DisableClear] # NIL THEN ShowShapes[context] ELSE MakeTheFrame[context]; }; ShowShapes: PUBLIC ContextProc ~ { <> delay: BOOL _ context.delayClear; context.delayClear _ FALSE; -- avoids frame clear before making new frame MakeTheFrame[context: context, clear: FALSE]; context.delayClear _ delay; }; MakeTheFrame: PROC[context: Context, clear: BOOL _ TRUE] ~ { <> Action: PROC ~ { allLines: BOOL _ TRUE; time: REAL _ CurrentTime[]; log: IO.STREAM _ NARROW[ Atom.GetPropFromList[context.props, $Log] ]; context.stopMe^ _ FALSE; -- get stop flag unstuck, if necessary context.imageReady _ FALSE; -- discourage use of bits by other processes context.screenExtent _ [[0,0], [0,0]]; -- clear bounding box of coverage IF log # NIL THEN { report: IO.STREAM _ OpenLogCarefully["FrameProgress.log"]; message: ROPE _ Rope.Cat[ Rope.Cat["\n Making frame ", Convert.RopeFromReal[ context.viewPort.w ]], Rope.Cat[" by ", Convert.RopeFromReal[ context.viewPort.h ]], Rope.Cat[" - Frame # ", Convert.RopeFromInt[ context.frameNumber ], "\n"] ]; log.PutRope[ message ]; IO.Flush[log]; report.PutRope[ message ]; IO.Close[ report]; -- make readable from elsewhere }; ValidateContext[context]; -- update anything changed since last frame FOR i: NAT IN [0..context.shapes.length) DO render: REF RenderData _ G3dRender.RenderDataFrom[context.shapes[i]]; IF render.class.doBeforeFrame # NIL THEN -- execute animation updates FOR doList: LIST OF ShapeProc _ render.class.doBeforeFrame, doList.rest UNTIL doList = NIL DO context.shapes[i] _ doList.first[context, context.shapes[i]]; ENDLOOP; ENDLOOP; IF log # NIL THEN log.PutRope[ Rope.Cat[ " VtxOps: ", ElapsedTime[time] ] ]; IF clear AND NOT context.delayClear THEN context.class.loadBackground[context]; -- load background IF context.visibleShapes # NIL AND context.visibleShapes.length > 0 THEN { FOR i: NAT IN [0 .. context.visibleShapes.length) DO -- all line drawing? render: REF RenderData _ G3dRender.RenderDataFrom[context.visibleShapes[i]]; IF NARROW[render.shadingClass.renderMethod, REF RenderStyle]^ # lines THEN allLines _ FALSE; ENDLOOP; IF allLines AND NOT context.antiAliasing THEN ShowWireFrameObjects[ context ] ELSE ShowObjects[ context: context, frontToBack: context.antiAliasing ]; context.imageReady _ TRUE; -- encourage use of bits by other processes IF log # NIL THEN { report: IO.STREAM _ OpenLogCarefully["FrameProgress.log"]; message: ROPE _ Rope.Cat[ Rope.Cat["\n Frame: ", ElapsedTime[time], "\n"], Rope.Cat[" Completed at: ", Convert.RopeFromTime[BasicTime.Now[]], "\n"] ]; report.PutRope[ message ]; IO.Close[ report]; log.PutRope[ Rope.Cat[" Frame: ", ElapsedTime[time], "\n"] ]; IO.Flush[log]; }; } ELSE { IF log # NIL THEN { log.PutRope[" No visible objects in frame "]; IO.Flush[log]; }; }; }; CedarProcess.DoWithPriority[background, Action]; }; END. <<>>