G3dClipXfmShadeImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, November 6, 1989 5:26:31 pm PST
Glassner, July 5, 1989 6:32:55 pm PDT
Bloomenthal, June 19, 1989 12:15:25 pm PDT
DIRECTORY Atom, Basics, BasicTime, Convert, G3dMatrix, G3dPlane, G3dRender, G3dScanConvert, G3dClipXfmShade, G3dShape, G3dSortandDisplay, G3dVector, Real, Rope;
G3dClipXfmShadeImpl: CEDAR MONITOR
IMPORTS Atom, Basics, BasicTime, Convert, G3dMatrix, G3dPlane, G3dRender, G3dVector, Real, Rope
EXPORTS G3dClipXfmShade
~ BEGIN
Internal Declarations
Context:    TYPE ~ G3dRender.Context;
RGB:     TYPE ~ G3dRender.RGB;      --  [ r, g, b: REAL];
SixSides:    TYPE ~ G3dRender.SixSides;
ScaleAndAddXfm: TYPE ~ G3dRender.ScaleAndAddXfm;
OutCode:    TYPE ~ G3dRender.OutCode;
Matrix:    TYPE ~ G3dRender.Matrix;
Triple:    TYPE ~ G3dRender.Triple;
TripleSequence:  TYPE ~ G3dRender.TripleSequence;
NatSequence:   TYPE ~ G3dRender.NatSequence;
IntegerSequence:  TYPE ~ G3dRender.IntegerSequence;
RealSequence:  TYPE ~ G3dRender.RealSequence;
Shape:     TYPE ~ G3dRender.Shape;
FacingDir:   TYPE ~ G3dRender.FacingDir;
Patch:     TYPE ~ G3dRender.Patch;
VertexSequence:  TYPE ~ G3dShape.VertexSequence;
PatchSequence:  TYPE ~ G3dRender.PatchSequence;
PatchSequenceRep: TYPE ~ G3dRender.PatchSequenceRep;
CtlPoint:    TYPE ~ G3dRender.CtlPoint;
CtlPointSequence: TYPE ~ G3dRender.CtlPointSequence;
CtlPtInfo:    TYPE ~ G3dRender.CtlPtInfo;
CtlPtInfoSequence: TYPE ~ G3dRender.CtlPtInfoSequence;
CtlPtInfoSequenceRep: TYPE ~ G3dRender.CtlPtInfoSequenceRep;
CtlPtInfoProc:  TYPE ~ G3dRender.CtlPtInfoProc;
Shading:    TYPE ~ G3dRender.Shading;
ShadingSequence: TYPE ~ G3dRender.ShadingSequence;
ShapeClass:   TYPE ~ G3dRender.ShapeClass;
ShadingClass:  TYPE ~ G3dRender.ShadingClass;
ShapeProc:   TYPE ~ G3dRender.ShapeProc;  -- PROC[ Context, Shape ]
LORA:     TYPE ~ LIST OF REF ANY;
NoneOut:    OutCode ~ G3dRender.NoneOut;
AllOut:    OutCode ~ G3dRender.AllOut;
Renamed Procedures
Transform: PROC[p: Triple, mat: Matrix] RETURNS[Triple] ~ G3dMatrix.Transform;
TransformVec: PROC[vec: Triple, mat: Matrix] RETURNS[Triple] ~
                  G3dMatrix.TransformVec;
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
nullTriple: Triple ← [0.0, 0.0, 0.0];
Caching Procedures
allocation avoidance structures - caches of peculiar data types
vertexStore: CtlPtInfoSequence ← NEW[ CtlPtInfoSequenceRep[96] ]; -- CtlPoint Cache
vertexStoreLength: NAT ← 96;
vertexStorePtr: NAT ← 0;        -- place to return next free record
GetCtlPtInfo: PUBLIC ENTRY PROC[] RETURNS[REF CtlPtInfo] ~ {
ENABLE UNWIND => NULL;
vtx: REF CtlPtInfo;
IF vertexStorePtr = 0
THEN vtx ← NEW[CtlPtInfo]
ELSE {
vertexStorePtr ← vertexStorePtr - 1;
vtx ← vertexStore[vertexStorePtr];
vertexStore[vertexStorePtr] ← NIL;
};
RETURN[ vtx ];
};
ReleaseCtlPtInfo: PUBLIC ENTRY PROC[vtx: REF CtlPtInfo] ~ {
ENABLE UNWIND => NULL;
IF vertexStorePtr = vertexStoreLength THEN {
vertexStore ← NEW[ CtlPtInfoSequenceRep[vertexStoreLength + 32] ];
vertexStoreLength ← vertexStoreLength + 32;
vertexStorePtr ← 0;
};
IF vertexStorePtr > 0 AND vertexStore[vertexStorePtr-1] = vtx
THEN SIGNAL G3dRender.Error[$MisMatch, "Double vertexInfo release"]
ELSE vertexStore[vertexStorePtr] ← vtx;
vertexStorePtr ← vertexStorePtr + 1;
};
patchCache: PatchSequence ← NEW[ PatchSequenceRep[16] ]; -- temps for clipping, etc.
patchCacheLength: NAT ← 16;
patchCachePtr: NAT ← 0;        -- place to return next free record
GetPatch: PUBLIC ENTRY PROC[size: NAT] RETURNS[REF Patch] ~ {
ENABLE UNWIND => NULL;
p: REF Patch;
IF patchCachePtr = 0
THEN p ← NEW[Patch[size]]
ELSE {
patchCachePtr ← patchCachePtr - 1;
p ← patchCache[patchCachePtr];
patchCache[patchCachePtr] ← NIL;
IF p.maxLength < size THEN p ← NEW[Patch[size]];
};
RETURN[ p ];
};
ReleasePatch: PUBLIC ENTRY PROC[p: REF Patch] ~ {
ENABLE UNWIND => NULL;
IF p = NIL THEN RETURN[];
IF patchCachePtr = patchCacheLength THEN {
patchCache ← NEW[ PatchSequenceRep[patchCacheLength + 2] ];
patchCacheLength ← patchCacheLength + 2;
patchCachePtr ← 0;
};
IF patchCachePtr > 0 AND patchCache[patchCachePtr-1] = p
THEN SIGNAL G3dRender.Error[$MisMatch, "Double patch release"]
ELSE patchCache[patchCachePtr] ← p;
patchCachePtr ← patchCachePtr + 1;
p ← NIL;
};
Utility Procedures
Sgn: PROCEDURE [number: REAL] RETURNS [INT] ~ INLINE {
IF number < 0. THEN RETURN[-1] ELSE RETURN[1];
};
Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ {
result ← Real.Round[number];
IF result < number THEN result ← result + 1;
};
ElapsedTime: PROC[startTime: REAL] RETURNS[Rope.ROPE] ~ {
timeX10: REAL ← 10.0 * (BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] - startTime);
RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[timeX10] / 10.0 ], " secs. " ] ];
};
CurrentTime: PROC[] RETURNS[REAL] ~ {
RETURN[ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] ];
};
DiffPosns: PROC[vtx1, vtx2: CtlPoint] RETURNS[Triple] ~ {
RETURN[[vtx1.x - vtx2.x, vtx1.y - vtx2.y, vtx1.z - vtx2.z]] };
GetNormal: PROC[ vertices: VertexSequence, poly: NatSequence, cVtx: NAT ]
    RETURNS
[ normal: Triple ] ~ {
lVtx: NAT ← poly[ (cVtx + poly.length - 1) MOD poly.length ];
nVtx: NAT ← poly[ (cVtx + 1) MOD poly.length ];
cVtx ← poly[cVtx];
normal ← G3dVector.Cross[       -- in object space so do right-handed
G3dVector.Sub[ vertices[lVtx].point, vertices[cVtx].point ],
G3dVector.Sub[ vertices[nVtx].point, vertices[cVtx].point ]
];
};
HoldEverything: PROCEDURE [] ~ {
ERROR ABORTED;
};
Procedures for Derived Data for Shapes
GetPolyNmls: PUBLIC PROC[context: Context, shape: Shape] ~ {
Compute Normals, Sum normals at polygon corners to get polygon normal
Sum normals for vertices given by adjacent polygon corners, only for polygons!
xfm: Matrix ← G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm];
IF shape.faces.valid.normal THEN RETURN;  -- don't mess with pre-existing normals
FOR i: NAT IN [0..shape.surfaces.length) DO
sumNmls: Triple ← [0., 0., 0.];
FOR cVtx: NAT IN [0..shape.surfaces[i].vertices.length) DO
sumNmls ← G3dVector.Add[ sumNmls,
        GetNormal[shape.vertices, shape.surfaces[i].vertices, cVtx] ];
ENDLOOP;
shape.faces[i].normal ← G3dVector.Unit[sumNmls];
ENDLOOP;
shape.faces.valid.normal ← TRUE;
};
GetVtxNmls: PUBLIC PROC[context: Context, shape: Shape] ~ {
Sum normals for vertices given by adjacent polygon corners, only for polygons!
xfm: Matrix ← G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm];
IF shape.vertices.valid.normal THEN RETURN;  -- don't mess with pre-existing normals
FOR i: NAT IN [0..shape.surfaces.length) DO -- get normals at vertices, add to earlier ones
IF shape.surfaces[i].vertices # NIL THEN FOR cVtx: NAT IN [0..shape.surfaces[i].vertices.length) DO
OPEN shape.vertices[shape.surfaces[i].vertices[cVtx]]; 
cornerNormal: Triple ← GetNormal[ shape.vertices, shape.surfaces[i].vertices, cVtx ];
normal ← G3dVector.Add[ cornerNormal, normal];  -- assumes normal initally zero
ENDLOOP;
ENDLOOP;
FOR i: NAT IN [0..shape.vertices.length) DO
IF shape.vertices[i] # NIL THEN {
OPEN shape.vertices[i];
IF G3dVector.Length[ normal ] > shape.sphereExtent.radius * .0001
THEN normal ← G3dVector.Unit[ normal ]
ELSE normal ← nullTriple;
};
ENDLOOP;
shape.vertices.valid.normal ← TRUE;
};
Procedures for Clipping
ClipBoundingSphere: PUBLIC PROC[context: Context, shape: Shape, xfm: Matrix]
       RETURNS[G3dRender.ClipState] ~ {
Do gross clip test on bounding sphere, all in or all out can avoid clipping entire object
clipFlag: BOOLEANFALSE;
center: Triple ← shape.sphereExtent.center;
surface: Triple ← G3dVector.Add[center, [shape.sphereExtent.radius, 0.0, 0.0] ];
eyeCtr: Triple ← G3dMatrix.Transform[ center, xfm];
eyeRadius: REAL ← G3dVector.Length[
G3dVector.Sub[ eyeCtr, G3dMatrix.Transform[ surface, xfm] ]
];
FOR plane: SixSides IN SixSides DO
distance: REAL ← G3dPlane.DistanceToPoint[ eyeCtr, context.clippingPlanes[plane] ];
IF distance < -eyeRadius THEN RETURN[out]
ELSE IF distance < eyeRadius THEN clipFlag ← TRUE;
ENDLOOP;
IF clipFlag THEN RETURN[clipped] ELSE RETURN[in];
};
ClipPoly: PUBLIC PROC[ context: Context, poly: REF Patch] RETURNS [REF Patch] ~ {
Clip: PROC[side: SixSides, pIn, pOut: REF Patch] RETURNS [REF Patch, REF Patch] = {
Dist: PROC[side: SixSides, vtx: CtlPoint] RETURNS [REAL] = {  -- + inside, - outside
RETURN[ G3dPlane.DistanceToPoint[
[vtx.ex, vtx.ey, vtx.ez],
context.clippingPlanes[side]
] ];
};
lastDist, dist: REAL; outCnt, last: NAT;
IF pIn.nVtces < 2 THEN RETURN[ pIn, pOut ]; -- return if degenerate (line needs 2)
outCnt ← 0;
IF pIn.type # $PolyLine THEN {
lastDist ← Dist[side, pIn.ctlPt[pIn.nVtces - 1].coord];
last ← pIn.nVtces - 1;
};
IF pOut.maxLength < 2 * pIn.nVtces THEN pOut ← NEW[Patch[2 * pIn.nVtces]];
FOR i: NAT IN [0..pIn.nVtces) DO
a, b: REAL;
dist ← Dist[side, pIn.ctlPt[i].coord];
IF (i # 0 OR pIn.type # $PolyLine) AND lastDist * dist < 0. THEN { 
Put out point if clip plane crossed
b ← dist / (dist - lastDist); a ← 1.0 - b;
pOut.ctlPt[outCnt].coord.x ← pIn.ctlPt[i].coord.x * a + pIn.ctlPt[last].coord.x * b;
pOut.ctlPt[outCnt].coord.y ← pIn.ctlPt[i].coord.y * a + pIn.ctlPt[last].coord.y * b;
pOut.ctlPt[outCnt].coord.z ← pIn.ctlPt[i].coord.z * a + pIn.ctlPt[last].coord.z * b;
pOut.ctlPt[outCnt].coord.ex ← pIn.ctlPt[i].coord.ex * a + pIn.ctlPt[last].coord.ex * b;
pOut.ctlPt[outCnt].coord.ey ← pIn.ctlPt[i].coord.ey * a + pIn.ctlPt[last].coord.ey * b;
pOut.ctlPt[outCnt].coord.ez ← pIn.ctlPt[i].coord.ez * a + pIn.ctlPt[last].coord.ez * b;
pOut.ctlPt[outCnt].shade.exn ← pIn.ctlPt[i].shade.exn*a + pIn.ctlPt[last].shade.exn*b;
pOut.ctlPt[outCnt].shade.eyn ← pIn.ctlPt[i].shade.eyn*a + pIn.ctlPt[last].shade.eyn*b;
pOut.ctlPt[outCnt].shade.ezn ← pIn.ctlPt[i].shade.ezn*a + pIn.ctlPt[last].shade.ezn*b;
pOut.ctlPt[outCnt].shade.r ← pIn.ctlPt[i].shade.r * a + pIn.ctlPt[last].shade.r * b;
pOut.ctlPt[outCnt].shade.g ← pIn.ctlPt[i].shade.g * a + pIn.ctlPt[last].shade.g * b;
pOut.ctlPt[outCnt].shade.b ← pIn.ctlPt[i].shade.b * a + pIn.ctlPt[last].shade.b * b;
pOut.ctlPt[outCnt].shade.t ← pIn.ctlPt[i].shade.t * a + pIn.ctlPt[last].shade.t* b;
pOut.ctlPt[outCnt].shade.txtrX ← pIn.ctlPt[i].shade.txtrX * a
               + pIn.ctlPt[last].shade.txtrX* b;
pOut.ctlPt[outCnt].shade.txtrY ← pIn.ctlPt[i].shade.txtrY * a
               + pIn.ctlPt[last].shade.txtrY* b;
pOut.ctlPt[outCnt].shade.er ← pIn.ctlPt[i].shade.er * a + pIn.ctlPt[last].shade.er * b;
pOut.ctlPt[outCnt].shade.eg ← pIn.ctlPt[i].shade.eg * a + pIn.ctlPt[last].shade.eg * b;
pOut.ctlPt[outCnt].shade.eb ← pIn.ctlPt[i].shade.eb * a + pIn.ctlPt[last].shade.eb * b;
pOut.ctlPt[outCnt].shade.et ← pIn.ctlPt[i].shade.et * a + pIn.ctlPt[last].shade.et * b;
outCnt ← outCnt + 1;
};
IF dist >= 0. THEN {      -- put out point if inside
pOut.ctlPt[outCnt].coord.x ← pIn.ctlPt[i].coord.x;
pOut.ctlPt[outCnt].coord.y ← pIn.ctlPt[i].coord.y;
pOut.ctlPt[outCnt].coord.z ← pIn.ctlPt[i].coord.z;
pOut.ctlPt[outCnt].coord.ex ← pIn.ctlPt[i].coord.ex;
pOut.ctlPt[outCnt].coord.ey ← pIn.ctlPt[i].coord.ey;
pOut.ctlPt[outCnt].coord.ez ← pIn.ctlPt[i].coord.ez;
pOut.ctlPt[outCnt].shade.exn ← pIn.ctlPt[i].shade.exn;
pOut.ctlPt[outCnt].shade.eyn ← pIn.ctlPt[i].shade.eyn;
pOut.ctlPt[outCnt].shade.ezn ← pIn.ctlPt[i].shade.ezn;
pOut.ctlPt[outCnt].shade.r ← pIn.ctlPt[i].shade.r;
pOut.ctlPt[outCnt].shade.g ← pIn.ctlPt[i].shade.g;
pOut.ctlPt[outCnt].shade.b ← pIn.ctlPt[i].shade.b;
pOut.ctlPt[outCnt].shade.t ← pIn.ctlPt[i].shade.t;
pOut.ctlPt[outCnt].shade.txtrX ← pIn.ctlPt[i].shade.txtrX;
pOut.ctlPt[outCnt].shade.txtrY ← pIn.ctlPt[i].shade.txtrY;
pOut.ctlPt[outCnt].shade.er ← pIn.ctlPt[i].shade.er;
pOut.ctlPt[outCnt].shade.eg ← pIn.ctlPt[i].shade.eg;
pOut.ctlPt[outCnt].shade.eb ← pIn.ctlPt[i].shade.eb;
pOut.ctlPt[outCnt].shade.et ← pIn.ctlPt[i].shade.et;
outCnt ← outCnt + 1;
};
lastDist ← dist; last ← i;
ENDLOOP;
pOut.type ← pIn.type;
pOut.oneSided ← pIn.oneSided;
pOut.dir ← pIn.dir;
pOut.nVtces ← outCnt;
pOut.renderData ← pIn.renderData;
pOut.props ← pIn.props;
RETURN [ pOut, pIn ];
}; -- end Clip Proc
orOfCodes: OutCode ← NoneOut;
poly2: REF Patch ← GetPatch[2 * poly.nVtces];  -- get temp patch, released below
IF poly.type # $ConvexPolygon AND poly.type # $Poly AND poly.type # $PolyLine THEN {
SIGNAL G3dRender.Error[$Unimplemented, "Not clippable as polygon"];
RETURN[ NIL ];
};
FOR i: NAT IN [0..poly.nVtces) DO
orOfCodes ← LOOPHOLE[
Basics.BITOR[LOOPHOLE[orOfCodes], LOOPHOLE[poly.ctlPt[i].coord.clip]],
OutCode];
ENDLOOP;
IF orOfCodes.near THEN [poly, poly2] ← Clip[ Near, poly, poly2];
IF orOfCodes.far THEN [poly, poly2] ← Clip[ Far, poly, poly2];
IF orOfCodes.left THEN [poly, poly2] ← Clip[ Left, poly, poly2];
IF orOfCodes.right THEN [poly, poly2] ← Clip[ Right, poly, poly2];
IF orOfCodes.bottom THEN [poly, poly2] ← Clip[Bottom, poly, poly2];
IF orOfCodes.top THEN [poly, poly2] ← Clip[ Top, poly, poly2];
ReleasePatch[poly2];        -- done with temp patch
RETURN[ poly ];
};
GetPatchClipState: PUBLIC PROC[ patch: REF Patch] ~ {
orOfCodes: OutCode ← NoneOut;
andOfCodes: OutCode ← AllOut;
FOR i: NAT IN [0..patch.nVtces) DO
orOfCodes ← LOOPHOLE[
Basics.BITOR[ LOOPHOLE[orOfCodes], LOOPHOLE[patch[i].coord.clip]],
OutCode];
andOfCodes ← LOOPHOLE[
Basics.BITAND[LOOPHOLE[andOfCodes], LOOPHOLE[patch[i].coord.clip]],
OutCode];
ENDLOOP;
IF andOfCodes # NoneOut
THEN patch.clipState ← out
ELSE IF orOfCodes = NoneOut
THEN patch.clipState ← in
ELSE patch.clipState ← clipped;
};
GetClipCodeForPt: PUBLIC PROC[context: Context, pt: Triple] RETURNS[clip: OutCode] ~ {
Compute outcode for one set of coordinates in eyespace
clip.bottom← G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Bottom]] < 0.;
clip.top ← G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Top] ] < 0.;
clip.left ← G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Left] ] < 0.;
clip.right ← G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Right] ] < 0.;
clip.near ← G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Near] ] < 0.;
clip.far ← G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Far] ] < 0.;
};
Procedures for Transformations
XfmPtToEyeSpace: PUBLIC PROC[context: Context, pt: Triple, xfm: Matrix ← NIL]
      RETURNS[Triple, OutCode] ~ {
 Transform CtlPoint to Eye Space
IF xfm = NIL THEN xfm ← context.eyeSpaceXfm;
pt ← Transform[ pt, xfm ];
RETURN[ pt, GetClipCodeForPt[context, pt] ];
};
XfmTripleToDisplay: PUBLIC PROC[pt: Triple, xfm, display: ScaleAndAddXfm, offset: REAL]
       RETURNS[result: Triple] ~ {
 Transform vertex from eyespace to display coordinates - local utility
aLilBit: REAL ← G3dScanConvert.justNoticeable * G3dScanConvert.justNoticeable;
IF pt.z <= 0.0 THEN {
SIGNAL G3dRender.Error[$MisMatch, "Negative depth"];
pt.z ← .001;  -- fudge bad depths
};
result.x ← xfm.scaleX*pt.x / pt.z + xfm.addX;    -- convert to unit display coords
result.y ← xfm.scaleY*pt.y / pt.z + xfm.addY;
result.z ← (xfm.scaleZ*pt.z + xfm.addZ) / pt.z;
result.x ← display.scaleX * result.x + display.addX;  -- convert to screen coordinates
result.y ← display.scaleY * result.y + display.addY;
result.z ← display.scaleZ * result.z + display.addZ;
result.x ← result.x - offset;  -- nojaggy tiler offsets by 1/2 pixel and spreads out by 1
result.y ← result.y - offset;
RETURN [ result ];
};
XfmPtToDisplay: PUBLIC PROC[context: Context, pt: Triple,
          shape: Shape ← NIL]
       RETURNS[Triple] ~ {
 Transform vertex from eyespace to display coordinates
result: Triple ← XfmTripleToDisplay[
pt: pt,
xfm: context.eyeToNdc,
display: context.ndcToPixels,
offset: IF context.antiAliasing THEN .5 ELSE 0.0   -- nojaggy tiler offsets by 1/2 pixel
];
IF shape # NIL THEN {
xLo: INTEGER ← Real.Fix[ MAX[0.0, result.x - 2.0] ];
xHi: INTEGER ← Ceiling[ MIN[context.viewPort.w, result.x + 2.0] ];
yLo: INTEGER ← Real.Fix[ MAX[0.0, result.y - 2.0] ];
yHi: INTEGER ← Ceiling[ MIN[context.viewPort.h, result.y + 2.0] ];
IF shape.screenExtent.min.x > xLo THEN shape.screenExtent.min.x ← xLo;
IF shape.screenExtent.max.x < xHi THEN shape.screenExtent.max.x ← xHi;
IF shape.screenExtent.min.y > yLo THEN shape.screenExtent.min.y ← yLo;
IF shape.screenExtent.max.y < yHi THEN shape.screenExtent.max.y ← yHi;
};
RETURN [ result ];
};
Procedures for Shading Patches
BackFacing: PUBLIC PROC[ context: Context, poly: REF Patch,
        useEyeSpace: BOOLEANFALSE ]
    RETURNS [FacingDir] ~ {
Assumes left-handed space (eye space or screen space)
SELECT poly.type FROM
$ConvexPolygon, $Poly  => IF useEyeSpace
THEN {
Backfacing test based on first vertex and adjacent vertices
this: CtlPtInfo ← poly.ctlPt[0];
next: CtlPtInfo ← poly.ctlPt[1];
last: CtlPtInfo ← poly.ctlPt[2];
last: CtlPtInfo ← poly.ctlPt[poly.nVtces - 1];
direction: Triple ← G3dVector.Unit[G3dVector.Cross[
[next.coord.ex - this.coord.ex, next.coord.ey - this.coord.ey, next.coord.ez - this.coord.ez],
[last.coord.ex - this.coord.ex, last.coord.ey - this.coord.ey, last.coord.ez - this.coord.ez] ] ];
dotDir: REAL ← G3dVector.Dot[[this.coord.ex, this.coord.ey, this.coord.ez] , direction];
IF dotDir > 0.0 THEN RETURN[back] ELSE IF dotDir < 0.0 THEN RETURN[front];
}
ELSE {      -- do check in image space, rejecting eensy polygon edges
s: REAL ← 1.0 / G3dScanConvert.justNoticeable;  -- scales visible feature size to 1
FOR i: NAT IN [0..poly.nVtces) DO
this: CtlPtInfo ← poly.ctlPt[i];
next: CtlPtInfo ← poly.ctlPt[(i+1) MOD poly.nVtces];
last: CtlPtInfo ← poly.ctlPt[(i+poly.nVtces-1) MOD poly.nVtces];
zNorm: INT ←   -- integer computation of z-coord of normal (left-handed)
  ( Real.Fix[s*next.coord.sx - s*this.coord.sx] )
* ( Real.Fix[s*last.coord.sy - s*this.coord.sy] )
- ( Real.Fix[s*next.coord.sy - s*this.coord.sy] )
* ( Real.Fix[s*last.coord.sx - s*this.coord.sx] );
zNorm ← zNorm *Sgn[context.ndcToPixels.scaleY] *Sgn[context.ndcToPixels.scaleX];
Adjusts for flip in screen-space transform (ndcToPixels) in y and/or x
IF zNorm > 0 THEN RETURN[ back ] ELSE IF zNorm < 0 THEN RETURN[ front ];
ENDLOOP;
SIGNAL G3dRender.Error[$Condition, "Edges too small for stable arithmetic"];
};
$Bezier    => { 
Backfacing test based on convex hull being hidden behind its base
SIGNAL G3dRender.Error[$Unimplemented, "Backfacing for patches not done"];
};
ENDCASE => SIGNAL G3dRender.Error[$Unimplemented, "Unknown type"];
RETURN[ unknown ];
};
ShadePoly: PUBLIC PROC[ context: Context, poly: REF Patch] ~ {
 Cache shininess to get vertex shades without highlights (yuk)
tmpShininess: REAL ← poly.renderData.shadingClass.shininess;
poly.renderData.shadingClass.shininess ← 0.0;
IF poly.renderData.shadingClass.renderMethod # $HiddenLines THEN FOR i: NAT IN [0..poly.nVtces) DO
IF poly.renderData.shadingClass.renderMethod = $Faceted THEN { -- nmls from vtx coords
lVtx: NAT ← (i + poly.nVtces - 1) MOD poly.nVtces;
nVtx: NAT ← (i + 1) MOD poly.nVtces;
[[ poly[i].shade.exn, poly[i].shade.eyn, poly[i].shade.ezn ]] ← G3dVector.Cross[
[ poly[nVtx].coord.ex - poly[i].coord.ex,    -- in eyespace, so do left-handed
poly[nVtx].coord.ey - poly[i].coord.ey,
poly[nVtx].coord.ez - poly[i].coord.ez ],
[ poly[lVtx].coord.ex - poly[i].coord.ex,
poly[lVtx].coord.ey - poly[i].coord.ey,
poly[lVtx].coord.ez - poly[i].coord.ez ]
];
};
poly[i] ← poly.renderData.shadingClass.shadeVtx[
context, poly[i], poly.renderData.shadingClass
];
ENDLOOP;
poly.renderData.shadingClass.shininess ← tmpShininess; -- restore shininess (yuk)
};
END.