G3dSortandDisplayImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, November 6, 1989 6:19:46 pm PST
Glassner, July 5, 1989 6:35:20 pm PDT
Bloomenthal, June 27, 1989 12:35:17 pm PDT
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
Internal Declarations
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:    BOOLFALSE;
Renamed Procedures
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;
Global Variables
nullPtr: INT ~ 0;    -- handy name for zero in sort sequences
progressReportsPerFrame: INT ← 0;  -- set to non zero for progress reports on long frames
Utility Procedures
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] ~ {
Prevent hangups on FS.LockConflict by catching error, pausing and retrying
conflict: BOOLEANFALSE;
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];
};
Procedures for Updating Context
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 ] ~ {
This eventually assumes that context.viewPort # NIL
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 ~ {
PROC[context: Context, shape: Shape, data: REF] RETURNS[Shape];
Update shading and transform matrices, return visibility (based on clipping and "hidden")
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];
Load shape and shading classes
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
Update clip state based on bounding sphere
IF shape.sphereExtent.radius = 0.0        -- validate bounding Sphere
THEN shape.sphereExtent ← G3dShape.BoundingSphere[ shape ];
shape.clipState ← G3dClipXfmShade.ClipBoundingSphere[context, shape, xfm]; -- clip state
Get shading, do transforms, etc. based on shape type
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 ~ {
PROC[context: Context, shape: Shape, data: REF] RETURNS[Shape];
Update patch structure under renderData to ready everything for display, etc.
Shape class, shading class, matrix, bounding Sphere, and gross clip state are expected valid
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: BOOLEANTRUE;
WITH shadingClass.renderMethod SELECT FROM
style: REF RenderStyle => renderStyle ← style^;
ENDCASE => renderStyle ← smooth;  -- default to smooth to get normals and shading
Get vertex, face normals if not yet present
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
Create or update patch-by-patch description
IF render.patch = NIL THEN {
Build patch sequence for shape
render.patch ← NEW[PatchSequenceRep[shape.surfaces.length]];
render.patch.lengthshape.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
Store REF to parent shape and polygon number in object
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;
};
Patches built, update shading, transformed vertices, clip state
Catch unclipped line drawings and expedite
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
Transform and shade vertices, get clip state for each polygon
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 vertex normals used for shading, transform and get shading
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];
Cache shininess to get vertex shades without highlights
tmpShininess ← shadingClass.shininess; shadingClass.shininess ← 0.0;
render.patch[i][j] ← shadingClass.shadeVtx[
context, render.patch[i][j], shadingClass
];
shadingClass.shininess ← tmpShininess;
};
Plain lines take object color
IF renderStyle = lines THEN {
OPEN render.patch[i][j].shade;
[er, eg, eb] ← shadingClass.color;
};
ENDLOOP;
Collect clipping data for vertices to tag patches, patches to tag shapes
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 {
Check for backfacing
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;
};
Update polygons for faceted shading
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;
};
Get screen coordinates for on-screen polygons
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;
Refine shape clipstate based on polygon state
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 ~ {
PROC[context: Context, shape: Shape, data: REF] RETURNS[Shape];
Do what always must be done (precious little, it turns out)
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[];
transform from world to eye
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]];
unit not really needed (to avoid roundoff problems)
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];
bound window by -1.0 < x, y < 1.0
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
generate clipping planes
context.clippingPlanes[Near] ← [0., 0., 1., -context.hitherLimit];
context.clippingPlanes[Far] ← [0., 0., -1., context.yonLimit];
unit plane equation for true distances
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.];
compute the transformation from the eye coord sys to unit display coordinates (0.—1.) after perspective divide
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] ~ {
Fits window to shape of viewport
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 ];
};
Simple Depth Sort
LoadDepthSequence: PUBLIC PROC[ context: Context, sortOrder: LIST OF REF ANYNIL ]
      RETURNS[LIST OF REF ANY, CARDINAL] ~ {
Builds front-to-back sorted array of buckets, bidirectional linked lists within buckets
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: BOOLFALSE;
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;
Maintain doubly-linked list with back pointer to tail at list head
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 ];
};
Procedures for Display
GetDepths: PROC[ context: Context] ~ {
Calculate depths scaled over max and min depth for depth-buffering
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]];
Warning!! Will not work with anisotropic scaling
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
replace effective hither and yon stretching (min-max) over (0.0-1.0)
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: BOOLFALSE;
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: BOOLFALSE ] ~ {
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.STREAMNARROW[ 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] ~ { 
Unclipped shape output
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 ~ {
PROC[ context: Context, patch: REF Patch, data: REF ANYNIL ] RETURNS[REF Patch]
Make a polygon for each edge and tile it, this causes shading as seen from outside
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: NATIF 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 ~ {
PROC[ context: Context, patch: REF Patch, data: REF ANYNIL ] RETURNS[REF Patch]
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] ~ {
PROC[ context: Context, patch: REF Patch, data: REF ANYNIL ] 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: ROPENARROW[ GetProp[renderData.props, $RopeMessage ] ];
font: ROPENARROW[ 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] ];
IF context.antiAliasing          -- ensure entire rope represented
THEN shape.screenExtent.max.x ← Real.Round[context.viewPort.w];
context.class.draw2DRope[
context, msg,
[ position.x/context.ndcToPixels.scaleX, 1.0 - position.y/ABS[context.ndcToPixels.scaleY] ],
color, size, font
];
RETURN[patch];
};
Frame Generation and Animation
MakeFrame: PUBLIC ContextProc ~ {
IF Atom.GetPropFromList[context.props, $DisableClear] # NIL
THEN ShowShapes[context]
ELSE MakeTheFrame[context];
};
ShowShapes: PUBLIC ContextProc ~ {
Makes image without clearing frame, for compositing contexts, etc.
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: BOOLTRUE] ~ {
Makes image, clears frame, etc.
Action: PROC ~ {
allLines: BOOLTRUE;
time: REAL ← CurrentTime[];
log: IO.STREAMNARROW[ 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.