ThreeDScenesImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, December 16, 1986 4:41:16 pm PST
Bloomenthal, February 26, 1987 7:51:06 pm PST
DIRECTORY
Basics     USING [ BITOR, BITAND ],
Atom     USING [ PropList, PutPropOnList, GetPropFromList, GetPName ],
Real     USING [ Fix, Float, FixC, RoundC, RoundI ],
RealFns    USING [ CosDeg, SinDeg, SqRt, TanDeg ],
Rope     USING [ ROPE, Equal, Cat, FromChar, Length, Substr ],
IO      USING [ STREAM, GetAtom, GetBool, GetChar, GetReal,
         PutRope, EndOfStream, SkipWhitespace, SP, CR ],
Convert    USING [ RopeFromReal ],
Terminal    USING [ Current, Virtual ],
Commander   USING [ PrependWorkingDir ],
QuickViewer   USING [ DrawInViewer, QuickView ],
Imager    USING [ Context, Rectangle, MaskRectangle, SetColor ],
ImagerColor   USING [ ColorFromRGB, RGB ],
ImagerColorMap  USING [ SetStandardColorMap ],
Vector3d    USING [ Dot, Pair, Triple, Quad, Normalize, Sub, Cross, Add, Length,
         Mul, Null ],
Matrix3d    USING [ Matrix, MatrixRep, Mul, Identity, MakeRotate,
        Transform, TransformVec ],
Plane3d    USING [ DistanceToPt ],
Pixels     USING [ AddToBuffer, Clip, Copy, Create, Extent, GetFromImagerContext,
         GetFromTerminal, GetSampleSet, Interleave, maxNoOfSamples,
         PixelBuffer, PixelOp, SampleSet],
ScanConvert   USING [ MappedRGB, justNoticeable, GetColorProc],
ThreeDBasics  USING [ ScaleAndAddXfm, SixSides, OutCode, NoneOut, Quad,
         AllOut, ShapeInstance, ShapeSequence, Context,
         ShadingSequence, ShadingValue, ClipState, Vertex, VertexInfo,
         VertexSequence, VtxToRealSeqProc ],
TextureMaps   USING [ MakeTxtrCoordsFromVtxNos, MakeTxtrCoordsFromNormals,
         SetTexture, SumTexture, TextureMap, TextureFromAIS ],
SolidTextures   USING [ ProcToRope ],
ShapeTwiddle  USING [ ScaleShape, ScaleTexture ],
ThreeDSurfaces  USING [ GetVtxNormals ],
ThreeDMisc   USING [ AddShapeAt, GetMappedColor, LoadColorRamp,
         LoadStd8BitClrMap, SetFacetedColor, SetSmoothColor,
         SetShininess, SetTransmittance, ShadingProcName ],
ThreeDScenes  USING [ ErrorDesc, ShadingProcs ];
ThreeDScenesImpl: CEDAR PROGRAM
IMPORTS Atom, Basics, Commander, Convert, Imager, ImagerColor, ImagerColorMap, IO, Matrix3d, Pixels, Plane3d, QuickViewer, Real, RealFns, Rope, ScanConvert, ShapeTwiddle, SolidTextures, Terminal, TextureMaps, ThreeDMisc, ThreeDSurfaces, Vector3d
EXPORTS ThreeDScenes
~ BEGIN
Error: PUBLIC SIGNAL [reason: ThreeDScenes.ErrorDesc] = CODE;
Basic Types
RGB: TYPE ~ ImagerColor.RGB;
Pair: TYPE ~ Vector3d.Pair;          -- RECORD [ x, y: REAL];
Triple: TYPE ~ Vector3d.Triple;        -- RECORD [ x, y, z: REAL];
Quad: TYPE ~ Vector3d.Quad;         -- RECORD [ x, y, z, w: REAL];
Rectangle: TYPE ~ Pixels.Extent;
SampleSet: TYPE ~ Pixels.SampleSet;
Xfm3d: TYPE ~ Matrix3d.Matrix; -- REF ARRAY [0..4) OF ARRAY [0..4) OF REAL;
Xfm3dRep: TYPE ~ Matrix3d.MatrixRep;
ScaleAndAddXfm: TYPE ~ ThreeDBasics.ScaleAndAddXfm;
RECORD[scaleX, scaleY, scaleZ, addX, addY, addZ: REAL];
SixSides: TYPE ~ ThreeDBasics.SixSides;  -- {Left, Right, Bottom, Top, Near, Far}
ClipState: TYPE ~ ThreeDBasics.ClipState;   -- {in, out, clipped}
OutCode: TYPE ~ ThreeDBasics.OutCode; -- RECORD[bottom,top,left,right,near,far: BOOLEAN]
NoneOut: OutCode ~ ThreeDBasics.NoneOut; -- [FALSE,FALSE,FALSE,FALSE,FALSE,FALSE]
AllOut: OutCode ~ ThreeDBasics.AllOut; -- [TRUE, TRUE, TRUE, TRUE, TRUE, TRUE]
Context: TYPE ~ ThreeDBasics.Context;
ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance;
ShapeSequence: TYPE ~ ThreeDBasics.ShapeSequence;
ShadingSequence: TYPE ~ ThreeDBasics.ShadingSequence;
ShadingValue: TYPE ~ ThreeDBasics.ShadingValue;
Vertex: TYPE ~ ThreeDBasics.Vertex;
VertexInfo: TYPE ~ ThreeDBasics.VertexInfo;
VertexSequence: TYPE ~ ThreeDBasics.VertexSequence;
Constants
aLilBit: REAL ← ScanConvert.justNoticeable * ScanConvert.justNoticeable;
Utility Procedures
Sqr: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { RETURN[number * number]; };
Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ {
result ← Real.RoundI[number];
IF result < number THEN result ← result + 1;
};
Floor: PROC[ in: REAL ] RETURNS[ out: INTEGER ] ~ {
out ← Real.RoundI[in];
IF Real.Float[out] > in THEN out ← out - 1;
};
Procedures for Setting up Contexts
Create: PUBLIC PROC[] RETURNS [REF Context] ~ {
context: REF ThreeDBasics.Context ← NEW[ThreeDBasics.Context];
Get Working directory
wDir: Rope.ROPE ← Commander.PrependWorkingDir[" "];  -- add needed space (wierdness)
wDir ← Rope.Substr[ base: wDir, len: Rope.Length[wDir] - 1 ];   -- drop space
context.props ← Atom.PutPropOnList[context.props, $WDir, wDir]; -- keep directory
context.eyeSpaceXfm ← Matrix3d.Identity[];  -- can't do this in initialization, so do it here
RETURN[context];
};
DisplayFromImagerContext: PUBLIC PROC[context: REF Context, imagerCtx: Imager.Context] ~{       
Sets up context using imager context
newMode: ATOM;
x, y, w, h: REAL;
context.display ← Pixels.GetFromImagerContext[ imagerCtx ];
newMode ← NARROW[Atom.GetPropFromList[ context.display.props, $RenderMode ]];
IF newMode = $Dithered
THEN {
SELECT context.preferredRenderMode FROM
$Dithered => IF context.renderMode # $Dithered
THEN ImagerColorMap.SetStandardColorMap[ Terminal.Current[] ];
$Grey => IF context.renderMode # $Grey THEN
ThreeDMisc.LoadColorRamp[ Terminal.Current[], [0.,0.,0.], [1.,1.,1.], [.43, .43, .43] ];
$PseudoColor, NIL => IF context.renderMode # $PseudoColor
THEN ThreeDMisc.LoadStd8BitClrMap[ Terminal.Current[] ];
ENDCASE => SIGNAL Error[[$MisMatch, "Unexpected renderMode"]];
context.renderMode ← context.preferredRenderMode;
}
ELSE context.renderMode ← newMode;
x ← MAX[0.0, context.preferredViewPort.x];
y ← MAX[0.0, context.preferredViewPort.y];
w ← MIN[context.preferredViewPort.w, Real.Float[context.display.width] - x ];
h ← MIN[context.preferredViewPort.h, Real.Float[context.display.height] - y ];
context.viewPort ← [x, y, w, h];
Pixels.Clip[context.display, [ Floor[x], Floor[y], Ceiling[w], Ceiling[h] ] ];
context.window ← WindowFromViewPort[context.viewPort];
};
DisplayFromTerminal: PUBLIC PROC[ context: REF Context, terminal: Terminal.Virtual ] ~ {
Sets up context.display using virtual terminal, render mode from state of color display
context.display ← Pixels.GetFromTerminal[terminal];
context.renderMode ← IF context.display.samplesPerPixel = 3
THEN $Dorado24
ELSE IF context.display.pixels[0].subMap.sampleMap.bitsPerSample = 1
THEN $Bitmap
ELSE $PseudoColor;
context.viewPort ← [
0.0, 0.0,
Real.Float[context.display.width], Real.Float[context.display.height]
];
context.window ← WindowFromViewPort[context.viewPort];
};
DisplayFromVM: PUBLIC PROC[ context: REF Context, width, height: NAT,
          renderMode: ATOM ← $FullColor ] ~ {
Sets up context.display using virtual memory (not visible) full-color assumed
pixelSizes: SampleSet;
SELECT renderMode FROM 
$Bitmap => { pixelSizes ← Pixels.GetSampleSet[1]; pixelSizes[0] ← 1; };
$PseudoColor, $Dithered, $Grey => {
pixelSizes ← Pixels.GetSampleSet[1]; pixelSizes[0] ← 8;
};
$FullColor  => {
pixelSizes ← Pixels.GetSampleSet[3]; pixelSizes[0] ← pixelSizes[1] ← pixelSizes[2] ← 8;
};
$Dorado24  => {
pixelSizes ← Pixels.GetSampleSet[3];
pixelSizes[0] ← 16; pixelSizes[1] ← 0; pixelSizes[2] ← 8;
};
ENDCASE  => SIGNAL Error[[$Unimplemented, "Unknown renderMode"]];
context.display ← Pixels.Create[ width, height, pixelSizes ];
IF renderMode = $Dorado24 THEN Pixels.Interleave[context.display.pixels]; -- interleave r-g
context.renderMode ← renderMode;
context.viewPort ← [
0.0, 0.0,
Real.Float[width], Real.Float[height]
];
context.window ← WindowFromViewPort[context.viewPort];
};
AddDepthBuffer: PUBLIC PROC[ context: REF Context ] ~ {
Makes depth buffer in VM for context.display
pixelSizes: SampleSet ← Pixels.GetSampleSet[1];
pixelSizes[0] ← 16;
context.display ← Pixels.AddToBuffer[context.display, pixelSizes];
context.display.props ← Atom.PutPropOnList[
context.display.props, $Depth, NEW[NAT ← context.display.samplesPerPixel-1] ];
context.depthBuffer ← TRUE;
};
AddAlphaBuffer: PUBLIC PROC[ context: REF Context ] ~ {
Makes alpha buffer in VM for context.display
pixelSizes: SampleSet ← Pixels.GetSampleSet[1];
pixelSizes[0] ← 8;
context.display ← Pixels.AddToBuffer[context.display, pixelSizes];
context.display.props ← Atom.PutPropOnList[
context.display.props, $Alpha, NEW[NAT ← context.display.samplesPerPixel-1] ];
context.alphaBuffer ← TRUE;
};

Procedures for Defining and Altering Environments
SetEyeSpace: PUBLIC PROC[ context: REF Context ] ~ {
in, right, up, normal: Triple;
mtx: Xfm3d;
wndw: Imager.Rectangle;
aspectRatio, viewWidth, viewHeight: REAL;
bounds: Pixels.Extent ← [
     0, 0, Ceiling[context.viewPort.w], Ceiling[context.viewPort.h] ];
IF bounds.h > 0 AND bounds.w > 0
THEN aspectRatio ← Real.Float[bounds.w] / bounds.h
ELSE RETURN[];
transform from world to eye
in ← Vector3d.Normalize[Vector3d.Sub[context.ptOfInterest, context.eyePoint]];
IF Vector3d.Null[in] THEN SIGNAL Error[[$MisMatch, "Eye and Pt of Interest identical"]];
right ← Vector3d.Normalize[Vector3d.Cross[in, context.upDirection]];
IF Vector3d.Null[right] THEN right ← [1.0, 0.0, 0.0];  -- looking straight down
up ← Vector3d.Normalize[Vector3d.Cross[right, in]];
normalize not really needed (to avoid roundoff problems)
context.eyeSpaceXfm ← Matrix3d.Identity[];
context.eyeSpaceXfm[0][0] ← right.x;
context.eyeSpaceXfm[1][0] ← right.y;
context.eyeSpaceXfm[2][0] ← right.z;
context.eyeSpaceXfm[3][0] ← -Vector3d.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] ← -Vector3d.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] ← -Vector3d.Dot[in, context.eyePoint];
mtx ← Matrix3d.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 ← Matrix3d.Mul[context.eyeSpaceXfm, mtx];
generate clipping planes
IF NOT (context.window.w > 0.0 AND context.window.h > 0.0) THEN RETURN[];
viewWidth ← 2.*RealFns.TanDeg[context.fieldOfView/2.];
wndw.x ← MIN[1.0, MAX[-1.0, context.window.x] ];
wndw.w ← MIN[1.0-wndw.x, context.window.w ];
wndw.y ← MIN[1.0, MAX[-1.0, context.window.y] ];
wndw.h ← MIN[1.0-wndw.y, context.window.h ];
context.clippingPlanes[Near] ← [0., 0., 1., -context.hitherLimit];
context.clippingPlanes[Far] ← [0., 0., -1., context.yonLimit];
normalize plane equation for true distances
normal ← Vector3d.Normalize[[1., 0., -(wndw.x * viewWidth/2.)]];
context.clippingPlanes[Left] ← [normal.x, 0., normal.z, 0.];
normal ← Vector3d.Normalize[[-1., 0., (wndw.x + wndw.w) * viewWidth/2.]];
context.clippingPlanes[Right] ← [normal.x, 0., normal.z, 0.];
normal ← Vector3d.Normalize[[0., 1., -(wndw.y * viewWidth/2.)]];
context.clippingPlanes[Bottom] ← [0., normal.y, normal.z, 0.];
normal ← Vector3d.Normalize[[0., -1., (wndw.y + wndw.h) * viewWidth/2.]];
context.clippingPlanes[Top] ← [0., normal.y, normal.z, 0.];
compute the transformation from the eye coord sys to normalized display coordinates (-1.—1.)
IF aspectRatio > 1.0    -- set angle of view on smallest viewport dimension
THEN { viewHeight ← viewWidth; viewWidth ← viewWidth * aspectRatio; }
ELSE { viewHeight ← viewWidth / aspectRatio; };
IF wndw.w / wndw.h > 1.0
THEN { viewWidth ← viewWidth * wndw.h / wndw.w; }
ELSE { viewHeight ← viewHeight * wndw.w / wndw.h; };
context.eyeToNDC.addX ← -(wndw.x / wndw.w);
context.eyeToNDC.addY ← -(wndw.y / wndw.h);
context.eyeToNDC.addZ ← 1./(1. - context.hitherLimit/context.yonLimit);
context.eyeToNDC.scaleX ← 1./(wndw.w * viewWidth / 2.0);
context.eyeToNDC.scaleY ← 1./(wndw.h * viewHeight / 2.0);
context.eyeToNDC.scaleZ ← -context.hitherLimit/(1. - context.hitherLimit/context.yonLimit);
};
SetWindow: PUBLIC PROC[context: REF Context, size: Imager.Rectangle] ~{
context.window ← size;
SetEyeSpace[context];
IF context.shapes # NIL THEN FOR i: NAT IN [0..context.shapes.length) DO
context.shapes[i].vtcesInValid ← TRUE;    -- eyespace and image space will change
ENDLOOP;
};
WindowFromViewPort: PUBLIC PROC[viewPort: Imager.Rectangle] RETURNS[Imager.Rectangle] ~ {
window: Imager.Rectangle;
IF viewPort.h <= 0.0 THEN RETURN[ [0.0, 0.0, 0.0, 0.0] ];
IF viewPort.w > viewPort.h
THEN {
window.x ← -1.0; window.w ← 2.0;
window.y ← -viewPort.h / viewPort.w; window.h ← 2.0 * viewPort.h / viewPort.w;
}
ELSE {
window.y ← -1.0; window.h ← 2.0;
window.x ← -viewPort.w / viewPort.h; window.w ← 2.0 * viewPort.w / viewPort.h;
};
RETURN[ window ];
};
SetViewPort: PUBLIC PROC[context: REF Context, size: Imager.Rectangle] ~{
bounds: Pixels.Extent;
IF size.w <= 0.0 OR size.h <= 0.0
THEN SIGNAL Error[[$MisMatch, "Null clipper"]];
bounds ← [ Floor[size.x], Floor[size.y], Ceiling[size.w], Ceiling[size.h] ];
Pixels.Clip[context.display, bounds ];
context.viewPort ← size;
context.preferredViewPort ← context.viewPort;
};
FillViewPort: PUBLIC PROC[context: REF Context, clr: RGB] ~ {
pixelBytes: SampleSet ← Pixels.GetSampleSet[Pixels.maxNoOfSamples];
area: Pixels.Extent ← [ 0, 0, Ceiling[context.viewPort.w], Ceiling[context.viewPort.h] ];
SELECT context.renderMode FROM 
$Dithered, $Bitmap => {     -- these modes use imager where possible
DoFill: PROC[ imagerCtx: Imager.Context ] ~ {
Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ];
Imager.MaskRectangle[ imagerCtx, context.viewPort ];
};
QuickViewer.DrawInViewer[  -- gets up-to-date imager context
NARROW[context.viewer,REF QuickViewer.QuickView],
DoFill
];
RETURN;
};
$PseudoColor => {
pixelBytes[0] ← ThreeDMisc.GetMappedColor[ context, clr ]; pixelBytes.length ← 1;
};
$Grey   => {
pixelBytes[0] ← Real.RoundC[255.0 * (clr.R + clr.G + clr.B) / 3]; pixelBytes.length ← 1;
};
$FullColor, $Dorado24  => {
pixelBytes[0] ← Real.RoundC[clr.R*255];
pixelBytes[1] ← Real.RoundC[clr.G*255];
pixelBytes[2] ← Real.RoundC[clr.B*255];
pixelBytes.length ← 3;
};
ENDCASE  => SIGNAL Error[[$Unimplemented, "Unknown renderMode"]];
IF context.alphaBuffer THEN {
alpha: REF NATNARROW[ Atom.GetPropFromList[context.display.props, $Alpha] ];
IF pixelBytes.length <= alpha^ THEN pixelBytes.length ← alpha^ + 1;
pixelBytes[alpha^] ← 0;     -- clear alpha buffer to transparent (or uncovered)
Set uncovered (negative width and height) indicates no alpha information
context.extentCovered ← [Ceiling[context.viewPort.w], 0,  -- left, right
       Ceiling[context.viewPort.h], 0];  -- bottom, top
};
IF context.depthBuffer THEN {
depth: REF NATNARROW[ Atom.GetPropFromList[context.display.props, $Depth] ];
IF pixelBytes.length <= depth^ THEN pixelBytes.length ← depth^ + 1;
pixelBytes[depth^] ← LAST[CARDINAL];     -- clear depth buffer to max depth
};
Pixels.PixelOp[context.display, area, pixelBytes];
};
FillInBackGround: PUBLIC PROC[context: REF Context] ~ { 
Loads background behind current image
WITH Atom.GetPropFromList[context.props, $BackGround] SELECT FROM
bkgrdClr: REF RGB => IF context.alphaBuffer         -- constant color
THEN FillInConstantBackGround[context, bkgrdClr^]
ELSE FillViewPort[context, bkgrdClr^];
bkgrdImage: REF Pixels.PixelBuffer => FillInBackGroundImage[context, bkgrdImage^];
ENDCASE => IF NOT context.alphaBuffer THEN FillViewPort[context, [0.0,0.0,0.0] ]; -- NIL
};
FillInBackGroundImage: PROC[context: REF Context, bkgrdImage: Pixels.PixelBuffer] ~ {
Pixels.Copy[
destination: context.display,
source:  bkgrdImage,
destArea:  [Real.RoundC[context.viewPort.x], Real.RoundC[context.viewPort.y],
     Real.RoundC[context.viewPort.w], Real.RoundC[context.viewPort.h] ],
srcArea:  [ 0, 0, bkgrdImage.width, bkgrdImage.height ],
op:   IF context.alphaBuffer THEN $WriteUnder ELSE $Write
];
};
FillInConstantBackGround: PROC[context: REF Context, bkgrdClr: RGB] ~ {
For use with alpha buffer to load background behind shapes
addOn: NAT ← 0;
bkgrdBytes: SampleSet;
alpha: REF NATNARROW[ Atom.GetPropFromList[context.display.props, $Alpha] ];
depth: REF NATNARROW[ Atom.GetPropFromList[context.display.props, $Depth] ];
IF depth # NIL THEN addOn ← addOn + 1;
SELECT context.renderMode FROM
$Bitmap => SIGNAL Error[[$MisMatch, "Improper renderMode"]];
$FullColor, $Dorado24 => {
bkgrdBytes ← Pixels.GetSampleSet[4+addOn];
bkgrdBytes[0] ← Real.FixC[bkgrdClr.R * 255.0];
bkgrdBytes[1] ← Real.FixC[bkgrdClr.G * 255.0];
bkgrdBytes[2] ← Real.FixC[bkgrdClr.B * 255.0];
};
$Grey => {
bkgrdBytes ← Pixels.GetSampleSet[2+addOn];
bkgrdBytes[0] ← Real.FixC[(bkgrdClr.R+bkgrdClr.G+bkgrdClr.B)/3.0 * 255.0];
};
ENDCASE => {
bkgrdBytes ← Pixels.GetSampleSet[2+addOn];
bkgrdBytes[0] ← ScanConvert.MappedRGB[ context.renderMode, [
Real.FixC[bkgrdClr.R * 255.0],
Real.FixC[bkgrdClr.G * 255.0],
Real.FixC[bkgrdClr.B * 255.0]
] ];
};
bkgrdBytes[alpha^] ← 255;           -- fill in behind, full coverage
IF depth # NIL THEN bkgrdBytes[depth^] ← LAST[CARDINAL]; -- set uncovered to max depth
IF context.extentCovered.bottom > context.extentCovered.top
THEN Pixels.PixelOp[        -- whole viewport, nothing covered
context.display,
[ 0, 0, Ceiling[context.viewPort.w], Ceiling[context.viewPort.h] ],
bkgrdBytes,
$Write
]
ELSE {
Pixels.PixelOp[            -- below previously affected area
context.display,
[ 0, 0, Ceiling[context.viewPort.w], context.extentCovered.bottom ],
bkgrdBytes,
$Write
];
IF Ceiling[context.viewPort.h] > context.extentCovered.top THEN Pixels.PixelOp[
context.display,           -- above previously affected area
[ 0,
context.extentCovered.top,
Ceiling[context.viewPort.w],
Ceiling[context.viewPort.h] - context.extentCovered.top
],
bkgrdBytes,
$Write
];
Pixels.PixelOp[            -- left of previously affected area
context.display,
[ 0,
context.extentCovered.bottom,
context.extentCovered.left,
context.extentCovered.top - context.extentCovered.bottom
],
bkgrdBytes,
$Write
];
IF Ceiling[context.viewPort.w] > context.extentCovered.right THEN Pixels.PixelOp[
context.display,           -- right of previously affected area
[ context.extentCovered.right,
context.extentCovered.bottom,
Ceiling[context.viewPort.w] - context.extentCovered.right,
context.extentCovered.top - context.extentCovered.bottom
],
bkgrdBytes,
$Write
];
Pixels.PixelOp[            -- previously affected area
context.display,
[ context.extentCovered.left,
context.extentCovered.bottom,
context.extentCovered.right - context.extentCovered.left,
context.extentCovered.top - context.extentCovered.bottom
],
bkgrdBytes,
$WriteUnder
];
};
Set uncovered (negative width and height) indicates no alpha information
context.extentCovered ← [Ceiling[context.viewPort.w], 0,
       Ceiling[context.viewPort.h], 0];
};
SetView: PUBLIC PROC[context: REF Context, eyePoint, ptOfInterest: Triple,
       fieldOfView: REAL �.0,
       rollAngle: REAL ← 0.0, upDirection: Triple ← [ 0., 0., 1.],
       hitherLimit: REAL ← .01, yonLimit: REAL ← 1000.0] ~ {
context.eyePoint ← eyePoint;
context.ptOfInterest ← ptOfInterest;
context.fieldOfView ← fieldOfView;
context.rollAngle ← rollAngle;
context.upDirection ← upDirection;
context.hitherLimit ← hitherLimit;
context.yonLimit ← yonLimit;
SetEyeSpace[ context ];
FOR i: NAT IN [0..context.shapes.length) DO
context.shapes[i].vtcesInValid ← TRUE;    -- eyespace and image space will change
IF GetShading[ context.shapes[i], $Shininess] # NIL
THEN context.shapes[i].shadingInValid ← TRUE;     -- highlights will move
ENDLOOP;
};
SetLight: PUBLIC PROC[context: REF Context, name: Rope.ROPE, position: Triple,
       color: RGB ← [1.,1.,1.] ]
    RETURNS
[REF ShapeInstance] ~ {
light: REF ShapeInstance ← FindShape[ context.shapes, name ! Error =>
             IF reason.code = $MisMatch THEN RESUME ];
IF light = NIL THEN {
light ← NewShape[ name ];  -- name not used before
context.lights ← AddShape[ context.lights, light ];  -- used just for lighting
context.shapes ← AddShape[ context.shapes, light ];  -- all shapes
};
light.type ← $Light;
light.location ← position;
light.boundingRadius ← 2 * 93000000.0 * 1609.344; -- twice solar distance in meters
light.orientation ← [0.,0.,0.];       -- no orientation by default
light.positionInValid ← TRUE;
light.vtcesInValid ← TRUE;
PutShading[ light, $Color, NEW[RGB ← color] ];
light.props ← Atom.PutPropOnList[light.props, $Hidden, $DoIt]; -- hide from display routines
FOR i: NAT IN [0..context.shapes.length) DO
context.shapes[i].shadingInValid ← TRUE;
ENDLOOP;
RETURN[light];
};
DeleteLight: PUBLIC PROC[context: REF Context, name: Rope.ROPE] ~ {
context.shapes ← DeleteShape[context.shapes, name];
context.lights ← DeleteShape[context.lights, name];
FOR i: NAT IN [0..context.shapes.length) DO
context.shapes[i].shadingInValid ← TRUE;
ENDLOOP;
};
GetAmbientLight: PUBLIC PROC[context: REF Context, normal: Triple] RETURNS[ambnt: RGB] ~ {
Get an ambient light value from the eyespace normal to the surface
IF context.environment = NIL THEN RETURN[[.2, .2, .2]];
WITH Atom.GetPropFromList[context.environment, $AmbientLight] SELECT FROM
clr: REF RGB => ambnt ← clr^;
light: REF ShapeInstance => {  -- light must be far away, treated as simple direction
dotNL: REAL ← Vector3d.Dot[
Vector3d.Normalize[ [light.centroid.ex, light.centroid.ey, light.centroid.ez] ],
Vector3d.Normalize[ normal ]
];
dotNL ← (dotNL + 1.0) / 2.0;   -- range ambient light over shadowed portions too
ambnt ← NARROW[GetShading[light, $Color], REF RGB]^;
ambnt.R ← ambnt.R*dotNL; ambnt.G ← ambnt.G*dotNL; ambnt.B ← ambnt.B*dotNL;
};
ENDCASE => ambnt ← [.2, .2, .2];
};
ReadScene: PUBLIC PROC[context: REF Context, input: IO.STREAM] ~ {
GetRope: PROC[input: IO.STREAM] RETURNS[Rope.ROPE] ~ { -- chars bound by white space
output: Rope.ROPENIL;
char: CHAR;
[] ← IO.SkipWhitespace[input];       -- Strip whitespace and comments
char ← IO.GetChar[input];
WHILE char # IO.SP AND char # IO.CR DO     -- do until trailing space or CR
output ← Rope.Cat[ output, Rope.FromChar[char] ];
char ← IO.GetChar[input];
ENDLOOP;
RETURN[output];
};
shape: REF ThreeDBasics.ShapeInstance ← NIL;
done: BOOLEANFALSE;
WHILE NOT done DO
keyWd: Rope.ROPE ← GetRope[ input ! IO.EndOfStream => EXIT ];
SELECT TRUE FROM
Rope.Equal[ "View:", keyWd, FALSE] => {
context.eyePoint ← [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ];
context.ptOfInterest ← [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ];
context.upDirection ← [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ];
context.rollAngle ← IO.GetReal[input];
context.fieldOfView ← IO.GetReal[input];
context.hitherLimit ← IO.GetReal[input];
context.yonLimit ← IO.GetReal[input];
SetEyeSpace[context];
};
Rope.Equal[ "ViewPort:", keyWd, FALSE] => {
SetViewPort[ context, [ x: IO.GetReal[input], y: IO.GetReal[input],
        w: IO.GetReal[input], h: IO.GetReal[input] ]
   ];
};
Rope.Equal[ "Window:", keyWd, FALSE] => {
SetWindow[ context, [ x: IO.GetReal[input], y: IO.GetReal[input],
       w: IO.GetReal[input], h: IO.GetReal[input] ]
   ];
};
Rope.Equal[ "BackgroundColor:", keyWd, FALSE] => {
bkgrdColor: REF RGBNEW[RGB];
bkgrdColor^ ← [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ];
context.props ← Atom.PutPropOnList[context.props, $BackGround, bkgrdColor];
};
Rope.Equal[ "Light:",  keyWd, FALSE] => {
[] ← SetLight[
context: context,
name: GetRope[input],
position: [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ],
color: [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ]
];
};
Rope.Equal[ "Shape:",  keyWd, FALSE] => {         -- get shape.
name: Rope.ROPE ← GetRope[input];
fileName: Rope.ROPE ← GetRope[input];
type: ATOMIO.GetAtom[ input ];
insideVisible: BOOLEANIO.GetBool[ input ];
ThreeDMisc.AddShapeAt[context, name, fileName, [0.,0.,0.]];
shape ← FindShape[context.shapes, name];
};
Rope.Equal[ "ScaleShape:",  keyWd, FALSE] => {
scale: REALIO.GetReal[input];
xRatio: REALIO.GetReal[input];
yRatio: REALIO.GetReal[input];
zRatio: REALIO.GetReal[input];
ShapeTwiddle.ScaleShape[ context, shape.name, scale, xRatio, yRatio, zRatio ];
};
Rope.Equal[ "Position:",  keyWd, FALSE] => {
position: Triple ← [ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ];
PlaceShape[ shape, position ];
};
Rope.Equal[ "ShapeXfm:", keyWd, FALSE] => {
shape.position ← NEW[ Matrix3d.MatrixRep ← [
[ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input], IO.GetReal[input], IO.GetReal[input] ]
] ];
shape.positionInValid ← FALSE;
};
Rope.Equal[ "FacetedColor:", keyWd, FALSE] => {
color: RGB;
color.R ← IO.GetReal[input]; color.G ← IO.GetReal[input]; color.B ← IO.GetReal[input];
ThreeDMisc.SetFacetedColor[context, shape.name, color];
};
Rope.Equal[ "SmoothColor:",  keyWd, FALSE] => {
color: RGB;
color.R ← IO.GetReal[input]; color.G ← IO.GetReal[input]; color.B ← IO.GetReal[input];
ThreeDMisc.SetSmoothColor[context, shape.name, color];
};
Rope.Equal[ "Shininess:",  keyWd, FALSE] => {
shininess: REALIO.GetReal[input];
ThreeDMisc.SetShininess[context, shape.name, shininess];
};
Rope.Equal[ "Transmittance:",  keyWd, FALSE] => {
transmittance: REALIO.GetReal[input];
ThreeDMisc.SetTransmittance[context, shape.name, transmittance];
};
Rope.Equal[ "MapTexture:", keyWd, FALSE] => {
IF shape # NIL
THEN {
textureFile: Rope.ROPENIL;
textureImageFileName: Rope.ROPE ← GetRope[input];
textureType: ATOMIO.GetAtom[ input ];
SELECT IO.GetAtom[ input ] FROM
$FromVtxNos => TextureMaps.MakeTxtrCoordsFromVtxNos[ shape,
Real.FixC[IO.GetReal[input]], Real.FixC[IO.GetReal[input]],
[ IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input] ]
];
$FromNormals => {
ThreeDSurfaces.GetVtxNormals[ shape
! Error => IF reason.code = $OnlyForPolygons
THEN CONTINUE
];   -- make sure there are normals to calculate from
TextureMaps.MakeTxtrCoordsFromNormals[shape,
[ IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input] ],
[ IO.GetReal[input], IO.GetReal[input] ]
];
};
ENDCASE => Error[[$MisMatch, "Bad texture coord type"]];
TextureMaps.SetTexture[
shape: shape,
texture: TextureMaps.TextureFromAIS[
context: context,
fileName: textureImageFileName,
type: textureType
]
];
TextureMaps.SumTexture[ shape ];
}
ELSE Error[[$MisMatch, "No shape for texture"]]; -- shape not defined
};
Rope.Equal[ "ScaleTexture:",  keyWd, FALSE] => {
scale: REALIO.GetReal[input];
xRatio: REALIO.GetReal[input];
yRatio: REALIO.GetReal[input];
zRatio: REALIO.GetReal[input];
ShapeTwiddle.ScaleTexture[ context, shape.name, scale, xRatio, yRatio, zRatio ];
};
Rope.Equal[ "SolidTexture:", keyWd, FALSE] => {
IF shape # NIL
THEN ThreeDMisc.ShadingProcName[context, shape.name, GetRope[input]]
ELSE Error[[$MisMatch, "No shape for texture"]]; -- shape not defined
};
Rope.Equal[ "EndOfScene:", keyWd, FALSE] => done ← TRUE;
ENDCASE => Error[[$MisMatch, Rope.Cat[keyWd, " - not understood"]]];
ENDLOOP;
};
WriteScene: PUBLIC PROC[context: REF Context, output: IO.STREAM] ~ {
Vec3toRope: PROC[ r1, r2, r3: REAL] RETURNS[Rope.ROPE] ~ {
rope: Rope.ROPE;
rope ← Rope.Cat[ " ", Convert.RopeFromReal[r1], " ", Convert.RopeFromReal[r2] ];
rope ← Rope.Cat[ rope, " ", Convert.RopeFromReal[r3], " " ];
RETURN[ rope ];
};
Vec4toRope: PROC[ r1, r2, r3, r4: REAL] RETURNS[Rope.ROPE] ~ {
rope: Rope.ROPE;
rope ← Rope.Cat[ " ", Convert.RopeFromReal[r1], " ", Convert.RopeFromReal[r2], " " ];
rope ← Rope.Cat[ rope, Convert.RopeFromReal[r3], " ", Convert.RopeFromReal[r4], " " ];
RETURN[ rope ];
};
ref: REF;
line: Rope.ROPE;
line ← Rope.Cat["View: ",
Vec3toRope[context.eyePoint.x, context.eyePoint.y, context.eyePoint.z],
Vec3toRope[context.ptOfInterest.x, context.ptOfInterest.y, context.ptOfInterest.z],
Vec3toRope[context.upDirection.x, context.upDirection.y, context.upDirection.z]
];
IO.PutRope[ output, Rope.Cat[line,
Vec4toRope[ context.rollAngle, context.fieldOfView, context.hitherLimit, context.yonLimit ],
"\n"
] ];
IO.PutRope[ output, Rope.Cat["ViewPort: ",
Vec4toRope[context.viewPort.x, context.viewPort.y, context.viewPort.w, context.viewPort.h],
"\n"
] ];
IO.PutRope[ output, Rope.Cat["Window: ",
Vec4toRope[context.window.x, context.window.y, context.window.w, context.window.h],
"\n"
] ];
ref ← Atom.GetPropFromList[context.props, $BackGround]; -- get background color
IF ref # NIL THEN {
color: RGBNARROW[ref, REF RGB]^;
IO.PutRope[ output,
    Rope.Cat[ "BackgroundColor: ", Vec3toRope[color.R, color.G, color.B], "\n" ]
   ];
};
Light Sources
FOR i: NAT IN [0..context.lights.length) DO
color: REF RGBNARROW[ GetShading[ context.lights[i], $Color ] ];
IO.PutRope[ output, Rope.Cat["Light: ", context.lights[i].name,
Vec3toRope[
 context.lights[i].location.x, context.lights[i].location.y, context.lights[i].location.z ],
Vec3toRope[color.R, color.G, color.B],
"\n"
] ];
ENDLOOP;
Shapes
FOR i: NAT IN [0..context.shapes.length) DO
shape: REF ThreeDBasics.ShapeInstance ← context.shapes[i];
IF shape # NIL AND Atom.GetPropFromList[shape.props, $Hidden] = NIL
 AND
shape.clipState # out AND shape.surface # NIL THEN {
ref: REF ANYNIL;
xfm: Matrix3d.Matrix ← shape.position;
scale: REF ThreeDBasics.Quad ← NARROW[ GetShading[ shape, $Scale ] ];
color: REF RGBNARROW[ GetShading[ shape, $Color ] ];
shadingType: ATOMNARROW[GetShading[ shape, $Type ] ];
transmtnce: REF REALNARROW[GetShading[ shape, $Transmittance]];
shininess: REF REALNARROW[GetShading[ shape, $Shininess ] ];
texture: REF TextureMaps.TextureMap ← NARROW[ GetShading[ shape, $TextureMap ] ];
textureScale: REF ThreeDBasics.Quad ← NARROW[ GetShading[ shape, $TextureScale ] ];
File Name, instance name, and Surface Type
line ← Rope.Cat[ "Shape: ", shape.name, " ", shape.fileName, " " ];
line ← Rope.Cat[ line, Atom.GetPName[shape.type] ];
IF shape.insideVisible
THEN line ← Rope.Cat[line, " TRUE \n"]
ELSE line ← Rope.Cat[line, " FALSE \n"];
IO.PutRope[ output, line];
Scaling
IF scale # NIL THEN {
line ← Rope.Cat[" ScaleShape: ",
     Vec4toRope[scale.x, scale.y, scale.z, scale.w],
     "\n" ];
IO.PutRope[ output, line];
};
Shape Transform
IF xfm = NIL THEN { SetPosition[shape]; xfm ← shape.position; };
line ← Rope.Cat[" ShapeXfm: ",
     Vec4toRope[xfm[0][0], xfm[0][1], xfm[0][2], xfm[0][3]],
     Vec4toRope[xfm[1][0], xfm[1][1], xfm[1][2], xfm[1][3]] ];
line ← Rope.Cat[line,
     Vec4toRope[xfm[2][0], xfm[2][1], xfm[2][2], xfm[2][3]],
     Vec4toRope[xfm[3][0], xfm[3][1], xfm[3][2], xfm[3][3]], "\n" ];
IO.PutRope[ output, line];
Color, Transmittance, Shininess
IF shadingType = $Smooth
AND GetShading[ shape, $VertexColorsInFile ] = NIL
AND color # NIL THEN IO.PutRope[output,
  Rope.Cat[" SmoothColor: ", Vec3toRope[color.R, color.G, color.B], "\n"] ];
IF shadingType = $Faceted
AND GetShading[ shape, $PatchColorsInFile ] = NIL
AND color # NIL THEN IO.PutRope[output,
  Rope.Cat[" FacetedColor: ", Vec3toRope[color.R, color.G, color.B], "\n"] ];
IF transmtnce # NIL THEN IO.PutRope[ output,
  Rope.Cat[" Transmittance: ", Convert.RopeFromReal[transmtnce^], "\n" ] ];
IF shininess # NIL THEN IO.PutRope[ output,
    Rope.Cat[" Shininess: ", Convert.RopeFromReal[shininess^], "\n" ] ];
Mapped Texture
IF texture # NIL THEN {
fileName: Rope.ROPENARROW[ Atom.GetPropFromList[texture.props, $FileName] ];
coordType: ATOMNARROW[ Atom.GetPropFromList[texture.props, $CoordType] ];
coords: REF ← Atom.GetPropFromList[texture.props, $Coords];
IF coordType = NIL THEN coordType ← $NoCoords;
line ← Rope.Cat[" MapTexture: ", fileName, " ", Atom.GetPName[texture.type] ];
line ← Rope.Cat[line, " ", Atom.GetPName[coordType], " " ];
SELECT coordType FROM
$FromVtxNos, $FromNormals => {
argList: LIST OF REALNARROW[coords];
WHILE argList # NIL DO
line ← Rope.Cat[line, Convert.RopeFromReal[argList.first], " " ];
argList ← argList.rest;
ENDLOOP;
};
ENDCASE => SIGNAL Error[[$Unimplemented, "Unknown texture coordType"]];
IO.PutRope[ output, Rope.Cat[line, "\n"] ];
IF textureScale # NIL THEN {
line ← Rope.Cat[
" ScaleTexture: ",
Vec4toRope[textureScale.x, textureScale.y, textureScale.z, textureScale.w],
"\n" ];
IO.PutRope[ output, line];
};
};
Solid Texture
ref ← GetShading[ shape, $ShadingProcs];
IF ref # NIL THEN {
getVtxProc: ThreeDBasics.VtxToRealSeqProc ← NIL;
getColorProc: ScanConvert.GetColorProc ← NIL;
[getVtxProc, getColorProc] ← NARROW[ ref, REF ThreeDScenes.ShadingProcs ]^;
IF getColorProc # NIL THEN {
procName: Rope.ROPE ← SolidTextures.ProcToRope[getColorProc];
IO.PutRope[ output, Rope.Cat[" SolidTexture: ", procName, "\n"] ];
};
};
};
ENDLOOP;
IO.PutRope[ output, "EndOfScene:\n"];
};
Procedures for Manipulating Shapes
NewShape: PUBLIC PROC[ name: Rope.ROPE ] RETURNS[REF ShapeInstance] ~ {
shape: REF ShapeInstance ← NEW [ShapeInstance ];
shape.namename;
RETURN [shape];
};
FindShape: PUBLIC PROC[ set: REF ShapeSequence, name: Rope.ROPE ]
     RETURNS
[REF ShapeInstance] ~ {
IF set = NIL THEN RETURN [NIL];
FOR i: NAT IN [0..set.length) DO
IF Rope.Equal[ name, set[i].name, FALSE ] THEN RETURN[set[i]];
ENDLOOP;
SIGNAL Error[[$MisMatch, "No such shape name"]];
RETURN [NIL];
};
AddShape: PUBLIC PROC[ set: REF ShapeSequence, shape: REF ShapeInstance ]
     RETURNS
[REF ShapeSequence] ~ {
newSet: REF ShapeSequence;
IF set # NIL
THEN {
newSet ← NEW [ ShapeSequence[set.length + 1] ];
FOR i: NAT IN [0..set.length) DO newSet[i] ← set[i]; ENDLOOP;
newSet[set.length] ← shape;
}
ELSE {
newSet ← NEW [ ShapeSequence[1] ];
newSet[0] ← shape;
};
RETURN [newSet];
};
DeleteShape: PUBLIC PROC[ set: REF ShapeSequence, name: Rope.ROPE ]
     RETURNS
[REF ShapeSequence] ~ {
newSet: REF ShapeSequence;
j: NAT ← 0;
IF set # NIL
THEN newSet ← NEW [ ShapeSequence[set.length - 1] ]
ELSE SIGNAL Error[[$MisMatch, "No shapes to delete from"]];
FOR i: NAT IN [0..set.length) DO
IF NOT Rope.Equal[ name, set[i].name, FALSE ]
THEN IF j < newSet.length
THEN { newSet[j] ← set[i]; j ← j + 1; }
ELSE { SIGNAL Error[[$MisMatch, "Can't delete - not there"]];
   RETURN[set]; };
ENDLOOP;
RETURN [newSet];
};
CopyShape: PUBLIC PROC[ shape: REF ShapeInstance, newName: Rope.ROPE ]
    RETURNS
[REF ShapeInstance] ~ {
list: Atom.PropList;
newShape: REF ShapeInstance ← NEW[ShapeInstance];
newShape^ ← shape^;
newShape.name ← newName;
newShape.vertex ← NEW[ VertexSequence[shape.vertex.length] ];
FOR i: NAT IN [0..shape.vertex.length] DO
newShape.vertex[i] ← NEW[ThreeDBasics.Vertex ← shape.vertex[i]^ ];
ENDLOOP;
newShape.shade ← NEW[ ShadingSequence[shape.shade.length] ];
FOR i: NAT IN [0..shape.shade.length] DO
newShape.shade[i] ← NEW[ThreeDBasics.ShadingValue ← shape.shade[i]^ ];
ENDLOOP;
newShape.surface ← ?
- Can't copy this since we can't know the type here, will point to the original
newShape.shadingProps ← NIL;
list ← shape.shadingProps;
WHILE list # NIL DO
newShape.shadingProps ← CONS[list.first, newShape.shadingProps];
list ← list.rest;
ENDLOOP;
newShape.props ← NIL;
list ← shape.props;
WHILE list # NIL DO
newShape.props ← CONS[list.first, newShape.props];
list ← list.rest;
ENDLOOP;
RETURN[newShape];
};
PlaceShape: PUBLIC PROC[ shape: REF ShapeInstance, location: Triple ] ~ {
shape.location ← location;
shape.positionInValid ← TRUE;
shape.vtcesInValid ← TRUE;
shape.shadingInValid ← TRUE;
};
MoveShape: PUBLIC PROC[ shape: REF ShapeInstance, delta: Triple ] ~ {
shape.location ← Vector3d.Add[shape.location, delta];
shape.positionInValid ← TRUE;
shape.vtcesInValid ← TRUE;
shape.shadingInValid ← TRUE;
};
OrientShape: PUBLIC PROC[ shape: REF ShapeInstance, axis: Triple] ~ {
shape.orientation ← axis;
shape.positionInValid ← TRUE;
shape.vtcesInValid ← TRUE;
shape.shadingInValid ← TRUE;
};
RotateShape: PUBLIC PROC[ shape: REF ShapeInstance, axisBase, axisEnd: Triple, theta: REAL ] ~ {
shape.axisBase ← axisBase;
shape.axisEnd ← axisEnd;
shape.rotation ← theta;
shape.positionInValid ← TRUE;
shape.vtcesInValid ← TRUE;
shape.shadingInValid ← TRUE;
};
SetPosition: PUBLIC PROC[shape: REF ShapeInstance, concat: BOOLEANFALSE] ~ {
hypotenuse: REAL ← RealFns.SqRt[ Sqr[shape.orientation.x] + Sqr[shape.orientation.y] ];
IF NOT concat THEN shape.position ← Matrix3d.Identity[];  -- clear to identity transform
shape.position ← Matrix3d.Mul[
shape.position,              -- rotation about arbitrary axis
Matrix3d.MakeRotate[
axis: Vector3d.Sub[shape.axisEnd, shape.axisBase],
theta: shape.rotation,
base: shape.axisBase
]
];
IF hypotenuse > 0.0             -- orientation
THEN {
length: REAL ← Vector3d.Length[shape.orientation];
cosA, sinA, cosB, sinB: REAL;
cosA ← shape.orientation.x / hypotenuse;
sinA ← shape.orientation.y / hypotenuse;
shape.position ← Matrix3d.Mul[
shape.position,   -- longitudinal rotation into x-z plane, left handed about z-up
NEW[ Xfm3dRep ← [ [cosA,-sinA,0.,0.], [sinA,cosA,0.,0.], [0.,0.,1.,0.], [0.,0.,0.,1.] ] ]
];
cosB ← shape.orientation.z / length;
sinB ← hypotenuse / length;
shape.position ← Matrix3d.Mul[
shape.position,       -- latitudinal rotation, right-handed about y-north
NEW[ Xfm3dRep ← [ [cosB,0.,-sinB,0.], [0.,1.,0.,0.], [sinB,0.,cosB,0.], [0.,0.,0.,1.] ] ]
];
shape.position ← Matrix3d.Mul[
shape.position,   -- longitudinal rotation from x-z plane, right handed about z-up
NEW[ Xfm3dRep ← [ [cosA,sinA,0.,0.], [-sinA,cosA,0.,0.], [0.,0.,1.,0.], [0.,0.,0.,1.] ] ]
];
}
ELSE IF shape.orientation.z < 0.0 THEN shape.position ← Matrix3d.Mul[
shape.position,              -- turn upside down
NEW[ Xfm3dRep ← [ [-1.,0.,0.,0.], [0.,1.,0.,0.], [0.,0.,-1.,0.], [0.,0.,0.,1.] ] ]
];
shape.position ← Matrix3d.Mul[
shape.position,              -- translation
NEW[ Xfm3dRep ← [[1.,0.,0.,0.], [0.,1.,0.,0.], [0.,0.,1.,0.],
     [shape.location.x, shape.location.y, shape.location.z, 1.]] ]
];
shape.positionInValid ← FALSE;
};
PutShading: PUBLIC PROC[ shape: REF ShapeInstance, key: ATOM, value: REF ANY] ~ {
shape.shadingProps ← Atom.PutPropOnList[ shape.shadingProps, key, value ];
};
GetShading: PUBLIC PROC[ shape: REF ShapeInstance, key: ATOM ]
     RETURNS
[value: REF ANY] ~ {
value ← Atom.GetPropFromList[ shape.shadingProps, key ];
};
Procedures for Manipulating Vertices
InitShades: PUBLIC PROC[shape: REF ShapeInstance] RETURNS[shade: REF ShadingSequence] ~{
color: RGB;
transmittance: REAL;
IF GetShading[ shape, $Color ] # NIL
THEN color ← NARROW[ GetShading[shape, $Color], REF RGB ]^
ELSE color ← [0.7, 0.7, 0.7];       -- default grey
IF GetShading[ shape, $Transmittance ] # NIL
THEN transmittance ← NARROW[ GetShading[shape, $Transmittance], REF REAL ]^
ELSE transmittance ← 0.0;
shade ← shape.shade;
FOR i: NAT IN [0..shape.shade.length) DO
shade[i].r ← color.R;
shade[i].g ← color.G;
shade[i].b ← color.B;
shade[i].t ← transmittance;
ENDLOOP;
};
GetClipCodeForPt: PUBLIC PROC[context: REF Context, pt: Triple] RETURNS[clip: OutCode] ~ {
Compute outcode for one set of coordinates in eyespace
clip.bottom← Plane3d.DistanceToPt[ pt, context.clippingPlanes[Bottom]] < 0.;
clip.top ← Plane3d.DistanceToPt[ pt, context.clippingPlanes[Top] ] < 0.;
clip.left ← Plane3d.DistanceToPt[ pt, context.clippingPlanes[Left] ] < 0.;
clip.right ← Plane3d.DistanceToPt[ pt, context.clippingPlanes[Right] ] < 0.;
clip.near ← Plane3d.DistanceToPt[ pt, context.clippingPlanes[Near] ] < 0.;
clip.far ← Plane3d.DistanceToPt[ pt, context.clippingPlanes[Far] ] < 0.;
};
XfmPtToEyeSpace: PUBLIC PROC[context: REF Context, pt: Triple] RETURNS[Triple, OutCode] ~ {
 Transform Vertex to Eye Space
pt ← Matrix3d.Transform[ pt, context.eyeSpaceXfm ];
RETURN[ pt, GetClipCodeForPt[context, pt] ];
};
XfmTripleToDisplay: PROC[pt: Triple, xfm: ScaleAndAddXfm, resFctr: Triple, offset: REAL]
       RETURNS[result: Triple] ~ {
 Transform vertex from eyespace to display coordinates - local utility
result.x ← xfm.scaleX*pt.x/pt.z + xfm.addX;  -- convert to normalized display coords
result.y ← xfm.scaleY*pt.y/pt.z + xfm.addY;
result.z ← xfm.scaleZ/pt.z + xfm.addZ;
result.x ← MAX[0.0, MIN[resFctr.x - aLilBit, resFctr.x * result.x] ]; -- convert to screen space
result.y ← MAX[0.0, MIN[resFctr.y - aLilBit, resFctr.y * result.y] ];
result.z ← MAX[0.0, MIN[resFctr.z - aLilBit, resFctr.z * result.z] ];
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: REF Context, shape: REF ShapeInstance, pt: Triple]
       RETURNS[Triple] ~ {
 Transform vertex from eyespace to display coordinates
result: Triple ← XfmTripleToDisplay[
pt: pt,
xfm: context.eyeToNDC,
resFctr: [context.viewPort.w, context.viewPort.h, context.depthResolution],
offset: IF context.alphaBuffer THEN .5 ELSE 0.0   -- nojaggy tiler offsets by 1/2 pixel
];
IF shape # NIL THEN {
xLo: NAT ← Real.FixC[ MAX[0.0, result.x - 2.0] ];
xHi: NAT ← Ceiling[ MIN[context.viewPort.w, result.x + 2.0] ];
yLo: NAT ← Real.FixC[ MAX[0.0, result.y - 2.0] ];
yHi: NAT ← Ceiling[ MIN[context.viewPort.h, result.y + 2.0] ];
IF shape.screenExtent.left > xLo THEN shape.screenExtent.left ← xLo;
IF shape.screenExtent.right < xHi THEN shape.screenExtent.right ← xHi;
IF shape.screenExtent.bottom > yLo THEN shape.screenExtent.bottom ← yLo;
IF shape.screenExtent.top < yHi THEN shape.screenExtent.top ← yHi;
};
RETURN [ result ];
};
ShadeVtx: PUBLIC PROC [context: REF Context, pt: VertexInfo, shininess: REAL]
   RETURNS [result: RGB, transmittance: REAL ← 0.0] ~ {
shinyPwr: NAT ← Real.Fix[shininess] * 2; -- makes highlights same size as in ShinyTiler
toLightSrc, toEye: Triple;
dotNL, dotNE, specularSum: REAL ← 0.0;
ambient, diffuse, specular: RGB ← [0.0, 0.0, 0.0];
result ← [0., 0., 0.];
toEye ← Vector3d.Normalize[[-pt.coord.ex, -pt.coord.ey, -pt.coord.ez]]; -- direction to eye
[ [pt.shade.exn, pt.shade.eyn, pt.shade.ezn] ] ← Vector3d.Normalize[
[pt.shade.exn, pt.shade.eyn, pt.shade.ezn]    -- often not normalized
];
Get ambient component of light
ambient ← GetAmbientLight[ context, [pt.shade.exn, pt.shade.eyn, pt.shade.ezn] ];
result.R ← ambient.R * pt.shade.r;
result.G ← ambient.G * pt.shade.g;
result.B ← ambient.B * pt.shade.b;
FOR i: NAT IN [0..context.lights.length) DO   -- do for each light source
lightClr: RGBNARROW[GetShading[ context.lights[i], $Color], REF RGB ]^; -- get color
Get Light Direction from Surface
toLightSrc ← Vector3d.Normalize[ [    -- vector to light source from surface pt.
context.lights[i].centroid.ex - pt.coord.ex,
context.lights[i].centroid.ey - pt.coord.ey,
context.lights[i].centroid.ez - pt.coord.ez
] ];
Get Light Strength
IF context.lights[i].orientation # [0.0, 0.0, 0.0] THEN {   -- spotlight, get direction
dotLS, intensity: REAL;
shineDirection: Triple ← Matrix3d.TransformVec[ context.lights[i].orientation,
               context.eyeSpaceXfm ];
spotSpread: NAT ← Real.Fix[ NARROW[GetShading[context.lights[i], $Shininess],
           REF REAL]^ ];
dotLS ← -Vector3d.Dot[toLightSrc, shineDirection];
IF dotLS > 0. THEN {        -- compute spotlight factor
binaryCount: NAT ← spotSpread;
intensity ← 1.;
WHILE binaryCount > 0 DO    -- compute power by repeated squares
IF (binaryCount MOD 2) = 1 THEN intensity ← intensity*dotLS;
dotLS ← dotLS*dotLS;
binaryCount ← binaryCount/2;
ENDLOOP;
}
ELSE intensity ← 0.;
IF intensity < ScanConvert.justNoticeable THEN LOOP; -- no effect, skip this light 
lightClr.R ← lightClr.R*intensity;
lightClr.G ← lightClr.G*intensity;
lightClr.B ← lightClr.B*intensity;
};
Get Basic Lambertian Shade
dotNL ← Vector3d.Dot[toLightSrc, [pt.shade.exn, pt.shade.eyn, pt.shade.ezn]];
IF dotNL <= 0. THEN LOOP;       -- surface faces away from light, skip
diffuse.R ← (1. - ambient.R) * dotNL * lightClr.R * pt.shade.r; -- surface facing the light
diffuse.G ← (1. - ambient.G) * dotNL * lightClr.G * pt.shade.g;
diffuse.B ← (1. - ambient.B) * dotNL * lightClr.B * pt.shade.b;
Get Highlight Contribution
IF shinyPwr > 0 THEN {    -- compute Phong specular component
pctHilite: REAL ← 0.0;
halfWay: Triple ← Vector3d.Normalize[   -- normalized average of vectors
Vector3d.Mul[
Vector3d.Add[toEye, toLightSrc],
0.5
]
];
dotNormHalfWay: REAL ← Vector3d.Dot[  -- cos angle betw. normal and average
[pt.shade.exn, pt.shade.eyn, pt.shade.ezn],
halfWay
];
IF dotNormHalfWay > 0. THEN {
binaryCount: NAT ← shinyPwr;
pctHilite ← 1.0;
WHILE binaryCount > 0 DO    -- compute power by repeated squares
IF (binaryCount MOD 2) = 1 THEN pctHilite ← pctHilite*dotNormHalfWay;
dotNormHalfWay ← dotNormHalfWay*dotNormHalfWay;
binaryCount ← binaryCount/2;
ENDLOOP;
};
Add in Highlight
specular.R ← (1.0 - diffuse.R - ambient.R) * pctHilite * lightClr.R;
specular.G ← (1.0 - diffuse.G - ambient.G) * pctHilite * lightClr.G;
specular.B ← (1.0 - diffuse.B - ambient.B) * pctHilite * lightClr.B;
specularSum ← specularSum + pctHilite;
};
result.R ← result.R + diffuse.R + specular.R;
result.G ← result.G + diffuse.G + specular.G;
result.B ← result.B + diffuse.B + specular.B;
ENDLOOP;
Get transmittance if transparent
IF pt.shade.t > 0.0 THEN {  -- transmittance is cosine of angle between to eye and normal
dotNE ← ABS[ Vector3d.Dot[toEye, [pt.shade.exn, pt.shade.eyn, pt.shade.ezn]] ];
transmittance ← MIN[1.0 - specularSum, dotNE*pt.shade.t]; -- make highlights more opaque
transmittance ← MAX[0.0, MIN[transmittance, 1.]];
};
result.R ← MAX[0.0, MIN[result.R, 1.]];
result.G ← MAX[0.0, MIN[result.G, 1.]];
result.B ← MAX[0.0, MIN[result.B, 1.]];
};
XfmToEyeSpace: PUBLIC PROC[context: REF Context, shape: REF ShapeInstance]
  RETURNS
[ClipState] ~ {
Transform Vertices and Centroid to Eye Space, calculate clip codes at vertices
ClipBoundingBall: PROC[] RETURNS[ClipState] ~ {
Do gross clip test on bounding sphere, all in or all out allow rejection of entire object
clipFlag: BOOLEANFALSE;
FOR plane: SixSides IN SixSides DO
distance: REAL ← Plane3d.DistanceToPt[
[shape.centroid.ex, shape.centroid.ey, shape.centroid.ez],
context.clippingPlanes[plane]
];
IF distance < -shape.boundingRadius THEN RETURN[out]
ELSE IF distance < shape.boundingRadius THEN clipFlag ← TRUE;
ENDLOOP;
IF clipFlag THEN RETURN[clipped] ELSE RETURN[in];
};
xfm: Xfm3d;
IF shape.positionInValid THEN SetPosition[shape];   -- fix bnding ball & position matrix
xfm ← Matrix3d.Mul[shape.position, context.eyeSpaceXfm];
[[shape.centroid.ex, shape.centroid.ey, shape.centroid.ez]] ← Matrix3d.Transform[
[shape.centroid.x, shape.centroid.y, shape.centroid.z],   -- Update shape centroid
xfm
];
shape.clipState ← ClipBoundingBall[];
IF shape.clipState # out THEN {-- run through vertices and shading and transform
patchInfo: REF ThreeDBasics.ShadingSequence ← NARROW[
GetShading[ shape, $PatchColors ]
];
andOfCodes: OutCode ← AllOut;     -- test for trivially out
orOfCodes: OutCode ← NoneOut;     -- test for trivially in
IF shape.vertex # NIL THEN FOR i: NAT IN [0..shape.vertex.length) DO
IF shape.vertex[i] # NIL THEN {
OPEN shape.vertex[i];
[ [ex, ey, ez] ] ← Matrix3d.Transform[ [x, y, z] , xfm];  -- transform points to eyespace
IF shape.clipState # in
THEN {
clip ← 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;
};
ENDLOOP;
IF orOfCodes = NoneOut  THEN shape.clipState ← in
ELSE IF andOfCodes # NoneOut THEN shape.clipState ← out;
IF shape.shade # NIL AND shape.clipState # out
THEN FOR i: NAT IN [0..shape.shade.length) DO
IF shape.shade[i] # NIL THEN {
OPEN shape.shade[i];          -- transform normal vectors
[ [exn, eyn, ezn] ] ← Matrix3d.TransformVec[ [xn, yn, zn] , xfm];
};
ENDLOOP;
IF patchInfo # NIL AND shape.clipState # out
THEN FOR i: NAT IN [0..patchInfo.length) DO
IF patchInfo[i] # NIL THEN {
OPEN patchInfo[i];          -- transform normal vectors
[ [exn, eyn, ezn] ] ← Matrix3d.TransformVec[ [xn, yn, zn] , xfm];
};
ENDLOOP;
};
shape.vtcesInValid ← FALSE;
RETURN[shape.clipState];
};
XfmToDisplay: PUBLIC PROC[context: REF Context, shape: REF ShapeInstance] ~ {
xMin, yMin: REAL ← 32767.0;
xMax, yMax: REAL ← 0.0;
run through vertices and transform
IF shape.vertex # NIL THEN FOR i: NAT IN [0..shape.vertex.length) DO
IF shape.vertex[i] # NIL THEN {
OPEN shape.vertex[i];
IF clip = NoneOut THEN {
[ [sx, sy, sz] ] ← XfmTripleToDisplay[
pt: [ex, ey, ez],
xfm: context.eyeToNDC,
resFctr: [context.viewPort.w, context.viewPort.h, context.depthResolution],
offset: IF context.alphaBuffer THEN .5 ELSE 0.0 -- nojaggy tiler offsets by 1/2 pixel
];
IF sx < xMin THEN xMin ← sx; IF sy < yMin THEN yMin ← sy;
IF sx > xMax THEN xMax ← sx; IF sy > yMax THEN yMax ← sy;
}
ELSE {
IF clip.left THEN xMin ← 0.0;  
IF clip.right THEN xMax ← context.viewPort.w;
IF clip.bottom THEN yMin ← 0.0;
IF clip.top THEN yMax ← context.viewPort.h;
};
};
ENDLOOP;
Expand extent by two pixels, NOTE!! this is not complete in the presence of clipping
xMin ← MAX[0.0, xMin-2.0]; xMax ← MIN[context.viewPort.w, xMax+2.0];
yMin ← MAX[0.0, yMin-2.0]; yMax ← MIN[context.viewPort.h, yMax+2.0];
shape.screenExtent ← [ left: Real.FixC[xMin], right: Ceiling[xMax],
       bottom: Real.FixC[yMin], top: Ceiling[yMax] ];
};
GetVtxShades: PUBLIC PROC[ context: REF Context, shape: REF ShapeInstance] ~ {
IF shape.shade # NIL THEN FOR i: NAT IN [0..shape.shade.length) DO
IF shape.shade[i] # NIL THEN {
trns: REAL ← 0.0;   -- transmittance
pt: VertexInfo ← [ shape.vertex[i]^, shape.shade[i]^, NIL ];
[ [shape.shade[i].er, shape.shade[i].eg, shape.shade[i].eb], shape.shade[i].et]
← ShadeVtx[context, pt, 0.0];  -- calculate shade
};
ENDLOOP;
};
END.