<> <> <> DIRECTORY Atom USING [ GetPropFromList, PutPropOnList, PropList ], Real USING [ Fix, Round ], Basics USING [ BITOR, BITAND ], BasicTime USING [ PulsesToSeconds, GetClockPulses ], Rope USING [ ROPE, Cat ], Convert USING [ RopeFromReal ], ScanConvert USING [ justNoticeable ], G3dVector USING [ Add, Cross, Dot, Length, Mul, Normalize ], G3dMatrix USING [ Mul, Transform, TransformVec ], G3dPlane USING [ DistanceToPoint ], ThreeDBasics USING [ AllOut, ClipState, Context, Error, FacingDir, IntegerSequence, NatSequence, NoneOut, OutCode, Patch, PatchSequence, PtrPatchSequence, PtrPatch, RealSequence, RGB, RegisterShadingClass, RegisterSurfaceType, ScaleAndAddXfm, ShadingClass, ShadingSequence, ShadingValue, ShapeClass, ShapeInstance, ShapeSequence, ShapeProc, SixSides, Triple, TripleSequence, Vertex, VertexInfo, VertexInfoProc, VertexInfoSequence, VertexSequence, Xfm3D ], SurfaceRender USING [ OutputPolygon, RopeDisplay, ValidatePolyhedron, ValidateRopeShape ], ShapeUtilities USING [ ShapePatch ]; ShapeUtilitiesImpl: CEDAR MONITOR IMPORTS Atom, Basics, BasicTime, Convert, G3dPlane, G3dMatrix, Real, Rope, SurfaceRender, ThreeDBasics, G3dVector EXPORTS ShapeUtilities ~ BEGIN <> Context: TYPE ~ ThreeDBasics.Context; RGB: TYPE ~ ThreeDBasics.RGB; -- [ r, g, b: REAL]; SixSides: TYPE ~ ThreeDBasics.SixSides; ScaleAndAddXfm: TYPE ~ ThreeDBasics.ScaleAndAddXfm; OutCode: TYPE ~ ThreeDBasics.OutCode; NoneOut: OutCode ~ ThreeDBasics.NoneOut; AllOut: OutCode ~ ThreeDBasics.AllOut; Xfm3D: TYPE ~ ThreeDBasics.Xfm3D; Triple: TYPE ~ ThreeDBasics.Triple; TripleSequence: TYPE ~ ThreeDBasics.TripleSequence; NatSequence: TYPE ~ ThreeDBasics.NatSequence; IntegerSequence: TYPE ~ ThreeDBasics.IntegerSequence; RealSequence: TYPE ~ ThreeDBasics.RealSequence; ShapeInstance: TYPE ~ ThreeDBasics.ShapeInstance; FacingDir: TYPE ~ ThreeDBasics.FacingDir; Patch: TYPE ~ ThreeDBasics.Patch; PatchSequence: TYPE ~ ThreeDBasics.PatchSequence; PtrPatch: TYPE ~ ThreeDBasics.PtrPatch; PtrPatchSequence: TYPE ~ ThreeDBasics.PtrPatchSequence; ShapePatch: TYPE ~ ShapeUtilities.ShapePatch; Vertex: TYPE ~ ThreeDBasics.Vertex; VertexSequence: TYPE ~ ThreeDBasics.VertexSequence; VertexInfo: TYPE ~ ThreeDBasics.VertexInfo; VertexInfoSequence: TYPE ~ ThreeDBasics.VertexInfoSequence; VertexInfoProc: TYPE ~ ThreeDBasics.VertexInfoProc; ShadingValue: TYPE ~ ThreeDBasics.ShadingValue; ShadingSequence: TYPE ~ ThreeDBasics.ShadingSequence; ShapeClass: TYPE ~ ThreeDBasics.ShapeClass; ShadingClass: TYPE ~ ThreeDBasics.ShadingClass; ShapeProc: TYPE ~ ThreeDBasics.ShapeProc; -- PROC[ Context, ShapeInstance ] LORA: TYPE ~ LIST OF REF ANY; <> Transform: PROC[p: Triple, mat: Xfm3D] RETURNS[Triple] ~ G3dMatrix.Transform; TransformVec: PROC[vec: Triple, mat: Xfm3D] RETURNS[Triple] ~ G3dMatrix.TransformVec; GetProp: PROC [propList: Atom.PropList, prop: REF ANY] RETURNS [REF ANY] ~ Atom.GetPropFromList; PutProp: PROC [propList: Atom.PropList, prop: REF ANY, val: REF ANY] RETURNS [Atom.PropList] ~ Atom.PutPropOnList; <> nullPtr: INT ~ 0; -- handy name for zero in sort sequences nullTriple: Triple _ [0.0, 0.0, 0.0]; defaultWhite: RGB _ [1.0, 1.0, 1.0]; <> <> vertexStore: REF VertexInfoSequence _ NEW[ VertexInfoSequence[96] ]; -- Vertex Cache vertexStoreLength: NAT _ 96; vertexStorePtr: NAT _ 0; -- place to return next free record GetVertexInfo: PUBLIC ENTRY PROC[] RETURNS[REF VertexInfo] ~ { ENABLE UNWIND => NULL; vtx: REF VertexInfo; IF vertexStorePtr = 0 THEN vtx _ NEW[VertexInfo] ELSE { vertexStorePtr _ vertexStorePtr - 1; vtx _ vertexStore[vertexStorePtr]; vertexStore[vertexStorePtr] _ NIL; }; vtx.aux _ vtx.props _ NIL; RETURN[ vtx ]; }; ReleaseVertexInfo: PUBLIC ENTRY PROC[vtx: REF VertexInfo] ~ { ENABLE UNWIND => NULL; IF vertexStorePtr = vertexStoreLength THEN { vertexStore _ NEW[ VertexInfoSequence[vertexStoreLength + 32] ]; vertexStoreLength _ vertexStoreLength + 32; vertexStorePtr _ 0; }; IF vertexStorePtr > 0 AND vertexStore[vertexStorePtr-1] = vtx THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Double vertexInfo release"]] ELSE vertexStore[vertexStorePtr] _ vtx; vertexStorePtr _ vertexStorePtr + 1; }; patchCache: REF PatchSequence _ NEW[ PatchSequence[16] ]; -- temps for clipping, etc. patchCacheLength: NAT _ 16; patchCachePtr: NAT _ 0; -- place to return next free record GetPatch: PUBLIC ENTRY PROC[size: NAT] RETURNS[REF Patch] ~ { ENABLE UNWIND => NULL; p: REF Patch; IF patchCachePtr = 0 THEN p _ NEW[Patch[size]] ELSE { patchCachePtr _ patchCachePtr - 1; p _ patchCache[patchCachePtr]; patchCache[patchCachePtr] _ NIL; IF p.maxLength < size THEN p _ NEW[Patch[size]]; }; RETURN[ p ]; }; ReleasePatch: PUBLIC ENTRY PROC[p: REF Patch] ~ { ENABLE UNWIND => NULL; IF p = NIL THEN RETURN[]; IF patchCachePtr = patchCacheLength THEN { patchCache _ NEW[ PatchSequence[patchCacheLength + 2] ]; patchCacheLength _ patchCacheLength + 2; patchCachePtr _ 0; }; IF patchCachePtr > 0 AND patchCache[patchCachePtr-1] = p THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Double patch release"]] ELSE patchCache[patchCachePtr] _ p; patchCachePtr _ patchCachePtr + 1; }; <> Sgn: PROCEDURE [number: REAL] RETURNS [INT] ~ INLINE { IF number < 0. THEN RETURN[-1] ELSE RETURN[1]; }; Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ { result _ Real.Round[number]; IF result < number THEN result _ result + 1; }; ElapsedTime: PROC[startTime: REAL] RETURNS[Rope.ROPE] ~ { timeX10: REAL _ 10.0 * (BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] - startTime); RETURN[ Rope.Cat[ Convert.RopeFromReal[ Real.Fix[timeX10] / 10.0 ], " secs. " ] ]; }; CurrentTime: PROC[] RETURNS[REAL] ~ { RETURN[ BasicTime.PulsesToSeconds[BasicTime.GetClockPulses[]] ]; }; DiffPosns: PROC[vtx1, vtx2: REF Vertex] RETURNS[Triple] ~ { RETURN[[vtx1.x - vtx2.x, vtx1.y - vtx2.y, vtx1.z - vtx2.z]] }; GetNormal: PROC[vertex: REF ThreeDBasics.VertexSequence, poly: REF PtrPatch, cVtx: NAT] RETURNS[normal: Triple] ~ { lVtx: NAT _ (cVtx + poly.nVtces - 1) MOD poly.nVtces; nVtx: NAT _ (cVtx + 1) MOD poly.nVtces; normal _ G3dVector.Cross[ -- in object space so do right-handed DiffPosns[ vertex[poly.vtxPtr[lVtx]], vertex[poly.vtxPtr[cVtx]] ], DiffPosns[ vertex[poly.vtxPtr[nVtx]], vertex[poly.vtxPtr[cVtx]] ] ]; }; HoldEverything: PROCEDURE [] ~ { ERROR ABORTED; }; ShapePatchToPatch: PUBLIC PROC[context: REF Context, sPatch: REF ShapePatch] RETURNS [patch: REF Patch] ~ { ptrPatch: REF PtrPatch _ NARROW[sPatch.shape.surface, REF PtrPatchSequence][sPatch.patch]; auxData: REF _ GetProp[ sPatch.shape.shadingProps, $AuxiliaryVtxData ]; shapeColor: RGB _ sPatch.shape.shadingClass.color; shapeTransmittance: REAL _ sPatch.shape.shadingClass.transmittance; faceted: BOOLEAN _ sPatch.shape.shadingClass.shadingType = $Faceted AND sPatch.shape.class.type = $ConvexPolygon; lines: BOOLEAN _ sPatch.shape.shadingClass.shadingType = $Lines OR sPatch.shape.shadingClass.shadingType = $HiddenLines; patchInfo: REF ThreeDBasics.ShadingSequence _ sPatch.shape.shadingClass.patchShade; coloredPatches: BOOLEAN _ IF NOT faceted THEN GetProp[ sPatch.shape.fixedProps, $PatchColorsInFile ] # NIL OR GetProp[ sPatch.shape.fixedProps, $PatchTransmittancesInFile ] # NIL ELSE FALSE; IF ptrPatch.clipState = out THEN RETURN[NIL]; -- reject if outside frame patch _ GetPatch[2 * ptrPatch.nVtces]; -- get temp patch, released by OutputPatch patch.nVtces _ ptrPatch.nVtces; patch.clipState _ ptrPatch.clipState; patch.type _ sPatch.shape.class.type; -- take type from class patch.oneSided _ IF NOT ptrPatch.oneSided THEN FALSE ELSE (shapeTransmittance = 0.0); patch.dir _ ptrPatch.dir; <> patch.props _ PutProp[ ptrPatch.props, $Shape, sPatch.shape ]; patch.props _ PutProp[ patch.props, $PatchNo, NEW[NAT _ sPatch.patch] ]; FOR i: NAT IN [0..ptrPatch.nVtces) DO j: NAT _ ptrPatch.vtxPtr[i]; patch[i].props _ patch.props; -- pass info through in case needed patch[i].coord _ sPatch.shape.vertex[j]^; patch[i].shade _ sPatch.shape.shade[j]^; IF auxData # NIL THEN { data: LORA _ LIST[ auxData, NEW[INTEGER _ j] ]; patch[i] _ sPatch.shape.shadingClass.loadVtxAux[context, patch[i], data]; } ELSE patch[i].aux _ NIL; IF lines THEN { -- unshaded lines use shape color patch[i].shade.er _ shapeColor.R; patch[i].shade.eg _ shapeColor.G; patch[i].shade.eb _ shapeColor.B; } ELSE IF patchInfo # NIL THEN IF faceted -- shades for individual patches THEN patch[i].shade _ patchInfo[sPatch.patch]^ -- faceted, take patch shade ELSE IF coloredPatches THEN { -- smooth, combine vtx and patch shades patch[i].shade.r _ sPatch.shape.shade[j].r * patchInfo[sPatch.patch].r; patch[i].shade.g _ sPatch.shape.shade[j].g * patchInfo[sPatch.patch].g; patch[i].shade.b _ sPatch.shape.shade[j].b * patchInfo[sPatch.patch].b; patch[i].shade.t _ sPatch.shape.shade[j].t * patchInfo[sPatch.patch].t; patch[i] _ sPatch.shape.shadingClass.shadeVtx[ context, patch[i], sPatch.shape.shadingClass ]; }; ENDLOOP; }; <> InitClasses: PROC[] ~ { -- register procedures for basic surface types standardClass: ShapeClass _ [ type: $Light, display: NIL, displayPatch: NIL ]; defaultShadingClass: ShadingClass _ [ -- procs for standard shading (no texture) type: $Default, shadingType: $Faceted, shadeVtx: ShadeVtx ]; ThreeDBasics.RegisterSurfaceType[standardClass, $Light]; -- placeholder class for light source standardClass.type _ $ConvexPolygon; standardClass.validate _ SurfaceRender.ValidatePolyhedron; standardClass.displayPatch _ SurfaceRender.OutputPolygon; ThreeDBasics.RegisterSurfaceType[standardClass, $ConvexPolygon]; -- ConvexPolygon procs standardClass.type _ $RopeShape; standardClass.validate _ SurfaceRender.ValidateRopeShape; standardClass.displayPatch _ SurfaceRender.RopeDisplay; ThreeDBasics.RegisterSurfaceType[standardClass, $RopeShape]; -- RopeShape procs ThreeDBasics.RegisterShadingClass[defaultShadingClass, $Default]; defaultShadingClass.type _ $NoShading; defaultShadingClass.shadingType _ $Faceted; defaultShadingClass.shadeVtx _ NoShadeVtx; ThreeDBasics.RegisterShadingClass[defaultShadingClass, $NoShading]; }; XfmToEyeSpace: PUBLIC PROC[context: REF Context, shape: REF ShapeInstance] RETURNS[ThreeDBasics.ClipState] ~ { <> ClipBoundingBall: PROC[] RETURNS[ThreeDBasics.ClipState] ~ { <> clipFlag: BOOLEAN _ FALSE; FOR plane: SixSides IN SixSides DO distance: REAL _ G3dPlane.DistanceToPoint[ [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 _ G3dMatrix.Mul[shape.position, context.eyeSpaceXfm]; [[shape.centroid.ex, shape.centroid.ey, shape.centroid.ez]] _ 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 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] ] _ Transform[ [x, y, z] , xfm]; -- transform pts 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; }; RETURN[shape.clipState]; }; XfmToDisplay: PUBLIC PROC[ context: REF Context, shape: REF ShapeInstance, getBox: BOOL _ FALSE ] ~ { <> xMin, yMin: REAL _ 32767.0; xMax, yMax: REAL _ 0.0; bboxNeeded: BOOLEAN _ context.antiAliasing OR getBox; <> 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, display: context.ndcToPixels, offset: IF context.antiAliasing THEN .5 ELSE 0.0 -- nojaggy tiler offsets by 1/2 pixel ]; IF bboxNeeded THEN { 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 bboxNeeded THEN { IF clip.left THEN xMin _ 0.0; IF clip.right THEN xMax _ context.viewPort.w; IF clip.top THEN yMin _ 0.0; IF clip.bottom THEN yMax _ context.viewPort.h; }; }; ENDLOOP; IF bboxNeeded THEN { <> 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 _ [ min: [f: Real.Fix[xMin], s: Real.Fix[yMin]], max: [f: Ceiling[xMax], s: Ceiling[yMax]] ]; }; }; GetPolyInfo: PROC[context: REF Context, shape: REF ShapeInstance] ~ { <> surface: REF PtrPatchSequence; xfm: Xfm3D; preNormaled: BOOLEAN _ GetProp[ shape.fixedProps, $PatchNormalsInFile] # NIL; polyShade: REF ShadingSequence _ shape.shadingClass.patchShade; IF shape.surface = NIL THEN RETURN; -- not a shape (probably a light source) IF shape.class.type # $ConvexPolygon THEN { SIGNAL ThreeDBasics.Error[[$MisMatch, "Operation only for polygons"]]; RETURN[]; }; IF polyShade = NIL THEN { polyShade _ NEW[ ShadingSequence[shape.numSurfaces] ]; polyShade.length _ shape.numSurfaces; }; surface _ NARROW[shape.surface, REF PtrPatchSequence]; IF NOT shape.vtcesInValid THEN xfm _ G3dMatrix.Mul[shape.position, context.eyeSpaceXfm]; FOR i: NAT IN [0..shape.numSurfaces) DO IF surface[i] # NIL THEN { sumNmls: Triple _ [0., 0., 0.]; IF polyShade[i] = NIL THEN polyShade[i] _ NEW[ ThreeDBasics.ShadingValue ]; IF NOT preNormaled THEN { FOR cVtx: NAT IN [0..surface[i].nVtces) DO sumNmls _ G3dVector.Add[ sumNmls, GetNormal[shape.vertex, surface[i], cVtx] ]; ENDLOOP; sumNmls _ G3dVector.Normalize[sumNmls]; polyShade[i].xn _ sumNmls.x; polyShade[i].yn _ sumNmls.y; polyShade[i].zn _ sumNmls.z; IF NOT shape.vtcesInValid THEN { -- if vertices already transformed to eyespace OPEN polyShade[i]; -- transform normal vectors [ [exn, eyn, ezn] ] _ TransformVec[ [xn, yn, zn] , xfm]; }; }; }; ENDLOOP; shape.shadingProps _ PutProp[shape.shadingProps, $PolygonInfoComputed, $ok]; }; <<>> GetPolyShades: PUBLIC PROC[context: REF Context, shape: REF ShapeInstance] ~ { <> AverageVertices: PROC[poly: REF PtrPatch] 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] ], ThreeDBasics.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 PtrPatchSequence; shapeColor: RGB _ shape.shadingClass.color; shapeTransmittance: REAL _ shape.shadingClass.transmittance; polyShade: REF ShadingSequence _ shape.shadingClass.patchShade; IF shape.class.type # $ConvexPolygon THEN RETURN; -- patches, etc. shaded after expansion IF shape.surface = NIL OR shape.shadingClass.shadingType # $Faceted THEN { SIGNAL ThreeDBasics.Error[[$Condition, "Data missing for faceted shading"]]; RETURN; }; IF GetProp[ shape.shadingProps, $PolygonInfoComputed] = NIL THEN { IF polyShade = NIL THEN { polyShade _ NEW[ ShadingSequence[shape.numSurfaces] ]; polyShade.length _ shape.numSurfaces; FOR i: NAT IN [0..shape.numSurfaces) DO polyShade[i] _ NEW[ ShadingValue ]; ENDLOOP; shape.shadingClass.patchShade _ polyShade; }; GetPolyInfo[context, shape]; }; poly _ NARROW[ shape.surface, REF PtrPatchSequence ]; FOR i: NAT IN [0..shape.numSurfaces) DO IF poly[i] # NIL THEN { vtx: Vertex _ AverageVertices[poly[i]]; pt: VertexInfo _ [ vtx, polyShade[i]^, NIL ]; pt _ shape.shadingClass.shadeVtx[ context, pt, shape.shadingClass ]; -- calculate shade polyShade[i].er _ pt.shade.er; polyShade[i].eg _ pt.shade.eg; polyShade[i].eb _ pt.shade.eb; polyShade[i].et _ pt.shade.et; }; ENDLOOP; }; GetVtxNmls: PUBLIC PROC[context: REF Context, shape: REF ShapeInstance] ~ { <> surface: REF PtrPatchSequence _ NARROW[shape.surface]; preNormaled: BOOLEAN _ GetProp[ shape.fixedProps, $VertexNormalsInFile ] # NIL; IF shape.shade = NIL THEN { shape.shade _ NEW[ ThreeDBasics.ShadingSequence[shape.vertex.length] ]; shape.shade.length _ shape.vertex.length; }; FOR i: NAT IN [0..shape.vertex.length) DO IF shape.shade[i] = NIL THEN { shape.shade[i] _ NEW[ ThreeDBasics.ShadingValue ]; shape.shade[i].xn _ shape.shade[i].yn _ shape.shade[i].zn _ 0.0; -- debugging aid }; ENDLOOP; IF NOT preNormaled THEN { -- get vertex normal xfm: Xfm3D _ G3dMatrix.Mul[shape.position, context.eyeSpaceXfm]; FOR i: NAT IN [0..shape.numSurfaces) DO -- get normals at vertices, add to earlier ones IF surface[i] # NIL THEN 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]] _ G3dVector.Add[ cornerNormal, [xn, yn, zn]]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..shape.shade.length) DO IF shape.shade[i] # NIL THEN { OPEN shape.shade[i]; IF G3dVector.Length[ [xn, yn, zn] ] > shape.boundingRadius * .0001 THEN { [[xn, yn, zn]] _ G3dVector.Normalize[ [xn, yn, zn] ]; [[exn, eyn, ezn]] _ TransformVec[ [xn, yn, zn] , xfm]; } ELSE { [xn, yn, zn] _ [exn, eyn, ezn] _ nullTriple; }; }; ENDLOOP; }; shape.shadingProps _ PutProp[shape.shadingProps, $VtxInfoComputed, $ok]; }; GetVtxShades: PUBLIC PROC[ context: REF Context, shape: REF ShapeInstance] ~ { tmpShininess: REAL; IF shape.shadingClass # NIL THEN { -- cache highlight power, no highlights here please tmpShininess _ shape.shadingClass.shininess; shape.shadingClass.shininess _ 0.0; }; IF shape.shade # NIL THEN FOR i: NAT IN [0..shape.shade.length) DO IF shape.shade[i] # NIL THEN { pt: VertexInfo _ [ coord: shape.vertex[i]^, shade: shape.shade[i]^ ]; pt _ shape.shadingClass.shadeVtx[context, pt, shape.shadingClass]; shape.shade[i].er _ pt.shade.er; shape.shade[i].eg _ pt.shade.eg; shape.shade[i].eb _ pt.shade.eb; shape.shade[i].et _ pt.shade.et; }; ENDLOOP; IF shape.shadingClass # NIL THEN shape.shadingClass.shininess _ tmpShininess; }; <> ClipPoly: PUBLIC PROC[ context: REF Context, poly: REF Patch] RETURNS [REF Patch] ~ { Clip: PROC[side: SixSides, pIn, pOut: REF Patch] RETURNS [REF Patch, REF Patch] = { Dist: PROC[side: SixSides, vtx: Vertex] RETURNS [REAL] = { -- + inside, - outside RETURN[ G3dPlane.DistanceToPoint[ [vtx.ex, vtx.ey, vtx.ez], context.clippingPlanes[side] ] ]; }; lastDist, dist: REAL; outCnt, last: NAT; IF pIn.nVtces < 2 THEN RETURN[ pIn, pOut ]; -- return if degenerate (line needs 2) outCnt _ 0; IF pIn.type # $PolyLine THEN { lastDist _ Dist[side, pIn.vtx[pIn.nVtces - 1].coord]; last _ pIn.nVtces - 1; }; IF pOut.maxLength < 2 * pIn.nVtces THEN pOut _ NEW[Patch[2 * pIn.nVtces]]; FOR i: NAT IN [0..pIn.nVtces) DO a, b: REAL; dist _ Dist[side, pIn.vtx[i].coord]; IF (i # 0 OR pIn.type # $PolyLine) AND lastDist * dist < 0. THEN { <> b _ dist / (dist - lastDist); a _ 1.0 - 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; 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].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; 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.er _ pIn.vtx[i].shade.er * a + pIn.vtx[last].shade.er * b; pOut.vtx[outCnt].shade.eg _ pIn.vtx[i].shade.eg * a + pIn.vtx[last].shade.eg * b; pOut.vtx[outCnt].shade.eb _ pIn.vtx[i].shade.eb * a + pIn.vtx[last].shade.eb * b; pOut.vtx[outCnt].shade.et _ pIn.vtx[i].shade.et * a + pIn.vtx[last].shade.et * b; IF pIn.vtx[i].aux # NIL AND pIn.vtx[last].aux # NIL THEN { data: LORA _ LIST[ pIn.vtx[i].aux, pIn.vtx[last].aux, NEW[REAL _ a], NEW[REAL _ b] ]; pOut.vtx[outCnt] _ lerpProc[ context, pOut.vtx[outCnt], data ]; } ELSE pOut.vtx[outCnt].aux _ pIn.vtx[i].aux; outCnt _ outCnt + 1; }; IF dist >= 0. THEN { -- put out point if inside 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; 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].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; 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.er _ pIn.vtx[i].shade.er; pOut.vtx[outCnt].shade.eg _ pIn.vtx[i].shade.eg; pOut.vtx[outCnt].shade.eb _ pIn.vtx[i].shade.eb; pOut.vtx[outCnt].shade.et _ pIn.vtx[i].shade.et; pOut.vtx[outCnt].aux _ pIn.vtx[i].aux; outCnt _ outCnt + 1; }; lastDist _ dist; last _ i; ENDLOOP; pOut.type _ pIn.type; pOut.oneSided _ pIn.oneSided; pOut.dir _ pIn.dir; pOut.nVtces _ outCnt; pOut.props _ pIn.props; RETURN [ pOut, pIn ]; }; -- end Clip Proc shape: REF ShapeInstance _ NARROW[ GetProp[poly.props, $Shape] ]; lerpProc: VertexInfoProc _ shape.shadingClass.lerpVtxAux; orOfCodes: OutCode _ NoneOut; poly2: REF Patch _ GetPatch[2 * poly.nVtces]; -- get temp patch, released below IF poly.type # $ConvexPolygon AND poly.type # $PolyLine THEN { SIGNAL ThreeDBasics.Error[[$Unimplemented, "Not clippable as polygon"]]; RETURN[ NIL ]; }; FOR i: NAT IN [0..poly.nVtces) DO orOfCodes _ LOOPHOLE[ Basics.BITOR[LOOPHOLE[orOfCodes], LOOPHOLE[poly.vtx[i].coord.clip]], 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]; ReleasePatch[poly2]; -- done with temp patch RETURN[ poly ]; }; GetPatchClipState: PUBLIC PROC[ patch: REF Patch] ~ { orOfCodes: OutCode _ NoneOut; andOfCodes: OutCode _ AllOut; FOR i: NAT IN [0..patch.nVtces) DO orOfCodes _ LOOPHOLE[ Basics.BITOR[ LOOPHOLE[orOfCodes], LOOPHOLE[patch[i].coord.clip]], OutCode]; andOfCodes _ LOOPHOLE[ Basics.BITAND[LOOPHOLE[andOfCodes], LOOPHOLE[patch[i].coord.clip]], OutCode]; ENDLOOP; IF andOfCodes # NoneOut THEN patch.clipState _ out ELSE IF orOfCodes = NoneOut THEN patch.clipState _ in ELSE patch.clipState _ clipped; }; GetClipCodeForPt: PUBLIC PROC[context: REF Context, pt: Triple] RETURNS[clip: OutCode] ~ { <> clip.bottom_ G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Bottom]] < 0.; clip.top _ G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Top] ] < 0.; clip.left _ G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Left] ] < 0.; clip.right _ G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Right] ] < 0.; clip.near _ G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Near] ] < 0.; clip.far _ G3dPlane.DistanceToPoint[ pt, context.clippingPlanes[Far] ] < 0.; }; XfmPtToEyeSpace: PUBLIC PROC[context: REF Context, pt: Triple, xfm: Xfm3D _ NIL] RETURNS[Triple, OutCode] ~ { << Transform Vertex to Eye Space >> IF xfm = NIL THEN xfm _ context.eyeSpaceXfm; pt _ Transform[ pt, xfm ]; RETURN[ pt, GetClipCodeForPt[context, pt] ]; }; XfmTripleToDisplay: PUBLIC PROC[pt: Triple, xfm, display: ScaleAndAddXfm, offset: REAL] RETURNS[result: Triple] ~ { << Transform vertex from eyespace to display coordinates - local utility>> aLilBit: REAL _ ScanConvert.justNoticeable * ScanConvert.justNoticeable; IF pt.z <= 0.0 THEN { SIGNAL ThreeDBasics.Error[[$MisMatch, "Negative depth"]]; pt.z _ .001; -- fudge bad depths }; 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 _ display.scaleX * result.x + display.addX; -- convert to screen coordinates result.y _ display.scaleY * result.y + display.addY; result.z _ display.scaleZ * result.z + display.addZ; 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, shape: REF ShapeInstance _ NIL] RETURNS[Triple] ~ { << Transform vertex from eyespace to display coordinates>> result: Triple _ XfmTripleToDisplay[ pt: pt, xfm: context.eyeToNdc, display: context.ndcToPixels, offset: IF context.antiAliasing THEN .5 ELSE 0.0 -- nojaggy tiler offsets by 1/2 pixel ]; IF shape # NIL THEN { xLo: NAT _ Real.Fix[ MAX[0.0, result.x - 2.0] ]; xHi: NAT _ Ceiling[ MIN[context.viewPort.w, result.x + 2.0] ]; yLo: NAT _ Real.Fix[ MAX[0.0, result.y - 2.0] ]; yHi: NAT _ Ceiling[ MIN[context.viewPort.h, result.y + 2.0] ]; IF shape.screenExtent.min.f > xLo THEN shape.screenExtent.min.f _ xLo; IF shape.screenExtent.max.f < xHi THEN shape.screenExtent.max.f _ xHi; IF shape.screenExtent.min.s > yLo THEN shape.screenExtent.min.s _ yLo; IF shape.screenExtent.max.s < yHi THEN shape.screenExtent.max.s _ yHi; }; RETURN [ result ]; }; <> BackFacing: PUBLIC PROC[ context: REF Context, poly: REF Patch, useEyeSpace: BOOLEAN _ FALSE ] RETURNS [FacingDir] ~ { <> SELECT poly.type FROM $ConvexPolygon => IF useEyeSpace THEN { <> this: VertexInfo _ poly.vtx[0]; next: VertexInfo _ poly.vtx[1]; last: VertexInfo _ poly.vtx[2]; <> direction: Triple _ G3dVector.Normalize[G3dVector.Cross[ [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] ] ]; dotDir: REAL _ G3dVector.Dot[[this.coord.ex, this.coord.ey, this.coord.ez] , direction]; IF dotDir > 0.0 THEN RETURN[back] ELSE IF dotDir < 0.0 THEN RETURN[front]; } ELSE { -- do check in image space, rejecting eensy polygon edges s: REAL _ 1.0 / ScanConvert.justNoticeable; -- scales visible feature size to 1 FOR i: NAT IN [0..poly.nVtces) DO this: VertexInfo _ poly.vtx[i]; next: VertexInfo _ poly.vtx[(i+1) MOD poly.nVtces]; last: VertexInfo _ poly.vtx[(i+poly.nVtces-1) MOD poly.nVtces]; zNorm: INT _ -- integer computation of z-coord of normal (left-handed) ( Real.Fix[s*next.coord.sx - s*this.coord.sx] ) * ( Real.Fix[s*last.coord.sy - s*this.coord.sy] ) - ( Real.Fix[s*next.coord.sy - s*this.coord.sy] ) * ( Real.Fix[s*last.coord.sx - s*this.coord.sx] ); zNorm _ zNorm *Sgn[context.ndcToPixels.scaleY] *Sgn[context.ndcToPixels.scaleX]; <> IF zNorm > 0 THEN RETURN[ back ] ELSE IF zNorm < 0 THEN RETURN[ front ]; ENDLOOP; SIGNAL ThreeDBasics.Error[[$Condition, "Edges too small for stable arithmetic"]]; }; $Bezier => { <