DIRECTORY Atom USING [GetPropFromList], Real USING [RoundC, FixI, FixC], RealFns USING [SqRt], Basics USING [BITOR, BITAND], Rope USING [ROPE], IO USING [STREAM, GetInt], Imager USING [Context, MaskVectorI], ImagerColor USING [RGB], Pixels USING [GetSampleSet, SampleSet], ScanConvert USING [PutLine], Linear3d USING [DotProd, Triple, Quad, Normalize, CrossProd, SumTriple, EvalPlane], ThreeDMisc USING [GetMappedColor, GetImagerContext, SetNamedColor], ThreeDScenes USING [SixSides, OutCode, NoneOut, AllOut, ShapeInstance, ShapeSequence, Context, ShadingSequence, ClipState, Vertex, VertexInfo, VertexSequence, SetUpStandardFile, ReadColors, PutShading, GetShading, ShadePt, FillInBackGround, XfmToEyeSpace, XfmToDisplay, ReadVertexCoords, FillViewPort, GetVtxShades, XfmPtToDisplay], Tilers USING [PolygonTiler], ThreeDPolygons USING [Polygon, PtrPolySequence, PtrPoly, SortSequence, ShapePolygon]; ThreeDPolygonsImpl: CEDAR PROGRAM IMPORTS Atom, Real, RealFns, IO, Basics, ThreeDScenes, Tilers, Linear3d, ScanConvert, ThreeDMisc, Pixels, Imager EXPORTS ThreeDPolygons = BEGIN ThreeDPolygonsError: PUBLIC SIGNAL [reason: ATOM] = CODE; RGB: TYPE ~ ImagerColor.RGB; -- [ r, g, b: REAL]; SixSides: TYPE ~ ThreeDScenes.SixSides; Triple: TYPE ~ Linear3d.Triple; SampleSet: TYPE ~ Pixels.SampleSet; ShapeInstance: TYPE ~ ThreeDScenes.ShapeInstance; Polygon: TYPE ~ ThreeDPolygons.Polygon; Vertex: TYPE ~ ThreeDScenes.Vertex; tempPoly, tempPoly2: REF Polygon _ NIL; -- temps for clipping, etc. pixelBytes: SampleSet _ Pixels.GetSampleSet[1]; -- cache for pixel size stopMe: BOOLEAN _ FALSE; Sqr: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { RETURN[number * number]; }; Sgn: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { IF number < 0. THEN RETURN[-1.] ELSE RETURN[1.]; }; DiffPosns: PROC[vtx1, vtx2: Vertex] RETURNS[Triple] ~ { RETURN[[vtx1.x - vtx2.x, vtx1.y - vtx2.y, vtx1.z - vtx2.z]] }; HoldEverything: PROCEDURE [] ~ { stopMe _ FALSE; ERROR ABORTED; }; EnableDisplay: PUBLIC PROC [] ~ { stopMe _ FALSE; }; StopDisplay: PUBLIC PROC [] ~ { stopMe _ TRUE; }; ShapetoColorBytes: PROC [context: REF ThreeDScenes.Context, s: REF ShapeInstance] RETURNS[SampleSet] ~ { ref: REF ANY _ ThreeDScenes.GetShading[ s, $Color]; r, g, b: REAL; ir, ig, ib: INTEGER; IF ref # NIL THEN { [r, g, b] _ NARROW[ ref, REF RGB]^; -- shape color ir _ Real.RoundC[r*255.0]; ig _ Real.RoundC[g*255.0]; ib _ Real.RoundC[b*255.0]; } ELSE ir _ ig _ ib _ 255; SELECT context.renderMode FROM -- convert color to byte sequence $LF => {}; $Dithered, $PseudoClr => { IF pixelBytes.length # 1 THEN pixelBytes _ Pixels.GetSampleSet[1]; pixelBytes[0] _ ThreeDMisc.GetMappedColor[context, [ir, ig, ib] ]; }; $Grey => { IF pixelBytes.length # 1 THEN pixelBytes _ Pixels.GetSampleSet[1]; pixelBytes[0] _ (ir + ig + ib)/3; }; $FullClr, $Dorado24 => { IF pixelBytes.length # 3 THEN pixelBytes _ Pixels.GetSampleSet[3]; pixelBytes[0] _ ir; pixelBytes[1] _ ig; pixelBytes[2] _ ib; }; ENDCASE => SIGNAL ThreeDPolygonsError[$BadRenderMode]; RETURN[pixelBytes]; }; CombineBoxes: PROC[context: REF ThreeDScenes.Context] ~ { -- get combined bounding box context.extentCovered _ [LAST[NAT], 0, LAST[NAT], 0]; FOR i: NAT IN [0..context.shapes.length) DO IF context.shapes[i].clipState # out THEN { IF context.extentCovered.left > context.shapes[i].screenExtent.left THEN context.extentCovered.left _ context.shapes[i].screenExtent.left; IF context.extentCovered.right < context.shapes[i].screenExtent.right THEN context.extentCovered.right _ context.shapes[i].screenExtent.right; IF context.extentCovered.bottom > context.shapes[i].screenExtent.bottom THEN context.extentCovered.bottom _ context.shapes[i].screenExtent.bottom; IF context.extentCovered.top < context.shapes[i].screenExtent.top THEN context.extentCovered.top _ context.shapes[i].screenExtent.top; }; ENDLOOP; }; LoadShape: PUBLIC PROC[ shape: REF ShapeInstance, fileName: Rope.ROPE, type: ATOM _ $Polyhedron ] ~ { numVtces: NAT; min, max: Triple; radius: REAL; input: IO.STREAM; [input, numVtces] _ ThreeDScenes.SetUpStandardFile[fileName]; shape.numSurfaces _ IO.GetInt[input]; shape.type _ type; ThreeDScenes.ReadVertexCoords[shape, input, numVtces]; -- read vertices ReadPolygons[shape, input, shape.numSurfaces]; -- read polygons min _ max _ [shape.vertex[0].x, shape.vertex[0].y, shape.vertex[0].z]; FOR i: NAT IN (0..numVtces) DO -- get min and max in x, y, and z IF shape.vertex[i].x < min.x THEN min.x _ shape.vertex[i].x ELSE IF shape.vertex[i].x > max.x THEN max.x _ shape.vertex[i].x; IF shape.vertex[i].y < min.y THEN min.y _ shape.vertex[i].y ELSE IF shape.vertex[i].y > max.y THEN max.y _ shape.vertex[i].y; IF shape.vertex[i].z < min.z THEN min.z _ shape.vertex[i].z ELSE IF shape.vertex[i].x > max.z THEN max.z _ shape.vertex[i].z; ENDLOOP; shape.centroid.x _ (min.x + max.x) / 2; -- get middle point in each coordinate shape.centroid.y _ (min.y + max.y) / 2; shape.centroid.z _ (min.z + max.z) / 2; shape.boundingRadius _ 0.; FOR i: NAT IN [0..numVtces) DO -- find radius radius _ RealFns.SqRt[ Sqr[shape.vertex[i].x - shape.centroid.x] + Sqr[shape.vertex[i].y - shape.centroid.y] + Sqr[shape.vertex[i].z - shape.centroid.z] ]; IF radius > shape.boundingRadius THEN shape.boundingRadius _ radius; ENDLOOP; }; ReadPolygons: PUBLIC PROC[shape: REF ShapeInstance, in: IO.STREAM, nPolys: NAT] ~ { surface: REF ThreeDPolygons.PtrPolySequence; shape.surface _ NEW[ ThreeDPolygons.PtrPolySequence[shape.numSurfaces] ]; surface _ NARROW[shape.surface, REF ThreeDPolygons.PtrPolySequence]; FOR i: NAT IN [0..nPolys) DO -- Read Polygons nVtces: NAT _ IO.GetInt[in]; surface[i] _ NEW[ThreeDPolygons.PtrPoly[nVtces]]; surface[i].nVtces _ nVtces; FOR j: NAT IN [0..nVtces) DO surface[i].vtxPtr[j] _ IO.GetInt[in] - 1; -- switch to counting from zero ENDLOOP; ENDLOOP; }; GetPolygonColors: PUBLIC PROC[shape: REF ShapeInstance, fileName: Rope.ROPE] ~ { in: IO.STREAM; nPolys: NAT; ThreeDScenes.PutShading[ shape, $Type, $Faceted ]; [in, nPolys] _ ThreeDScenes.SetUpStandardFile[fileName]; ThreeDScenes.ReadColors[shape, in, nPolys]; GetPolyNormals[shape]; ThreeDScenes.PutShading[ shape, $PolyColors, fileName ]; shape.shadingInValid _ TRUE; }; ClipPoly: PUBLIC PROC[ context: REF ThreeDScenes.Context, shape: REF ShapeInstance, poly, poly2: REF Polygon] RETURNS [REF Polygon, REF Polygon] ~ { Clip: PROC[side: SixSides, pIn, pOut: REF Polygon] RETURNS [REF Polygon, REF Polygon] = { Dist: PROC[side: SixSides, vtx: Vertex] RETURNS [REAL] = { -- + inside, - outside RETURN[ Linear3d.EvalPlane[ context.clippingPlanes[side], [vtx.ex, vtx.ey, vtx.ez] ] ]; }; shading: ATOM _ NARROW[ ThreeDScenes.GetShading[shape, $Type] ]; lastDist, dist: REAL; outCnt, last: NAT; IF pIn.nVtces < 3 THEN RETURN[ pIn, pOut ]; -- return if degenerate last _ pIn.nVtces - 1; outCnt _ 0; lastDist _ Dist[side, pIn.vtx[last].coord]; IF pOut.length < 2 * pIn.nVtces THEN pOut _ NEW[Polygon[2 * pIn.nVtces]]; FOR i: NAT IN [0..pIn.nVtces) DO a, b: REAL; dist _ Dist[side, pIn.vtx[i].coord]; IF lastDist * dist < 0. THEN { -- put out point if clip plane crossed b _ dist / (dist - lastDist); a _ 1.0 - b; pOut.vtx[outCnt].coord.ex _ pIn.vtx[i].coord.ex * a + pIn.vtx[last].coord.ex * b; pOut.vtx[outCnt].coord.ey _ pIn.vtx[i].coord.ey * a + pIn.vtx[last].coord.ey * b; pOut.vtx[outCnt].coord.ez _ pIn.vtx[i].coord.ez * a + pIn.vtx[last].coord.ez * b; pOut.vtx[outCnt].coord.x _ pIn.vtx[i].coord.x * a + pIn.vtx[last].coord.x * b; pOut.vtx[outCnt].coord.y _ pIn.vtx[i].coord.y * a + pIn.vtx[last].coord.y * b; pOut.vtx[outCnt].coord.z _ pIn.vtx[i].coord.z * a + pIn.vtx[last].coord.z * b; IF shading # $Fancy THEN { -- Integer shading, using precomputed colors pOut.vtx[outCnt].shade.ir _ Real.RoundC[ pIn.vtx[i].shade.ir * a + pIn.vtx[last].shade.ir * b ]; pOut.vtx[outCnt].shade.ig _ Real.RoundC[ pIn.vtx[i].shade.ig * a + pIn.vtx[last].shade.ig * b ]; pOut.vtx[outCnt].shade.ib _ Real.RoundC[ pIn.vtx[i].shade.ib * a + pIn.vtx[last].shade.ib * b ]; pOut.vtx[outCnt].shade.it _ Real.RoundC[ pIn.vtx[i].shade.it * a + pIn.vtx[last].shade.it * b ]; } ELSE { -- fancy shading, using original colors and texture pOut.vtx[outCnt].shade.r _ pIn.vtx[i].shade.r * a + pIn.vtx[last].shade.r * b; pOut.vtx[outCnt].shade.g _ pIn.vtx[i].shade.g * a + pIn.vtx[last].shade.g * b; pOut.vtx[outCnt].shade.b _ pIn.vtx[i].shade.b * a + pIn.vtx[last].shade.b * b; pOut.vtx[outCnt].shade.t _ pIn.vtx[i].shade.t * a + pIn.vtx[last].shade.t * b; pOut.vtx[outCnt].shade.txtrX _ pIn.vtx[i].shade.txtrX * a + pIn.vtx[last].shade.txtrX * b; pOut.vtx[outCnt].shade.txtrY _ pIn.vtx[i].shade.txtrY * a + pIn.vtx[last].shade.txtrY * b; }; IF (shading = $Shiny OR shading = $Fancy) THEN { -- normals pOut.vtx[outCnt].shade.exn _ pIn.vtx[i].shade.exn*a + pIn.vtx[last].shade.exn*b; pOut.vtx[outCnt].shade.eyn _ pIn.vtx[i].shade.eyn*a + pIn.vtx[last].shade.eyn*b; pOut.vtx[outCnt].shade.ezn _ pIn.vtx[i].shade.ezn*a + pIn.vtx[last].shade.ezn*b; }; outCnt _ outCnt + 1; }; IF dist >= 0. THEN { -- put out point if inside pOut.vtx[outCnt].coord.ex _ pIn.vtx[i].coord.ex; pOut.vtx[outCnt].coord.ey _ pIn.vtx[i].coord.ey; pOut.vtx[outCnt].coord.ez _ pIn.vtx[i].coord.ez; pOut.vtx[outCnt].coord.x _ pIn.vtx[i].coord.x; pOut.vtx[outCnt].coord.y _ pIn.vtx[i].coord.y; pOut.vtx[outCnt].coord.z _ pIn.vtx[i].coord.z; IF shading # $Fancy THEN { -- Integer shading, using precomputed colors pOut.vtx[outCnt].shade.ir _ pIn.vtx[i].shade.ir; pOut.vtx[outCnt].shade.ig _ pIn.vtx[i].shade.ig; pOut.vtx[outCnt].shade.ib _ pIn.vtx[i].shade.ib; pOut.vtx[outCnt].shade.it _ pIn.vtx[i].shade.it; } ELSE { -- fancy shading, using original colors and texture pOut.vtx[outCnt].shade.r _ pIn.vtx[i].shade.r; pOut.vtx[outCnt].shade.g _ pIn.vtx[i].shade.g; pOut.vtx[outCnt].shade.b _ pIn.vtx[i].shade.b; pOut.vtx[outCnt].shade.t _ pIn.vtx[i].shade.t; pOut.vtx[outCnt].shade.txtrX _ pIn.vtx[i].shade.txtrX; pOut.vtx[outCnt].shade.txtrY _ pIn.vtx[i].shade.txtrY; }; IF (shading = $Shiny OR shading = $Fancy) THEN { -- normals pOut.vtx[outCnt].shade.exn _ pIn.vtx[i].shade.exn; pOut.vtx[outCnt].shade.eyn _ pIn.vtx[i].shade.eyn; pOut.vtx[outCnt].shade.ezn _ pIn.vtx[i].shade.ezn; }; outCnt _ outCnt + 1; }; lastDist _ dist; last _ i; ENDLOOP; pOut.nVtces _ outCnt; RETURN [ pOut, pIn ]; }; -- end Clip Proc orOfCodes: ThreeDScenes.OutCode _ ThreeDScenes.NoneOut; FOR i: NAT IN [0..poly.nVtces) DO orOfCodes _ LOOPHOLE[ Basics.BITOR[LOOPHOLE[orOfCodes], LOOPHOLE[poly.vtx[i].coord.clip]], ThreeDScenes.OutCode]; ENDLOOP; IF orOfCodes.near THEN [poly, poly2] _ Clip[ Near, poly, poly2]; IF orOfCodes.far THEN [poly, poly2] _ Clip[ Far, poly, poly2]; IF orOfCodes.left THEN [poly, poly2] _ Clip[ Left, poly, poly2]; IF orOfCodes.right THEN [poly, poly2] _ Clip[ Right, poly, poly2]; IF orOfCodes.bottom THEN [poly, poly2] _ Clip[Bottom, poly, poly2]; IF orOfCodes.top THEN [poly, poly2] _ Clip[ Top, poly, poly2]; RETURN[ poly, poly2 ]; }; BackFacing: PUBLIC PROC[ poly: REF Polygon] RETURNS [BOOLEAN] ~ { this: ThreeDScenes.VertexInfo _ poly.vtx[0]; next: ThreeDScenes.VertexInfo _ poly.vtx[1]; last: ThreeDScenes.VertexInfo _ poly.vtx[poly.nVtces - 1]; direction: Triple _ Linear3d.Normalize[Linear3d.CrossProd[ [next.coord.ex - this.coord.ex, next.coord.ey - this.coord.ey, next.coord.ez - this.coord.ez], [last.coord.ex - this.coord.ex, last.coord.ey - this.coord.ey, last.coord.ez - this.coord.ez] ] ]; RETURN[ Linear3d.DotProd[[this.coord.ex, this.coord.ey, this.coord.ez] , direction] > 0. ]; }; ShadePoly: PUBLIC PROC[ context: REF ThreeDScenes.Context, poly: REF Polygon] ~ { ref: REF _ Atom.GetPropFromList[poly.props, $Shininess]; shininess: REAL _ IF ref # NIL THEN NARROW[ref, REF REAL]^ ELSE 0.0; clr: RGB; transmittance: REAL; shading: ATOM _ NARROW[ Atom.GetPropFromList[ poly.props, $Type ] ]; IF shading = $Shiny OR shading = $Fancy THEN shininess _ 0.0; -- highlights later FOR i: NAT IN [0..poly.nVtces) DO -- Check limits and store with vertices [clr, transmittance] _ ThreeDScenes.ShadePt[context, poly.vtx[i], shininess]; poly.vtx[i].shade.ir _ Real.FixC[clr.R * 255.0]; poly.vtx[i].shade.ig _ Real.FixC[clr.G * 255.0]; poly.vtx[i].shade.ib _ Real.FixC[clr.B * 255.0]; poly.vtx[i].shade.it _ Real.FixC[ transmittance * 255.0]; ENDLOOP; }; GetShades: PUBLIC PROC[context: REF ThreeDScenes.Context, shape: REF ShapeInstance] ~ { AverageVertices: PROC[poly: REF ThreeDPolygons.PtrPoly] RETURNS[vtx: Vertex] ~ { FOR i: NAT IN [0..poly.nVtces) DO addVtx: Vertex _ shape.vertex[poly.vtxPtr[i]]; vtx.x _ vtx.x + addVtx.x; vtx.y _ vtx.y + addVtx.y; vtx.z _ vtx.z + addVtx.z; vtx.ex _ vtx.ex + addVtx.ex; vtx.ey _ vtx.ey + addVtx.ey; vtx.ez _ vtx.ez + addVtx.ez; vtx.sx _ vtx.sx + addVtx.sx; vtx.sy _ vtx.sy + addVtx.sy; vtx.sz _ vtx.sz + addVtx.sz; vtx.clip _ LOOPHOLE[ Basics.BITOR[ LOOPHOLE[vtx.clip], LOOPHOLE[addVtx.clip] ], ThreeDScenes.OutCode]; ENDLOOP; vtx.x _ vtx.x/poly.nVtces; vtx.y _ vtx.y/poly.nVtces; vtx.z _ vtx.z/poly.nVtces; vtx.ex _ vtx.ex/poly.nVtces; vtx.ey _ vtx.ey/poly.nVtces; vtx.ez _ vtx.ez/poly.nVtces; vtx.sx _ vtx.sx/poly.nVtces; vtx.sy _ vtx.sy/poly.nVtces; vtx.sz _ vtx.sz/poly.nVtces; }; poly: REF ThreeDPolygons.PtrPolySequence; ref: REF _ Atom.GetPropFromList[shape.shadingProps, $Shininess]; shininess: REAL _ IF ref # NIL THEN NARROW[ref, REF REAL]^ ELSE 0.0; clr: RGB; IF shape.surface = NIL OR ThreeDScenes.GetShading[shape, $Type] # $Faceted THEN { SIGNAL ThreeDPolygonsError[$InsufficientInfo]; RETURN; }; poly _ NARROW[ shape.surface, REF ThreeDPolygons.PtrPolySequence ]; FOR i: NAT IN [0..shape.shade.length) DO IF poly[i].clipState # out THEN { trns: REAL _ 0.0; -- transmittance vtx: Vertex _ AverageVertices[poly[i]]; pt: ThreeDScenes.VertexInfo _ [ vtx, shape.shade[i], NIL ]; [clr, trns] _ ThreeDScenes.ShadePt[context, pt, shininess]; -- 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; shape.shadingInValid _ FALSE; }; GetNormal: PROC[vertex: REF ThreeDScenes.VertexSequence, poly: REF ThreeDPolygons.PtrPoly, cVtx: NAT] RETURNS[normal: Triple] ~ { lVtx: NAT _ (cVtx + poly.nVtces - 1) MOD poly.nVtces; nVtx: NAT _ (cVtx + 1) MOD poly.nVtces; normal _ Linear3d.CrossProd[ DiffPosns[ vertex[poly.vtxPtr[lVtx]], vertex[poly.vtxPtr[cVtx]] ], DiffPosns[ vertex[poly.vtxPtr[nVtx]], vertex[poly.vtxPtr[cVtx]] ] ]; }; GetPolyNormals: PUBLIC PROC[shape: REF ShapeInstance] ~ { surface: REF ThreeDPolygons.PtrPolySequence; IF shape.surface = NIL THEN RETURN; -- not a shape (probably a light source) surface _ NARROW[shape.surface, REF ThreeDPolygons.PtrPolySequence]; IF shape.shade = NIL OR shape.shade.length # shape.numSurfaces THEN { shape.shade _ NEW[ ThreeDScenes.ShadingSequence[shape.numSurfaces] ]; ThreeDScenes.PutShading[shape, $Type, $Faceted]; }; FOR i: NAT IN [0..shape.numSurfaces) DO sumNmls: Triple _ [0., 0., 0.]; FOR cVtx: NAT IN [0..surface[i].nVtces) DO sumNmls _ Linear3d.SumTriple[ sumNmls, GetNormal[shape.vertex, surface[i], cVtx] ]; ENDLOOP; sumNmls _ Linear3d.Normalize[sumNmls]; shape.shade[i].xn _ sumNmls.x; shape.shade[i].yn _ sumNmls.y; shape.shade[i].zn _ sumNmls.z; ENDLOOP; shape.vtcesInValid _ TRUE; -- make sure eye-space normals correct }; GetVtxNormals: PUBLIC PROC[shape: REF ShapeInstance] ~ { surface: REF ThreeDPolygons.PtrPolySequence _ NARROW[shape.surface]; IF shape.shade = NIL OR shape.shade.length # shape.vertex.length THEN { shape.shade _ NEW[ ThreeDScenes.ShadingSequence[shape.vertex.length] ]; IF ThreeDScenes.GetShading[ shape, $Type ] = NIL OR ThreeDScenes.GetShading[ shape, $Type ] = $Faceted THEN ThreeDScenes.PutShading[ shape, $Type, $Smooth]; }; FOR i: NAT IN [0..shape.vertex.length) DO shape.shade[i].xn _ shape.shade[i].yn _ shape.shade[i].zn _ 0.; ENDLOOP; FOR i: NAT IN [0..shape.numSurfaces) DO -- get normals at vertices, add to earlier ones FOR cVtx: NAT IN [0..surface[i].nVtces) DO OPEN shape.shade[surface[i].vtxPtr[cVtx]]; cornerNormal: Triple _ GetNormal[ shape.vertex, surface[i], cVtx ]; [[xn, yn, zn]] _ Linear3d.SumTriple[ cornerNormal, [xn, yn, zn]]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..shape.shade.length) DO OPEN shape.shade[i]; [[xn, yn, zn]] _ Linear3d.Normalize[ [xn, yn, zn] ]; ENDLOOP; shape.vtcesInValid _ TRUE; -- make sure eye-space normals correct }; LoadSortSequence: PUBLIC PROC[ context: REF ThreeDScenes.Context, buckets: REF ThreeDPolygons.SortSequence _ NIL ] RETURNS[REF ThreeDPolygons.SortSequence] ~ { shape: REF ThreeDScenes.ShapeSequence _ context.shapes; polygonCount: NAT _ 0; bucketListPtr: NAT _ context.depthResolution; minDepth, maxDepth: REAL _ shape[0].centroid.ez; zScale: REAL _ 1.; FOR i: NAT IN [0.. shape.length) DO -- get minimum and maximum depth and polygon count IF shape[i].clipState # out AND shape[i].surface # NIL THEN { radius: REAL _ shape[i].boundingRadius; IF shape[i].centroid.ez - radius < minDepth THEN minDepth _ shape[i].centroid.ez - radius; IF shape[i].centroid.ez + radius > maxDepth THEN maxDepth _ shape[i].centroid.ez + radius; polygonCount _ polygonCount + shape[i].numSurfaces; }; ENDLOOP; minDepth _ MAX[minDepth, 0]; -- nothing allowed behind the eyepoint IF (maxDepth - minDepth) > 0. THEN zScale _ (context.depthResolution - 1) / (maxDepth - minDepth); IF buckets = NIL OR buckets.length < polygonCount + context.depthResolution THEN buckets _ NEW[ThreeDPolygons.SortSequence[polygonCount + context.depthResolution]]; FOR i: NAT IN [0..context.depthResolution) DO buckets[i].shape _ NIL; buckets[i].next _ 0; ENDLOOP; FOR s: NAT IN [0.. shape.length) DO -- Enter polygons (by z-centroid) in bucket lists IF shape[s].clipState # out AND shape[s].surface # NIL THEN { poly: REF ThreeDPolygons.PtrPolySequence _ NARROW[shape[s].surface]; FOR i: NAT IN [0..shape[s].numSurfaces) DO zSum: REAL _ 0.; iz: INTEGER; IF shape[s].clipState = in THEN { -- unclipped, inside FOR j: NAT IN [0..poly[i].nVtces) DO zSum _ zSum + shape[s].vertex[poly[i].vtxPtr[j]].ez; ENDLOOP; iz _ Real.FixI[zScale * ((zSum / poly[i].nVtces) - minDepth)]; poly[i].clipState _ in; } ELSE IF shape[s].clipState = clipped THEN { -- clipped, evaluate clipping tags orOfCodes: ThreeDScenes.OutCode _ ThreeDScenes.NoneOut; andOfCodes: ThreeDScenes.OutCode _ ThreeDScenes.AllOut; FOR j: NAT IN [0..poly[i].nVtces) DO k: NAT _ poly[i].vtxPtr[j]; zSum _ zSum + shape[s].vertex[k].ez; orOfCodes _ LOOPHOLE[ Basics.BITOR[ LOOPHOLE[orOfCodes], LOOPHOLE[shape[s].vertex[k].clip] ], ThreeDScenes.OutCode]; andOfCodes _ LOOPHOLE[ Basics.BITAND[LOOPHOLE[andOfCodes], LOOPHOLE[shape[s].vertex[k].clip]], ThreeDScenes.OutCode]; ENDLOOP; IF andOfCodes # ThreeDScenes.NoneOut THEN poly[i].clipState _ out ELSE IF orOfCodes = ThreeDScenes.NoneOut THEN poly[i].clipState _ in ELSE poly[i].clipState _ clipped; IF poly[i].clipState # out THEN { iz _ Real.FixI[zScale * ((zSum / poly[i].nVtces) - minDepth)]; IF iz < 0 THEN iz _ 0; }; }; IF poly[i].clipState # out THEN { IF buckets[iz].shape # NIL THEN { -- previous entry in this bucket buckets[bucketListPtr].next _ buckets[iz].next; buckets[iz].next _ bucketListPtr; iz _ bucketListPtr; bucketListPtr _ bucketListPtr + 1; }; buckets[iz].shape _ shape[s]; buckets[iz].polygon _ i; }; ENDLOOP; }; ENDLOOP; RETURN[ buckets ]; }; DoBackToFront: PUBLIC PROC[ context: REF ThreeDScenes.Context, sortSeq: REF ThreeDPolygons.SortSequence, action: PROC[ThreeDPolygons.ShapePolygon] ] ~ { ThreeDScenes.FillInBackGround[context]; FOR i: NAT DECREASING IN [0..context.depthResolution) DO j: NAT _ i; WHILE sortSeq[j].shape # NIL DO action[sortSeq[j]]; -- call back with next polygon in order j _ sortSeq[j].next; IF j = 0 THEN EXIT; ENDLOOP; ENDLOOP; CombineBoxes[context]; -- get combined bounding box on scene }; DoFrontToBack: PUBLIC PROC[ context: REF ThreeDScenes.Context, sortSeq: REF ThreeDPolygons.SortSequence, action: PROC[ThreeDPolygons.ShapePolygon] ] ~ { ThreeDScenes.FillViewPort[context, [0.0,0.0,0.0] ]; -- clear screen and alpha buffer FOR i: NAT IN [0..context.depthResolution) DO j: NAT _ i; WHILE sortSeq[j].shape # NIL DO action[sortSeq[j]]; -- call back with next polygon in order j _ sortSeq[j].next; IF j = 0 THEN EXIT; ENDLOOP; ENDLOOP; CombineBoxes[context]; -- get combined bounding box on scene ThreeDScenes.FillInBackGround[context]; }; DoForPolygons: PUBLIC PROC[ set: REF ThreeDScenes.ShapeSequence, action1: PROC[ThreeDPolygons.ShapePolygon], action2: PROC[REF ThreeDScenes.ShapeInstance] _ NIL ] ~ { FOR s: NAT IN [0..set.length) DO IF set[s].clipState = in AND action2 # NIL THEN action2[set[s]] ELSE IF set[s].clipState # out AND set[s].surface # NIL THEN { poly: REF ThreeDPolygons.PtrPolySequence _ NARROW[set[s].surface]; FOR i: NAT IN [0..set[s].numSurfaces) DO IF set[s].clipState = in THEN poly[i].clipState _ in -- unclipped, inside ELSE IF set[s].clipState = clipped THEN { -- clipped, evaluate clipping tags orOfCodes: ThreeDScenes.OutCode _ ThreeDScenes.NoneOut; andOfCodes: ThreeDScenes.OutCode _ ThreeDScenes.AllOut; FOR j: NAT IN [0..poly[i].nVtces) DO k: NAT _ poly[i].vtxPtr[j]; orOfCodes _ LOOPHOLE[ Basics.BITOR[ LOOPHOLE[orOfCodes], LOOPHOLE[set[s].vertex[k].clip] ], ThreeDScenes.OutCode]; andOfCodes _ LOOPHOLE[ Basics.BITAND[LOOPHOLE[andOfCodes], LOOPHOLE[set[s].vertex[k].clip]], ThreeDScenes.OutCode]; ENDLOOP; IF andOfCodes # ThreeDScenes.NoneOut THEN poly[i].clipState _ out ELSE IF orOfCodes = ThreeDScenes.NoneOut THEN poly[i].clipState _ in ELSE poly[i].clipState _ clipped; }; IF poly[i].clipState # out THEN action1[ [set[s], i, 0] ]; ENDLOOP; }; ENDLOOP; }; ShowObjects: PUBLIC PROC[ context: REF ThreeDScenes.Context, frontToBack: BOOLEAN _ FALSE ] ~ { ShowPolygon: PROC[p: ThreeDPolygons.ShapePolygon] ~{ OutputPoly[ context, p ]; }; shape: REF ThreeDScenes.ShapeSequence _ context.shapes; sortSeq: REF ThreeDPolygons.SortSequence; FOR i: NAT IN [0.. shape.length) DO IF shape[i].vtcesInValid THEN { shape[i].clipState _ ThreeDScenes.XfmToEyeSpace[ context, shape[i] ]; IF shape[i].clipState # out THEN ThreeDScenes.XfmToDisplay[context, shape[i] ]; }; ENDLOOP; FOR i: NAT IN [0.. shape.length) DO -- now, get everything shaded IF shape[i].shadingInValid AND (shape[i].clipState # out) THEN { IF shape[i].shade = NIL THEN GetPolyNormals[shape[i]]; -- makes default faceted IF ThreeDScenes.GetShading[ shape[i], $Type ] = $Faceted THEN GetShades[ context, shape[i] ] ELSE ThreeDScenes.GetVtxShades[ context, shape[i] ]; }; ENDLOOP; sortSeq _ LoadSortSequence[context]; IF frontToBack THEN DoFrontToBack[context, sortSeq, ShowPolygon] ELSE DoBackToFront[context, sortSeq, ShowPolygon]; }; ShowWireFrameObjects: PUBLIC PROC[context: REF ThreeDScenes.Context ] ~ { ShowPolygon: PROC[p: ThreeDPolygons.ShapePolygon] ~{ OutputPlygnLines[ context, p ]; }; ShowShape: PROC[s: REF ThreeDScenes.ShapeInstance] ~{ OutputShapeLines[ context, s ]; }; shape: REF ThreeDScenes.ShapeSequence _ context.shapes; FOR i: NAT IN [0.. shape.length) DO IF shape[i].vtcesInValid THEN { shape[i].clipState _ ThreeDScenes.XfmToEyeSpace[ context, shape[i] ]; IF shape[i].clipState # out THEN ThreeDScenes.XfmToDisplay[context, shape[i] ]; }; ENDLOOP; ThreeDScenes.FillInBackGround[context]; IF context.renderMode = $LF OR context.renderMode = $Dithered THEN ThreeDMisc.SetNamedColor[context, "White" ]; DoForPolygons[context.shapes, ShowPolygon, ShowShape]; CombineBoxes[context]; -- get combined bounding box on scene }; OutputShapeLines: PUBLIC PROC[context: REF ThreeDScenes.Context, s: REF ThreeDScenes.ShapeInstance] ~ { ax, ay, bx, by: NAT; color: SampleSet _ ShapetoColorBytes[context, s]; imagerCtx: Imager.Context; usingImager: BOOLEAN _ FALSE; polygons: REF ThreeDPolygons.PtrPolySequence; IF s.surface = NIL THEN RETURN; IF context.renderMode = $LF OR context.renderMode = $Dithered THEN { usingImager _ TRUE; imagerCtx _ ThreeDMisc.GetImagerContext[context]; }; polygons _ NARROW[s.surface, REF ThreeDPolygons.PtrPolySequence]; FOR i: NAT IN [0..s.numSurfaces) DO FOR j: NAT IN [0..polygons[i].nVtces] DO k: NAT _ IF j = polygons[i].nVtces THEN 0 ELSE j; bx _ Real.RoundC[s.vertex[polygons[i].vtxPtr[k]].sx]; by _ Real.RoundC[s.vertex[polygons[i].vtxPtr[k]].sy]; IF j > 0 THEN IF NOT usingImager THEN ScanConvert.PutLine[ context.display, [ax,ay], [bx,by], color ] ELSE Imager.MaskVectorI[ imagerCtx, ax, ay, bx, by ]; ax _ bx; ay _ by; ENDLOOP; IF stopMe THEN HoldEverything[]; -- shut down if stop signal received ENDLOOP; }; OutputPlygnLines: PUBLIC PROC[context: REF ThreeDScenes.Context, p: ThreeDPolygons.ShapePolygon] ~ { poly: REF ThreeDPolygons.PtrPoly _ NARROW[p.shape.surface, REF ThreeDPolygons.PtrPolySequence][p.polygon]; vertex: REF ThreeDScenes.VertexSequence _ p.shape.vertex; color: SampleSet _ ShapetoColorBytes[context, p.shape]; imagerCtx: Imager.Context; usingImager: BOOLEAN _ FALSE; IF poly.clipState = out THEN RETURN[]; -- reject if outside frame IF context.renderMode = $LF OR context.renderMode = $Dithered THEN { usingImager _ TRUE; imagerCtx _ ThreeDMisc.GetImagerContext[context]; }; IF poly.clipState = in THEN { ax, ay, bx, by: NAT; FOR j: NAT IN [0..poly.nVtces] DO i: NAT _ IF j = poly.nVtces THEN 0 ELSE j; bx _ Real.RoundC[vertex[poly.vtxPtr[i]].sx]; by _ Real.RoundC[vertex[poly.vtxPtr[i]].sy]; IF j > 0 THEN IF NOT usingImager THEN ScanConvert.PutLine[ context.display, [ax,ay], [bx,by], color ] ELSE Imager.MaskVectorI[ imagerCtx, ax, ay, bx, by ]; ax _ bx; ay _ by; ENDLOOP; } ELSE IF poly.clipState = clipped THEN { IF (tempPoly = NIL) OR (tempPoly.length < 2 * poly.nVtces) THEN { tempPoly _ NEW[Polygon[2 * poly.nVtces] ]; tempPoly2 _ NEW[Polygon[2 * poly.nVtces] ]; }; tempPoly.nVtces _ poly.nVtces; FOR i: NAT IN [0..poly.nVtces) DO tempPoly.vtx[i].coord _ p.shape.vertex[poly.vtxPtr[i]]; ENDLOOP; [tempPoly, tempPoly2] _ ClipPoly[ context, p.shape, tempPoly, tempPoly2 ]; IF tempPoly.nVtces > 2 THEN { ax, ay, bx, by: NAT; FOR j: NAT IN [0..tempPoly.nVtces] DO i: NAT _ IF j = tempPoly.nVtces THEN 0 ELSE j; x, y, z: REAL; [[x, y, z]] _ ThreeDScenes.XfmPtToDisplay[ context, [tempPoly.vtx[i].coord.ex, tempPoly.vtx[i].coord.ey, tempPoly.vtx[i].coord.ez] ]; bx _ Real.RoundC[x]; by _ Real.RoundC[y]; IF j > 0 THEN IF NOT usingImager THEN ScanConvert.PutLine[ context.display, [ax,ay], [bx,by], color ] ELSE Imager.MaskVectorI[ imagerCtx, ax, ay, bx, by ]; ax _ bx; ay _ by; ENDLOOP; }; }; IF stopMe THEN HoldEverything[]; -- shut down if stop signal received }; OutputPoly: PUBLIC PROC[context: REF ThreeDScenes.Context, p: ThreeDPolygons.ShapePolygon] ~ { poly: REF ThreeDPolygons.PtrPoly _ NARROW[p.shape.surface, REF ThreeDPolygons.PtrPolySequence][p.polygon]; vertex: REF ThreeDScenes.VertexSequence _ p.shape.vertex; shade: REF ThreeDScenes.ShadingSequence _ p.shape.shade; shading: ATOM _ NARROW[ ThreeDScenes.GetShading[ p.shape, $Type ] ]; faceted: BOOLEAN _ shading = $Faceted; IF poly.clipState = out THEN RETURN[]; -- reject if outside frame IF (tempPoly = NIL) OR (tempPoly.length < 2 * poly.nVtces) THEN { tempPoly _ NEW[Polygon[2 * poly.nVtces] ]; tempPoly2 _ NEW[Polygon[2 * poly.nVtces] ]; }; tempPoly.nVtces _ poly.nVtces; FOR i: NAT IN [0..poly.nVtces) DO j: NAT _ poly.vtxPtr[i]; k: NAT _ IF faceted THEN p.polygon ELSE j; -- shades go with polygons when faceted tempPoly.vtx[i].coord _ vertex[j]; tempPoly.vtx[i].shade _ shade[k]; ENDLOOP; IF BackFacing[tempPoly] THEN IF p.shape.type = $Polyhedron -- backfacing poly!! THEN RETURN[] -- Reject if closed surface ELSE { FOR i: NAT IN [0..tempPoly.nVtces) DO -- recalculate shading for back side r, g, b, t: REAL; tempPoly.vtx[i].shade.exn _ -tempPoly.vtx[i].shade.exn; -- reverse normals tempPoly.vtx[i].shade.eyn _ -tempPoly.vtx[i].shade.eyn; tempPoly.vtx[i].shade.ezn _ -tempPoly.vtx[i].shade.ezn; [[r,g,b], t] _ ThreeDScenes.ShadePt[ context, tempPoly.vtx[i], 0.0 ]; tempPoly.vtx[i].shade.ir _ Real.FixC[r * 255.0]; tempPoly.vtx[i].shade.ig _ Real.FixC[g * 255.0]; tempPoly.vtx[i].shade.ib _ Real.FixC[b * 255.0]; tempPoly.vtx[i].shade.it _ Real.FixC[ t * 255.0]; ENDLOOP; FOR i: NAT IN [0..tempPoly.nVtces/2) DO -- reorder to keep clockwise on display tempVtx: ThreeDScenes.VertexInfo _ tempPoly.vtx[i]; tempPoly.vtx[i] _ tempPoly.vtx[tempPoly.nVtces-1 - i]; tempPoly.vtx[tempPoly.nVtces-1 - i] _ tempVtx; ENDLOOP; }; IF poly.clipState = clipped THEN { [tempPoly, tempPoly2] _ ClipPoly[context, p.shape, tempPoly, tempPoly2]; FOR i: NAT IN [0..tempPoly.nVtces) DO OPEN tempPoly.vtx[i].coord; [[sx, sy, sz]] _ ThreeDScenes.XfmPtToDisplay[context, [ex, ey, ez]]; ENDLOOP; }; IF tempPoly.nVtces > 2 THEN { tempPoly.props _ p.shape.shadingProps; -- transmit shading info Tilers.PolygonTiler[context, shading, tempPoly]; }; IF stopMe THEN HoldEverything[]; -- shut down if stop signal received }; END. ^ThreeDPolygonsImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Last Edited by: Crow, January 22, 1986 10:05:40 pm PST Internal Declarations sortSeq, buckets: REF ThreeDPolygons.SortSequence _ NIL; -- made global to limit garbage collection Utility Procedures Procedures for Reading in Shape Descriptions Get File, Read number of points and number of polygons Find approximation to bounding sphere Procedures for Transformations and Clipping Procedures for Shading Backfacing test based on first vertex and adjacent vertices Calculate shades for Faceted shading (Used for quick display) Compute Normals, Sum normals at polygon corners to get polygon normal Sum normals for vertices given by adjacent polygon corners Procedures for Sorting and Display Get Everything (including light centroids) into eyespace Unclipped shape output ΚW˜Iheadšœ™šœ Οmœ1™J˜—š‘œž œ˜!Jšœ žœ˜Nšžœžœ˜J˜J˜—Jš‘ œžœžœžœ˜7Jš‘ œžœžœžœ˜4š ‘œžœ žœžœžœ˜qNšœžœžœ'˜3Lšž˜Lšœ žœ˜šžœžœ˜ šžœ˜Lšœ žœžœžœ ˜9LšœV˜VL˜—Lšžœ˜—šžœžœ !˜GJšœ ˜ šœ˜Jšžœžœ%˜BJšœB˜BJšœ˜—šœ ˜ Jšžœžœ%˜BJšœ!˜!J˜—šœ˜Jšžœžœ%˜BJšœ˜Jšœ˜Jšœ˜Jšœ˜—Jšžœžœ%˜7—Jšžœ ˜Jšœ˜—š‘ œžœ žœ ˜VIfdefaultš œžœžœžœžœ˜5šžœžœžœžœ˜,šžœ#žœ˜+šžœB˜DJšžœB˜F—šžœD˜FJšžœD˜H—šžœF˜HJšžœF˜J—šžœ@˜BJšžœ@˜D—Jšœ˜—Jšžœ˜—J˜——šœŸœ ™,š ‘Πbnœžœžœ žœžœžœ˜pJšœ žœ˜Jšœ˜šœžœ˜ Jšœ6™6—Jšœžœžœ˜Jšœ>˜>Jšœ&˜&J˜Jšœ˜Ošœ8 ˜Hšœ. ˜LO™%—LšœF˜Fš žœžœžœžœ !˜Wšžœ˜Jšžœ˜Jšžœžœžœ˜A—šžœ˜Jšžœ˜Jšžœžœžœ˜A—šžœ˜Jšžœ˜Jšžœžœžœ˜A—Jšžœ˜—Jšœ- &˜SJšœ)˜)Jšœ(˜(Jšœ˜š žœžœžœžœ ˜BJšœ˜šœ-˜-Jšœ+˜+Jšœ.˜.—Jšžœ˜!Jšžœ˜#Jšžœ˜ —J˜J˜—š‘ œžœžœžœžœžœ žœ˜SLšœ žœ ˜,Lšœžœžœ7˜JNšœ žœžœ!˜Dš žœžœžœ žœ Οi ˜BJšœžœžœ ˜Jšœ žœ!˜1Jšœ˜šžœžœžœ ž˜Jšœžœ ˜JJšžœ˜—Jšžœ˜—J˜J˜—š ‘œžœžœžœžœ˜PJšœžœžœ˜Jšœžœ˜ Jšœ2˜2Jšœ8˜8Jšœ+˜+Jšœ˜Jšœ8˜8Jšœžœ˜J˜J˜——šœ+™+š‘œžœžœ žœžœ$žœžœžœ žœ ˜Ÿš ‘œžœžœ žœžœ žœ˜Zš ‘œžœžœžœ ˜Ršžœ˜Nšœ˜Nšœ˜Nšœ˜—Nšœ˜—Nšœ žœžœ*˜@Nšœžœžœ˜+Ošžœžœžœ ˜CNšœ˜N˜ Nšœ+˜+Nšžœžœžœ˜Išžœžœžœž˜ Nšœžœ˜ Nšœ$˜$šžœžœ &˜GNšœ-˜-NšœQ˜QNšœQ˜QNšœQ˜QNšœN˜NNšœN˜NNšœN˜Nšžœžœ ,˜HNšœ)˜)Nšœ@˜@Nšœ)˜)Nšœ@˜@Nšœ)˜)Nšœ@˜@Nšœ)˜)Nšœ@˜@N˜—šžœ  3˜@NšœN˜NNšœN˜NNšœN˜NNšœN˜NNšœ˜NšœB˜BNšœ˜NšœB˜BNšœ˜—šžœžœžœ  ˜=NšœP˜PNšœP˜PNšœP˜PN˜—N˜N˜—šžœ žœ ˜4Nšœ0˜0Nšœ0˜0Nšœ0˜0Nšœ.˜.Nšœ.˜.Nšœ.˜.šžœžœ ,˜HNšœ0˜0Nšœ0˜0Nšœ0˜0Nšœ0˜0Nšœ˜—šžœ  3˜@Nšœ.˜.Nšœ.˜.Nšœ.˜.Nšœ.˜.Nšœ6˜6Nšœ6˜6N˜—šžœžœžœ  ˜=Nšœ2˜2Nšœ2˜2Nšœ2˜2N˜—N˜N˜—Nšœ˜Nšžœ˜—N˜Nšžœ˜Nšœ ˜—Ošœ7˜7šžœžœžœžœ˜"šœ žœ˜Jšœžœžœ žœ˜DJšœ˜—Jšžœ˜—Jšžœžœ,˜EJšžœžœ-˜GJšžœžœ.˜HJšžœžœ-˜FJšžœžœ+˜CJšžœžœ,˜EJšžœ˜N˜——šœ™š ‘ œžœžœžœ žœžœ˜ANšœ;™;Nšœ,˜,Nšœ,˜,Nšœ<˜<šœ:˜:Nšœ_˜_Nšœd˜d—OšžœU˜[Nšœ˜N™—š ‘ œžœžœ žœžœ ˜QJšœžœ0˜8Jšœ žœžœžœžœžœžœžœžœ˜DNšœžœ˜ Nšœž˜Jšœ žœžœ.˜DJšžœžœžœ ž˜Rš žœžœžœžœ '˜JNšœM˜MNšœ0˜0Nšœ0˜0Nšœ0˜0Nšœ9˜9Nšžœ˜—Nšœ˜N™—š ‘ œžœžœ žœžœ˜WJ™=š‘œžœžœžœ˜Pšžœžœžœž˜!Jšœ.˜.JšœV˜VJšœX˜XJšœY˜YJš œ žœ žœžœ žœ1˜pJšžœ˜—JšœW˜WJšœX˜XJšœY˜YJšœ˜—Jšœžœ ˜)Jšœžœ8˜@Jšœ žœžœžœžœžœžœžœžœ˜DLšœžœ˜ šžœžœžœ2žœ˜QLšžœ(˜.Lšžœ˜L˜—šœžœžœ"˜CL˜—š žœžœžœžœžœžœ˜JLšœžœ  ˜$Lšœ'˜'Lšœ5žœ˜;Ošœ; ˜NOšœ.˜.Lšœ-˜-Lšœ-˜-Jšœ-˜-Jšœž œ˜ —Jšœžœ˜J˜J˜—š ‘ œžœ žœ+žœžœžœ˜ŽJšœžœžœ˜6Jšœžœžœ ˜'šœ˜JšœB˜BJšœB˜BJ˜—L˜L˜—š‘œžœžœžœ˜:Nš£œ4™ELšœ žœ ˜,Lš žœžœžœžœ (˜OLšœ žœžœ!˜Dšžœžœžœ(žœ˜EJšœžœ5˜FJšœ0˜0J˜—šžœžœžœžœ˜(J˜šžœžœžœžœ˜+Jšœ^˜^Jšžœ˜—Jšœ&˜&Jšœ˜Jšœ˜Jšœ˜Jšžœ˜ —Jšœžœ -˜HJ˜J™—š‘ œžœžœžœ˜8Jšœ:™:Lšœ žœ"žœ˜Dšžœžœžœ*žœ˜GJšœžœ7˜Hšžœ.žœžœ4˜lJšžœ1˜5—J˜—šžœžœžœžœ˜*JšœA˜AJšžœ˜ —š žœžœžœžœ 0˜\š žœžœžœžœžœ'˜VJšœC˜CJšœA˜AJšžœ˜—Jšžœ˜ —š žœžœžœžœžœ˜=Jšœ4˜4Jšžœ˜—Jšœžœ -˜HJ˜——šœ"™"š‘œžœžœ žœ+žœžœ žœžœ!˜±Nšœžœ-˜7Nšœžœ˜Nšœžœ˜-Nšœžœ˜0Nšœžœ˜š žœžœžœžœ 2˜Všžœžœžœžœ˜=Nšœžœ˜'Nšžœ)ž˜,Nšœžœ*˜/šžœ)˜+Nšžœ*˜.—Nšœ3˜3N˜—Nšžœ˜—Nšœ žœ &˜Gšžœ˜Nšžœ@˜D—šžœ žœžœ9ž˜PLšœ žœF˜S—šžœžœžœžœ˜.Nšœžœžœ˜7—š žœžœžœžœ 5˜Zšžœžœžœ˜>Jšœžœ"žœ˜Dšžœžœžœžœ˜+Jšœžœ ˜Jšœžœ˜ šžœžœ  ˜>šžœžœžœžœ˜%Jšœ5˜5Jšžœ˜ —Jšœ?˜?Jšœ˜J˜—šžœžœžœ "˜PJšœ;˜;Jšœ8˜8šžœžœžœž˜$Jšœžœ˜Jšœ$˜$šœ žœ˜Jšœžœž œ žœ˜GJšœ˜—šœ žœ˜Jšœžœžœžœ˜GJšœ˜—Jšžœ˜—šžœ#˜%Jšžœ˜šžœžœ"˜)Jšžœ˜Jšžœ˜!——šžœžœ˜!Jšœ>˜>Jšžœžœ˜J˜—J˜—šžœžœ˜!šžœžœžœ  ˜FJ˜3J˜!J˜9J˜—Jšœ!˜!J˜J˜—Jšžœ˜—Jšœ˜—Nšžœ˜—Nšžœ ˜Nšœ˜N˜—š ‘ œžœžœ žœ+žœ0žœ#˜­Nšœ'˜'šžœžœž œžœ˜9Nšœžœ˜ šžœžœž˜Nšœ ,˜BNšœ˜Nšžœžœžœ˜Nšžœ˜—Nšžœ˜—Jšœ %˜>Nšœ˜N˜—š ‘ œžœžœ žœ+žœ0žœ#˜­N˜Nšœ6  ˜Všžœžœžœžœ˜.Nšœžœ˜ šžœžœž˜Nšœ ,˜BNšœ˜Nšžœžœžœ˜Nšžœ˜—Nšžœ˜—Jšœ %˜>Nšœ'˜'Nšœ˜N™—š‘ œžœžœžœ1žœ3žœžœžœ˜Όšžœžœžœž˜"Nšžœžœ žœžœ˜?šžœžœžœžœ˜?Jšœžœ"žœ˜Bšžœžœžœžœ˜)šžœ˜Jšžœ ˜9šžœžœžœ "˜NJšœ;˜;Jšœ8˜8šžœžœžœž˜$Jšœžœ˜šœ žœ˜Jšœžœž œ žœ˜EJšœ˜—šœ žœ˜Jšœžœžœžœ˜EJšœ˜—Jšžœ˜—Jšžœ#žœ˜BJšžœžœ"žœ˜DJšžœ˜!J˜——Jšžœžœ˜:Jšžœ˜ —J˜—Nšžœ˜—Nšœ˜J˜—š ‘ œžœžœ žœ$žœžœ˜_š‘ œžœ#˜4Jšœ˜J˜—Nšœžœ-˜7šœ žœ˜)O™8—š žœžœžœžœžœžœ˜DJšœE˜EJšžœžœ/˜OJšœ˜Jšžœ˜—š žœžœžœžœ ˜Fšžœžœžœ˜AJšžœžœžœ ˜Pšžœ6˜8Jšžœ˜#Jšžœ0˜4—J˜—Jšžœ˜—Jšœ$˜$Jšžœ žœ4žœ.˜yJšœ˜J˜—š‘œžœžœ žœ˜Iš‘ œžœ#˜4Jšœ˜J˜—š‘ œžœžœ˜5Jšœ˜J˜—Nšœžœ-˜7šžœžœžœž˜#šžœžœ˜JšœE˜EJšžœžœ/˜OJšœ˜—Jšžœ˜—Nšœ'˜'šžœžœ˜=Jšžœ-˜1—Jšœ6˜6Jšœ %˜>Jšœ˜J˜—š ‘œžœžœ žœ%žœ!˜sJš ™Jšœžœ˜Jšœ1˜1J˜Jšœ žœžœ˜Jšœ žœ ˜-Jšžœ žœžœžœ˜!šžœžœ žœ˜DLšœžœ˜Lšœ1˜1L˜—Jšœžœžœ žœ!˜Ašžœžœžœž˜#šžœžœžœžœ˜)Jš œžœžœžœžœ˜1Jšœ5˜5Jšœ5˜5šžœžœ˜šžœžœ ˜Jšžœ@˜DJšžœ1˜5——Jšœ˜Jšžœ˜—Jšžœžœ $˜FJšžœ˜ —J˜—š‘œžœžœ žœC˜mJšœžœž œžœ,˜oNšœžœ.˜9Jšœ7˜7J˜Jšœ žœžœ˜Jšžœžœžœ ˜Gšžœžœ žœ˜DLšœžœ˜Lšœ1˜1L˜—šžœžœ˜Jšœžœ˜šžœžœžœžœ˜"Jš œžœžœžœžœ˜*Jšœ,˜,Jšœ,˜,šžœžœ˜šžœžœ ˜Jšžœ@˜DJšžœ1˜5——Jšœ˜Jšžœ˜—J˜—šžœžœžœ˜'šžœ žœžœ%žœ˜BJšœ žœ˜*Jšœ žœ˜+J˜—J˜šžœžœžœžœ˜"Jšœ8˜8Jšžœ˜—JšœGžœ˜Kšžœžœ˜Jšœžœ˜šžœžœžœž˜&Jš œžœžœžœžœ˜.Jšœ žœ˜šœ*˜*Jšœ ˜ JšœN˜NJšœ˜—Jšœ,˜,šžœžœ˜šžœžœ ˜Jšžœ@˜DJšžœ1˜5——Jšœ˜Jšžœ˜—J˜—J˜—Jšžœžœ $˜FJšœ˜J˜J˜—š‘ œžœžœ žœF˜jJšœžœž œžœ,˜oNšœžœ.˜9Jšœžœ.˜8Jšœ žœžœ.˜DJšœ žœ˜&J˜Jšžœžœžœ ˜Fšžœ žœžœ%žœ˜BJšœ žœ˜*Jšœ žœ˜+J˜—J˜šžœžœžœž˜!Jšœžœ˜Jš œžœžœ žœ žœ '˜RJšœ#˜#J˜!Jšžœ˜—šžœžœžœ ˜OJšžœžœ )˜7šžœ˜š žœžœžœžœ $˜JJšœ žœ˜Jšœ9 ˜LJšœ7˜7Jšœ7˜7JšœE˜EJšœ1˜1Lšœ0˜0Lšœ0˜0Jšœ1˜1Jšžœ˜—š žœžœžœžœ '˜PJšœ3˜3Jšœ6˜6Jšœ.˜.Jšžœ˜——J˜—šžœžœ˜"JšœH˜Hšžœžœžœžœ˜AJšœD˜DJšžœ˜—J˜—šžœžœ˜Jšœ+ ˜CJšœ0˜0J˜—Jšžœžœ $˜FJ˜J˜J˜—J˜—Jšžœ˜—…—sl•!