RenderWithPixelsImpl.mesa
Last Edited by: Crow, April 4, 1989 9:56:23 am PDT
Glassner, March 14, 1989 2:18:34 pm PST
Bloomenthal, October 28, 1988 5:05:48 pm PDT
DIRECTORY
Atom      USING [ PropList, GetPropFromList, PutPropOnList,
          RemPropFromList ],
Basics      USING [ BITAND, BITOR, BITSHIFT, BytePair, LowByte,
          HighByte ],
Real      USING [ Fix, Float, InlineRoundI, Round, LargestNumber ],
RealFns     USING [ SqRt, Power],
Rope      USING [ ROPE ],
SF       USING [ Displace, InlineSize, Vec ],
Imager     USING [ Context, Font, SetColor, SetFont, SetXY, ShowRope ],
ImagerColor    USING [ ColorFromRGB ],
ImagerFont    USING [ Find, Scale ],
ImagerSmoothContext USING [ Create, SetOutputBuffer ],
ImagerFullColorContext USING [ Create, SetPixelMap ],
ImagerDitherContext USING [ Create, SetSampleMap ],
ImagerGrayContext  USING [ Create, SetSampleMap ],
ImagerPixel    USING [ NewPixelMap, GetPixels, NewPixels, ObtainScratchPixels,
          PixelBuffer, PixelProc, PutPixels, ReleaseScratchPixels ],
ImagerSample   USING [ BasicTransfer, Fill, Transfer ],
G3dVector    USING [ Normalize, Mul, Add ],
AISAnimation   USING [ GetAIS ],
ScanConvert    USING [ FastFlatTiler, HiliteTiler, justNoticeable, LerpTiler, PutLine],
ThreeDBasics   USING [ Box, BoxFromRectangle, ClipState, Context, ContextClass,
          ContextProc, Create, Error, GetDisplayType, IntegerPair,
          ImagerProc, IntegerPairSequence, NatRGB, NatRGBSequence,
          NoneOut, OutCode, Pair, PairSequence, Patch, PatchProc,
          Pixel, Quad, RealSequence, Rectangle, RegisterDisplayType,
          RGB, ShadingClass, ShadingValue, ShapeClass,
          ShapeInstance, Spot, SpotProc, TextureFunction, TextureMap,
          Triple, Vertex, VertexInfo, VertexInfoProc, VtxToRealSeqProc
          ],
ShapeUtilities   USING [ GetClipCodeForPt, XfmPtToDisplay ],
SurfaceRender   USING [ MakeFrame, ShowShapes, ValidateContext ],
MappedAndSolidTexture USING [ AdjustTexture ],
RenderWithPixels  USING [ LerpVtx, LerpVtxSequence, FancyPatch, RopeDesc ];
RenderWithPixelsImpl: CEDAR MONITOR
IMPORTS AISAnimation, Atom, Basics, Imager, ImagerColor, ImagerDitherContext, ImagerFont, ImagerFullColorContext, ImagerGrayContext, ImagerPixel, ImagerSample, ImagerSmoothContext, Real, RealFns, ScanConvert, SF, ShapeUtilities, SurfaceRender, MappedAndSolidTexture, ThreeDBasics, G3dVector
EXPORTS RenderWithPixels
= BEGIN
Types
LORA: TYPE ~ LIST OF REF ANY;
PixelBuffer: TYPE ~ ImagerPixel.PixelBuffer;
ImagerProc: TYPE ~ ThreeDBasics.ImagerProc;
Context: TYPE ~ ThreeDBasics.Context;
ContextProc: TYPE ~ ThreeDBasics.ContextProc;
ContextClass: TYPE ~ ThreeDBasics.ContextClass;
ShadingClass: TYPE ~ ThreeDBasics.ShadingClass;
ShapeClass: TYPE ~ ThreeDBasics.ShapeClass;
RGB: TYPE ~ ThreeDBasics.RGB;
NatRGB: TYPE ~ ThreeDBasics.NatRGB;
NatRGBSequence: TYPE ~ ThreeDBasics.NatRGBSequence;
Pixel: TYPE ~ ThreeDBasics.Pixel;
OutCode: TYPE ~ ThreeDBasics.OutCode;
NoneOut: OutCode ~ ThreeDBasics.NoneOut;
Pair: TYPE ~ ThreeDBasics.Pair;          -- RECORD [ x, y: REAL];
PairSequence: TYPE ~ ThreeDBasics.PairSequence;
IntegerPair: TYPE ~ ThreeDBasics.IntegerPair;
IntegerPairSequence: TYPE ~ ThreeDBasics.IntegerPairSequence;
Triple: TYPE ~ ThreeDBasics.Triple;        -- RECORD [ x, y, z: REAL];
Quad: TYPE ~ ThreeDBasics.Quad;
Box: TYPE ~ ThreeDBasics.Box;
Patch: TYPE ~ ThreeDBasics.Patch;
PatchProc: TYPE ~ ThreeDBasics.PatchProc;
ColorPrimary: TYPE ~ { red, green, blue, gray };
BooleanSequence: TYPE ~ RECORD [ length: NAT, s: SEQUENCE maxLength: NAT OF BOOLEAN ];
RealSequence: TYPE ~ ThreeDBasics.RealSequence;
TextureMap: TYPE ~ ThreeDBasics.TextureMap;
TextureFunction: TYPE ~ ThreeDBasics.TextureFunction;
Vertex: TYPE ~ ThreeDBasics.Vertex;
VertexInfo: TYPE ~ ThreeDBasics.VertexInfo;
ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance;
Spot: TYPE ~ ThreeDBasics.Spot;
RECORD[ coverage: REAL, mask: WORD, txtrTransmittance: REAL, class: REF ShadingClass,
   val, yIncr, xIncr: REF RealSequence, props: Atom.PropList ]
LerpVtx: TYPE ~ RenderWithPixels.LerpVtx;
RECORD [x, y: REAL, val: REF RealSequence];
LerpVtxSequence: TYPE ~ RenderWithPixels.LerpVtxSequence;
RECORD [ length: NAT, s: SEQUENCE maxLength: NAT OF REF LerpVtx ];
FancyPatch: TYPE ~ RenderWithPixels.FancyPatch;
RECORD [ recurseLevel: NAT ← 0, shadingClass: REF ShadingClass,
vtx: SEQUENCE length: NAT OF LerpVtx ];
RopeDesc: TYPE ~ RenderWithPixels.RopeDesc;
RECORD[rope: ROPE, position: Pair, color: Pixel, size: REAL, font: ROPE];
Data Structure for trapezoid edges
EdgeBlock: TYPE ~ RECORD [
moreVertical: BOOLEAN, start, end: REAL,
x, y, xIncr, yIncr: REAL,
val, incr: REF RealSequence
];
ScanSegment: TYPE ~ RECORD [
start, end: REAL,
coverage, cvrgIncr: REAL,
lMask, rMask: CARDINAL,
val, yIncr, xIncrVal, xIncrForY: REF RealSequence
];
ScanSegSequence: TYPE ~ RECORD [
length: NAT,
segs: SEQUENCE maxLength: NAT OF REF ScanSegment
];
HilitSeqs: TYPE ~ RECORD [   -- reflection vectors and light source flags for hilites
refls: REF IntegerPairSequence,
flags: REF BooleanSequence
];
HilitSeqsSequence: TYPE ~ RECORD [
length: NAT,
s: SEQUENCE maxLength: NAT OF REF HilitSeqs
];
Global Constants
tblLngth: NAT ~ 256;
justNoticeable: REAL ~ ScanConvert.justNoticeable;         -- 0.02
Global Variables
statistics: BOOLEANFALSE;
polyCount: INT ← 0;
recurseLimit: NAT ← 16;       -- limits recursion in fancy tiler
depthSlop: INT16 ← 300;    -- limit of indeterminate depth comparisons
noHighLights: BOOLEANFALSE;
weight: ARRAY [0..tblLngth] OF REAL;   -- filter table
allocation avoidance structures - caches of peculiar data types
scanSegCache: REF ScanSegSequence ← NEW[ ScanSegSequence[2] ]; -- for ShowSteepTrap
scanSegCacheLength: NAT ← 2;
scanSegCachePtr: NAT ← 0;
hilitSeqCache: REF HilitSeqsSequence ← NEW[ HilitSeqsSequence[2] ]; -- for hilites
hilitSeqCacheLength: NAT ← 2;
hilitSeqCachePtr: NAT ← 0;
vertexCache: REF LerpVtxSequence ← NEW[ LerpVtxSequence[2] ]; -- for fancy tiler
vertexCacheLength: NAT ← 2;
vertexCachePtr: NAT ← 0;
spotCacheSize: NAT ~ 4;
spotCacheArray: ARRAY [0..spotCacheSize) OF REF Spot ← ALL[NIL];
Caching Procedures
GetScanSeg: ENTRY PROC[size: NAT] RETURNS[REF ScanSegment] ~ {
ENABLE UNWIND => NULL;
seg: REF ScanSegment;
IF scanSegCachePtr = 0
THEN seg ← NEW[ ScanSegment ]
ELSE {
scanSegCachePtr ← scanSegCachePtr - 1;
seg ← scanSegCache[scanSegCachePtr];
scanSegCache[scanSegCachePtr] ← NIL;
};
IF seg.val = NIL OR seg.val.maxLength < size THEN {
seg.val ← NEW[ RealSequence[size] ];
seg.yIncr ← NEW[ RealSequence[size] ];
seg.xIncrVal ← NEW[ RealSequence[size] ];
seg.xIncrForY ← NEW[ RealSequence[size] ];
};
RETURN[ seg ];
};
ReleaseScanSeg: ENTRY PROC[s: REF ScanSegment] ~ {
ENABLE UNWIND => NULL;
IF scanSegCachePtr = scanSegCacheLength THEN {
scanSegCache ← NEW[ ScanSegSequence[scanSegCacheLength + 2] ];
scanSegCacheLength ← scanSegCacheLength + 2;
scanSegCachePtr ← 0;
};
scanSegCache[scanSegCachePtr] ← s;
scanSegCachePtr ← scanSegCachePtr + 1;
};
GetHilitSeqs: ENTRY PROC[reflSize, flagSize: NAT]
     RETURNS[REF HilitSeqs] ~ {
ENABLE UNWIND => NULL;
s: REF HilitSeqs;
IF hilitSeqCachePtr = 0
THEN {
s ← NEW[ HilitSeqs ];
s.refls ← NEW[ IntegerPairSequence[reflSize] ];
s.flags ← NEW[ BooleanSequence[flagSize] ];
}
ELSE {
hilitSeqCachePtr ← hilitSeqCachePtr - 1;
s ← hilitSeqCache[hilitSeqCachePtr];
hilitSeqCache[hilitSeqCachePtr] ← NIL;
IF s.refls.maxLength < reflSize THEN s.refls ← NEW[ IntegerPairSequence[reflSize] ];
IF s.flags.maxLength < flagSize THEN s.flags ← NEW[ BooleanSequence[flagSize] ];
};
FOR i: NAT IN [0..flagSize) DO s.flags[i] ← FALSE; ENDLOOP;
RETURN[ s ];
};
ReleaseHilitSeqs: ENTRY PROC[s: REF HilitSeqs] ~ {
ENABLE UNWIND => NULL;
IF hilitSeqCachePtr = hilitSeqCacheLength THEN {
hilitSeqCache ← NEW[ HilitSeqsSequence[hilitSeqCacheLength + 2] ];
hilitSeqCacheLength ← hilitSeqCacheLength + 2;
hilitSeqCachePtr ← 0;
};
hilitSeqCache[hilitSeqCachePtr] ← s;
hilitSeqCachePtr ← hilitSeqCachePtr + 1;
};
GetVertex: ENTRY PROC[size: NAT] RETURNS[REF LerpVtx] ~ {
ENABLE UNWIND => NULL;
vtx: REF LerpVtx;
IF vertexCachePtr = 0
THEN vtx ← NEW[ LerpVtx ]
ELSE {
vertexCachePtr ← vertexCachePtr - 1;
vtx ← vertexCache[vertexCachePtr];
vertexCache[vertexCachePtr] ← NIL;
};
IF vtx.val = NIL OR vtx.val.maxLength < size THEN vtx.val ← NEW[ RealSequence[size] ];
RETURN[ vtx ];
};
ReleaseVertex: ENTRY PROC[vtx: REF LerpVtx] ~ {
ENABLE UNWIND => NULL;
IF vertexCachePtr = vertexCacheLength THEN {
vertexCache ← NEW[ LerpVtxSequence[vertexCacheLength + 2] ];
vertexCacheLength ← vertexCacheLength + 2;
vertexCachePtr ← 0;
};
vertexCache[vertexCachePtr] ← vtx;
vertexCachePtr ← vertexCachePtr + 1;
};
GetSpot: ENTRY PROC[] RETURNS[REF Spot] ~ {
ENABLE UNWIND => NULL;
spot: REF Spot ← NIL;
FOR i: NAT IN [0..spotCacheSize) DO IF spotCacheArray[i] # NIL
THEN { spot ← spotCacheArray[i]; spotCacheArray[i] ← NIL; RETURN[spot]; };
ENDLOOP;
RETURN[ NEW[Spot] ];
};
ReleaseSpot: ENTRY PROC[spot: REF Spot] ~ {
ENABLE UNWIND => NULL;
place: NAT ← 0;
FOR i: NAT IN [0..spotCacheSize) DO
IF spotCacheArray[i] = NIL
THEN place ← i
ELSE IF spotCacheArray[i] = spot
THEN SIGNAL ThreeDBasics.Error[[$Fatal, "Multiple Spot release"]];
ENDLOOP;
spotCacheArray[place] ← spot;
};
Utility procedures
HoldEverything: PROCEDURE [] ~ {
ERROR ABORTED;
};
Sqr: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { RETURN[number * number]; };
Sgn: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE {
IF number < 0. THEN RETURN[-1.] ELSE RETURN[1.];
};
Ceiling: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ {
out ← Real.Round[in];
IF Real.Float[out] < in THEN out ← out + 1;
};
Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ {
out ← Real.Round[in];
IF Real.Float[out] > in THEN out ← out - 1;
};
DupLerpVtx: PROC[vtx: LerpVtx] RETURNS[newVtx: LerpVtx] ~ {
newVtx.x ← vtx.x;
newVtx.y ← vtx.y;
newVtx.val ← NEW[RealSequence[vtx.val.length]];
FOR i: NAT IN [0..vtx.val.length) DO newVtx.val[i] ← vtx.val[i] ENDLOOP;
newVtx.val.length ← vtx.val.length;
};
DupEdgeBlock: PROC[srce: EdgeBlock] RETURNS[dest : EdgeBlock] ~ {
dest.moreVertical ← srce.moreVertical;
dest.start ← srce.start; dest.end ← srce.end;
dest.x ← srce.x; dest.xIncr ← srce.xIncr;
dest.y ← srce.y; dest.yIncr ← srce.yIncr;
dest.val ← NEW[RealSequence[srce.val.length]]; dest.val.length ← srce.val.length;
FOR i: NAT IN [0..srce.val.length) DO dest.val[i] ← srce.val[i] ENDLOOP;
dest.incr ← NEW[RealSequence[srce.incr.length]]; dest.incr.length ← srce.incr.length;
FOR i: NAT IN [0..srce.incr.length) DO dest.incr[i] ← srce.incr[i] ENDLOOP;
};
GetLerpedVals: ThreeDBasics.VtxToRealSeqProc ~ {
PROC[dest: REF RealSequence, source: VertexInfo, data: REF ANY] RTRNS[REF RealSequence];
length: NATIF data = $PixelShading THEN 11 ELSE 5;
IF dest = NIL OR dest.maxLength < length THEN dest ← NEW[RealSequence[length]];
IF data = $PixelShading
THEN {        -- shade will be computed anew at each pixel
dest[0] ← source.shade.r;
dest[1] ← source.shade.g;
dest[2] ← source.shade.b;
dest[3] ← source.shade.t;
dest[4] ← source.shade.exn;
dest[5]  ← source.shade.eyn;
dest[6] ← source.shade.ezn;
dest[7] ← source.coord.ex;
dest[8] ← source.coord.ey;
dest[9] ← source.coord.ez;
dest[10] ← source.coord.sz;   -- for depth buffering
}
ELSE {      -- Shade will only be multiplied or interpolated
dest[0] ← source.shade.er;
dest[1] ← source.shade.eg;
dest[2] ← source.shade.eb;
dest[3] ← source.shade.et;
dest[4] ← source.coord.sz;   -- for depth buffering
};
dest.length ← length;
RETURN [dest];
};
ValueFromRGB: PROC[context: REF Context, red, green, blue: REAL] RETURNS[NAT] ~ {
SELECT context.class.displayType FROM
$PseudoColor => RETURN[     -- standard PseudoColor colormap
Real.Fix[red*5.999]*42 + Real.Fix[green*6.999]*6 + Real.Fix[blue*5.999] + 2
];
$Gray   => RETURN[ Real.Round[255.0 * (red + green + blue) / 3.0] ];
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Unknown display type"]];
RETURN[0];    -- error return
};
PairToScreen: PROC[context: REF Context, p: Pair] RETURNS[ip: IntegerPair] ~ {
ip.x ← Real.Round[ context.ndcToPixels.scaleX * p.x + context.ndcToPixels.addX ];
ip.y ← Real.Round[ context.ndcToPixels.scaleY * p.y + context.ndcToPixels.addY ];
};
GetImagerCtx: PROC[context: REF Context] RETURNS[imagerCtx: Imager.Context] ~ {
IF context.antiAliasing
THEN {
imagerCtx ← ImagerSmoothContext.Create[SF.InlineSize[context.pixels.box]];
SELECT context.class.displayType FROM
$FullColor => ImagerSmoothContext.SetOutputBuffer[
imagerCtx, context.pixels, LIST[$Red, $Green, $Blue, $Alpha] ];
$Gray => ImagerSmoothContext.SetOutputBuffer[
imagerCtx, context.pixels, LIST[$Intensity, $Alpha] ];
ENDCASE => SIGNAL ThreeDBasics.Error[[$Mismatch, "Bad display type"]];
}
ELSE {
SELECT context.class.displayType FROM
$FullColor => {
imagerCtx ← ImagerFullColorContext.Create[
deviceSpaceSize: SF.InlineSize[context.pixels.box],
scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72, 72]
];
ImagerFullColorContext.SetPixelMap[ imagerCtx, context.pixels ];
};
$PseudoColor => {
imagerCtx ← ImagerDitherContext.Create[
deviceSpaceSize: SF.InlineSize[context.pixels.box],
scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72, 72]
];
ImagerDitherContext.SetSampleMap[ imagerCtx, context.pixels[0] ];
};
$Gray => {
imagerCtx ← ImagerGrayContext.Create[
deviceSpaceSize: SF.InlineSize[context.pixels.box],
scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72, 72]
];
ImagerGrayContext.SetSampleMap[ imagerCtx, context.pixels[0] ];
};
ENDCASE => SIGNAL ThreeDBasics.Error[[$Mismatch, "Bad display type"]];
};
context.props ← Atom.PutPropOnList[context.props, $ImagerCtx, imagerCtx];
};
Initializing and Updating Pixel Maps
Init: PROC[] ~ {       -- For Pixels in VM, no display
contextClass: ContextClass ← [
displayType: $FullColor,
setUpDisplayType: AllocatePixelMemory,
validateDisplay: DummyValidateDisplay,
render: MakeFrame,
loadBackground: FillInBackGround,
draw2DLine: Draw2DLine,
draw2DPolygon: Draw2DPoly,
draw2DRope: DrawRope,
displayPolygon: PolygonTiler 
];
ThreeDBasics.RegisterDisplayType[contextClass, $FullColor];
ThreeDBasics.RegisterDisplayType[contextClass, $FullColorInVM];
contextClass.displayType ← $PseudoColor;   -- set different fields for PseudoColor
ThreeDBasics.RegisterDisplayType[contextClass, $PseudoColor];
ThreeDBasics.RegisterDisplayType[contextClass, $PseudoColorInVM];
contextClass.displayType ← $Gray;       -- set different fields for Gray
ThreeDBasics.RegisterDisplayType[contextClass, $Gray];
ThreeDBasics.RegisterDisplayType[contextClass, $GrayInVM];
Calculates the integral over the left half of a pyramid function, equal to the left half of a parabolic window or B-spline basis function
FOR i: NAT IN [0..tblLngth/2] DO
t: REAL ← i * 1.0 / (tblLngth/2);
weight[i]     ← Sqr[t] / 2.;
weight[i + tblLngth/2] ← 1. - Sqr[1. - t] / 2.;
ENDLOOP;
Calculate a table mapping 0-255 into 0.0-1.0
FOR i: NAT IN [0..256) DO realFromByte[i] ← REAL[i] / 255.0; ENDLOOP;
Calculate a table for population count over 0-255
FOR i: NAT IN [0..256) DO
total: NAT ← 0;
FOR j: NAT IN [0..8) DO
IF Basics.BITAND[ i, Basics.BITSHIFT[1, j] ] # 0 THEN total ← total + 1;
ENDLOOP;
populationCount[i] ← total;
ENDLOOP;
};
AllocatePixelMemory: PUBLIC ThreeDBasics.ContextProc ~ {
PixelCount: ImagerPixel.PixelProc ~ {
PROC [i: NAT] RETURNS [Sample]
IF i < samplesPerColor
THEN RETURN[255]      -- red, green, and blue have 8 bits
ELSE RETURN[LAST[WORD]];   -- alpha and depth have 16 bits
};
samplesPerColor, samplesPerPixel: NATIF
context.class.displayType = $FullColor THEN 3 ELSE 1;
box: Box ← IF context.viewPort # NIL
THEN ThreeDBasics.BoxFromRectangle[context.viewPort^]   -- taken from viewport
ELSE [[0, 0], [1023, 767]];         -- default to 1024 x 768 display
context.displayProps ← NIL;      -- remove any lingering props
IF context.antiAliasing THEN {       -- set up alpha buffer (before depth)
context.displayProps ← Atom.PutPropOnList[ context.displayProps, $Alpha,
             NEW[NAT ← samplesPerPixel] ];
samplesPerPixel ← samplesPerPixel + 1;
};
IF context.depthBuffering THEN {      -- set up depth buffer
context.displayProps ← Atom.PutPropOnList[ context.displayProps, $Depth,
             NEW[NAT ← samplesPerPixel] ];
samplesPerPixel ← samplesPerPixel + 1;
};
IF Atom.GetPropFromList[context.props, $NormalBuffer] # NIL THEN { 
context.displayProps ← Atom.PutPropOnList[ context.displayProps, $NormalBuffer,
             NEW[NAT ← samplesPerPixel] ];
samplesPerPixel ← samplesPerPixel + 3;
};
Pixel maps all start at [0,0]
box ← [ min: [f: 0, s: 0], max: [f: box.max.f - box.min.f, s: box.max.s - box.min.s] ];
context.pixels ← NIL;  -- No pixel memory needed if viewer can be used directly
IF samplesPerPixel > 1 OR context.doVisibly OR context.viewer = NIL THEN {
context.pixels ← ImagerPixel.NewPixelMap[samplesPerPixel, box, PixelCount];
context.displayProps ← Atom.PutPropOnList[ context.displayProps, $FullDisplayMemory,
             context.pixels ]; -- for clipping etc.
};
};
GetContext: PUBLIC PROC [type: ATOM, width, height: NAT] RETURNS[ctx: REF Context] ~ {
gets a bare context for making images independent of color display
vmType: ATOMSELECT type FROM
$FullColor => $FullColorInVM,
$PseudoColor => $PseudoColorInVM,
$Gray   => $GrayInVM,
ENDCASE  => NIL;
class: ContextClass ← ThreeDBasics.GetDisplayType[vmType];
ctx ← ThreeDBasics.Create[];
ctx.class ← NEW[ ContextClass ← class ];
ctx.viewPort ← NEW[
ThreeDBasics.Rectangle ← [ x: 0.0, y: 0.0, w: Real.Float[width], h: Real.Float[height] ]
];
AllocatePixelMemory[ctx];     -- get display memory
};
DummyValidateDisplay: ContextProc ~{   -- update viewPort, etc., (placeholder)
};
DepthBuffering: PUBLIC PROC[ context: REF Context, on: BOOLEANTRUE ] ~ {
Sets up or destroys depth buffer in VM for context.pixels
IF on # context.depthBuffering THEN {
context.depthBuffering ← on;
context.class.setUpDisplayType[context];
};
};
AntiAliasing: PUBLIC PROC[ context: REF Context, on: BOOLEANTRUE ] ~ {
Sets up or destroys alpha buffer in VM for context.pixels (used for antialiasing)
IF on # context.antiAliasing THEN {
context.antiAliasing ← on;
context.class.setUpDisplayType[context];
};
};
NormalBuffering: PUBLIC PROC[ context: REF Context, on: BOOLEANTRUE ] ~ {
Sets up or destroys normals buffer in VM for context.pixels
IF on
THEN {
IF Atom.GetPropFromList[context.props, $NormalBuffer] = NIL THEN {
context.props ← Atom.PutPropOnList[context.props, $NormalBuffer, $On ];
context.class.setUpDisplayType[context];
};
}
ELSE IF Atom.GetPropFromList[context.props, $NormalBuffer] # NIL THEN {
context.props ← Atom.RemPropFromList[context.props, $NormalBuffer ];
context.displayProps ← Atom.RemPropFromList[context.displayProps, $NormalBuffer ];
};
};
BufferRendering: PUBLIC PROC[ context: REF Context, on: BOOLEANTRUE ] ~ {
Renders through buffer and blits to screen when on, directly to screen (slowly) when off
IF on = context.doVisibly THEN {
context.doVisibly ← NOT on;
context.class.setUpDisplayType[context];
};
};
FillInBackGround: PUBLIC ContextProc ~ { 
Loads background behind current image
IF context.stopMe^ THEN RETURN;
WITH Atom.GetPropFromList[context.props, $BackGround] SELECT FROM
bkgrdClr: REF RGB => FillInConstantBackGround[context, bkgrdClr^];  -- constant color
bkgrdCtx: REF Context => FillInBackGroundImage[context, bkgrdCtx]; -- background ctx
ENDCASE => IF NOT context.antiAliasing -- leave alpha channel alone if no background
THEN FillInConstantBackGround[context, [0.0,0.0,0.0] ]; -- NIL, clear to black
};
FillInConstantBackGround: PUBLIC PROC[context: REF Context,
             bkgrdClr: RGB, cvrge: BYTE ← 255 ] ~ {
Clears frame, use with alpha buffer to load background color behind shapes
FillPixels: PROC[box: Box] ~ {
box ← SF.Displace[box, context.pixels.box.min]; -- offset box to address base of samplemap
FOR i: NAT IN [0..context.pixels.samplesPerPixel) DO
ImagerSample.Fill[ context.pixels[i], box, value[i] ];
ENDLOOP;
};
value: ARRAY[0..5) OF WORD;
alpha: REF NATNARROW[ Atom.GetPropFromList[context.displayProps, $Alpha] ];
depth: REF NATNARROW[ Atom.GetPropFromList[context.displayProps, $Depth] ];
normals: REF NATNARROW[ Atom.GetPropFromList[context.displayProps, $NormalBuffer] ];
samplesPerColor: NAT;
IF context.class.displayType = $FullColor
THEN {
value[0] ← Real.Fix[255.0 * bkgrdClr.R];
value[1] ← Real.Fix[255.0 * bkgrdClr.G];
value[2] ← Real.Fix[255.0 * bkgrdClr.B];
samplesPerColor ← 3;
}
ELSE {
value[0] ← ValueFromRGB[context, bkgrdClr.R, bkgrdClr.G, bkgrdClr.B];
samplesPerColor ← 1;
};
IF alpha # NIL THEN value[alpha^] ← cvrge;
IF depth # NIL THEN value[depth^] ← LAST[WORD];
IF normals # NIL THEN value[normals^] ← value[normals^+1] ← value[normals^+2] ← 0;
IF context.viewPort = NIL THEN SurfaceRender.ValidateContext[context];
IF context.extentCovered.min.s >= context.extentCovered.max.s OR alpha = NIL
THEN FillPixels[[        -- whole viewport, nothing covered
min: [ f: 0, s: 0 ],
max: [ f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h] ]
]]
ELSE {
screenExtent: Box ← SF.Displace[context.extentCovered, context.pixels.box.min];
scanLength: NAT ← context.extentCovered.max.f - context.extentCovered.min.f;
srcBuf: PixelBuffer ← ImagerPixel.NewPixels[context.pixels.samplesPerPixel, scanLength];
FillPixels[[             -- below previously affected area
min: [f: 0, s: 0 ],
max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.min.s]
]];
IF Ceiling[context.viewPort.h] > context.extentCovered.max.s THEN FillPixels[[  -- above
min: [f: 0, s: context.extentCovered.max.s],
max: [f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h] ]
]];
FillPixels[[             -- left of previously affected area
min: [f: 0, s: context.extentCovered.min.s],
max: [f: context.extentCovered.min.f, s: context.extentCovered.max.s]
]];
IF Ceiling[context.viewPort.w] > context.extentCovered.max.f THEN FillPixels[[
             -- right of previously affected area
min: [f: context.extentCovered.max.f, s: context.extentCovered.min.s],
max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.max.s]
]];
Fill in behind previously affected area, using alpha values to blend
FOR i: NAT IN [screenExtent.min.s..screenExtent.max.s) DO
ImagerPixel.GetPixels[          -- get foreground pixels
self: context.pixels, pixels: srcBuf,
initIndex: [f: screenExtent.min.f, s: i], count: scanLength
];
FOR j: NAT IN [0..scanLength) DO    -- blend background behind foreground
uncvrd: NAT ← 255 - Basics.LowByte[ srcBuf[alpha^][j] ]; -- top byte may be mask
IF uncvrd > 0 THEN IF uncvrd < 255
THEN FOR k: NAT IN [0..samplesPerColor) DO-- partially covered
newPart: CARDINAL ← Basics.HighByte[ uncvrd * value[k] + 128 ];
srcBuf[k][j] ← srcBuf[k][j] + newPart;    -- ( + 128 for rounding )
ENDLOOP
ELSE FOR k: NAT IN [0..samplesPerColor) DO  -- no previous coverage
srcBuf[k][j] ← value[k];
ENDLOOP;
ENDLOOP;
ImagerPixel.PutPixels[          -- store result in foreground
self: context.pixels, pixels: srcBuf,
initIndex: [f: screenExtent.min.f, s: i], count: scanLength
];
ENDLOOP;
};
Set uncovered: indicates no alpha information
context.extentCovered ← [[0,0], [0,0]];
};
FillInBackGroundImage: PROC[context, bkgrdCtx: REF Context] ~ {
IF NOT bkgrdCtx.imageReady THEN
IF Atom.GetPropFromList[bkgrdCtx.props, $BackGrdImage] # NIL THEN {
aisFile: Rope.ROPENARROW[Atom.GetPropFromList[bkgrdCtx.props, $BackGrdImage]];
bkgrdCtx.viewPort ← context.viewPort; -- assumes we want consistency of viewports
AllocatePixelMemory[ bkgrdCtx ];
FillInConstantBackGround[bkgrdCtx, [0,0,0] ]; -- ensure alpha, depth, etc. clean
[] ← AISAnimation.GetAIS[bkgrdCtx, aisFile];
bkgrdCtx.imageReady ← TRUE;
}
ELSE SIGNAL ThreeDBasics.Error[[$MisMatch, "Background image not ready"]];
IF context.antiAliasing
THEN CopyUnder[context, bkgrdCtx]
ELSE FOR i: NAT IN[0..bkgrdCtx.pixels.samplesPerPixel) DO
ImagerSample.Transfer[ dst: context.pixels[i], src: bkgrdCtx.pixels[i] ];
ENDLOOP;
IF context.antiAliasing     -- try to add yet another background behind this one
THEN WITH Atom.GetPropFromList[bkgrdCtx.props, $BackGround] SELECT FROM
bkgrdClr: REF RGB => FillInConstantBackGround[context, bkgrdClr^]; -- constant color
bhndCtx: REF Context => FillInBackGroundImage[context, bhndCtx]; -- background ctx
ENDCASE => {};             -- NIL or garbage, all done
Set uncovered (negative width and height) indicates no alpha information
context.extentCovered ← [[0,0], [0,0]];
};
CopyUnder: PROC[context, bkgrdCtx: REF Context] ~ {
Transfer: PROC[box: Box] ~ {
FOR i: NAT IN [0..bkgrdCtx.pixels.samplesPerPixel) DO
ImagerSample.BasicTransfer[
dst: context.pixels[i], src: bkgrdCtx.pixels[i],
dstMin: box.min, srcMin: box.min,
size: [f: box.max.f - box.min.f, s: box.max.s - box.min.s]
];
ENDLOOP;
};
samplesPerColor: NATIF context.class.displayType = $FullColor THEN 3 ELSE 1;
alpha: REF NATNARROW[ Atom.GetPropFromList[context.displayProps, $Alpha] ];
scanLength: NAT ← context.extentCovered.max.f - context.extentCovered.min.f;
srcBuf: PixelBuffer ← ImagerPixel.NewPixels[bkgrdCtx.pixels.samplesPerPixel, scanLength];
dstBuf: PixelBuffer ← ImagerPixel.NewPixels[context.pixels.samplesPerPixel, scanLength];
IF alpha = NIL THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "No A buffer"]];
Fill in around edges
Transfer[[             -- below previously affected area
min: [0, 0],
max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.min.s]
]];
IF Ceiling[context.viewPort.h] > context.extentCovered.max.s THEN Transfer[[
min: [f: 0, s: context.extentCovered.max.s],   -- above previously affected area
max: [f: Ceiling[context.viewPort.w], s: Ceiling[context.viewPort.h]]
]];
Transfer[[             -- left of previously affected area
min: [f: 0, s: context.extentCovered.min.s],
max: [f: context.extentCovered.min.f, s: context.extentCovered.max.s]
]];
IF Ceiling[context.viewPort.w] > context.extentCovered.max.f THEN Transfer[[
              -- right of previously affected area
min: [f: context.extentCovered.max.f, s: context.extentCovered.min.s],
max: [f: Ceiling[context.viewPort.w], s: context.extentCovered.max.s]
]];
Now, do previously affected area
FOR i: NAT IN [context.extentCovered.min.s .. context.extentCovered.max.s) DO
ImagerPixel.GetPixels[          -- get foreground pixels
self: bkgrdCtx.pixels, pixels: srcBuf,
initIndex: [f: context.extentCovered.min.f, s: i], count: scanLength
];
ImagerPixel.GetPixels[          -- get background pixels
self: context.pixels, pixels: dstBuf,
initIndex: [f: context.extentCovered.min.f, s: i], count: scanLength
];
FOR j: NAT IN [0..scanLength) DO     -- blend background behind foreground
uncvrd: NAT ← 255 - Basics.LowByte[ dstBuf[alpha^][j] ]; -- top byte may be mask
IF uncvrd > 0 THEN FOR k: NAT IN [0..samplesPerColor) DO
dstBuf already weighted by its coverage (dst ← a * dst), so dst ← dst + (1-a) * src
dstBuf[k][j] ← dstBuf[k][j] + Basics.HighByte[ uncvrd * srcBuf[k][j] + 128 ]
ENDLOOP;               -- ( + 128 for rounding )
dstBuf[alpha^][j] ← 255; -- set to full coverage
LOOPHOLE[dstBuf[alpha^][j], Basics.BytePair].high ← 255; -- mask now meaningless
ENDLOOP;
ImagerPixel.PutPixels[          -- store result in foreground
self: context.pixels, pixels: dstBuf,
initIndex: [f: context.extentCovered.min.f, s: i], count: scanLength
];
ENDLOOP;
};
Frame Generation
MakeFrame: PUBLIC ContextProc ~ {
Makes image, clears frame, adds background, etc.
IF context.antiAliasing
THEN {
SurfaceRender.ValidateContext[context];     -- ensure viewPort etc. updated
FillInConstantBackGround[context, [0.0,0.0,0.0], 0 ]; -- clear screen, alpha buffer
SurfaceRender.ShowShapes[context];      -- render image
context.class.loadBackground[context];     -- load background
}
ELSE SurfaceRender.MakeFrame[context];
};
Low-level drawing
ShadeSpot: PUBLIC ThreeDBasics.SpotProc ~ {
PROC[context: REF Context, shading: REF ShadingClass, spot: REF Spot]
pt: VertexInfo;
pt.coord.ex ← spot.val[7]; pt.coord.ey ← spot.val[8]; pt.coord.ez ← spot.val[9];
pt.shade.exn ← spot.val[4]; pt.shade.eyn ← spot.val[5]; pt.shade.ezn ← spot.val[6];
pt.shade.r ← spot.val[0]; pt.shade.g ← spot.val[1]; pt.shade.b ← spot.val[2];
pt.shade.t ← spot.val[3];
IF spot.partShiny < 1.0
THEN pt.props ← Atom.PutPropOnList[pt.props, $PartShiny, NEW[REAL ← spot.partShiny] ];
pt ← shading.shadeVtx[ context, pt, shading ];
spot.val[0] ← pt.shade.er; spot.val[1] ← pt.shade.eg; spot.val[2] ← pt.shade.eb;
spot.val[3] ← pt.shade.et;
};
Draw2DLine: PUBLIC PROC[context: REF Context, p1, p2: Pair, color: Pixel] ~ {
ip1, ip2: IntegerPair;
ip1 ← PairToScreen[context, p1]; ip2 ← PairToScreen[context, p2];
ScanConvert.PutLine[ context, ip1, ip2, color, color ]
};
Draw2DPoly: PUBLIC PROC[context: REF Context, poly: REF PairSequence, color: Pixel] ~ {
plygn: REF Patch ← NEW[Patch[poly.length]];
FOR i: NAT IN [0..poly.length) DO
p: IntegerPair ← PairToScreen[ context, poly[i] ];
plygn[i].coord.sx ← p.x; plygn[i].coord.sy ← p.y;
ENDLOOP;
plygn.nVtces ← poly.length;
ScanConvert.FastFlatTiler[ context, plygn, color ]
};
DrawRope: PUBLIC PROC[context: REF Context, rope: Rope.ROPE, position: Pair,
      color: Pixel ← [255,255,255,0,0] , size: REAL ← 20, font: Rope.ROPENIL] ~ {
imagerCtx: Imager.Context ← NARROW[ Atom.GetPropFromList[context.props, $ImagerCtx] ];
ropeData: REF RopeDesc ← NEW[ RopeDesc ← [rope, position, color, size, font] ];
IF imagerCtx = NIL THEN imagerCtx ← GetImagerCtx[context];
DoRope[ context, imagerCtx, ropeData ];
};
DoRope: PUBLIC ThreeDBasics.ImagerProc ~ {
PROC[ context: REF Context, imagerCtx: Imager.Context, data: REF ANY ]
ropeData: REF RopeDesc ← NARROW[data];
p: Pair ← ropeData.position;
color: Pixel ← ropeData.color;
theFont: Imager.Font;
Imager.SetColor[     -- ok but how do you set transparency??
imagerCtx,
ImagerColor.ColorFromRGB[[color[r]/255.0, color[g]/255.0, color[b]/255.0]]
];
IF ropeData.font = NIL THEN ropeData.font ← "Xerox/Pressfonts/TimesRoman-MRR";
theFont ← ImagerFont.Find[ropeData.font];
theFont ← ImagerFont.Scale[theFont, ropeData.size];
Imager.SetFont[imagerCtx, theFont];
Imager.SetXY[ imagerCtx,
    [context.ndcToPixels.scaleX * p.x, ABS[context.ndcToPixels.scaleY] * p.y] ];
Imager.ShowRope[imagerCtx, ropeData.rope];
};
Simple, Fast Tilers
PolygonTiler: PUBLIC PatchProc ~ {
PROC[ context: REF Context, patch: REF Patch, data: REF ANYNIL ] RETURNS[REF Patch]
shape: REF ShapeInstance ← NARROW[ Atom.GetPropFromList[patch.props, $Shape] ];
IF patch.type = $PolyLine
THEN OutLineTiler[context, patch]
ELSE IF context.antiAliasing
THEN FancyTiler[context, patch]   -- standard anti-aliasing tiler
ELSE SELECT shape.shadingClass.shadingType FROM
$Lines => OutLineTiler[context, patch];   -- lines with constant shading
$Faceted => IF NOT context.depthBuffering  -- filled polygons with flat shading
THEN FastFlatTiler[context, patch]
ELSE LerpTiler[context, patch];
$ShadedLines => OutLineTiler[context, patch];  -- lines with varying shading
$HiddenLines, $NormaledLines => {   -- white faceted polygon with black outlines
IF shape.shadingClass.shadingType = $NormaledLines AND patch.dir = back
THEN DrawNormals[context, patch];
FOR i: NAT IN [0..patch.nVtces) DO  -- force patch white for opaque polygon
patch[i].shade.er ← patch[i].shade.eg ← patch[i].shade.eb ← 1.0;
ENDLOOP;
IF NOT context.depthBuffering
THEN FastFlatTiler[context, patch]
ELSE LerpTiler[context, patch];
IF shape.shadingClass.shadingType = $HiddenLines
THEN FOR i: NAT IN [0..patch.nVtces) DO-- use shape color for outlining
patch[i].shade.er ← shape.shadingClass.color.R;
patch[i].shade.eg ← shape.shadingClass.color.G;
patch[i].shade.eb ← shape.shadingClass.color.B;
ENDLOOP
ELSE FOR i: NAT IN [0..patch.nVtces) DO-- force vertices black for outlining
patch[i].shade.er ← patch[i].shade.eg ← patch[i].shade.eb ← 0.0;
ENDLOOP;
OutLineTiler[context, patch];
IF shape.shadingClass.shadingType = $NormaledLines AND patch.dir # back
THEN DrawNormals[context, patch];
};
ENDCASE =>
IF shape.shadingClass.shininess > 0.0 OR shape.shadingClass.texture # NIL
THEN ShinyTiler[context, patch]   -- highlight tiler
ELSE LerpTiler[context, patch];   -- default is smooth shading
RETURN[NIL];
};
OutLineTiler: PROC[ context: REF Context, poly: REF Patch ] ~ {
last: NATIF poly.type = $PolyLine THEN 0 ELSE poly.nVtces - 1;
p1: IntegerPair ← [ Real.Round[poly[last].coord.sx], Real.Round[poly[last].coord.sy] ];
clr1: Pixel ← [ Real.Fix[poly[last].shade.er*255.0], Real.Fix[poly[last].shade.eg*255.0],
    Real.Fix[poly[last].shade.eb*255.0],
    Real.Fix[poly[last].shade.et*255.0], Real.Round[poly[last].coord.sz] ];
SELECT context.class.displayType FROM
$PseudoColor =>
clr1[r] ← 42 * (clr1[r] * 6 / 256) + 6 * (clr1[g] * 7 / 256) + (clr1[b] * 6 / 256) +2;
$Gray => clr1[r] ← (clr1[r] + clr1[g] + clr1[b]) / 3;
ENDCASE;
FOR i: NAT IN [0..poly.nVtces) DO
p2: IntegerPair ← [ Real.Round[poly[i].coord.sx], Real.Round[poly[i].coord.sy] ];
clr2: Pixel ← [ Real.Fix[poly[i].shade.er*255.0], Real.Fix[poly[i].shade.eg*255.0],
    Real.Fix[poly[i].shade.eb*255.0],
    Real.Fix[poly[i].shade.et*255.0], Real.Round[poly[i].coord.sz] ];
SELECT context.class.displayType FROM
$PseudoColor =>
clr2[r] ← 42 * (clr2[r] * 6 / 256) + 6 * (clr2[g] * 7 / 256) + (clr2[b] * 6 / 256) +2;
$Gray => clr2[r] ← (clr2[r] + clr2[g] + clr2[b]) / 3;
ENDCASE;
ScanConvert.PutLine[context, p1, p2, clr1, clr2];
p1 ← p2; clr1 ← clr2;
ENDLOOP;
};
DrawNormals: PROC[ context: REF Context, poly: REF Patch ] ~ {
pixel: Pixel ← [0, 0, 0, 0, 0];
FOR i: NAT IN [0..poly.nVtces) DO
p1: IntegerPair ← [ Real.Round[poly[i].coord.sx], Real.Round[poly[i].coord.sy] ];
ep2: Triple ← G3dVector.Add[
[poly[i].coord.ex, poly[i].coord.ey, poly[i].coord.ez],
[poly[i].shade.exn/4, poly[i].shade.eyn/4, poly[i].shade.ezn/4]
];
clip: OutCode ← ShapeUtilities.GetClipCodeForPt[ context, ep2 ];
IF clip = NoneOut THEN {
sp2: Triple ← ShapeUtilities.XfmPtToDisplay[ context, ep2 ];
p2: IntegerPair ← [ Real.Round[sp2.x], Real.Round[sp2.y] ];
ScanConvert.PutLine[context, p1, p2, pixel, pixel];
};
ENDLOOP;
};
FastFlatTiler: PROC[ context: REF Context, poly: REF Patch ] ~ {
pixel: Pixel ← [
Real.Fix[poly.vtx[0].shade.er * 255.0],   -- red
Real.Fix[poly.vtx[0].shade.eg * 255.0],   -- green
Real.Fix[poly.vtx[0].shade.eb * 255.0],   -- blu
255,             -- full coverage
LAST[CARD16] - 1        -- depth
];
ScanConvert.FastFlatTiler[context, poly, pixel];
};
LerpTiler: PROC[ context: REF Context, poly: REF Patch] ~ {
IF context.class.displayType = $Gray THEN FOR i: NAT IN [0..poly.nVtces) DO
OPEN poly.vtx[i].shade; er ← (er + eg + eb) * (1.0/3.0);
ENDLOOP;
IF context.depthBuffering THEN {
zScale: REALREAL[LAST[INT16]] / context.depthResolution;
FOR i: NAT IN [0..poly.nVtces) DO
OPEN poly.vtx[i].coord;
sz ← sz * zScale;   -- scale for max depth range
ENDLOOP;
};
ScanConvert.LerpTiler[context, poly];
};
ShinyTiler for Phong shading and Simple Texture, and Highlight Utilities
ShinyTiler: PROC[context: REF Context, poly: REF Patch] ~ {
shape: REF ShapeInstance ← NARROW[ Atom.GetPropFromList[poly.props, $Shape] ];
zScale: REALREAL[LAST[INT16]] / context.depthResolution;
hltCnt: NAT ← 0;
lightColor: REF NatRGBSequence;
hiliteInfo: REF HilitSeqs ← IF shape.shadingClass.shininess > 0.0
THEN GotAHilite[context, poly, shape.shadingClass.shininess] -- hilight?
ELSE NIL;
IF (hiliteInfo = NIL OR noHighLights) AND shape.shadingClass.texture = NIL THEN {
no highlight or texture
IF shape.shadingClass.shadingType = $Faceted AND NOT context.depthBuffering
THEN FastFlatTiler[context, poly]
ELSE LerpTiler[context, poly];
RETURN[];
};
Got highlight(s)!
IF hiliteInfo # NIL THEN FOR j: NAT IN [0..context.lightSources.length) DO
IF hiliteInfo.flags[j] THEN hltCnt ← hltCnt + 1;  -- figure storage for extra normal info
ENDLOOP;
IF shape.shadingClass.texture # NIL THEN {
txtrRange: REF Pair ← NARROW[
Atom.GetPropFromList[shape.shadingProps, $TxtrCoordRange]
];
IF txtrRange # NIL                -- fix texture seams
THEN MappedAndSolidTexture.AdjustTexture[
poly, shape.shadingClass.texture, txtrRange.x, txtrRange.y
]
ELSE MappedAndSolidTexture.AdjustTexture[poly, shape.shadingClass.texture];
hltCnt ← hltCnt + 1;
};
FOR i: NAT IN [0..poly.nVtces) DO       -- get extra storage for each vertex
txtrX, txtrY: REAL; intPairs: REF IntegerPairSequence;
IF shape.shadingClass.texture # NIL
THEN [txtrX, txtrY] ← NARROW[poly.vtx[i].aux, REF Pair]^;
poly.vtx[i].aux ← intPairs ← NEW[IntegerPairSequence[hltCnt]];
IF shape.shadingClass.texture # NIL THEN {
intPairs[0] ← [ Real.Round[LAST[NAT]/32*txtrX], Real.Round[LAST[NAT]/32*txtrY] ];
};
ENDLOOP;
IF hiliteInfo # NIL THEN {
lightColor ← NEW[NatRGBSequence[hltCnt]]; lightColor.length ← hltCnt; -- light colors
hltCnt ← IF shape.shadingClass.texture # NIL THEN 1 ELSE 0;
FOR j: NAT IN [0..context.lightSources.length) DO
IF hiliteInfo.flags[j] THEN {   -- pick up info for each highlight-causing light
clr: RGB ← context.lightSources[j].shadingClass.color;
lightColor[hltCnt] ← [ Real.Round[LAST[NAT]*clr.R], Real.Round[LAST[NAT]*clr.G],
       Real.Round[LAST[NAT]*clr.B] ];
FOR i: NAT IN [0..poly.nVtces) DO
vtxAux: REF IntegerPairSequence ← NARROW[poly.vtx[i].aux];
vtxAux[hltCnt] ← hiliteInfo.refls[i + j*poly.nVtces];  -- store with vertex
ENDLOOP;
hltCnt ← hltCnt + 1;
};
ENDLOOP;
poly.props ← Atom.PutPropOnList[poly.props, $LightColors, lightColor]; -- store light colors
};
IF context.class.displayType = $Gray THEN FOR i: NAT IN [0..poly.nVtces) DO
OPEN poly.vtx[i].shade; er ← (er + eg + eb) / 3.0;
ENDLOOP;
IF context.depthBuffering THEN FOR i: NAT IN [0..poly.nVtces) DO
poly.vtx[i].coord.sz ← poly.vtx[i].coord.sz * zScale;   -- scale for max depth range
ENDLOOP;   
ScanConvert.HiliteTiler[ context, poly, Real.Round[shape.shadingClass.shininess] ];
IF hiliteInfo # NIL THEN ReleaseHilitSeqs[hiliteInfo];
};
GotAHilite: PROC[context: REF Context, poly: REF Patch, shininess: REAL]
    RETURNS[REF HilitSeqs ] ~ {
XfmNormal: PROC[light: Vertex, vtx: VertexInfo] RETURNS[ Triple ] ~ {
Transform the normal to a space in which a normal aligned with the z-axis would reflect the light straight into the eye
toLightSrc: Triple ← G3dVector.Normalize[     -- normalized direction to light
[ light.ex - vtx.coord.ex, light.ey - vtx.coord.ey, light.ez - vtx.coord.ez ] ];
toEye: Triple ← G3dVector.Normalize[      -- normalized direction to eye
[ -vtx.coord.ex, -vtx.coord.ey, -vtx.coord.ez ] ];
idealRefl: Triple ← G3dVector.Mul[ G3dVector.Add[toLightSrc, toEye], 0.5 ];
hypotA: REAL ← RealFns.SqRt[ Sqr[idealRefl.x] + Sqr[idealRefl.z] ]; -- rotate about y
cosA: REAL ← idealRefl.z / hypotA; sinA: REAL ← idealRefl.x / hypotA;
hypotB: REAL ← RealFns.SqRt[ Sqr[idealRefl.y] + Sqr[hypotA] ];  -- rotate about x
cosB: REAL ← hypotA / hypotB; sinB: REAL ← idealRefl.y / hypotB;
tx: REAL ← cosA*vtx.shade.exn - sinA*vtx.shade.ezn;
ty: REAL ← vtx.shade.eyn;
tz: REAL ← sinA*vtx.shade.exn + cosA*vtx.shade.ezn;
IF tx = 0.0 AND ty = 0.0 AND tz = 0.0 THEN RETURN [[0.0, 0.0, 0.0]];   -- null normal
RETURN[ G3dVector.Normalize[ [x: tx, y: cosB*ty - sinB*tz, z: sinB*ty + cosB*tz] ] ];
};
gotAHilite: BOOLEANFALSE;
hilitInfo: REF HilitSeqs ← GetHilitSeqs[
reflSize: poly.nVtces * context.lightSources.length, flagSize: context.lightSources.length
];
idealReflSeq: REF IntegerPairSequence ← hilitInfo.refls;
lightFlags: REF BooleanSequence ← hilitInfo.flags;
IF shininess <= 0.0 THEN {
ThreeDBasics.Error[[$Warning, "Shininess not positive"]];
RETURN[NIL];       -- illegitimate input
};
FOR j: NAT IN [0..context.lightSources.length) DO
minX, minY: REAL ← Real.LargestNumber;
maxX, maxY: REAL ← -Real.LargestNumber;
FOR i: NAT IN [0..poly.nVtces) DO-- get bounding box on highlight reflection vectors
reflVec: Triple ← XfmNormal[ context.lightSources[j].centroid, poly[i] ];
k: NAT ← (j * poly.nVtces) + i;
minX ← MIN[minX, reflVec.x];   maxX ← MAX[maxX, reflVec.x];
minY ← MIN[minY, reflVec.y];  maxY ← MAX[maxY, reflVec.y];
idealReflSeq[k] ← [
Real.Round[reflVec.x*LAST[NAT]/2], -- scale to half of integer range (to allow sums)
Real.Round[reflVec.y*LAST[NAT]/2]
];  
ENDLOOP;
Get closest point to zero spanned in bounding box
minX ← IF Sgn[minX] # Sgn[maxX] THEN 0.0 ELSE MIN[ABS[minX], ABS[maxX]];
minY ← IF Sgn[minY] # Sgn[maxY] THEN 0.0 ELSE MIN[ABS[minY], ABS[maxY]];
IF RealFns.Power[ MAX[ 0.0, 1.0 - Sqr[minX] - Sqr[minY] ], shininess ] > .25*justNoticeable
THEN { gotAHilite ← TRUE; lightFlags[j] ← TRUE; }
ELSE lightFlags[j] ← FALSE;
ENDLOOP;
idealReflSeq.length ← poly.nVtces * context.lightSources.length;
IF NOT gotAHilite THEN { ReleaseHilitSeqs[hilitInfo]; hilitInfo ← NIL; };
RETURN[hilitInfo];
};
Anti-Aliasing Tiler for Fancier shading, etc.
FancyTiler: PUBLIC PROC[context: REF Context, poly: REF Patch] ~ {
shape: REF ShapeInstance ← NARROW[ Atom.GetPropFromList[poly.props, $Shape] ];
shadingClass: REF ShadingClass ← shape.shadingClass;
zScale: REALREAL[LAST[INT16]] / context.depthResolution;
pixelShading: ATOM ← context.class.displayType;    -- flags shading per pixel
textureMapping, solidTexture, yIncrements: BOOLEANFALSE;
fancyPoly: REF FancyPatch ← NEW[ FancyPatch[poly.nVtces] ];
IF NOT context.antiAliasing
THEN ThreeDBasics.Error[[$MisMatch, "Not AntiAliased - wrong tiler"]];
IF context.depthBuffering THEN FOR i: NAT IN [0..poly.nVtces) DO
poly.vtx[i].coord.sz ← poly.vtx[i].coord.sz * zScale;   -- scale for max depth range
ENDLOOP;   
IF shadingClass.cnvrtVtx = NIL THEN shadingClass.cnvrtVtx ← GetLerpedVals;
IF shadingClass.getColor = NIL THEN shadingClass.getColor ← ShadeSpot;
Do we have a highlight?
IF shadingClass.shininess > 0.0 THEN {
hilitInfo: REF HilitSeqs ← GotAHilite[context, poly, shadingClass.shininess];
IF hilitInfo # NIL THEN {
pixelShading ← $PixelShading;
ReleaseHilitSeqs[hilitInfo];
};
};
Do we have texture?
IF shadingClass.texture # NIL THEN {
txtrRange: REF Pair ← NARROW[
Atom.GetPropFromList[shape.shadingProps, $TxtrCoordRange]
];
IF txtrRange # NIL                -- fix texture seams
THEN MappedAndSolidTexture.AdjustTexture[
poly, shadingClass.texture, txtrRange.x, txtrRange.y
]
ELSE MappedAndSolidTexture.AdjustTexture[poly, shadingClass.texture];
pixelShading ← $PixelShading;  -- shading at each pixel (could be avoided for intensity)
};
Do we have normal vectors stored?
IF Atom.GetPropFromList[context.displayProps, $NormalBuffer] # NIL
THEN pixelShading ← $PixelShading;
FOR i: NAT IN [0..poly.nVtces) DO -- convert vertices to lerp
fancyPoly[i] ← [
x:  poly[i].coord.sx,
y:  poly[i].coord.sy,
val: shadingClass.cnvrtVtx[ dest: fancyPoly[i].val, source: poly[i], data: pixelShading ]
];
ENDLOOP;
fancyPoly.shadingClass ← IF pixelShading = $PixelShading OR shadingClass.texture # NIL
THEN shadingClass ELSE NIL;
RealFancyTiler[context, fancyPoly];   -- now go tile it
IF statistics THEN polyCount ← polyCount + 1;
};
RealFancyTiler: PUBLIC PROC[context: REF Context, poly: REF FancyPatch] ~ {
least, top, bottom: REAL;
lEdge, rEdge: EdgeBlock;
vtxCount, lVtx, rVtx, nxtlVtx, nxtrVtx: NAT;
lftStep: NAT ← poly.length - 1; rgtStep: NAT ← 1;  -- increments to next vtx in order
leftVtxNeeded, rightVtxNeeded: BOOLEAN;
least ← poly.vtx[0].y; nxtlVtx ← 0;
FOR i: CARDINAL IN [1..poly.length) DO -- find bottom vertex
IF poly.vtx[i].y < least
THEN { least ← poly[i].y; nxtlVtx ← i; };
ENDLOOP;
nxtrVtx ← nxtlVtx; -- set pointers to bottom vertex
leftVtxNeeded ← rightVtxNeeded ← TRUE;
vtxCount ← 1;
WHILE vtxCount < poly.length DO -- Do until all vertices reached
IF leftVtxNeeded THEN { -- work around left side
lVtx ← nxtlVtx; nxtlVtx ← (nxtlVtx + lftStep) MOD poly.length;
lEdge ← MakeEdge[poly[lVtx], poly[nxtlVtx]];
leftVtxNeeded ← FALSE;
};
IF rightVtxNeeded THEN { -- work around right side
rVtx ← nxtrVtx; nxtrVtx ← (nxtrVtx + rgtStep) MOD poly.length;
rEdge ← MakeEdge[poly[rVtx], poly[nxtrVtx]];
rightVtxNeeded ← FALSE;
};
IF poly[nxtlVtx].y < poly[nxtrVtx].y  -- get trapezoid given by next higher vertex
THEN {
top ← poly[nxtlVtx].y; -- next left vertex reached
leftVtxNeeded ← TRUE; vtxCount ← vtxCount + 1;
}
ELSE {
top ← poly[nxtrVtx].y; -- next right vertex reached
rightVtxNeeded ← TRUE; vtxCount ← vtxCount + 1;
};
bottom ← MAX[poly[lVtx].y, poly[rVtx].y];
ShowFancyTrap[
context, poly, bottom, top, lEdge, rEdge
! ThreeDBasics.Error => IF reason.code = $DeepRecursionInTiler THEN CONTINUE
];
ENDLOOP;
};
MakeEdge: PROC[vtx1, vtx2: LerpVtx] RETURNS[edge: EdgeBlock] ~ {
length: REAL;
edge.val ← NEW[ RealSequence[vtx1.val.length] ];
edge.incr ← NEW[ RealSequence[vtx1.val.length] ];
IF ABS[vtx2.y - vtx1.y] >= ABS[vtx2.x - vtx1.x]
THEN {
length ← vtx2.y - vtx1.y;
edge.start ← MIN[vtx1.y, vtx2.y];
edge.end ← MAX[vtx1.y, vtx2.y];
edge.moreVertical ← TRUE;
}
ELSE {
length ← vtx2.x - vtx1.x;
edge.start ← MIN[vtx1.x, vtx2.x];
edge.end ← MAX[vtx1.x, vtx2.x];
edge.moreVertical ← FALSE;
};
IF ABS[length] < justNoticeable THEN length ← 1.;        -- prevent divide errors
edge.x ← vtx1.x;  edge.xIncr ← (vtx2.x - vtx1.x) / length;
edge.y ← vtx1.y;  edge.yIncr ← (vtx2.y - vtx1.y) / length;
FOR i: NAT IN [0..vtx1.val.length) DO
edge.val[i] ← vtx1.val[i];  edge.incr[i] ← (vtx2.val[i] - vtx1.val[i]) / length;
ENDLOOP;
edge.val.length ← edge.incr.length ← vtx1.val.length;
RETURN[edge];
};
EvalEdgeAt: PROC[vtx: LerpVtx, edge: EdgeBlock, position: REAL] RETURNS[LerpVtx] ~ {
pos, dist: REAL;
IF vtx.val = NIL OR vtx.val.maxLength < edge.val.length
THEN vtx.val ← NEW[ RealSequence[edge.val.length] ];
IF position > edge.end THEN pos ← edge.end    -- keep values between vertex values
ELSE IF position < edge.start THEN pos ← edge.start
ELSE pos ← position;
dist ← IF edge.moreVertical THEN pos - edge.y ELSE pos - edge.x;
vtx.x ← edge.x + edge.xIncr * dist;
vtx.y ← edge.y + edge.yIncr * dist;
FOR i: NAT IN [0..edge.val.length) DO
vtx.val[i] ← edge.val[i] + edge.incr[i] * dist;
ENDLOOP;
vtx.val.length ← edge.val.length;
RETURN[vtx];
};
ShowFancyTrap: PROC[ context: REF Context, inPoly: REF FancyPatch,
        bottom, top: REAL, lEdge, rEdge: EdgeBlock] ~ {
GetXcoordAt: PROC[edge: EdgeBlock, yPos: REAL] RETURNS [REAL] ~ {
dist: REAL ← yPos - edge.y;
RETURN [ edge.x + dist / edge.yIncr ];
};
tEdge, bEdge, midlEdge, midrEdge: EdgeBlock;
leftTopVtx, leftBotVtx, rightTopVtx, rightBotVtx, vtx0, vtx1, vtx2, vtx3, vertex: LerpVtx;
sideways, midSection: BOOLEANTRUE;
toughCase: BOOLEANFALSE;
a, b: REAL;        -- parameters for defining 45 degree lines
IF bottom + justNoticeable >= top THEN RETURN[]; -- too thin to affect image
midlEdge ← DupEdgeBlock[lEdge];  -- copy sides for possible later use
midrEdge ← DupEdgeBlock[rEdge];
FOR i: NAT IN [0..midlEdge.val.length) DO
midlEdge.val[i] ← lEdge.val[i]; midlEdge.incr[i] ← lEdge.incr[i];
midrEdge.val[i] ← rEdge.val[i]; midrEdge.incr[i] ← rEdge.incr[i];
ENDLOOP;
IF NOT (lEdge.moreVertical AND rEdge.moreVertical) THEN { -- get corners
IF lEdge.moreVertical THEN {
leftTopVtx ← EvalEdgeAt[leftTopVtx, lEdge, top];
leftBotVtx ← EvalEdgeAt[leftBotVtx, lEdge, bottom];
}
ELSE {
topX: REAL ← GetXcoordAt[lEdge, top];
botX: REAL ← GetXcoordAt[lEdge, bottom];
leftTopVtx ← EvalEdgeAt[leftTopVtx, lEdge, topX];
leftBotVtx ← EvalEdgeAt[leftBotVtx, lEdge, botX];
};
IF rEdge.moreVertical THEN {
rightTopVtx ← EvalEdgeAt[rightTopVtx, rEdge, top];
rightBotVtx ← EvalEdgeAt[rightBotVtx, rEdge, bottom];
}
ELSE {
topX: REAL ← GetXcoordAt[rEdge, top];
botX: REAL ← GetXcoordAt[rEdge, bottom];
rightTopVtx ← EvalEdgeAt[rightTopVtx, rEdge, topX];
rightBotVtx ← EvalEdgeAt[rightBotVtx, rEdge, botX];
};
IF rightTopVtx.x + justNoticeable < leftTopVtx.x OR rightBotVtx.x + justNoticeable < leftBotVtx.x
THEN RETURN[]; -- twisted or backfacing
};

if left side more horizontal, check for slope, make top or bottom edge,
do right triangle, do new left edge
IF NOT lEdge.moreVertical THEN IF lEdge.yIncr < 0.
THEN {         -- left edge is more horizontal, top vertex is leftmost
tEdge ← MakeEdge[leftTopVtx, rightTopVtx]; bEdge ← DupEdgeBlock[lEdge];
IF leftBotVtx.x <= rightTopVtx.x
THEN {       -- easy case: right triangle containing whole left edge
ShowSteepTrap[context, inPoly.shadingClass,
     leftTopVtx.x, leftBotVtx.x, bEdge, tEdge, sideways];
midlEdge ← MakeEdge[leftBotVtx, EvalEdgeAt[vertex, tEdge, leftBotVtx.x]];
}
ELSE {        -- right top is left of left bottom
IF rEdge.moreVertical THEN {  -- difficult case bot. more horz. top more vert.
toughCase ← TRUE;
ShowSteepTrap[context, inPoly.shadingClass,
     leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways];
vtx0 ← EvalEdgeAt[vtx0, bEdge, rightTopVtx.x];  --build new polygon
vtx1 ← EvalEdgeAt[vtx1, rEdge, rightTopVtx.y];
vtx2 ← EvalEdgeAt[vtx2, rEdge, rightBotVtx.y];
vtx3 ← EvalEdgeAt[vtx3, bEdge, leftBotVtx.x];
a ← .707; b ← .707;       -- test against negative 45 degree slope
}
ELSE {        -- both more horz. do triangle then trapezoid
ShowSteepTrap[context, inPoly.shadingClass,
     leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways];
ShowSteepTrap[context, inPoly.shadingClass,
     rightTopVtx.x, leftBotVtx.x, bEdge, rEdge, sideways];
midSection ← FALSE;
};
};
}
ELSE {     -- left edge is more horizontal, bottom vertex is leftmost
bEdge ← MakeEdge[leftBotVtx, rightBotVtx]; tEdge ← DupEdgeBlock[lEdge];
IF leftTopVtx.x <= rightBotVtx.x
THEN {      -- easy case: right triangle containing whole left edge
ShowSteepTrap[context, inPoly.shadingClass,
     leftBotVtx.x, leftTopVtx.x, bEdge, tEdge, sideways];
midlEdge ← MakeEdge[leftTopVtx, EvalEdgeAt[vertex, bEdge, leftTopVtx.x]];
}
ELSE {      -- right bottom is left of left top
IF rEdge.moreVertical THEN {  -- difficult case bot. more vert. top more horz.
toughCase ← TRUE;
ShowSteepTrap[context, inPoly.shadingClass,
     leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways];
vtx0 ← EvalEdgeAt[vtx0, rEdge, rightBotVtx.y];  --build new polygon
vtx1 ← EvalEdgeAt[vtx1, tEdge, rightBotVtx.x];
vtx2 ← EvalEdgeAt[vtx2, tEdge, leftTopVtx.x];
vtx3 ← EvalEdgeAt[vtx3, rEdge, rightTopVtx.y];
a ← -.707; b ← .707;       -- test against positive 45 degree slope
}
ELSE {        -- both more horz. do triangle then trapezoid
ShowSteepTrap[context, inPoly.shadingClass,
     leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways];
ShowSteepTrap[context, inPoly.shadingClass,
     rightBotVtx.x, leftTopVtx.x, rEdge, tEdge, sideways];
midSection ← FALSE;
};
};
};
if right side more horizontal do likewise
IF NOT rEdge.moreVertical THEN IF rEdge.yIncr < 0.
THEN {        -- right edge is more horizontal, top vertex is leftmost
bEdge ← MakeEdge[leftBotVtx, rightBotVtx]; tEdge ← DupEdgeBlock[rEdge];
IF leftBotVtx.x <= rightTopVtx.x
THEN {       -- easy case: right triangle containing whole right edge
ShowSteepTrap[context, inPoly.shadingClass,
     rightTopVtx.x, rightBotVtx.x, bEdge, tEdge, sideways];
midrEdge ← MakeEdge[EvalEdgeAt[vertex, bEdge, rightTopVtx.x], rightTopVtx];
}
ELSE {       -- left bottom is right of right top
IF lEdge.moreVertical THEN {  -- difficult case bot. more vert. top more horz.
toughCase ← TRUE;
vtx0 ← EvalEdgeAt[vtx0, lEdge, rightTopVtx.y];  --build new polygon
vtx1 ← EvalEdgeAt[vtx1, tEdge, rightTopVtx.x];
vtx2 ← EvalEdgeAt[vtx2, tEdge, leftBotVtx.x];
vtx3 ← EvalEdgeAt[vtx3, lEdge, leftBotVtx.y];
a ← .707; b ← .707;       -- test against negative 45 degree slope
};
get triangle at right, if both horz. then left edge got middle trapezoid
ShowSteepTrap[context, inPoly.shadingClass,
     leftBotVtx.x, rightBotVtx.x, bEdge, tEdge, sideways];
};
}
ELSE {        -- right edge is more horizontal, bottom vertex is leftmost
tEdge ← MakeEdge[leftTopVtx, rightTopVtx]; bEdge ← DupEdgeBlock[rEdge];
IF leftTopVtx.x <= rightBotVtx.x
THEN {      -- easy case: right triangle containing whole right edge
ShowSteepTrap[context, inPoly.shadingClass,
     rightBotVtx.x, rightTopVtx.x, bEdge, tEdge, sideways];
midrEdge ← MakeEdge[rightBotVtx, EvalEdgeAt[vertex, tEdge, rightBotVtx.x]];
}
ELSE {       -- left top is right of right bottom
IF lEdge.moreVertical THEN {  -- difficult case bot. more vert. top more horz.
toughCase ← TRUE;
vtx0 ← EvalEdgeAt[vtx0, bEdge, rightBotVtx.x];  --build new polygon
vtx1 ← EvalEdgeAt[vtx1, lEdge, rightBotVtx.y];
vtx2 ← EvalEdgeAt[vtx2, lEdge, leftTopVtx.y];
vtx3 ← EvalEdgeAt[vtx3, bEdge, leftTopVtx.x];
a ← -.707; b ← .707;       -- test against positive 45 degree slope
};
get triangle at right, if both horz. then left edge got middle trapezoid
ShowSteepTrap[context, inPoly.shadingClass,
     leftTopVtx.x, rightTopVtx.x, bEdge, tEdge, sideways];
};
};
Do middle section
IF toughCase THEN { -- quadrilateral with top and bottom slopes on both sides of 45 deg.
c, d1, d2, g3d: REAL; -- evaluate area based on distance of vertices from 45 degree line
c ← -(a * vtx0.x + b * vtx0.y); -- equation for line through vtx0
d1 ← a * vtx1.x + b * vtx1.y + c;   -- distances of other vertices from line
d2 ← a * vtx2.x + b * vtx2.y + c;
g3d ← a * vtx3.x + b * vtx3.y + c;
IF (top - bottom) * ( MAX[d1, d2, g3d, 0.0] - MIN[d1, d2, g3d, 0.0] ) / 2.0 < justNoticeable
THEN RETURN[]         -- estimated area too small to matter
ELSE {
poly: REF FancyPatch ← NEW[FancyPatch[4]];
poly.vtx[0] ← DupLerpVtx[vtx3]; poly.vtx[1] ← DupLerpVtx[vtx2];
poly.vtx[2] ← DupLerpVtx[vtx1]; poly.vtx[3] ← DupLerpVtx[vtx0];
poly.shadingClass ← inPoly.shadingClass;
poly.recurseLevel ← inPoly.recurseLevel + 1;
IF poly.recurseLevel > recurseLimit
THEN SIGNAL ThreeDBasics.Error[[$DeepRecursionInTiler, "Needs a new tiler"]];
RealFancyTiler[context, poly]; -- go draw it (recursively)
};
}
ELSE IF midSection THEN ShowSteepTrap[ context, inPoly.shadingClass,
            bottom, top, midlEdge, midrEdge ];
};
ShowSteepTrap: PROC[ context: REF Context, shadingClass: REF ShadingClass,
       bottom, top: REAL, lEdge, rEdge: EdgeBlock,
        sideways: BOOLEANFALSE ] ~ {
scanSeg: REF ScanSegment ← GetScanSeg[lEdge.val.length];
yIncrements: BOOLEANFALSE;
lStartSave: REAL ← lEdge.start;  lEndSave: REAL ← lEdge.end; -- save limits to restore later
rStartSave: REAL ← rEdge.start; rEndSave: REAL ← rEdge.end;
yLimit: INTEGERIF sideways THEN Ceiling[context.viewPort.w-1]
          ELSE Ceiling[context.viewPort.h-1];
xLimit: INTEGERIF sideways THEN Ceiling[context.viewPort.h-1]
          ELSE
Ceiling[context.viewPort.w-1];
IF context.stopMe^ THEN RETURN;
IF bottom + justNoticeable >= top THEN RETURN[]; -- too vertically thin to affect image
IF shadingClass # NIL THEN yIncrements ←TRUE;  -- indicates texture or highlights
lEdge.start ← rEdge.start ← bottom; -- set edge limits for coverage calcs.
lEdge.end ← rEdge.end ← top;
FOR y: INTEGER IN [Floor[bottom]..Ceiling[top]] DO
IF context.stopMe^ THEN RETURN;
IF y < 0 OR y >= yLimit THEN LOOP;  -- scissor off if out-of-bounds
scanSeg ← MakeScanSeg[scanSeg, lEdge, rEdge, Real.Float[y], yIncrements];
IF scanSeg.end - scanSeg.start > justNoticeable THEN -- if wide enough to affect image
ShowScanSeg[context, y, scanSeg, xLimit, shadingClass, sideways];
ENDLOOP;
lEdge.start ← lStartSave; lEdge.end ← lEndSave; -- restore limits
rEdge.start ← rStartSave; rEdge.end ← rEndSave;
ReleaseScanSeg[scanSeg];
};
populationCount: ARRAY [0..256) OF NAT;
realFromByte: ARRAY [0..256) OF REAL;
MakeScanSeg: PROC[seg: REF ScanSegment, lEdge, rEdge: EdgeBlock, position: REAL,
       increments: BOOLEAN] RETURNS[REF ScanSegment] ~ {
length, lCvrge, rCvrge: REAL;
size: NAT ← lEdge.val.length;
lVtx: REF LerpVtx ← GetVertex[size];
rVtx: REF LerpVtx ← GetVertex[size];
lVtx^ ← EvalEdgeAt[lVtx^, lEdge, position];
rVtx^ ← EvalEdgeAt[rVtx^, rEdge, position];
IF lEdge.moreVertical -- horizontal scan segment
THEN {
length ← rVtx.x - lVtx.x;
seg.start ← lVtx.x; seg.end ← rVtx.x;
}
ELSE {      -- vertical scan segment
length ← rVtx.y - lVtx.y;
seg.start ← lVtx.y; seg.end ← rVtx.y;
};
IF ABS[length] > justNoticeable THEN { -- long enough to show up
[lCvrge, seg.lMask] ← EvalCvrgeAt[lEdge.start, lEdge.end, position];
[rCvrge, seg.rMask] ← EvalCvrgeAt[rEdge.start, rEdge.end, position];
seg.coverage ← lCvrge;  
seg.cvrgIncr ← (rCvrge - lCvrge) / length;
FOR i: NAT IN [0..lEdge.val.length) DO
seg.val[i] ← lVtx.val[i];  
seg.xIncrVal[i] ← (rVtx.val[i] - lVtx.val[i]) / length;    -- x-increments
ENDLOOP;
seg.val.length ← seg.xIncrVal.length ← lEdge.val.length;
IF increments THEN {
FOR i: NAT IN [0..lEdge.val.length) DO
seg.yIncr[i] ← lEdge.incr[i];          -- y—increments
seg.xIncrForY[i] ← (rEdge.incr[i] - lEdge.incr[i]) / length; -- x-incrs for y—incrs
ENDLOOP;
seg.yIncr.length ← seg.xIncrForY.length ← lEdge.val.length;
};
FOR i: NAT IN [0..3) DO IF seg.val[i] < 0.0
THEN IF seg.val[i] < -(justNoticeable * justNoticeable)
THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Negative Color"]]
ELSE seg.val[i] ← 0.0;
ENDLOOP;
};
ReleaseVertex[lVtx]; ReleaseVertex[rVtx];
RETURN[seg];
};
maskFromReal: ARRAY [0..8] OF BYTE ← [0, 1, 3, 7, 15, 31, 63, 127, 255];
EvalCvrgeAt: PROC[ start, end, position: REAL]
     RETURNS[ cvrge: REAL, mask: CARDINAL] ~ {
Get Pixel area coverage weighted by function stored in "weight"
lCoverage, rCoverage, rUnCoverage: REAL;
rMask, lMask: BYTE;
lCoverage ← position - start;
IF lCoverage >= 1. THEN lCoverage ← 2.0   -- fully covered
ELSE IF lCoverage > -1.0 THEN lCoverage ← 1.0 + lCoverage  -- partially covered
ELSE lCoverage ← 0.;
lMask ← maskFromReal[Real.Fix[lCoverage * 4.49]];
This fills bits in mask from right to left as converage increases from 0.0-2.0
lCoverage ← weight[Real.Fix[ tblLngth/2 * lCoverage ]];
rCoverage ← end - position;
IF rCoverage >= 1. THEN rCoverage ← 2.0   -- fully covered
ELSE IF rCoverage > -1.0 THEN rCoverage ← 1.0 + rCoverage -- partially covered
ELSE rCoverage ← 0.;
rMask ← 255 - maskFromReal[Real.Fix[(2.0 - rCoverage) * 4.49]];  -- inverted mask
This fills bits in mask from left to right as converage increases from 0.0-2.0
rUnCoverage ← weight[Real.Fix[ tblLngth/2 * (2.0 - rCoverage) ]]; -- weight uncovered part
cvrge ← lCoverage - rUnCoverage; -- l - r is total coverage
mask ← Basics.BITAND[lMask, rMask];
};
EvalScanSegAt: PROC[spot: REF Spot, seg: REF ScanSegment, position: REAL]
      RETURNS[REF Spot] ~ {
pos, dist: REAL;
IF spot.val = NIL OR spot.val.maxLength < seg.val.length THEN {
spot.val ← NEW[ RealSequence[seg.val.length] ];
spot.yIncr ← NEW[ RealSequence[seg.val.length] ];
spot.xIncr ← NEW[ RealSequence[seg.val.length] ];
};
IF position > seg.end THEN pos ← seg.end    -- keep values between vertex values
ELSE IF position < seg.start THEN pos ← seg.start
ELSE pos ← position;
dist ← pos - seg.start;
FOR i: NAT IN [0..seg.val.length) DO    -- load up spot values
spot.val[i] ← seg.val[i] + seg.xIncrVal[i] * dist;
spot.yIncr[i] ← seg.yIncr[i] + seg.xIncrForY[i] * dist;
ENDLOOP;
spot.xIncr ← seg.xIncrVal;            -- use x increments as is
spot.val.length ← spot.yIncr.length ← spot.xIncr.length ← seg.val.length;
IF position - seg.start > 1.0 AND seg.end - position > 1.0
THEN { spot.coverage ← 1.0; spot.mask ← 255; }   -- not near segment end
ELSE [spot.coverage, spot.mask] ← EvalCvrgeAt[seg.start, seg.end, position];
spot.coverage ← spot.coverage * (seg.coverage + seg.cvrgIncr * dist);
spot.partShiny ← 1.0;  -- initialize
RETURN[spot];
};
ShowScanSeg: PROC[ context: REF Context, y: NAT, scanSeg: REF ScanSegment,
      xLimit: INTEGER, shadingClass: REF ShadingClass, sideways: BOOLEAN ] ~ {
Swap: PROC[ref1, ref2: REF RealSequence] RETURNS [outRef1, outRef2: REF RealSequence] ~{
RETURN[ outRef1: ref2, outRef2: ref1 ];
};
a, d, v, numClrs: NATIF context.class.displayType = $FullColor THEN 3 ELSE 1;
refA: REF NATNARROW[ Atom.GetPropFromList[ context.displayProps, $Alpha] ];
refD: REF NATNARROW[ Atom.GetPropFromList[ context.displayProps, $Depth] ];
refV: REF NATNARROW[ Atom.GetPropFromList[ context.displayProps, $NormalBuffer] ];
trns: NAT ← 3;
spot: REF Spot ← GetSpot[];
segStart: INTEGER ← Floor[scanSeg.start];
segEnd: INTEGER ← Ceiling[scanSeg.end];
segLength: NATMIN[xLimit, segEnd] - MAX[0, segStart] + 1;
pixels: PixelBuffer ← ImagerPixel.ObtainScratchPixels[
context.pixels.samplesPerPixel, segLength
];
initIndex, delta: SF.Vec;
fMin: INTEGER ← context.pixels.box.min.f;
sMin: INTEGER ← context.pixels.box.min.s;
IF refA # NIL THEN a ← refA^;
IF refD # NIL THEN d ← refD^;
IF refV # NIL THEN v ← refV^;
IF sideways
THEN { initIndex ← [ f: y+fMin, s: MAX[sMin, segStart+sMin] ]; delta ← [ f: 0, s: 1 ]; }
ELSE { initIndex ← [ f: MAX[fMin, segStart+fMin], s: y+sMin ]; delta ← [ f: 1, s: 0 ]; };
ImagerPixel.GetPixels[             -- get pixels for segment
self: context.pixels, pixels: pixels,
initIndex: initIndex, delta: delta, count: segLength
];
FOR x: INTEGER IN [ segStart .. segEnd ] DO
writeBehind: BOOLEANTRUE;
cvrge: REAL;
i: NAT;
IF x < 0 OR x >= xLimit THEN LOOP;       -- scissor off if out-of-bounds
i ← x - MAX[segStart, 0];
IF Basics.LowByte[pixels[a][i]] > 254 AND NOT context.depthBuffering
THEN LOOP;       -- pixel already covered and not using depth buffer
spot ← EvalScanSegAt[ spot, scanSeg, Real.Float[x] ];
IF spot.coverage < justNoticeable THEN LOOP;    -- surface covers tiny area in pixel
IF shadingClass # NIL THEN {      -- evaluate texture and/or highlights
IF sideways THEN spot.xySwapped ← TRUE ELSE spot.xySwapped ← FALSE;
shadingClass.getColor[ context, shadingClass, spot ];
};
IF numClrs = 1 THEN {   -- average colors for gray image
spot.val[0] ← ( spot.val[0] + spot.val[1] + spot.val[2] ) / 3.0; spot.val[1] ← spot.val[3];
};
cvrge ← spot.coverage * (1.0 - spot.val[trns]);
IF context.depthBuffering THEN {
depth: REALIF spot.val.length > 10 THEN spot.val[10] ELSE spot.val[4];
IF pixels[d][i] > Real.Round[depth] THEN { -- new surface is in front
writeBehind ← FALSE;
pixels[d][i] ← Real.Round[depth];
};
};
Blend new pixel value with old
IF pixels[a][i] # 0
THEN {        -- previous coverage on this pixel, prepare to blend
oldAlpha: BYTE ← Basics.LowByte[pixels[a][i]];
oldMask: BYTE ← Basics.HighByte[pixels[a][i]];
IF writeBehind
THEN {          -- previous coverage blend under
IF oldAlpha < 255 AND oldMask < 255 THEN { -- check for partial coverage
Earlier surfaces did not fully cover pixel, check for overlap, skip out if found
intersection: NAT ← populationCount[Basics.BITAND[oldMask, spot.mask]];
oldCover: NAT ← populationCount[oldMask];
newCover: NAT ← populationCount[spot.mask];
IF oldCover = intersection AND newCover = intersection
THEN newCover ← intersection;       -- debug test point
};
cvrge ← MIN[ cvrge, 1.0 - realFromByte[oldAlpha] ]; -- up to uncovered part
FOR j: NAT IN [0..numClrs) DO
pixels[j][i] ← pixels[j][i] + Real.Round[ spot.val[j] * cvrge * 255.0 ]
ENDLOOP;
}
ELSE {         -- previous coverage blend over
oldCvrge: REAL ← oldAlpha / 255.0;
IF oldCvrge + cvrge > 1.0   -- is pixel completely covered?
THEN oldCvrge ← (1.0 - cvrge) / oldCvrge -- scale back prev. cvrge.
ELSE oldCvrge ← 1.0; -- assume non-overlapping (already scaled by cvrge.)
FOR j: NAT IN [0..numClrs) DO
pixels[j][i] ← MIN[
Real.Round[oldCvrge * pixels[j][i] + spot.val[j] * cvrge * 255.0],
255
];
ENDLOOP;
};
spot.mask ← Basics.BITOR[oldMask, spot.mask];
}
ELSE {
FOR j: NAT IN [0..numClrs) DO-- first surface, weight by coverage and transp.
pixels[j][i] ← Real.Round[ spot.val[j] * cvrge * 255.0 ]
ENDLOOP;
IF refV # NIL THEN {        -- store normals
pixels[v][i] ← LOOPHOLE[Real.InlineRoundI[LAST[NAT] * spot.val[4]]];
pixels[v+1][i] ← LOOPHOLE[Real.InlineRoundI[LAST[NAT] * spot.val[5]]];
pixels[v+2][i] ← LOOPHOLE[Real.InlineRoundI[LAST[NAT] * spot.val[6]]];
};
};
pixels[a][i] ← MIN[ 255,     -- coverage is sum of previous and current coverage
      INTEGER[Real.Round[ cvrge * 255.0 ]] + Basics.LowByte[pixels[a][i]]
     ];
LOOPHOLE[pixels[a][i], Basics.BytePair].high ← spot.mask;
ENDLOOP;
ImagerPixel.PutPixels[             -- return modified pixels
self: context.pixels, pixels: pixels,
initIndex: initIndex, delta: delta, count: segLength
];
ImagerPixel.ReleaseScratchPixels[pixels];
ReleaseSpot[spot];
};
Init[];
END.