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; 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; 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; aLilBit: REAL _ ScanConvert.justNoticeable * ScanConvert.justNoticeable; 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; }; Create: PUBLIC PROC[] RETURNS [REF Context] ~ { context: REF ThreeDBasics.Context _ NEW[ThreeDBasics.Context]; 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] ~{ 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 ] ~ { 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 ] ~ { 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 ] ~ { 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 ] ~ { 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; }; 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[]; 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]]; 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]; 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]; 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.]; 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 NAT _ NARROW[ 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) context.extentCovered _ [Ceiling[context.viewPort.w], 0, -- left, right Ceiling[context.viewPort.h], 0]; -- bottom, top }; IF context.depthBuffer THEN { depth: REF NAT _ NARROW[ 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] ~ { 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] ~ { addOn: NAT _ 0; bkgrdBytes: SampleSet; alpha: REF NAT _ NARROW[ Atom.GetPropFromList[context.display.props, $Alpha] ]; depth: REF NAT _ NARROW[ 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 ]; }; context.extentCovered _ [Ceiling[context.viewPort.w], 0, Ceiling[context.viewPort.h], 0]; }; SetView: PUBLIC PROC[context: REF Context, eyePoint, ptOfInterest: Triple, fieldOfView: REAL _40.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] ~ { 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.ROPE _ NIL; 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: BOOLEAN _ FALSE; 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 RGB _ NEW[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: ATOM _ IO.GetAtom[ input ]; insideVisible: BOOLEAN _ IO.GetBool[ input ]; ThreeDMisc.AddShapeAt[context, name, fileName, [0.,0.,0.]]; shape _ FindShape[context.shapes, name]; }; Rope.Equal[ "ScaleShape:", keyWd, FALSE] => { scale: REAL _ IO.GetReal[input]; xRatio: REAL _ IO.GetReal[input]; yRatio: REAL _ IO.GetReal[input]; zRatio: REAL _ IO.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: REAL _ IO.GetReal[input]; ThreeDMisc.SetShininess[context, shape.name, shininess]; }; Rope.Equal[ "Transmittance:", keyWd, FALSE] => { transmittance: REAL _ IO.GetReal[input]; ThreeDMisc.SetTransmittance[context, shape.name, transmittance]; }; Rope.Equal[ "MapTexture:", keyWd, FALSE] => { IF shape # NIL THEN { textureFile: Rope.ROPE _ NIL; textureImageFileName: Rope.ROPE _ GetRope[input]; textureType: ATOM _ IO.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: REAL _ IO.GetReal[input]; xRatio: REAL _ IO.GetReal[input]; yRatio: REAL _ IO.GetReal[input]; zRatio: REAL _ IO.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: RGB _ NARROW[ref, REF RGB]^; IO.PutRope[ output, Rope.Cat[ "BackgroundColor: ", Vec3toRope[color.R, color.G, color.B], "\n" ] ]; }; FOR i: NAT IN [0..context.lights.length) DO color: REF RGB _ NARROW[ 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; 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 ANY _ NIL; xfm: Matrix3d.Matrix _ shape.position; scale: REF ThreeDBasics.Quad _ NARROW[ GetShading[ shape, $Scale ] ]; color: REF RGB _ NARROW[ GetShading[ shape, $Color ] ]; shadingType: ATOM _ NARROW[GetShading[ shape, $Type ] ]; transmtnce: REF REAL _ NARROW[GetShading[ shape, $Transmittance]]; shininess: REF REAL _ NARROW[GetShading[ shape, $Shininess ] ]; texture: REF TextureMaps.TextureMap _ NARROW[ GetShading[ shape, $TextureMap ] ]; textureScale: REF ThreeDBasics.Quad _ NARROW[ GetShading[ shape, $TextureScale ] ]; 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]; IF scale # NIL THEN { line _ Rope.Cat[" ScaleShape: ", Vec4toRope[scale.x, scale.y, scale.z, scale.w], "\n" ]; IO.PutRope[ output, line]; }; 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]; 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" ] ]; IF texture # NIL THEN { fileName: Rope.ROPE _ NARROW[ Atom.GetPropFromList[texture.props, $FileName] ]; coordType: ATOM _ NARROW[ 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 REAL _ NARROW[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]; }; }; 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"]; }; NewShape: PUBLIC PROC[ name: Rope.ROPE ] RETURNS[REF ShapeInstance] ~ { shape: REF ShapeInstance _ NEW [ShapeInstance ]; shape.name _ name; 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.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: BOOLEAN _ FALSE] ~ { 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 ]; }; 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] ~ { 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] ~ { pt _ Matrix3d.Transform[ pt, context.eyeSpaceXfm ]; RETURN[ pt, GetClipCodeForPt[context, pt] ]; }; XfmTripleToDisplay: PROC[pt: Triple, xfm: ScaleAndAddXfm, resFctr: Triple, offset: REAL] RETURNS[result: Triple] ~ { 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] ~ { 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 ]; 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: RGB _ NARROW[GetShading[ context.lights[i], $Color], REF RGB ]^; -- get color 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 ] ]; 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; }; 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; 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; }; 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; 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] ~ { ClipBoundingBall: PROC[] RETURNS[ClipState] ~ { clipFlag: BOOLEAN _ FALSE; 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; 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] ~ { 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. ΦThreeDScenesImpl.mesa Copyright c 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 Basic Types RECORD[scaleX, scaleY, scaleZ, addX, addY, addZ: REAL]; Constants Utility Procedures Procedures for Setting up Contexts Get Working directory Sets up context using imager context Sets up context.display using virtual terminal, render mode from state of color display Sets up context.display using virtual memory (not visible) full-color assumed Makes depth buffer in VM for context.display Makes alpha buffer in VM for context.display Procedures for Defining and Altering Environments transform from world to eye normalize not really needed (to avoid roundoff problems) generate clipping planes normalize plane equation for true distances compute the transformation from the eye coord sys to normalized display coordinates (-1.1.) Set uncovered (negative width and height) indicates no alpha information Loads background behind current image For use with alpha buffer to load background behind shapes Set uncovered (negative width and height) indicates no alpha information Get an ambient light value from the eyespace normal to the surface Light Sources Shapes File Name, instance name, and Surface Type Scaling Shape Transform Color, Transmittance, Shininess Mapped Texture Solid Texture Procedures for Manipulating Shapes newShape.surface _ ? - Can't copy this since we can't know the type here, will point to the original Procedures for Manipulating Vertices Compute outcode for one set of coordinates in eyespace Transform Vertex to Eye Space Transform vertex from eyespace to display coordinates - local utility Transform vertex from eyespace to display coordinates Get ambient component of light Get Light Direction from Surface Get Light Strength Get Basic Lambertian Shade Get Highlight Contribution Add in Highlight Get transmittance if transparent Transform Vertices and Centroid to Eye Space, calculate clip codes at vertices Do gross clip test on bounding sphere, all in or all out allow rejection of entire object run through vertices and transform Expand extent by two pixels, NOTE!! this is not complete in the presence of clipping Κ1b˜Ihead3šΟb™šœ Οmœ1™Jš ŸœŸœŸœUŸœŸœ˜tJšœ Ÿœ˜"Jšœ Ÿœ˜'Jšœ Ÿœ˜(JšœŸœ˜0Jšœ Ÿœ1˜@JšœŸœŸœ˜*JšœŸœ˜.Jšœ Ÿœ?˜PJšœ˜Jšœ ŸœT˜eJšœ Ÿœ˜"Jšœ Ÿœ°˜ΐJšœŸœ,˜?JšœŸœΰ˜σJšœŸœv˜‰JšœŸœ˜%JšœŸœ˜2JšœŸœ˜)Jšœ Ÿœ¦˜ΈJšœŸœ˜0—J˜—head2šœŸœŸ˜IašŸœFŸœ₯˜φNšŸœ˜J˜JšœŸ˜J˜Nš œŸœŸœŸœ Ÿœ˜=—š ™ JšŸœŸœŸœ˜JšœŸœ ŸœΟc˜=JšœŸœ ˜BJšœŸœ ˜BJšœ Ÿœ˜ Jšœ Ÿœ˜#IunitšœŸœ ,˜KIdefaultšœ Ÿœ˜$šœŸœ˜3Jš 7™7—P˜Ošœ Ÿœ (˜QPšœ Ÿœ ˜APšœ Ÿœ 2˜ZJšœ( )˜QJšœ' '˜NJšœ Ÿœ˜%JšœŸœ˜2JšœŸœ˜1JšœŸœ ˜5JšœŸœ˜/JšœŸœ˜#Jšœ Ÿœ˜+JšœŸœ˜3—š ™ Pšœ Ÿœ;˜H—š™OšΟnœŸ œ ŸœŸœŸœŸœŸœ˜Tš ‘œŸœ ŸœŸœ Ÿœ˜8J˜JšŸœŸœ˜,Jšœ˜—š ‘œŸœŸœŸœŸœ˜3J˜JšŸœŸœ˜,˜J˜———š"™"š ‘œŸœŸœŸœŸœ ˜/šœ ŸœŸœ˜?Jš ™—Jšœ Ÿœ& ˜TJšœA  ˜NJšœ@ ˜QPšœ, 1˜]PšŸœ ˜Pšœ˜—š‘œŸœŸœ Ÿœ0˜bP™%Jšœ Ÿœ˜Jšœ Ÿœ˜Jšœ;˜;Pšœ Ÿœ=˜MšŸœ˜šŸœ˜šŸœŸ˜'šœ Ÿœ ˜/PšŸœ:˜>—šœ ŸœŸœ˜,PšœX˜X—šœŸœŸœ#˜:PšŸœ4˜8—PšŸœŸœ-˜>—Pšœ1˜1Pšœ˜—PšŸœ˜"—PšœŸœ$˜+PšœŸœ$˜+PšœŸœG˜NPšœŸœH˜OPšœ ˜ PšœN˜NPšœ6˜6Pšœ˜—š‘œŸœŸœ Ÿœ*˜XPšœW™WPšœ3˜3šœŸœ%˜˜EPšŸœ ˜ PšŸœ˜——šœ˜Pšœ ˜ PšœF˜FPšœ˜—Pšœ6˜6P˜P™—š ‘ œŸœŸœ ŸœŸœŸœ˜wJšœN™NJšœ˜šŸœ Ÿ˜JšœL˜Lšœ#˜#Jšœ<˜˜>Jš +™+—Jšœ@˜@Jšœ<˜˜>JšœI˜IJšœ;˜;J˜Jšœ\™\šŸœ 3˜KJšŸœD˜HJšŸœ.˜2—šŸœ˜JšŸœ/˜3JšŸœ2˜6—Jšœ+˜+Jšœ+˜+JšœG˜GJšœ8˜8Jšœ9˜9Jšœ[˜[Jšœ˜—š‘ œŸ œ Ÿœ$˜GJšœ˜Jšœ˜š ŸœŸœŸœŸœŸœŸœŸ˜HJšœ!Ÿœ '˜RJšŸœ˜—Jšœ˜—š‘œŸœŸœŸœ˜YPšœ˜PšŸœŸœŸœ˜9šŸœ˜šŸœ˜Pšœ%˜%PšœS˜SPšœ˜—šŸœ˜ Pšœ%˜%PšœS˜SPšœ˜——PšŸœ ˜Pšœ˜—š‘ œŸœŸœ Ÿœ$˜IJ˜šŸœŸœ˜"JšŸœŸœ$˜/—PšœL˜LPšœ&˜&Jšœ˜Jšœ-˜-Jšœ˜J˜—š ‘ œŸœŸœ ŸœŸœ˜=JšœD˜DJšœY˜YšŸœŸ˜šœ (˜Dš‘œŸœ!˜-Nšœ<˜Nšœ˜NšœE˜ENšœ ˜ Nšœ˜Nšœ˜—šŸœ˜šœ !˜Nšœ˜šœ˜Nšœ˜Nšœ˜Nšœ9˜9Nšœ˜—Nšœ ˜ Nšœ˜Nšœ˜—šŸœ;Ÿœ˜RNšœ $˜?šœ ˜ Nšœ˜Nšœ;˜;Nšœ8˜8Nšœ˜—Nšœ ˜ Nšœ˜Nšœ˜—šœ ˜6Nšœ˜šœ˜Nšœ˜Nšœ9˜9Nšœ8˜8Nšœ˜—Nšœ ˜ Nšœ ˜ Nšœ˜—N˜—J™H—Jšœ`˜`J˜—š‘œŸ œ ŸœAŸœŸœDŸœŸœ˜ςJšœ˜Jšœ$˜$Jšœ"˜"Jšœ˜Jšœ"˜"Jšœ"˜"Jšœ˜Jšœ˜šŸœŸœŸœŸ˜+Jšœ!Ÿœ '˜RšŸœ.Ÿœ˜4JšŸœ$Ÿœ ˜J—JšŸœ˜—Jšœ˜J˜—š‘œŸ œ ŸœŸœ%ŸœŸœŸœ˜˜Jš œŸœJŸœŸœŸœ˜}šŸœ ŸœŸœ˜Jšœ ˜2Jšœ5 ˜NJšœ5  ˜BJ˜—Jšœ˜Jšœ˜Jšœ2 !˜SJšœ& ˜BJšœŸœ˜JšœŸœ˜JšœŸœŸœ ˜.Nšœ> ˜\šŸœŸœŸœŸ˜+Jšœ#Ÿœ˜(JšŸœ˜—JšŸœ˜Jšœ˜J˜—š ‘ œŸœŸœ ŸœŸœ˜CJšœ3˜3Jšœ3˜3šŸœŸœŸœŸ˜+Jšœ#Ÿœ˜(JšŸœ˜—Jšœ˜J˜—š ‘œŸœŸœ ŸœŸœŸœ˜ZP™BPšŸœŸœŸœŸœ˜7šŸœ8ŸœŸ˜IPšœŸœŸœ˜šœŸœ 6˜VšœŸœ˜PšœP˜PPšœ˜Pšœ˜—Pšœ 1˜PPšœŸœŸœŸœ˜4PšœP˜PP˜—PšŸœ˜ —J˜—š ‘ œŸœŸœ ŸœŸœŸœ˜Bš ‘œŸœŸœŸœŸœŸœ ˜TJšœ ŸœŸœ˜JšœŸœ˜ JšœŸœ  ˜EJšœŸœ˜š ŸœŸœŸœŸœŸœŸœ  ˜KJšœ1˜1JšœŸœŸ˜JšŸœ˜—JšŸœ ˜Jšœ˜—JšœŸœŸœ˜,JšœŸœŸœ˜šŸœŸœŸ˜Jšœ ŸœŸœŸœ˜=šŸœŸœŸ˜šœ œ Ÿœ˜'JšœŸœŸœŸœ˜OJšœŸœŸœŸœ˜SJšœŸœŸœŸœ˜RJšœŸœ˜&JšœŸœ˜(JšœŸœ˜(JšœŸœ˜%Jšœ˜J˜—šœ  œ Ÿœ˜+Jš œŸœŸœ ŸœŸœ˜‚Jšœ˜—šœ œ Ÿœ˜)Jš œŸœŸœŸœŸœ˜|J˜—šœ œ Ÿœ˜2Jš œ ŸœŸœŸœŸœ˜JšœŸœŸœŸœ˜JJšœK˜KJ˜—šœ œ Ÿœ˜)šœ˜Jšœ˜Jšœ˜Jšœ ŸœŸœŸœ˜FJšœ ŸœŸœŸœ˜BJ˜—Jšœ˜—šœ œ Ÿœ  ˜?Jšœ Ÿœ˜!JšœŸœ˜%JšœŸœŸœ˜!JšœŸœŸœ˜-Jšœ;˜;Jšœ(˜(Jšœ˜—šœ  œ Ÿœ˜.JšœŸœŸœ˜ JšœŸœŸœ˜!JšœŸœŸœ˜!JšœŸœŸœ˜!JšœN˜NJ˜—šœ  œ Ÿœ˜,JšœŸœŸœŸœ˜OJšœ˜J˜—šœ  œ Ÿœ˜+šœŸœ˜,Jš œŸœŸœŸœŸœ˜OJš œŸœŸœŸœŸœ˜OJš œŸœŸœŸœŸœ˜OJš œŸœŸœŸœŸœ˜NJšœ˜—JšœŸœ˜Jšœ˜—šœ  œ Ÿœ˜/JšœŸœ˜ Jšœ ŸœŸœŸœ˜VJšœ7˜7J˜—šœ  œ Ÿœ˜/JšœŸœ˜ Jšœ ŸœŸœŸœ˜VJšœ6˜6Jšœ˜—šœ  œ Ÿœ˜-Jšœ ŸœŸœ˜$Jšœ8˜8J˜—šœ œ Ÿœ˜1JšœŸœŸœ˜(Jšœ@˜@J˜—šœ  œ Ÿœ˜-šŸœ Ÿœ˜šŸœ˜JšœŸœŸœ˜JšœŸœ˜5Jšœ ŸœŸœ˜(šŸœŸœŸ˜šœ;˜;Jšœ ŸœŸœ˜JšœŸœŸ˜JšœV˜VJšœV˜VJšŸœ ˜Jšœ˜—JšœŸœ˜ šœ Ÿœ˜J™—šœœ˜JšœG˜GJšœS˜SJšœO˜OJ˜—šŸœ!˜#Jšœ\˜\Jšœ˜Jšœ˜—šŸœœ˜+Jšœ[˜[Jšœ˜Jšœ˜—šŸœœ˜)JšœS˜SJšœ˜Jšœ˜—Jšœ8 ˜OšŸœŸœŸœ˜Jš œŸœŸœŸœŸœ˜#JšŸœ"œ:˜mJšœ˜JšΠbc ™ —šŸœŸœŸœŸ˜+Jš œŸœŸœŸœ)Ÿœ˜CšŸœœ˜@Jšœi˜iJšœ&˜&Jšœ˜Jšœ˜—JšŸœ˜Jš’™—šŸœŸœŸœŸ˜+NšœŸœ0˜:šŸœ ŸœŸœ.ŸœŸœŸœŸœŸœ˜~JšœŸœŸœŸœ˜Jšœ&˜&NšœŸœŸœ ˜ENšœŸœŸœŸœ ˜7Jšœ ŸœŸœ˜8Jšœ ŸœŸœŸœ%˜BJšœ ŸœŸœŸœ#˜?Jšœ ŸœŸœ%˜QNšœŸœŸœ'˜S˜Jš’*™*—Jšœœ,˜CJšœ3˜3šŸœ˜JšŸœ"˜&JšŸœ%˜)—šŸœ˜Jš’™—šŸœ ŸœŸœ˜Jšœ œK˜iJšŸœ˜J˜Jš’™—JšŸœŸœŸœ4˜EJšœœ œ˜‘Jšœ›˜›šŸœ˜Jš’™—šŸœ˜JšŸœ,Ÿœ˜3Jš Ÿœ ŸœŸœŸœ! œ4˜w—šŸœ˜JšŸœ+Ÿœ˜2Jš Ÿœ ŸœŸœŸœ! œ4˜x—Jš ŸœŸœŸœŸœ" œ1˜{š Ÿœ ŸœŸœŸœ$ œ0˜wJš™—šŸœ ŸœŸœ˜JšœŸœŸœ3˜OJšœ ŸœŸœ4˜LJšœŸœ0˜;JšŸœ ŸœŸœ˜.Jšœ œ2˜PJšœ;˜;šŸœ Ÿ˜šœ˜Jš œ ŸœŸœŸœŸœ ˜'šŸœ ŸœŸœ˜JšœE˜EJšœ˜JšŸœ˜—J˜—JšŸœŸœ6˜G—JšŸœ)˜+šŸœŸœŸœ˜šœ˜Jšœ œ˜JšœL˜LJšœ˜—JšŸœ˜J˜—Jšœ˜Jš ™ —Jšœ(˜(šŸœŸœŸœ˜Jšœ,Ÿœ˜0Jšœ)Ÿœ˜-JšœŸœŸœ˜KšŸœŸœŸœ˜JšœŸœ*˜=JšŸœ œ˜DJ˜—Jšœ˜—J˜—JšŸœ˜JšŸœ œ˜%—Jšœ˜——š"™"š ‘œŸ œ ŸœŸ œŸœ˜HJšœŸœŸœ˜0JšœŸœŸœŸ˜JšŸœ ˜J˜J˜—š ‘ œŸ œŸœŸœŸœŸœ˜hJš ŸœŸœŸœŸœŸœ˜šŸœŸœŸœŸœ˜"JšŸœ ŸœŸœŸœ ˜>JšŸœ˜—JšŸœ*˜0JšŸœŸœ˜ J˜J˜—š ‘œŸ œŸœŸœŸœŸœ˜oJšœŸœ˜šŸœŸœ˜ šŸœ˜Jšœ Ÿœ#˜/Jš ŸœŸœŸœŸœŸœ˜?Jšœ˜J˜—šŸœ˜Jšœ Ÿœ˜"Jšœ˜J˜——JšŸœ ˜Jšœ˜J˜—š ‘ œŸ œŸœŸœŸ œŸœ˜hJšœŸœ˜JšœŸœ˜ šŸœŸœ˜ JšŸœ Ÿœ"˜4JšŸœŸœ0˜;—šŸœŸœŸœŸœ˜"šŸœŸœ Ÿœ˜.šŸœŸœ˜JšŸœ(˜,JšŸœŸœ8Ÿœ ˜W——JšŸœ˜—JšŸœ ˜J˜J˜—š ‘ œŸ œ ŸœŸœŸœŸœ˜lJšœ˜J˜Jšœ ŸœŸœ˜1Jšœ˜Jšœ˜JšœŸœ(˜=šŸœŸœŸœŸ˜)JšœŸœ*˜BJšŸœ˜—JšœŸœ)˜=šŸœŸœŸœŸ˜(JšœŸœ/˜FJšŸœ˜—šœ™JšœO™O—JšœŸœ˜Jšœ˜šŸœŸœŸœ˜JšœŸœ$˜@Jšœ˜JšŸœ˜—JšœŸœ˜Jšœ˜šŸœŸœŸœ˜JšœŸœ˜2Jšœ˜JšŸœ˜—JšŸœ ˜Jšœ˜—š‘ œŸ œ Ÿœ&˜IJšœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜J˜J˜—š‘ œŸ œ Ÿœ#˜EJšœ5˜5JšœŸœ˜JšœŸœ˜JšœŸœ˜J˜J˜—š‘ œŸ œ Ÿœ!˜EJšœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜J˜J˜—š‘ œŸ œ Ÿœ2Ÿœ˜`Jšœ˜Jšœ˜Jšœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜J˜J˜—š ‘ œŸœŸœŸœŸœŸœ˜OJšœ ŸœG˜WJšŸœŸœŸœ( ˜Xšœ˜Jšœ  ˜=šœ˜Jšœ3˜3Jšœ˜Jšœ˜Jšœ˜—Jšœ˜—šŸœ ˜.šŸœ˜JšœŸœ&˜2JšœŸœ˜Jšœ(˜(Jšœ(˜(šœ˜Jšœ ?˜QJšŸœW˜ZJšœ˜—Jšœ$˜$Jšœ˜šœ˜Jšœ 3˜IJšŸœW˜ZJšœ˜—šœ˜Jšœ @˜RJšŸœV˜YJšœ˜—J˜—šŸœŸœŸœ ˜FJšœ ˜0JšŸœO˜RJšœ˜——šœ˜Jšœ ˜+JšŸœ˜‚Jšœ˜—JšœŸœ˜J˜J˜—š ‘ œŸ œ ŸœŸœ ŸœŸœ˜QJšœJ˜JPšœ˜P˜—š‘ œŸ œ ŸœŸœŸœ ŸœŸœ˜cJšœ8˜8Pšœ˜——š$™$š ‘ œŸœŸœŸœŸœŸœ˜XJšœŸœ˜ JšœŸœ˜šŸœŸœ˜%JšŸœ ŸœŸœŸœ˜:JšŸœ! ˜4—šŸœ'Ÿœ˜-JšŸœŸœ%ŸœŸœ˜KJšŸœ˜—Jšœ˜šŸœŸœŸœŸ˜(Jšœ˜Jšœ˜Jšœ˜Jšœ˜JšŸœ˜—Jšœ˜J˜—š ‘œŸœŸœ ŸœŸœ˜[Jšœ6™6JšœL˜LJšœH˜HJšœJ˜JJšœL˜LJšœJ˜JJšœH˜HJ˜J˜—š‘œŸ œ ŸœŸœ˜[Jšœ™Jšœ3˜3JšŸœ(˜.J˜Jšœ˜—š ‘œŸœ;Ÿœ œŸœ˜{PšœF™FOšœ- '˜TPšœ+˜+Pšœ&˜&P˜Jšœ ŸœŸœ1 ˜bJšœ ŸœŸœ.˜EJšœ ŸœŸœ.˜EJšœ :˜YJšœ˜JšŸœ ˜Jšœ˜Jšœ˜—š ‘œŸ œ ŸœŸœ#Ÿœ ˜rPšœ6™6šœ$˜$Pšœ˜Pšœ˜PšœK˜KPšœŸœŸœŸœ %˜WPšœ˜—šŸœ ŸœŸœ˜PšœŸœŸœ˜1PšœŸœ Ÿœ'˜>PšœŸœŸœ˜1PšœŸœ Ÿœ'˜>PšŸœŸœ˜DPšŸœ Ÿœ ˜FPšŸœ!Ÿœ!˜HPšŸœŸœ˜BP˜—PšŸœ ˜Jšœ˜Jšœ˜—š‘œŸœŸœ Ÿœ%ŸœŸœ ŸœŸœ ˜‡Pšœ Ÿœ .˜WP˜PšœŸœ˜&PšœŸœ˜2N˜Pšœ˜PšœH ˜[šœD˜DPšœ. ˜EPšœ˜—N˜Pšœ™PšœQ˜QPšœ#˜#Pšœ"˜"Pšœ"˜"N˜š ŸœŸœŸœŸœ ˜IPš œ ŸœŸœ)ŸœŸœ  ˜WPšœ ™ šœ& *˜PPšœ,˜,Pšœ,˜,Pšœ+˜+P˜—L˜P™šŸœ1Ÿœ ˜WPšœŸœ˜Pšœv˜vPš œ Ÿœ Ÿœ;ŸœŸœ˜jLšœ2˜2šŸœ Ÿœ #˜7Pšœ Ÿœ˜Pšœ˜šŸœŸœ $˜APšŸœŸœŸœ˜