ThreeDScenesImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, June 5, 1986 6:16:18 pm PDT
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],
IO      USING [STREAM, GetAtom, GetBool, GetChar, GetInt, GetReal,
        PutRope, EndOfStream, SkipWhitespace, SP, CR],
FS      USING [StreamOpen],
Convert    USING [RopeFromReal],
Terminal    USING [Current],
QuickViewer   USING [Reset],
Imager    USING [Context, Rectangle, MaskRectangle, ClipRectangle, TranslateT,
        SetColor],
ImagerColor   USING [ColorFromRGB, RGB],
ImagerBackdoor  USING [GetBounds],
SampleMapOps  USING [FromSubMap],
Vector3d    USING [Dot, Pair, Triple, Quad, Normalize, Sub, Cross, Add, Mag,
         Mul],
Matrix3d    USING [Matrix, MatrixRep, Mul, Identity, MakeRotate, Transform,
        TransformVec],
Plane3d    USING [DistanceToPt],
Pixels     USING [PixelBuffer, Extent, GetSampleSet, SampleSet, PixelOp,
        GetFromImagerContext, Create],
ScanConvert   USING [IntRGB, MappedRGB, justNoticeable, GetColorProc],
TextureMaps   USING [MakeTxtrCoordsFromVtxNos, MakeTxtrCoordsFromNormals,
        SetTexture, SumTexture, TextureMap, TextureFromAIS],
SolidTextures   USING [ProcToRope],
ThreeDSurfaces  USING [GetVtxNormals],
ThreeDMisc   USING [AddShapeAt, GetImagerContext, GetMappedColor,
        GetPolygonColors, GetVertexColors, LoadColorRamp,
        LoadStd8BitClrMap, SetFacetedColor, SetSmoothColor,
        SetShininess, SetTransmittance, ShadingProcName],
ThreeDScenes  USING [ErrorDesc, ScaleAndAddXfm, SixSides, OutCode, NoneOut,
        AllOut, ShapeInstance, ShapeSequence, Context,
        ShadingSequence, ShadingValue, ClipState, Vertex, VertexInfo,
        VertexSequence, VtxToRealSeqProc, ShadingProcs];
ThreeDScenesImpl: CEDAR PROGRAM
IMPORTS Atom, Real, RealFns, Imager, Rope, IO, FS, Basics, ImagerColor, Vector3d, Matrix3d,
   ThreeDMisc, Pixels, QuickViewer, ImagerBackdoor, Terminal, ScanConvert, Plane3d,
   SampleMapOps, TextureMaps, ThreeDSurfaces, Convert, SolidTextures
EXPORTS ThreeDScenes
~ BEGIN
Error: PUBLIC SIGNAL [reason: ThreeDScenes.ErrorDesc] = CODE;
Basic Types
RGB: TYPE ~ ImagerColor.RGB;
IntRGB: TYPE ~ ScanConvert.IntRGB;
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 ~ ThreeDScenes.ScaleAndAddXfm;
RECORD[scaleX, scaleY, scaleZ, addX, addY, addZ: REAL];
SixSides: TYPE ~ ThreeDScenes.SixSides;  -- {Left, Right, Bottom, Top, Near, Far}
ClipState: TYPE ~ ThreeDScenes.ClipState;   -- {in, out, clipped}
OutCode: TYPE ~ ThreeDScenes.OutCode; -- RECORD[bottom,top,left,right,near,far: BOOLEAN]
NoneOut: OutCode ~ ThreeDScenes.NoneOut; -- [FALSE,FALSE,FALSE,FALSE,FALSE,FALSE]
AllOut: OutCode ~ ThreeDScenes.AllOut; -- [TRUE, TRUE, TRUE, TRUE, TRUE, TRUE]
Context: TYPE ~ ThreeDScenes.Context;
ShapeInstance: TYPE ~ ThreeDScenes.ShapeInstance;
ShapeSequence: TYPE ~ ThreeDScenes.ShapeSequence;
ShadingSequence: TYPE ~ ThreeDScenes.ShadingSequence;
ShadingValue: TYPE ~ ThreeDScenes.ShadingValue;
Vertex: TYPE ~ ThreeDScenes.Vertex;
VertexInfo: TYPE ~ ThreeDScenes.VertexInfo;
VertexSequence: TYPE ~ ThreeDScenes.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;
};
Procedures for Setting up Contexts
GetFromImagerContext: PUBLIC PROC[ imagerCtx: Imager.Context,
            alpha, depth, grey: BOOLEANFALSE ]
        RETURNS [REF Context] ~ {
Sets up context using imager context where possible, optional alpha buffer and greyscale
bounds: Imager.Rectangle;
context: REF ThreeDScenes.Context ← NEW[ThreeDScenes.Context];
[context.display, bounds] ← Pixels.GetFromImagerContext[ imagerCtx, alpha, depth ];
IF context.display.samplesPerPixel > 2
THEN context.renderMode ← $Dorado24
ELSE IF Atom.GetPropFromList[ context.display.props, $RenderMode ] = $LF
THEN { context.renderMode ← $LF; alpha ← FALSE; }
ELSE IF grey OR alpha
THEN context.renderMode ← $Grey
ELSE context.renderMode ← $PseudoClr;
SELECT context.renderMode FROM
$PseudoClr => ThreeDMisc.LoadStd8BitClrMap[ Terminal.Current[] ];
$Grey   =>
ThreeDMisc.LoadColorRamp[ Terminal.Current[], [0.,0.,0.], [1.,1.,1.], [.43, .43, .43] ];
ENDCASE;
context.alphaBuffer ← alpha;
context.depthBuffer ← depth;
context.eyeSpaceXfm ← Matrix3d.Identity[];
context.viewPort ← bounds;
context.window ← WindowFromViewPort[context.viewPort];
RETURN[context];
};
Create: PUBLIC PROC[width, height: NAT, renderMode: ATOM, alpha, depth: BOOLEANFALSE]
   RETURNS [REF Context] ~ {
pixelSizes: SampleSet;
context: REF ThreeDScenes.Context ← NEW[ThreeDScenes.Context];
addOn: NAT ← 0;
AddAuxBuffers: PROC[] ~ {
IF alpha THEN pixelSizes[pixelSizes.length-addOn] ← 8;
IF depth THEN pixelSizes[pixelSizes.length-1] ← 16;
};
IF alpha THEN addOn ← addOn+1; IF depth THEN addOn ← addOn+1;
context.alphaBuffer ← alpha;
context.depthBuffer ← depth;
context.renderMode ← renderMode;
SELECT renderMode FROM
$LF => {
pixelSizes ← Pixels.GetSampleSet[1+addOn]; pixelSizes[0] ← 1;
AddAuxBuffers[];
context.display ← Pixels.Create[ width, height, pixelSizes ];
};
$Grey, $Dithered, $PseudoClr => {
pixelSizes ← Pixels.GetSampleSet[1+addOn]; pixelSizes[0] ← 8;
AddAuxBuffers[];
context.display ← Pixels.Create[ width, height, pixelSizes ];
};
$Dorado24 => {
big1: NAT;
pixelSizes ← Pixels.GetSampleSet[3+addOn]; big1 ← 0;
pixelSizes[1] ← 0; pixelSizes[0] ← 16; pixelSizes[2] ← 8;
AddAuxBuffers[];
context.display ← Pixels.Create[ width, height, pixelSizes ];
context.display.pixels[big1].subMap.sampleMap.bitsPerSample ← 8; -- make byte samples
context.display.pixels[big1].subMap.sampleMap.fSize ←
         context.display.pixels[big1].subMap.sampleMap.fSize * 2;
context.display.pixels[big1+1].subMap.sampleMap ← SampleMapOps.FromSubMap[ [
sampleMap: context.display.pixels[big1].subMap.sampleMap,
start: [f: 1, s: 0]         -- offset by one to get lower bytes
] ];
context.display.pixels[big1+1].df ← context.display.pixels[big1].df ← 2;
context.display.pixels[big1+1].subMap.size.f ← 2 *
             context.display.pixels[big1+1].subMap.size.f;
context.display.pixels[big1].subMap.size.f ← 2 * context.display.pixels[big1].subMap.size.f;
};
$FullClr => {
pixelSizes ← Pixels.GetSampleSet[3+addOn];
FOR i: NAT IN [0..3) DO pixelSizes[i] ← 8; ENDLOOP;
AddAuxBuffers[];
context.display ← Pixels.Create[ width, height, pixelSizes ];
};
ENDCASE => SIGNAL Error[[$Unimplemented, "Unknown render mode"]];
IF alpha THEN context.display.props ← Atom.PutPropOnList[
       context.display.props, $Alpha, NEW[NAT ← pixelSizes.length-addOn] ];
IF depth THEN context.display.props ← Atom.PutPropOnList[
       context.display.props, $Depth, NEW[NAT ← pixelSizes.length-1] ];
context.eyeSpaceXfm ← Matrix3d.Identity[];
context.viewPort ← [0, 0, width, height];
context.window ← WindowFromViewPort[context.viewPort];
RETURN[context];
};
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, Real.RoundC[context.viewPort.w], Real.RoundC[context.viewPort.h] ];
aspectRatio ← IF bounds.h > 0 THEN Real.Float[bounds.w] / bounds.h ELSE 1.0;
transform from world to eye
in ← Vector3d.Normalize[Vector3d.Sub[context.ptOfInterest, context.eyePoint]];
right ← Vector3d.Normalize[Vector3d.Cross[in, context.upDirection]];
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
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, MAX[0.0, context.window.w] ];
wndw.y ← MIN[1.0, MAX[-1.0, context.window.y] ];
wndw.h ← MIN[1.0-wndw.y, MAX[0.0, 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,
        clear: BOOLEANFALSE] ~{
This must be called before SetView since it defines the aspect ratio
bounds: Imager.Rectangle ← size;
imagerCtx: Imager.Context ← NARROW[
Atom.GetPropFromList[context.display.props, $ImagerContext]
];
IF size.w <= 0.0 OR size.h <= 0.0
THEN SIGNAL Error[[$MisMatch, "Null clipper"]];
IF context.renderMode # $LF THEN FOR i: NAT IN [0..context.display.pixels.length) DO
x: INTEGER ← MAX[ 0, MIN[ Real.RoundI[MAX[size.x, bounds.x]], context.display.width ] ];
y: INTEGER ← MAX[ 0, MIN[ Real.RoundI[MAX[size.y, bounds.y]], context.display.height ]];
w: INTEGER ← MIN[Real.RoundC[MIN[size.w, bounds.w] ], context.display.width];
h: INTEGER ← MIN[Real.RoundC[MIN[size.h, bounds.h] ], context.display.height];
y ← context.display.height - (y + h);     -- invert for upside-down sampleMap
context.display.pixels[i].subMap.start ← [ f: x, s: y ];     -- upper left corner
context.display.pixels[i].subMap.size ← [f: w, s: h];      -- dimensions
ENDLOOP;
context.viewPort ← size;
IF imagerCtx # NIL THEN {
QuickViewer.Reset[imagerCtx];
Imager.ClipRectangle[imagerCtx, [size.x, size.y, size.w, size.h] ];
Imager.TranslateT[ imagerCtx, [size.x, size.y] ];
IF clear THEN FillViewPort[context, [0.0, 0.0, 0.0] ];
};
};
FillViewPort: PUBLIC PROC[context: REF Context, clr: RGB] ~ {
iClr: IntRGB ← [ Real.RoundC[clr.R*255], Real.RoundC[clr.G*255], Real.RoundC[clr.B*255] ];
pixelBytes: SampleSet ← Pixels.GetSampleSet[1];
addOn: NAT ← 0;
IF context.alphaBuffer THEN addOn ← addOn + 1;
IF context.depthBuffer THEN addOn ← addOn + 1;
SELECT context.renderMode FROM 
$Dithered, $LF => {       -- use imager, where possible, in this mode
imagerCtx: Imager.Context ← ThreeDMisc.GetImagerContext[context];
bounds: Imager.Rectangle ← ImagerBackdoor.GetBounds[imagerCtx];
Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ];
Imager.MaskRectangle[ imagerCtx, bounds ];
RETURN;
};
$PseudoClr => {
IF pixelBytes.length < 1+addOn THEN pixelBytes ← Pixels.GetSampleSet[1+addOn];
pixelBytes[0] ← ThreeDMisc.GetMappedColor[ context, iClr ];
};
$Grey   => {
IF pixelBytes.length < 1+addOn THEN pixelBytes ← Pixels.GetSampleSet[1+addOn];
pixelBytes[0] ← (iClr.r + iClr.g + iClr.b)/3;
};
$FullClr, $Dorado24  => {
IF pixelBytes.length < 3+addOn THEN pixelBytes ← Pixels.GetSampleSet[3+addOn];
pixelBytes[0] ← iClr.r;
pixelBytes[1] ← iClr.g;
pixelBytes[2] ← iClr.b
};
ENDCASE  => SIGNAL Error[[$Unimplemented, "Unknown renderMode"]];
IF context.alphaBuffer THEN {
alpha: REF NATNARROW[ Atom.GetPropFromList[context.display.props, $Alpha] ];
pixelBytes[alpha^] ← 0;     -- clear alpha buffer to transparent (or uncovered)
Set uncovered (negative width and height) indicates no alpha information
context.extentCovered ← [Real.RoundC[context.viewPort.w], 0,  -- left, right
       Real.RoundC[context.viewPort.h], 0];  -- bottom, top
};
IF context.depthBuffer THEN {
depth: REF NATNARROW[ Atom.GetPropFromList[context.display.props, $Depth] ];
pixelBytes[depth^] ← LAST[CARDINAL];     -- clear depth buffer to max depth
};
Pixels.PixelOp[
buf: context.display,
area: [ 0, 0, Real.RoundC[context.viewPort.w], Real.RoundC[context.viewPort.h] ],
pixel: 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 =>
SIGNAL Error[[$Unimplemented, "No background images yet"]];
ENDCASE => IF NOT context.alphaBuffer THEN FillViewPort[context, [0.0,0.0,0.0] ]; -- NIL
};
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
$LF => SIGNAL Error[[$MisMatch, "Improper renderMode"]];
$FullClr, $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, Real.FixC[context.viewPort.w], Real.FixC[context.viewPort.h] ],
bkgrdBytes,
$Write
]
ELSE {
Pixels.PixelOp[            -- below previously affected area
context.display,
[ 0, 0, Real.FixC[context.viewPort.w], context.extentCovered.bottom ],
bkgrdBytes,
$Write
];
IF Real.FixC[context.viewPort.h] > context.extentCovered.top THEN Pixels.PixelOp[
context.display,           -- above previously affected area
[ 0,
context.extentCovered.top,
Real.FixC[context.viewPort.w],
Real.FixC[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 Real.FixC[context.viewPort.w] > context.extentCovered.right THEN Pixels.PixelOp[
context.display,           -- right of previously affected area
[ context.extentCovered.right,
context.extentCovered.bottom,
Real.FixC[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 ← [Real.RoundC[context.viewPort.w], 0,
       Real.RoundC[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 ThreeDScenes.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.], type, insideVisible];
shape ← FindShape[context.shapes, name];
};
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[ "VertexColorFile:",  keyWd, FALSE] => {
filename: Rope.ROPE ← GetRope[input];
ThreeDMisc.GetVertexColors[ context, shape.name, filename ];
};
Rope.Equal[ "PatchColorFile:",  keyWd, FALSE] => {
filename: Rope.ROPE ← GetRope[input];
ThreeDMisc.GetPolygonColors[ context, shape.name, filename ];
};
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] ]
];
};
$File => textureFile ← GetRope[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[ "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 ThreeDScenes.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;
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 ] ];
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];
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 color # NIL THEN IO.PutRope[output,
   Rope.Cat[" SmoothColor: ", Vec3toRope[color.R, color.G, color.B], "\n"] ];
IF shadingType = $Faceted 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" ] ];
Color Files
ref ← GetShading[ shape, $VertexColorFile ];
IF ref # NIL THEN IO.PutRope[ output,
     Rope.Cat[" VertexColorFile: " , NARROW[ref, Rope.ROPE], "\n"] ];
ref ← GetShading[ shape, $PatchColorFile ];
IF ref # NIL THEN IO.PutRope[ output,
     Rope.Cat[" PatchColorFile: " , NARROW[ref, Rope.ROPE], "\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;
};
$File => line ← Rope.Cat[line, " ", NARROW[coords, Rope.ROPE] ];
ENDCASE => SIGNAL Error[[$Unimplemented, "Unknown texture coordType"]];
IO.PutRope[ output, Rope.Cat[line, "\n"] ];
};
Solid Texture
ref ← GetShading[ shape, $ShadingProcs];
IF ref # NIL THEN {
getVtxProc: ThreeDScenes.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[ThreeDScenes.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[ThreeDScenes.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.Mag[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;
};
SetUpStandardFile: PUBLIC PROC[file: Rope.ROPE] RETURNS [stream: IO.STREAM, numEntries: NAT] ~{
char: CHAR ← ' ;
stream ← FS.StreamOpen[file];
[] ← IO.GetChar[stream]; -- in case file has leading CR
WHILE char # 15C DO char ← IO.GetChar[stream]; ENDLOOP; -- burn 1st line (comment)
numEntries ← IO.GetInt[stream];   -- number of items should be first number in file
};
ReadVertexCoords: PUBLIC PROC[vtx: REF VertexSequence, in: IO.STREAM, nVtces: NAT] ~ {
FOR i: NAT IN [0..nVtces) DO       -- Read Vertices
IF vtx[i] = NIL THEN vtx[i] ← NEW[Vertex];
vtx[i].x ← IO.GetReal[in];
vtx[i].y ← IO.GetReal[in];
vtx[i].z ← IO.GetReal[in];
ENDLOOP;
};
ReadTextureCoords: PUBLIC PROC[shade: REF ShadingSequence, in: IO.STREAM, nVtces: NAT] ~ {
FOR i: NAT IN [0..nVtces) DO
IF shade[i] = NIL THEN shade[i] ← NEW[ShadingValue];
shade[i].txtrX ← IO.GetReal[in];
shade[i].txtrY ← IO.GetReal[in];
shade[i].txtrZ ← IO.GetReal[in];
ENDLOOP;
};
ReadColors: PUBLIC PROC[shade: REF ShadingSequence, in: IO.STREAM, length: NAT] ~ {
FOR i: NAT IN [0..length) DO       -- Read Vertices
IF shade[i] = NIL THEN shade[i] ← NEW[ShadingValue];
shade[i].r ← IO.GetReal[in];
shade[i].g ← IO.GetReal[in];
shade[i].b ← IO.GetReal[in];
shade[i].t ← IO.GetReal[in];
ENDLOOP;
};
ReadNormals: PUBLIC PROC[shade: REF ShadingSequence, in: IO.STREAM, length: NAT] ~ {
FOR i: NAT IN [0..length) DO       -- Read Vertices
IF shade[i] = NIL THEN shade[i] ← NEW[ShadingValue];
shade[i].xn ← IO.GetReal[in];
shade[i].yn ← IO.GetReal[in];
shade[i].zn ← IO.GetReal[in];
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, pt: Triple] RETURNS[Triple] ~ {
 Transform vertex from eyespace to display coordinates
RETURN [ 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
] ];
};
ShadePt: 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 ThreeDScenes.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 ← context.viewPort.w;
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;
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] ~ {
clr: RGB;
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 ];
[clr, trns] ← ShadePt[context, pt, 0.0]; -- calculate shade
shape.shade[i].ir ← Real.FixC[clr.R * 255.0];
shape.shade[i].ig ← Real.FixC[clr.G * 255.0];
shape.shade[i].ib ← Real.FixC[clr.B * 255.0];
shape.shade[i].it ← Real.FixC[ trns * 255.0];
};
ENDLOOP;
};
END.