DIRECTORY Atom, Basics, RealFns, G3dVector, G3dMatrix, G3dBasic, G3dSpline, G3dScanConvert, G3dMappedAndSolidTexture, G3dRender, G3dShadeClipXfm, G3dSortandDisplay; PatchFromPolyProcs: CEDAR MONITOR IMPORTS Atom, Basics, G3dMatrix, G3dSpline, G3dVector, G3dMappedAndSolidTexture, RealFns, G3dShadeClipXfm, G3dSortandDisplay, G3dRender = BEGIN Ray: TYPE ~ G3dBasic.Ray; NatSequence: TYPE ~ G3dRender.NatSequence; Pair: TYPE ~ G3dRender.Pair; Triple: TYPE ~ G3dRender.Triple; TripleSequence: TYPE ~ G3dRender.TripleSequence; RGB: TYPE ~ G3dRender.RGB; RealSequence: TYPE ~ G3dRender.RealSequence; Context: TYPE ~ G3dRender.Context; SixSides: TYPE ~ G3dRender.SixSides; Xfm3D: TYPE ~ G3dRender.Xfm3D; ShapeInstance: TYPE ~ G3dRender.ShapeInstance; Patch: TYPE ~ G3dRender.Patch; PatchSequence: TYPE ~ G3dRender.PatchSequence; PatchProc: TYPE ~ G3dRender.PatchProc; PtrPatch: TYPE ~ G3dRender.PtrPatch; PtrPatchSequence: TYPE ~ G3dRender.PtrPatchSequence; Vertex: TYPE ~ G3dRender.Vertex; VertexInfo: TYPE ~ G3dRender.VertexInfo; VertexInfoSequence: TYPE ~ G3dRender.VertexInfoSequence; VertexInfoProc: TYPE ~ G3dRender.VertexInfoProc; ShadingValue: TYPE ~ G3dRender.ShadingValue; ShapeClass: TYPE ~ G3dRender.ShapeClass; ShapeProc: TYPE ~ G3dRender.ShapeProc; ClipState: TYPE ~ G3dRender.ClipState; OutCode: TYPE ~ G3dRender.OutCode; AllOut: OutCode ~ G3dRender.AllOut; NoneOut: OutCode ~ G3dRender.NoneOut; FacingDir: TYPE ~ G3dRender.FacingDir; LORA: TYPE = LIST OF REF ANY; MidPtProc: TYPE ~ PROC[v0, v1: REF VertexInfo] RETURNS[REF VertexInfo]; nullTriple: Triple ~ [0.0, 0.0, 0.0]; Corner: TYPE ~ RECORD [ inVtx, outVtx: NAT _ 0, inDir, outDir, normal, interiorKnot: Triple _ nullTriple, concave: BOOLEAN _ FALSE ]; CornerSeq: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF Corner ]; CornerSeqSeq: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF REF CornerSeq ]; TangentSet: TYPE ~ RECORD [t0, et0, t1, et1: Triple _ nullTriple]; TangentSeq: TYPE ~ RECORD [length: NAT _ 0, s: SEQUENCE maxLength: NAT OF TangentSet]; TangentTriple: TYPE ~ ARRAY [0..3) OF TangentSet; TangentQuad: TYPE ~ ARRAY [0..4) OF TangentSet; TangentSeqSeq: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF REF TangentSeq]; Triangle: TYPE ~ RECORD [ v: ARRAY[0..3) OF VertexInfo, t: ARRAY[0..3) OF TangentSet ]; NatSequenceSequence: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF REF NatSequence ]; BoolSequence: TYPE ~ RECORD [length: NAT _ 0, s: SEQUENCE maxLength: NAT OF BOOLEAN]; Add: PROC[v1, v2: Triple] RETURNS[Triple] ~ G3dVector.Add; Mul: PROC[v: Triple, s: REAL] RETURNS[Triple] ~ G3dVector.Mul; Cross: PROC[v1, v2: Triple] RETURNS[Triple] ~ G3dVector.Cross; Div: PROC[v: Triple, s: REAL] RETURNS[Triple] ~ G3dVector.Div; Dot: PROC[v1, v2: Triple] RETURNS[REAL] ~ G3dVector.Dot; Length: PROC[v: Triple] RETURNS[REAL] ~ G3dVector.Length; Negate: PROC[v: Triple] RETURNS[Triple] ~ G3dVector.Negate; Nmlize: PROC[v: Triple] RETURNS[Triple] ~ G3dVector.Normalize; Sub3: PROC[v1, v2: Triple] RETURNS[Triple] ~ G3dVector.Sub; 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; maxDeviation: REAL _ .5; -- subdivision tolerance with antialiasing maxJaggyDeviation: REAL _ 2.0; -- jaggy subdivision tolerance (pixels) closenessFactor: REAL _ 10.0; -- times maxDeviation gets clipping cutoff for straightness recurseLimit: NAT _ 14; -- safety valve on recursion minCosToAlign: REAL _ -0.866; -- Align edges if they meet at > 150 degrees minCosToAlignOpen: REAL _ 0.707; -- Align open edges if they meet at > 45 degrees stopIfStraight: BOOLEAN _ TRUE; -- set false to defeat termination algorithm unitizeNormals: BOOLEAN _ FALSE; -- treat all polygons equally when making vertex normal showLines: BOOLEAN; -- debug and pedagogical aid PutPropSafely: PROC[propList: Atom.PropList, prop, val: REF ANY] RETURNS[Atom.PropList] ~{ newProps: Atom.PropList _ NIL; FOR list: Atom.PropList _ propList, list.rest UNTIL list = NIL DO -- new proplist element: Atom.DottedPair _ NEW[Atom.DottedPairNode _ list.first^]; newProps _ CONS[element, newProps]; ENDLOOP; RETURN[ PutProp[ newProps, prop, val ] ]; }; RemPropSafely: PROC[ propList: Atom.PropList, prop: REF ANY ] RETURNS[Atom.PropList] ~ { newProps: Atom.PropList _ NIL; FOR list: Atom.PropList _ propList, list.rest UNTIL list = NIL DO -- new proplist element: Atom.DottedPair _ NEW[Atom.DottedPairNode _ list.first^]; newProps _ CONS[element, newProps]; ENDLOOP; RETURN[ Atom.RemPropFromList[ newProps, prop ] ]; }; Sqr: PROCEDURE [number: REAL] RETURNS [REAL] ~ INLINE { RETURN[number * number]; }; Sub: PROC[f, g: REAL] RETURNS[REAL] ~ { IF RealFns.AlmostEqual[f, g, -10] THEN RETURN [0.0] ELSE RETURN[f-g]; }; Sub1: PROC[f, g: REAL] RETURNS[REAL] ~ { result: REAL _ f-g; IF ABS[result] < G3dScanConvert.justNoticeable THEN RETURN [0.0] ELSE RETURN[result]; }; DiffPosns: PROC[vtx1, vtx2: Vertex, space: ATOM _ NIL] RETURNS[Triple] ~ { SELECT space FROM $Eye => RETURN[[Sub[vtx1.ex, vtx2.ex], Sub[vtx1.ey, vtx2.ey], Sub[vtx1.ez, vtx2.ez]]]; $Screen=> RETURN[[Sub1[vtx1.sx, vtx2.sx], Sub1[vtx1.sy, vtx2.sy], Sub1[vtx1.sz, vtx2.sz]]]; ENDCASE => -- object space RETURN[[Sub[vtx1.x, vtx2.x], Sub[vtx1.y, vtx2.y], Sub[vtx1.z, vtx2.z]]]; }; GetSlopeVec: PROC[normal, edge: Triple, hermite: BOOL _ FALSE] RETURNS[slope: Triple] ~ { slope _ Cross[ Cross[normal, edge], normal ]; IF hermite THEN slope _ G3dVector.Mul[ Nmlize[slope], Length[edge] ] ELSE slope _ ScaleTangent[ slope, edge ]; }; GetNmlVec: PROC[vec: Triple, p: VertexInfo, reverse: BOOL _ FALSE] RETURNS[nmlVec: Triple] ~ { normal: Triple _ [p.shade.exn, p.shade.eyn, p.shade.ezn]; nmlVec _ IF reverse THEN Nmlize[ Cross[normal, vec] ] ELSE Nmlize[ Cross[vec, normal] ]; }; ScaleTangent: PROC[tangent, edgeDir: Triple] RETURNS[Triple] ~ { adjSide, oppSide, scale: REAL; edgeLength: REAL _ Length[edgeDir]; hypotenuse: REAL _ Length[tangent]; IF edgeLength = 0.0 OR hypotenuse = 0.0 THEN RETURN [[0.0, 0.0, 0.0]]; adjSide _ ABS[Dot[edgeDir, tangent] / edgeLength]; oppSide _ IF adjSide >= hypotenuse THEN 0.0 ELSE RealFns.SqRt[hypotenuse*hypotenuse - adjSide*adjSide]; scale _ IF oppSide/hypotenuse > .01 THEN edgeLength * 2.0 * (hypotenuse - adjSide) / (3.0 * oppSide * oppSide) ELSE .333333 * edgeLength / hypotenuse; RETURN[ G3dVector.Mul[ tangent, scale ] ]; }; TooClose: PROC[ context: REF Context, v: Vertex, outCode: OutCode, tol: REAL ] RETURNS[BOOLEAN] ~ { maxDist: REAL _ 0.0; IF v.sz = 0.0 THEN RETURN [FALSE]; -- invalid screen coord, ignore IF outCode.left THEN maxDist _ MAX[maxDist, v.sx - context.viewPort.x]; IF outCode.right THEN maxDist _ MAX[maxDist, context.viewPort.w + context.viewPort.x - v.sx]; IF outCode.bottom THEN maxDist _ MAX[maxDist, v.sy - context.viewPort.y]; IF outCode.top THEN maxDist _ MAX[maxDist, context.viewPort.h + context.viewPort.y - v.sy]; IF maxDist < tol * closenessFactor THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; EdgeStraight: PROC[context: REF Context, v0, v1: VertexInfo, slope1, slope2: Triple, tol: REAL] RETURNS[BOOL] ~ { pos1: Triple _ G3dShadeClipXfm.XfmPtToDisplay[ context, -- near slope plus near end Add[[v0.coord.ex, v0.coord.ey, v0.coord.ez], slope1 ] ]; pos2: Triple _ G3dShadeClipXfm.XfmPtToDisplay[ context, -- far slope plus near end Add[[v0.coord.ex, v0.coord.ey, v0.coord.ez], slope2 ] ]; distance: REAL _ ABS[pos1.x - v1.coord.sx] + ABS[pos1.y - v1.coord.sy] + ABS[pos2.x - v1.coord.sx] + ABS[pos2.y - v1.coord.sy]; distance _ distance / 4.0; -- summed manhattan distances / 4 estimates deviation distance _ distance / 2.0; -- heuristic (fudge factor) IF distance <= tol THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; PatchDepthSort: PROC[context: REF Context, p: REF PatchSequence] RETURNS[REF PatchSequence] ~ { z: REF RealSequence _ NEW[RealSequence[p.length]]; pOut: REF PatchSequence _ NEW [PatchSequence[p.length]]; FOR i: NAT IN [0..p.length) DO -- Get average of depths at vertices z[i] _ 0.0; FOR j: NAT IN [0..p[i].nVtces) DO z[i] _ z[i] + p[i][j].coord.ez; ENDLOOP; z[i] _ z[i] / p[i].nVtces; pOut[i] _ p[i]; ENDLOOP; FOR i: NAT IN [1..p.length) DO FOR j: NAT DECREASING IN [0..i) DO -- bubble sort to increasing depth order IF z[j+1] < z[j] THEN { t: REAL _ z[j+1]; p: REF Patch _ pOut[j+1]; z[j+1] _ z[j]; pOut[j+1] _ pOut[j]; z[j] _ t; pOut[j] _ p; }; ENDLOOP; ENDLOOP; IF NOT context.antiAliasing THEN FOR i: NAT IN [0..p.length/2) DO -- re-order back-to-front j: NAT _ p.length-1 - i; tmpP: REF Patch _ pOut[i]; pOut[i] _ pOut[j]; pOut[j] _ tmpP; ENDLOOP; pOut.length _ p.length; RETURN[pOut]; }; ValidatePatchPoly: ShapeProc ~ { doAlways: BOOLEAN _ IF data # NIL THEN TRUE ELSE FALSE; IF GetProp[shape.shadingProps, $VtxInfoComputed] = NIL THEN G3dShadeClipXfm.GetVtxNmls[context, shape]; -- calculate normals if not read in IF shape.vtcesInValid THEN { xfm: Xfm3D _ G3dMatrix.Mul[shape.position, context.eyeSpaceXfm]; shape.clipState _ G3dShadeClipXfm.XfmToEyeSpace[ context, shape ]; IF shape.clipState # out THEN FOR i: NAT IN [0..shape.shade.length) DO OPEN shape.shade[i]; -- transform normal vectors [ [exn, eyn, ezn] ] _ G3dMatrix.TransformVec[ [xn, yn, zn] , xfm]; ENDLOOP; IF shape.surface # NIL THEN { -- get clipping info for patches patch: REF PtrPatchSequence _ NARROW[shape.surface]; FOR i: NAT IN [0..shape.numSurfaces) DO IF patch[i] # NIL THEN IF shape.clipState = in THEN patch[i].clipState _ in -- unclipped, inside ELSE IF shape.clipState = clipped -- evaluate clipping tags THEN G3dSortandDisplay.GetPtrPatchClipState[ shape, patch[i] ] ELSE patch[i].clipState _ out; ENDLOOP; }; shape.vtcesInValid _ FALSE; }; IF shape.shadingInValid AND (shape.clipState # out OR doAlways) THEN G3dShadeClipXfm.GetVtxShades[ context, shape ]; shape.shadingInValid _ FALSE; RETURN[shape]; }; DisplayNothing: PatchProc ~ { -- dummy routine for timing tests shape: REF ShapeInstance _ NARROW[ GetProp[patch.props, $Shape] ]; IF patch # NIL THEN G3dShadeClipXfm.ReleasePatch[patch]; -- end of life for patch RETURN[ NIL ]; }; DisplayPatchEdges: PatchProc ~ { shape: REF ShapeInstance _ NARROW[ GetProp[patch.props, $Shape] ]; patchNo: NAT _ NARROW[ GetProp[patch.props, $PatchNo], REF NAT ]^; tangents: REF TangentSeqSeq _ NARROW[ GetProp[shape.fixedProps, $PatchTangents] ]; corners: REF CornerSeqSeq _ NARROW[ GetProp[shape.fixedProps, $PatchCorners] ]; xfm: Xfm3D _ G3dMatrix.Mul[shape.position, context.eyeSpaceXfm]; clr: RGB _ shape.shadingClass.color; npts: NAT _ IF data # NIL THEN NARROW[data, REF NAT]^ ELSE 8; FOR i: NAT IN [0..patch.nVtces) DO j: NAT _ (i+1) MOD patch.nVtces; outP: REF Patch _ G3dShadeClipXfm.GetPatch[npts]; outState: OutCode _ AllOut; inState: OutCode _ NoneOut; coeffs: G3dSpline.Coeffs; SELECT patch.type FROM $PolygonWithNormals => coeffs _ GetEdgeCurveNmls[ patch[i], patch[j] ]; $PolygonWithTangents, $PolygonToTnsrPatch => coeffs _ GetEdgeCurveTngs[ patch[i], patch[j], tangents[patchNo][i] ]; ENDCASE => SIGNAL G3dRender.Error[[$Unimplemented, "Unknown surface type"]]; FOR k: NAT IN [0..npts) DO OPEN outP[k].coord; t: REAL _ 1.0 * k / (npts-1); [[x, y, z]] _ G3dSpline.Position[coeffs, t]; [[ex, ey, ez], clip] _ G3dShadeClipXfm.XfmPtToEyeSpace[context, [x, y, z], xfm]; clip _ G3dShadeClipXfm.GetClipCodeForPt[ context, [ex, ey, ez] ]; IF clip = NoneOut THEN [[sx, sy, sz]] _ G3dShadeClipXfm.XfmPtToDisplay[context, [ex, ey, ez]]; outState _ LOOPHOLE[ Basics.BITAND[LOOPHOLE[outState], LOOPHOLE[clip]] ]; inState _ LOOPHOLE[ Basics.BITOR[LOOPHOLE[inState], LOOPHOLE[clip]] ]; [outP[k].shade.er, outP[k].shade.eg, outP[k].shade.eb] _ clr; -- use shape color ENDLOOP; outP.type _ $PolyLine; outP.props _ patch.props; outP.nVtces _ npts; outP.clipState _ IF inState = NoneOut THEN in ELSE IF outState # NoneOut THEN out ELSE clipped; [outP] _ G3dSortandDisplay.OutputPolygon[context, outP]; ENDLOOP; RETURN[NIL]; }; InitClasses: PROC[] ~ { -- register procedures for basic surface types standardClass: ShapeClass _ G3dRender.GetSurfaceType[$ConvexPolygon]; standardClass.type _ $PolygonWithNormals; standardClass.validate _ ValidatePatchPoly; standardClass.displayPatch _ DisplayPatchNmls; G3dRender.RegisterSurfaceType[standardClass, $PolygonWithNormals]; standardClass.type _ $PolygonWithTangents; standardClass.validate _ ValidatePolyWTangents; standardClass.displayPatch _ DisplayPatchTngs; G3dRender.RegisterSurfaceType[standardClass, $PolygonWithTangents]; standardClass.type _ $PolygonToTnsrPatch; standardClass.displayPatch _ DisplayPatchTnsr; G3dRender.RegisterSurfaceType[standardClass, $PolygonToTnsrPatch]; standardClass.type _ $PolygonNoImage; standardClass.validate _ G3dSortandDisplay.ValidatePolyhedron; standardClass.displayPatch _ DisplayNothing; G3dRender.RegisterSurfaceType[standardClass, $PolygonNoImage]; }; TriangulateAndDisplay: PatchProc ~ { tol: REAL _ IF context.antiAliasing THEN maxDeviation ELSE maxJaggyDeviation; tangents: REF TangentSeq _ NARROW[ data ]; IF patch.nVtces < 3 THEN SIGNAL G3dRender.Error[[$MisMatch, "Not enough vertices"]]; IF patch.nVtces = 3 THEN { IF patch.type = $PolygonWithTangents THEN patch.props _ PutPropSafely[ patch.props, $Tangents, NEW[TangentTriple _ [ NmlizeTangentSet[ patch[0], patch[1], tangents[0] ], NmlizeTangentSet[ patch[1], patch[2], tangents[1] ], NmlizeTangentSet[ patch[2], patch[0], tangents[2] ] ]] ]; TriangleDisplay[context, patch, 0, tol]; patch _ NIL; -- nil out patch so it won't be returned by caller } ELSE { outPatch: REF PatchSequence _ NEW [PatchSequence[patch.nVtces]]; midPt: VertexInfo _ GetCenterPt[context, patch, tangents]; midTangents: REF TangentSeq _ NARROW[ GetProp[midPt.props, $Tangents] ]; FOR i: NAT IN [0..patch.nVtces) DO j: NAT _ (i + 1) MOD patch.nVtces; -- next vertex outPatch[i] _ G3dShadeClipXfm.GetPatch[3]; -- released by display action outPatch[i].type _ patch.type; outPatch[i].oneSided _ patch.oneSided; outPatch[i].nVtces _ 3; outPatch[i].clipState _ patch.clipState; outPatch[i].dir _ undetermined; outPatch[i].props _ patch.props; outPatch[i][0] _ patch[i]; outPatch[i][1] _ patch[(i+1) MOD patch.nVtces]; outPatch[i][2] _ midPt; IF patch.type = $PolygonWithTangents THEN outPatch[i].props _ PutPropSafely[ outPatch[i].props, $Tangents, NEW[TangentTriple _ [ NmlizeTangentSet[ outPatch[i][0], outPatch[i][1], tangents[i] ], -- orig. edge midTangents[j], -- in to middle, from next vertex [ t0: midTangents[i].t1, et0: midTangents[i].et1, -- back out to current vertex t1: midTangents[i].t0, et1: midTangents[i].et0 ] ]] ]; IF outPatch[i].clipState # in THEN G3dShadeClipXfm.GetPatchClipState[ outPatch[i] ]; ENDLOOP; outPatch.length _ patch.nVtces; outPatch _ PatchDepthSort[ context, outPatch ]; -- sort to depth order FOR i: NAT IN [0..patch.nVtces) DO TriangleDisplay[context, outPatch[i], 0, tol]; ENDLOOP; }; RETURN[patch]; }; GetCenterPt: PROC[context: REF Context, patch: REF Patch, tangents: REF TangentSeq _ NIL] RETURNS[midPt: VertexInfo] ~ { shape: REF ShapeInstance _ NARROW[ GetProp[patch.props, $Shape] ]; tol: REAL _ IF context.antiAliasing THEN maxDeviation ELSE maxJaggyDeviation; IF patch.nVtces <= 3 THEN SIGNAL G3dRender.Error[[$MisMatch, "Not enough vertices"]]; { midTangents: REF TangentSeq _ IF patch.type = $PolygonWithTangents OR patch.type = $PolygonToTnsrPatch THEN NEW[ TangentSeq[patch.nVtces] ] ELSE NIL; lerpProc: VertexInfoProc _ IF shape # NIL THEN shape.shadingClass.lerpVtxAux ELSE NIL; halfVtces: NAT _ patch.nVtces / 2; loopEnd: NAT _ IF halfVtces * 2 # patch.nVtces THEN patch.nVtces ELSE patch.nVtces/2; midPt.shade.r _ midPt.shade.g _ midPt.shade.b _ midPt.shade.t _ 0.0; FOR i: NAT IN [0..loopEnd) DO -- guess at middle point, (odd # of Vtces will be too flat) pt: VertexInfo; flat: BOOLEAN; -- sum vertices given by each opposing vertex pair t, t0, t1: TangentSet; j: NAT _ (i + halfVtces) MOD patch.nVtces; -- vertex across poly from this one IF patch.type = $PolygonWithTangents OR patch.type = $PolygonToTnsrPatch THEN { -- get sum of outgoing and incoming tangents at vertex for midpt direction offst0, offst1: Triple; k: NAT _ (i + patch.nVtces-1) MOD patch.nVtces; -- previous vertex in polygon offst0 _ tangents[i].et0; t.et0 _ Add[ tangents[i].et0, tangents[k].et1 ]; k _ (j + patch.nVtces-1) MOD patch.nVtces; -- previous vertex across polygon offst1 _ tangents[k].et1; t.et1 _ Add[ tangents[j].et0, tangents[k].et1 ]; t _ NmlizeTangentSet[ patch[i], patch[j], t ]; [pt, t0, t1, flat] _ CurveDivideTan[ context, patch[i], patch[j], t.et0, t.et1, offst0, offst1, 0.5, tol]; midTangents[i] _ t0; midTangents[j] _ [t0: t1.t1, et0: t1.et1, t1: t1.t0, et1: t1.et0 ]; -- reverse order } ELSE { [pt, flat] _ TriangleCurveDivideNml[context, patch[i], patch[j], 0, tol]; midPt.shade.exn _ midPt.shade.exn + pt.shade.exn; midPt.shade.eyn _ midPt.shade.eyn + pt.shade.eyn; midPt.shade.ezn _ midPt.shade.ezn + pt.shade.ezn; }; midPt.coord.ex _ midPt.coord.ex + pt.coord.ex; midPt.coord.ey _ midPt.coord.ey + pt.coord.ey; midPt.coord.ez _ midPt.coord.ez + pt.coord.ez; midPt.shade.r _ midPt.shade.r + pt.shade.r; midPt.shade.g _ midPt.shade.g + pt.shade.g; midPt.shade.b _ midPt.shade.b + pt.shade.b; midPt.shade.t _ midPt.shade.t + pt.shade.t; IF shape.shadingClass.texture # NIL THEN { -- for textures midPt.coord.x _ midPt.coord.x + pt.coord.x; midPt.coord.y _ midPt.coord.y + pt.coord.y; midPt.coord.z _ midPt.coord.z + pt.coord.z; midPt.shade.xn _ midPt.shade.xn + pt.shade.xn; midPt.shade.yn _ midPt.shade.yn + pt.shade.yn; midPt.shade.zn _ midPt.shade.zn + pt.shade.zn; IF lerpProc # NIL AND pt.aux # NIL THEN IF i = 0 -- get aux. info from class proc THEN midPt _ shape.shadingClass.loadVtxAux[ NIL, midPt, pt.aux] ELSE { data: LORA _ LIST[ midPt.aux, pt.aux, NEW[REAL _ 1.0], NEW[REAL _ 1.0] ]; midPt _ lerpProc[ NIL, midPt, data]; }; }; ENDLOOP; midPt.coord.ex _ midPt.coord.ex / loopEnd; -- get average by dividing by no. edges midPt.coord.ey _ midPt.coord.ey / loopEnd; midPt.coord.ez _ midPt.coord.ez / loopEnd; IF patch.type = $PolygonWithTangents OR patch.type = $PolygonToTnsrPatch THEN { -- get mid-normal by cross-products on mid-tangents FOR i: NAT IN [0..loopEnd-1) DO [[midPt.shade.exn, midPt.shade.eyn, midPt.shade.ezn]] _ Add[ [midPt.shade.exn, midPt.shade.eyn, midPt.shade.ezn], Cross[midTangents[i].et1, midTangents[i+1].et1] ]; ENDLOOP; [[midPt.shade.exn, midPt.shade.eyn, midPt.shade.ezn]] _ Nmlize[ [midPt.shade.exn, midPt.shade.eyn, midPt.shade.ezn] ]; FOR i: NAT IN [0..patch.nVtces) DO -- Rebuild midtangents using middle point midTangents[i].et0 _ GetSlopeVec[ [patch[i].shade.exn, patch[i].shade.eyn, patch[i].shade.ezn], DiffPosns[midPt.coord, patch[i].coord, $Eye] ]; midTangents[i].et1 _ GetSlopeVec[ [midPt.shade.exn, midPt.shade.eyn, midPt.shade.ezn], DiffPosns[patch[i].coord, midPt.coord, $Eye] ]; ENDLOOP; } ELSE { midPt.shade.exn _ midPt.shade.exn / loopEnd; midPt.shade.eyn _ midPt.shade.eyn / loopEnd; midPt.shade.ezn _ midPt.shade.ezn / loopEnd; }; midPt.shade.r _ midPt.shade.r / loopEnd; midPt.shade.g _ midPt.shade.g / loopEnd; midPt.shade.b _ midPt.shade.b / loopEnd; midPt.shade.t _ midPt.shade.t / loopEnd; IF shape.shadingClass.texture # NIL THEN { -- for solid textures midPt.coord.x _ midPt.coord.x / loopEnd; midPt.coord.y _ midPt.coord.y / loopEnd; midPt.coord.z _ midPt.coord.z / loopEnd; IF patch.type = $PolygonWithTangents OR patch.type = $PolygonToTnsrPatch THEN { -- get mid-normal by cross-products on mid-tangents FOR i: NAT IN [0..loopEnd-1) DO [[midPt.shade.xn, midPt.shade.yn, midPt.shade.zn]] _ Add[ [midPt.shade.xn, midPt.shade.yn, midPt.shade.zn], Cross[midTangents[i].t1, midTangents[i+1].t1] ]; ENDLOOP; [[midPt.shade.xn, midPt.shade.yn, midPt.shade.zn]] _ Nmlize[ [midPt.shade.xn, midPt.shade.yn, midPt.shade.zn] ]; FOR i: NAT IN [0..patch.nVtces) DO -- Rebuild midtangents using middle point midTangents[i].t0 _ GetSlopeVec[ [patch[i].shade.xn, patch[i].shade.yn, patch[i].shade.zn], DiffPosns[midPt.coord, patch[i].coord] ]; midTangents[i].t1 _ GetSlopeVec[ [midPt.shade.xn, midPt.shade.yn, midPt.shade.zn], DiffPosns[patch[i].coord, midPt.coord] ]; ENDLOOP; } ELSE { midPt.shade.xn _ midPt.shade.xn / loopEnd; midPt.shade.yn _ midPt.shade.yn / loopEnd; midPt.shade.zn _ midPt.shade.zn / loopEnd; }; IF lerpProc # NIL AND midPt.aux # NIL THEN { -- get aux. info from supplied proc data: LORA _ LIST[midPt.aux, midPt.aux, NEW[REAL _ 1.0/loopEnd], NEW[REAL _0.0]]; midPt _ lerpProc[ NIL, midPt, data]; }; }; { OPEN midPt.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; midPt.props _ patch[0].props; midPt.props _ PutProp[ midPt.props, $Tangents, midTangents ]; }; }; TriangleDisplay: PROC[ context: REF Context, p: REF Patch, level: NAT, tol: REAL] ~ { subP: REF PatchSequence _ NIL; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received IF p.dir = undetermined AND p.type = $PolygonWithTangents THEN [] _ TriangleTngsBackFacing[p]; -- backface test IF ( p.clipState # out ) AND ( NOT p.oneSided OR NOT p.dir = back ) THEN { IF level < recurseLimit THEN subP _ SubdivideTriangle[context, p, level, tol]; IF subP = NIL THEN { -- recursion limit hit or edges all straight IF showLines THEN p.type _ $PolyLine ELSE p.type _ $ConvexPolygon; G3dShadeClipXfm.ShadePoly[context, p]; [p] _ G3dSortandDisplay.OutputPolygon[context, p]; -- display } ELSE { subP _ PatchDepthSort[ context, subP ]; -- sort to display order TriangleDisplay[context, subP[0], level+1, tol]; TriangleDisplay[context, subP[1], level+1, tol]; TriangleDisplay[context, subP[2], level+1, tol]; TriangleDisplay[context, subP[3], level+1, tol]; }; }; IF p # NIL THEN G3dShadeClipXfm.ReleasePatch[p]; -- end of life for patch }; SubdivideTriangle: PROC[context: REF Context, p: REF Patch, level: NAT, tol: REAL] RETURNS[REF PatchSequence] ~ { shape: REF ShapeInstance _ NARROW[ GetProp[p.props, $Shape] ]; v0, v1, v2: VertexInfo; flat0, flat1, flat2: BOOLEAN; t: REF TangentTriple _ NARROW[GetProp[p.props, $Tangents] ]; t00, t01, t10, t11, t20, t21, tm0, tm1, tm2: TangentSet; outPatch: REF PatchSequence _ NEW[PatchSequence[4]]; IF p.type = $PolygonWithTangents AND t # NIL THEN { -- get midpoints and edge tangents [v0, t00, t01, flat0] _ CurveDivideTan[ context, p[0], p[1], t[0].et0, t[0].et1, GetNmlVec[t[0].et0, p[0]], GetNmlVec[t[0].et1, p[1], TRUE], 0.5, tol ]; [v1, t10, t11, flat1] _ CurveDivideTan[ context, p[1], p[2], t[1].et0, t[1].et1, GetNmlVec[t[1].et0, p[1]], GetNmlVec[t[1].et1, p[2], TRUE], 0.5, tol]; [v2, t20, t21, flat2] _ CurveDivideTan[ context, p[2], p[0], t[2].et0, t[2].et1, GetNmlVec[t[2].et0, p[2]], GetNmlVec[t[2].et1, p[0], TRUE], 0.5, tol]; tm0.et0 _ GetSlopeVec[ [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], DiffPosns[v1.coord, v0.coord, $Eye] ]; tm0.et1 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], DiffPosns[v0.coord, v1.coord, $Eye] ]; tm1.et0 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], DiffPosns[v2.coord, v1.coord, $Eye] ]; tm1.et1 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], DiffPosns[v1.coord, v2.coord, $Eye] ]; tm2.et0 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], DiffPosns[v0.coord, v2.coord, $Eye] ]; tm2.et1 _ GetSlopeVec[ [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], DiffPosns[v2.coord, v0.coord, $Eye] ]; } ELSE { [v0, flat0] _ TriangleCurveDivideNml[context, p[0], p[1], level, tol]; [v1, flat1] _ TriangleCurveDivideNml[context, p[1], p[2], level, tol]; [v2, flat2] _ TriangleCurveDivideNml[context, p[2], p[0], level, tol]; }; IF flat0 AND flat1 AND flat2 AND stopIfStraight THEN RETURN[NIL]; -- if all straight, done FOR i: NAT IN [0..4) DO outPatch[i] _ G3dShadeClipXfm.GetPatch[3]; -- 3 point patch, released by display action outPatch[i].type _ p.type; outPatch[i].oneSided _ p.oneSided; outPatch[i].nVtces _ 3; outPatch[i].clipState _ p.clipState; outPatch[i].dir _ p.dir; outPatch[i].props _ p.props; ENDLOOP; { OPEN v0.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v1.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v2.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; outPatch[0][0] _ p[0]; outPatch[0][1] _ v0; outPatch[0][2] _ v2; outPatch[1][0] _ p[1]; outPatch[1][1] _ v1; outPatch[1][2] _ v0; outPatch[2][0] _ p[2]; outPatch[2][1] _ v2; outPatch[2][2] _ v1; outPatch[3][0] _ v0; outPatch[3][1] _ v1; outPatch[3][2] _ v2; IF p.type = $PolygonWithTangents AND t # NIL THEN { outPatch[0].props _ PutPropSafely[ outPatch[0].props, $Tangents, NEW[TangentTriple _ [t00, [tm2.t1, tm2.et1, tm2.t0, tm2.et0], t21] ] ]; outPatch[1].props _ PutPropSafely[ outPatch[1].props, $Tangents, NEW[TangentTriple _ [t10, [tm0.t1, tm0.et1, tm0.t0, tm0.et0], t01] ] ]; outPatch[2].props _ PutPropSafely[ outPatch[2].props, $Tangents, NEW[TangentTriple _ [t20, [tm1.t1, tm1.et1, tm1.t0, tm1.et0], t11] ] ]; outPatch[3].props _ PutPropSafely[ outPatch[3].props, $Tangents, NEW[TangentTriple _ [tm0, tm1, tm2] ] ]; }; FOR i: NAT IN [0..4) DO G3dShadeClipXfm.GetPatchClipState[ outPatch[i] ]; ENDLOOP; --bad!! outPatch.length _ 4; RETURN[ outPatch ]; -- return four sub-patches }; DisplayPatchNmls: PatchProc ~ { shape: REF ShapeInstance _ NARROW[ GetProp[patch.props, $Shape] ]; patch.type _ $PolygonWithNormals; SELECT shape.shadingClass.shadingType FROM $Lines => { [] _ DisplayPatchEdges[context, patch]; RETURN[NIL]; }; ENDCASE; IF shape.shadingClass.texture # NIL THEN { txtrRange: REF Pair _ NARROW[ GetProp[shape.shadingProps, $TxtrCoordRange] ]; IF txtrRange # NIL -- fix texture seams THEN G3dMappedAndSolidTexture.AdjustTexture[ patch, shape.shadingClass.texture, txtrRange.x, txtrRange.y ] ELSE G3dMappedAndSolidTexture.AdjustTexture[patch, shape.shadingClass.texture]; }; patch _ TriangulateAndDisplay[context, patch]; -- make triangles, recursively subdivide IF patch # NIL THEN G3dShadeClipXfm.ReleasePatch[patch]; -- end of life for patch RETURN[ NIL ]; }; GetEdgeCurveNmls: PROC[p1, p2: VertexInfo] RETURNS[coeffs: G3dSpline.Coeffs] ~ { edge: Triple _ DiffPosns[p2.coord, p1.coord, $Object]; pos1: Triple _ [p1.coord.x, p1.coord.y, p1.coord.z]; pos2: Triple _ [p2.coord.x, p2.coord.y, p2.coord.z]; slope1: Triple _ GetSlopeVec[[p1.shade.xn, p1.shade.yn, p1.shade.zn], edge, TRUE]; slope2: Triple _ GetSlopeVec[[p2.shade.xn, p2.shade.yn, p2.shade.zn], edge, TRUE]; coeffs _ G3dSpline.CoeffsFromHermite[[pos1, pos2, slope1, slope2]]; }; TriangleNmlsBackFacing: PROC[p: REF Patch] RETURNS [BOOLEAN] ~ { FacingFromTrio: PROC[p0, p1, p2: Triple] ~ { direction: Triple _ Cross[ [p1.x - p0.x, p1.y - p0.y, p1.z - p0.z], [p2.x - p0.x, p2.y - p0.y, p2.z - p0.z] ]; dotDir: REAL _ Dot[p0 , direction]; IF dotDir > 0.0 THEN back _ TRUE ELSE IF dotDir < 0.0 THEN front _ TRUE ELSE undetermined _ TRUE; }; back, front, undetermined: BOOLEAN _ FALSE; t: REF TangentTriple _ NARROW[ GetProp[p.props, $Tangents] ]; pts: ARRAY [0..9) OF Triple; FOR i: NAT IN [0..3) DO pts[i*3] _ [ p[i].coord.ex, p[i].coord.ey, p[i].coord.ez ]; ENDLOOP; FOR i: NAT IN [0..3) DO i1: NAT _ i*3+1; i2: NAT _ i*3+2; pts[i*3+1] _ Add[ pts[i*3], t[i].et0]; pts[i*3+2] _ Add[ pts[((i+1) MOD 3) * 3], t[i].et1]; ENDLOOP; FOR i: NAT IN [0..3) DO j: NAT _ i*3; j1: NAT _ j+1; j2: NAT _ (j+8) MOD 9; FacingFromTrio[ pts[j], pts[j1], pts[j2] ]; FacingFromTrio[ pts[j1], pts[j1+1], pts[j2] ]; FacingFromTrio[ pts[j2], pts[j1], pts[j2-1] ]; ENDLOOP; IF (back AND front) OR undetermined THEN p.dir _ undetermined ELSE IF back THEN p.dir _ back ELSE IF front THEN p.dir _ front ELSE p.dir _ undetermined; IF p.dir = back THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; TriangleCurveDivideNml: PROC[ context: REF Context, vtx0, vtx1: VertexInfo, level: NAT, tol: REAL ] RETURNS[pt: VertexInfo, flat: BOOLEAN] ~ { t: REAL ~ 0.5; t2: REAL ~ 0.25; t3: REAL ~ 0.125; b0: REAL ~ 1.0 + 0.0*t - 3.*t2 + 2.*t3; -- value of basis functions at t=1/2 b1: REAL ~ 0.0 + 0.0*t + 3.*t2 - 2.*t3; b2: REAL ~ 0.0 + 1.0*t - 2.*t2 + 1.*t3; b3: REAL ~ 0.0 + 0.0*t - 1.*t2 + 1.*t3; s0: REAL ~ 0.0 - 3.*2.*t + 2.*3.*t2; -- slope of basis functions at t=1/2 s1: REAL ~ 0.0 + 3.*2.*t - 2.*3.*t2; s2: REAL ~ 1.0 - 2.*2.*t + 1.*3.*t2; s3: REAL ~ 0.0 - 1.*2.*t + 1.*3.*t2; pos1, pos2, slope1, slope2, slopeMid: Triple; shape: REF ShapeInstance _ NARROW[ GetProp[vtx0.props, $Shape] ]; v0: VertexInfo _ IF vtx0.coord.x < vtx1.coord.x THEN vtx0 ELSE vtx1; v1: VertexInfo _ IF vtx0.coord.x < vtx1.coord.x THEN vtx1 ELSE vtx0; clipStraight, tooShort: BOOLEAN _ FALSE; IF v1.coord.clip # NoneOut AND v0.coord.clip # NoneOut THEN clipStraight _ TRUE ELSE IF v0.coord.clip # NoneOut THEN clipStraight _ TooClose[context, v1.coord, v0.coord.clip, tol] ELSE IF v1.coord.clip # NoneOut THEN clipStraight _ TooClose[context, v0.coord, v1.coord.clip, tol]; { edge: Triple _ DiffPosns[v1.coord, v0.coord, $Eye]; IF Length[edge] <= G3dScanConvert.justNoticeable THEN tooShort _ TRUE ELSE { slope1 _ GetSlopeVec[[v0.shade.exn, v0.shade.eyn, v0.shade.ezn], edge, TRUE]; slope2 _ GetSlopeVec[[v1.shade.exn, v1.shade.eyn, v1.shade.ezn], edge, TRUE]; }; }; pos1 _ [v0.coord.ex, v0.coord.ey, v0.coord.ez]; pos2 _ [v1.coord.ex, v1.coord.ey, v1.coord.ez]; IF clipStraight OR tooShort OR EdgeStraight[context, v0, v1, slope1, slope2, tol] THEN { -- straight edge, average endpoints for midpoint pt.coord.ex _ (pos1.x + pos2.x) / 2; pt.shade.exn _ (v0.shade.exn + v1.shade.exn) / 2; pt.coord.ey _ (pos1.y + pos2.y) / 2; pt.shade.eyn _ (v0.shade.eyn + v1.shade.eyn) / 2; pt.coord.ez _ (pos1.z + pos2.z) / 2; pt.shade.ezn _ (v0.shade.ezn + v1.shade.ezn) / 2; flat _ TRUE; } ELSE { -- not straight, evaluate curve slopeMid.x _ pos1.x*s0 + pos2.x*s1 + slope1.x*s2 + slope2.x*s3; slopeMid.y _ pos1.y*s0 + pos2.y*s1 + slope1.y*s2 + slope2.y*s3; slopeMid.z _ pos1.z*s0 + pos2.z*s1 + slope1.z*s2 + slope2.z*s3; pt.coord.ex _ pos1.x*b0 + pos2.x*b1 + slope1.x*b2 + slope2.x*b3; pt.coord.ey _ pos1.y*b0 + pos2.y*b1 + slope1.y*b2 + slope2.y*b3; pt.coord.ez _ pos1.z*b0 + pos2.z*b1 + slope1.z*b2 + slope2.z*b3; [[pt.shade.exn, pt.shade.eyn, pt.shade.ezn]] _ Add[ Nmlize[GetSlopeVec[slopeMid, [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], TRUE]], Nmlize[GetSlopeVec[slopeMid, [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], TRUE]] ]; flat _ FALSE; }; pt.shade.r _ (v0.shade.r + v1.shade.r) / 2; pt.shade.g _ (v0.shade.g + v1.shade.g) / 2; pt.shade.b _ (v0.shade.b + v1.shade.b) / 2; pt.shade.t _ (v0.shade.t + v1.shade.t) / 2; IF shape.shadingClass.texture # NIL THEN { -- for solid textures lerpProc: VertexInfoProc _ shape.shadingClass.lerpVtxAux; pos1 _ [v0.coord.x, v0.coord.y, v0.coord.z]; pos2 _ [v1.coord.x, v1.coord.y, v1.coord.z]; SELECT shape.class.type FROM $PolygonWithNormals => { edge: Triple _ DiffPosns[v1.coord, v0.coord, $Object]; slope1 _ GetSlopeVec[[v0.shade.xn, v0.shade.yn, v0.shade.zn], edge, TRUE]; slope2 _ GetSlopeVec[[v1.shade.xn, v1.shade.yn, v1.shade.zn], edge, TRUE]; }; ENDCASE => SIGNAL G3dRender.Error[[$Unimplemented, "Unknown surface type"]]; IF clipStraight OR EdgeStraight[context, v0, v1, slope1, slope2, tol] THEN { -- straight edge, average endpoints for midpoint pt.coord.x _ (pos1.x + pos2.x) / 2; pt.shade.xn _ (v0.shade.xn + v1.shade.xn) / 2; pt.coord.y _ (pos1.y + pos2.y) / 2; pt.shade.yn _ (v0.shade.yn + v1.shade.yn) / 2; pt.coord.z _ (pos1.z + pos2.z) / 2; pt.shade.zn _ (v0.shade.zn + v1.shade.zn) / 2; } ELSE { -- not straight, evaluate curve slopeMid.x _ pos1.x*s0 + pos2.x*s1 + slope1.x*s2 + slope2.x*s3; slopeMid.y _ pos1.y*s0 + pos2.y*s1 + slope1.y*s2 + slope2.y*s3; slopeMid.z _ pos1.z*s0 + pos2.z*s1 + slope1.z*s2 + slope2.z*s3; pt.coord.x _ pos1.x*b0 + pos2.x*b1 + slope1.x*b2 + slope2.x*b3; pt.coord.y _ pos1.y*b0 + pos2.y*b1 + slope1.y*b2 + slope2.y*b3; pt.coord.z _ pos1.z*b0 + pos2.z*b1 + slope1.z*b2 + slope2.z*b3; [[pt.shade.xn, pt.shade.yn, pt.shade.zn]] _ Add[ GetSlopeVec[slopeMid, [v0.shade.xn, v0.shade.yn, v0.shade.zn], TRUE], GetSlopeVec[slopeMid, [v1.shade.xn, v1.shade.yn, v1.shade.zn], TRUE] ]; }; IF lerpProc # NIL AND v0.aux # NIL THEN { -- get auxiliary info from supplied proc data: LORA _ LIST[ v0.aux, v1.aux, NEW[REAL _ 0.5], NEW[REAL _ 0.5] ]; pt _ lerpProc[ NIL, pt, data]; -- average, for lack of better strategy }; }; pt.props _ vtx0.props; }; DisplayPatchTngs: PatchProc ~ { shape: REF ShapeInstance _ NARROW[ GetProp[patch.props, $Shape] ]; SELECT shape.shadingClass.shadingType FROM $Lines => { [] _ DisplayPatchEdges[context, patch]; RETURN[NIL]; }; ENDCASE; patch.type _ $PolygonWithTangents; IF data = NIL THEN { patchNo: NAT _ NARROW[ GetProp[patch.props, $PatchNo], REF NAT ]^; tangents: REF TangentSeqSeq _ NARROW[ GetProp[shape.fixedProps, $PatchTangents] ]; patch _ TriangulateAndDisplay[ context, patch, tangents[patchNo] ]; -- subdivide proc. } ELSE { tangents: REF TangentSeq _ NARROW[ data ]; patch _ TriangulateAndDisplay[ context, patch, tangents ]; -- subdivide proc. }; IF patch # NIL THEN G3dShadeClipXfm.ReleasePatch[patch]; -- end of life for patch RETURN[ NIL ]; }; ValidatePolyWTangents: ShapeProc ~ { doEyeSpace: BOOLEAN _ shape.vtcesInValid; -- grab before reset by ValidatePatchPoly shape _ ValidatePatchPoly[context, shape]; -- Update shading and transform matrices IF GetProp[shape.fixedProps, $PatchTangents] = NIL THEN shape _ GetTangents[context, shape]; -- calculate tangent vectors if not read in IF doEyeSpace AND shape.clipState # out THEN { xfm: Xfm3D _ G3dMatrix.Mul[shape.position, context.eyeSpaceXfm]; tangent: REF TangentSeqSeq _NARROW[GetProp[shape.fixedProps, $PatchTangents]]; FOR i: NAT IN [0..shape.numSurfaces) DO -- transform tangent vectors FOR j: NAT IN [0..tangent[i].length) DO OPEN tangent[i][j]; [ et0 ] _ G3dMatrix.TransformVec[ t0, xfm]; [ et1 ] _ G3dMatrix.TransformVec[ t1, xfm]; ENDLOOP; ENDLOOP; }; RETURN[ shape ]; }; GetEdgeCurveTngs: PROC[p1, p2: VertexInfo, tangent: TangentSet] RETURNS[coeffs: G3dSpline.Coeffs] ~ { b0, b1, b2, b3: Triple; [b0, b1, b2, b3] _ TngsToBezKnots[ p1, p2, tangent ]; coeffs _ G3dSpline.CoeffsFromBezier[ [b0, b1, b2, b3] ]; }; TngsToBezKnots: PROC[p1, p2: VertexInfo, tangent: TangentSet] RETURNS[b0, b1, b2, b3: Triple] ~ { edgeDir: Triple; b0 _ [ p1.coord.x, p1.coord.y, p1.coord.z ]; b3 _ [ p2.coord.x, p2.coord.y, p2.coord.z ]; edgeDir _ Sub3[b3, b0]; b1 _ Add[ b0, ScaleTangent[tangent.t0, edgeDir] ]; b2 _ Add[ b3, ScaleTangent[tangent.t1, Negate[edgeDir]] ]; }; TriangleTngsBackFacing: PROC[p: REF Patch] RETURNS [BOOLEAN] ~ { FacingFromTrio: PROC[p0, p1, p2: Triple] ~ { direction: Triple _ Cross[ [p1.x - p0.x, p1.y - p0.y, p1.z - p0.z], [p2.x - p0.x, p2.y - p0.y, p2.z - p0.z] ]; dotDir: REAL _ Dot[p0 , direction]; IF dotDir > 0.0 THEN back _ TRUE ELSE IF dotDir < 0.0 THEN front _ TRUE ELSE undetermined _ TRUE; }; back, front, undetermined: BOOLEAN _ FALSE; t: REF TangentTriple _ NARROW[ GetProp[p.props, $Tangents] ]; pts: ARRAY [0..9) OF Triple; FOR i: NAT IN [0..3) DO pts[i*3] _ [ p[i].coord.ex, p[i].coord.ey, p[i].coord.ez ]; ENDLOOP; FOR i: NAT IN [0..3) DO i1: NAT _ i*3+1; i2: NAT _ i*3+2; pts[i*3+1] _ Add[ pts[i*3], t[i].et0]; pts[i*3+2] _ Add[ pts[((i+1) MOD 3) * 3], t[i].et1]; ENDLOOP; FOR i: NAT IN [0..3) DO j: NAT _ i*3; j1: NAT _ j+1; j2: NAT _ (j+8) MOD 9; FacingFromTrio[ pts[j], pts[j1], pts[j2] ]; FacingFromTrio[ pts[j1], pts[j1+1], pts[j2] ]; FacingFromTrio[ pts[j2], pts[j1], pts[j2-1] ]; ENDLOOP; IF (back AND front) OR undetermined THEN p.dir _ undetermined ELSE IF back THEN p.dir _ back ELSE IF front THEN p.dir _ front ELSE p.dir _ undetermined; IF p.dir = back THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; NmlizeTangentSet: PROC[v0, v1: VertexInfo, tangent: TangentSet] RETURNS[TangentSet] ~ { ep0: Triple _ [ v0.coord.ex, v0.coord.ey, v0.coord.ez ]; ep1: Triple _ [ v1.coord.ex, v1.coord.ey, v1.coord.ez ]; p0: Triple _ [ v0.coord.x, v0.coord.y, v0.coord.z ]; p1: Triple _ [ v1.coord.x, v1.coord.y, v1.coord.z ]; edgeDir: Triple _ Sub3[p1, p0]; t0: Triple _ ScaleTangent[ tangent.t0, edgeDir ]; t1: Triple _ ScaleTangent[ tangent.t1, Negate[edgeDir] ]; et0, et1: Triple; edgeDir _ Sub3[ep1, ep0]; et0 _ ScaleTangent[ tangent.et0, edgeDir ]; et1 _ ScaleTangent[ tangent.et1, Negate[edgeDir] ]; RETURN[ [t0, et0, t1, et1] ]; }; IsStraight: PROC[context: REF Context, p0, p1, s0, s1: Triple, tol: REAL] RETURNS[BOOLEAN] ~{ DistOffEdge: PROC[v: Triple, line: Ray] RETURNS[REAL] ~ { ptOnEdge: Triple _ G3dShadeClipXfm.XfmPtToDisplay[ context, G3dVector.NearestToLine[line, v] ]; vs: Triple _ G3dShadeClipXfm.XfmPtToDisplay[ context, v ]; RETURN[ RealFns.SqRt[ Sqr[ptOnEdge.x - vs.x] + Sqr[ptOnEdge.y - vs.y] ] ]; }; line: Ray _ [ p0, Sub3[p1, p0] ]; dist0: REAL _ DistOffEdge[Add[p0, s0], line]; dist1: REAL _ DistOffEdge[Add[p1, s1], line]; IF dist0 > tol OR dist1 > tol THEN RETURN[FALSE] ELSE RETURN[TRUE]; }; CurveDivideTan: PROC[ context: REF Context, vtx0, vtx1: VertexInfo, slope1, slope2, offst1, offst2: Triple, parameter, tol: REAL ] RETURNS[ pt: VertexInfo, t0, t1: TangentSet, flat: BOOLEAN ] ~ { p1: REAL _ 1.0 - parameter; p2: REAL _ parameter; pos1, pos2, edge, oPt: Triple; shape: REF ShapeInstance _ NARROW[ GetProp[vtx0.props, $Shape] ]; lerpProc: VertexInfoProc _ IF shape # NIL THEN shape.shadingClass.lerpVtxAux ELSE NIL; v0: VertexInfo _ vtx0; v1: VertexInfo _ vtx1; clipStraight, tooShort: BOOLEAN _ FALSE; IF v1.coord.clip # NoneOut AND v0.coord.clip # NoneOut -- not a good test!! THEN clipStraight _ TRUE ELSE IF v0.coord.clip # NoneOut THEN clipStraight _ TooClose[context, v1.coord, v0.coord.clip, tol] ELSE IF v1.coord.clip # NoneOut THEN clipStraight _ TooClose[context, v0.coord, v1.coord.clip, tol]; edge _ DiffPosns[v1.coord, v0.coord, $Eye]; IF Length[edge] <= G3dScanConvert.justNoticeable THEN tooShort _ TRUE; pos1 _ [v0.coord.ex, v0.coord.ey, v0.coord.ez]; pos2 _ [v1.coord.ex, v1.coord.ey, v1.coord.ez]; IF clipStraight OR tooShort OR IsStraight[ context, pos1, pos2, slope1, slope2, tol ] THEN { -- straight edge, average endpoints for midpoint [[pt.coord.ex, pt.coord.ey, pt.coord.ez]] _ Add[ Mul[pos1, p1], Mul[pos2, p2] ]; t0.et0 _ Mul[ Sub3[pos2, pos1], p2/3.0]; t1.et0 _ Mul[ Sub3[pos2, pos1], p1/3.0]; t0.et1 _ Negate[ t0.et0 ]; t1.et1 _ Negate[ t1.et0 ]; oPt _ Add[ Mul[Add[pos1, offst1], p1], Mul[Add[pos2, offst2], p2] ]; flat _ TRUE; } ELSE { -- not straight, evaluate midpoint by subdivision b0: Triple _ pos1; b3: Triple _ pos2; b1: Triple _ Add[pos1, slope1]; b2: Triple _ Add[pos2, slope2]; m01, m12, m23, mm0, mm1: Triple; m01 _ Add[Mul[b0, p1], Mul[b1, p2]]; m12 _ Add[Mul[b1, p1], Mul[b2, p2]]; m23 _ Add[Mul[b2, p1], Mul[b3, p2]]; mm0 _ Add[Mul[m01, p1], Mul[m12, p2]]; mm1 _ Add[Mul[m23, p1], Mul[m12, p2]]; [[pt.coord.ex, pt.coord.ey, pt.coord.ez]] _ Add[ Mul[mm1, p1], Mul[mm0, p2] ]; t0.et0 _ Mul[ slope1, p2 ]; t0.et1 _ Sub3[mm0, [pt.coord.ex, pt.coord.ey, pt.coord.ez] ]; t1.et0 _ Sub3[mm1, [pt.coord.ex, pt.coord.ey, pt.coord.ez] ]; t1.et1 _ Mul[ slope2, p1 ]; b0 _ Add[pos1, offst1]; b3 _ Add[pos2, offst2]; -- get offset midpoint b1 _ Add[b0, slope1]; b2 _ Add[b3, slope2]; m01 _ Add[Mul[b0, p1], Mul[b1, p2]]; m12 _ Add[Mul[b1, p1], Mul[b2, p2]]; m23 _ Add[Mul[b2, p1], Mul[b3, p2]]; mm0 _ Add[Mul[m01, p1], Mul[m12, p2]]; mm1 _ Add[Mul[m23, p1], Mul[m12, p2]]; oPt _ Add[ Mul[mm1, p1], Mul[mm0, p2] ]; flat _ FALSE; }; [[pt.shade.exn, pt.shade.eyn, pt.shade.ezn]] _ Nmlize[ Cross[ Sub3[ oPt, [pt.coord.ex, pt.coord.ey, pt.coord.ez] ], t1.et0 ] ]; pt.shade.r _ p1 * v0.shade.r + p2 * v1.shade.r; pt.shade.g _ p1 * v0.shade.g + p2 * v1.shade.g; pt.shade.b _ p1 * v0.shade.b + p2 * v1.shade.b; pt.shade.t _ p1 * v0.shade.t + p2 * v1.shade.t; pt.props _ vtx0.props; }; DisplayPatchTnsr: PatchProc ~ { shape: REF ShapeInstance _ NARROW[ GetProp[patch.props, $Shape] ]; SELECT shape.shadingClass.shadingType FROM $Lines => { [] _ DisplayPatchEdges[context, patch]; RETURN[NIL]; }; ENDCASE; patch.type _ $PolygonToTnsrPatch; IF data = NIL THEN { patchNo: NAT _ NARROW[ GetProp[patch.props, $PatchNo], REF NAT ]^; tangents: REF TangentSeqSeq _ NARROW[ GetProp[shape.fixedProps, $PatchTangents] ]; patch _ SubDivideTnsr[ context, patch, tangents[patchNo] ]; -- subdivide proc. } ELSE { tangents: REF TangentSeq _ NARROW[ data ]; patch _ SubDivideTnsr[ context, patch, tangents ]; -- subdivide proc. }; IF patch # NIL THEN G3dShadeClipXfm.ReleasePatch[patch]; -- end of life for patch RETURN[ NIL ]; }; SubDivideTnsr: PatchProc ~ { tol: REAL _ IF context.antiAliasing THEN maxDeviation ELSE maxJaggyDeviation; tangents: REF TangentSeq _ NARROW[ data ]; IF patch.nVtces < 3 THEN SIGNAL G3dRender.Error[[$MisMatch, "Not enough vertices"]]; IF patch.nVtces = 3 THEN { patch.props _ PutPropSafely[ patch.props, $Tangents, NEW[TangentTriple _ [ NmlizeTangentSet[ patch[0], patch[1], tangents[0] ], NmlizeTangentSet[ patch[1], patch[2], tangents[1] ], NmlizeTangentSet[ patch[2], patch[0], tangents[2] ] ]] ]; TnsrTriangleDivide[context, patch, tol]; } ELSE IF patch.nVtces > 4 THEN { outPatch: REF PatchSequence _ NEW [PatchSequence[patch.nVtces]]; midPt: VertexInfo _ GetCenterPt[context, patch, tangents]; midTangents: REF TangentSeq _ NARROW[ GetProp[midPt.props, $Tangents] ]; FOR i: NAT IN [0..patch.nVtces) DO j: NAT _ (i + 1) MOD patch.nVtces; -- next vertex outPatch[i] _ G3dShadeClipXfm.GetPatch[3]; -- released by display action outPatch[i].type _ patch.type; outPatch[i].oneSided _ patch.oneSided; outPatch[i].nVtces _ 3; outPatch[i].clipState _ patch.clipState; outPatch[i].dir _ undetermined; outPatch[i].props _ patch.props; outPatch[i][0] _ patch[i]; outPatch[i][1] _ patch[(i+1) MOD patch.nVtces]; outPatch[i][2] _ midPt; outPatch[i].props _ PutPropSafely[ outPatch[i].props, $Tangents, NEW[TangentTriple _ [ NmlizeTangentSet[ outPatch[i][0], outPatch[i][1], tangents[i] ], -- orig. edge midTangents[j], -- in to middle, from next vertex [ t0: midTangents[i].t1, et0: midTangents[i].et1, -- back out to current vertex t1: midTangents[i].t0, et1: midTangents[i].et0 ] ]] ]; IF outPatch[i].clipState # in THEN G3dShadeClipXfm.GetPatchClipState[ outPatch[i] ]; ENDLOOP; outPatch.length _ patch.nVtces; outPatch _ PatchDepthSort[ context, outPatch ]; -- sort to depth order FOR i: NAT IN [0..patch.nVtces) DO TnsrTriangleDivide[context, outPatch[i], tol]; ENDLOOP; } ELSE { patch.props _ PutPropSafely[ patch.props, $Tangents, NEW[TangentQuad _ [ NmlizeTangentSet[ patch[0], patch[1], tangents[0] ], NmlizeTangentSet[ patch[1], patch[2], tangents[1] ], NmlizeTangentSet[ patch[2], patch[3], tangents[2] ], NmlizeTangentSet[ patch[3], patch[0], tangents[3] ] ]] ]; TnsrQuadDisplay[context, patch, 0, tol]; }; patch _ NIL; -- nil out patch so it won't be returned to cache by caller RETURN[patch]; }; TnsrQuadDisplay: PROC[ context: REF Context, p: REF Patch, level: NAT, tol: REAL] ~ { subP: REF PatchSequence _ NIL; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received IF p.dir = undetermined THEN [] _ TnsrQuadBackFacing[p]; -- backface test IF ( p.clipState # out ) AND ( NOT p.oneSided OR NOT p.dir = back ) THEN { IF level < recurseLimit THEN subP _ SubdivideTnsrQuad[context, p, level, tol]; IF subP = NIL THEN { -- recursion limit hit or edges all straight IF showLines THEN p.type _ $PolyLine ELSE p.type _ $ConvexPolygon; G3dShadeClipXfm.ShadePoly[context, p]; [p] _ G3dSortandDisplay.OutputPolygon[context, p]; -- display } ELSE { subP _ PatchDepthSort[ context, subP ]; -- sort to display order TnsrQuadDisplay[context, subP[0], level+1, tol]; TnsrQuadDisplay[context, subP[1], level+1, tol]; TnsrQuadDisplay[context, subP[2], level+1, tol]; TnsrQuadDisplay[context, subP[3], level+1, tol]; }; }; IF p # NIL THEN G3dShadeClipXfm.ReleasePatch[p]; -- end of life for patch }; TnsrTriangleDivide: PROC[ context: REF Context, p: REF Patch, tol: REAL] ~ { shape: REF ShapeInstance _ NARROW[ GetProp[p.props, $Shape] ]; v0, v1, v2, vCtr, vCtr1, vCtr2, vCtr3: VertexInfo; flat0, flat1, flat2: BOOLEAN; t: REF TangentTriple _ NARROW[GetProp[p.props, $Tangents] ]; t00, t01, t10, t11, t20, t21, tm0, tm1, tm2: TangentSet; outPatch: REF PatchSequence _ NEW[PatchSequence[3]]; [v0, t00, t01, flat0] _ CurveDivideTan[ context, p[0], p[1], t[0].et0, t[0].et1, GetNmlVec[t[0].et0, p[0]], GetNmlVec[t[0].et1, p[1], TRUE], 0.5, tol ]; [v1, t10, t11, flat1] _ CurveDivideTan[ context, p[1], p[2], t[1].et0, t[1].et1, GetNmlVec[t[1].et0, p[1]], GetNmlVec[t[1].et1, p[2], TRUE], 0.5, tol]; [v2, t20, t21, flat2] _ CurveDivideTan[ context, p[2], p[0], t[2].et0, t[2].et1, GetNmlVec[t[2].et0, p[2]], GetNmlVec[t[2].et1, p[0], TRUE], 0.5, tol]; IF flat0 AND flat1 AND flat2 AND stopIfStraight -- all straight? then done THEN TnsrQuadDisplay[context, p, recurseLimit, tol]; -- display as polygon tm0.et0 _ GetSlopeVec[ [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], Add[t10.et0, t21.et1] ]; tm0.et0 _ ScaleTangent[ tm0.et0, DiffPosns[p[2].coord, v0.coord, $Eye] ]; tm0.et1 _ GetSlopeVec[ [p[2].shade.exn, p[2].shade.eyn, p[2].shade.ezn], Add[t20.et0, t11.et1]]; tm0.et1 _ ScaleTangent[ tm0.et1, DiffPosns[v0.coord, p[2].coord, $Eye] ]; tm1.et0 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], Add[t20.et0, t01.et1]]; tm1.et0 _ ScaleTangent[ tm1.et0, DiffPosns[p[0].coord, v1.coord, $Eye] ]; tm1.et1 _ GetSlopeVec[ [p[0].shade.exn, p[0].shade.eyn, p[0].shade.ezn], Add[t00.et0, t21.et1]]; tm1.et1 _ ScaleTangent[ tm1.et1, DiffPosns[v1.coord, p[0].coord, $Eye] ]; tm2.et0 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], Add[t00.et0, t11.et1]]; tm2.et0 _ ScaleTangent[ tm2.et0, DiffPosns[p[1].coord, v2.coord, $Eye] ]; tm2.et1 _ GetSlopeVec[ [p[1].shade.exn, p[1].shade.eyn, p[1].shade.ezn], Add[t10.et0, t01.et1]]; tm2.et1 _ ScaleTangent[ tm2.et1, DiffPosns[v2.coord, p[1].coord, $Eye] ]; [vCtr1, tm0, , ] _ CurveDivideTan[ context, v0, p[2], tm0.et0, tm0.et1, GetNmlVec[tm0.et0, v0], GetNmlVec[tm0.et1, p[2], TRUE], 1.0/3.0, tol ]; [vCtr2, tm1, , ] _ CurveDivideTan[ context, v1, p[0], tm1.et0, tm1.et1, GetNmlVec[tm1.et0, v1], GetNmlVec[tm1.et1, p[0], TRUE], 1.0/3.0, tol ]; [vCtr3, tm2, , ] _ CurveDivideTan[ context, v2, p[1], tm2.et0, tm2.et1, GetNmlVec[tm2.et0, v2], GetNmlVec[tm2.et1, p[1], TRUE], 1.0/3.0, tol ]; [[vCtr.coord.ex, vCtr.coord.ey, vCtr.coord.ez]] _ Div[ -- average positions Add[ [vCtr1.coord.ex, vCtr1.coord.ey, vCtr1.coord.ez], Add[ [vCtr2.coord.ex, vCtr2.coord.ey, vCtr2.coord.ez], [vCtr3.coord.ex, vCtr3.coord.ey, vCtr3.coord.ez] ] ], 3.0 ]; [[vCtr.shade.exn, vCtr.shade.eyn, vCtr.shade.ezn]] _ Div[ -- average normals Add[ [vCtr1.shade.exn, vCtr1.shade.eyn, vCtr1.shade.ezn], Add[ [vCtr2.shade.exn, vCtr2.shade.eyn, vCtr2.shade.ezn], [vCtr3.shade.exn, vCtr3.shade.eyn, vCtr3.shade.ezn] ] ], 3.0 ]; FOR i: NAT IN [0..3) DO outPatch[i] _ G3dShadeClipXfm.GetPatch[4]; -- 4 point patch, released by display action outPatch[i].type _ p.type; outPatch[i].oneSided _ p.oneSided; outPatch[i].nVtces _ 4; outPatch[i].clipState _ p.clipState; outPatch[i].dir _ p.dir; outPatch[i].props _ p.props; ENDLOOP; { OPEN v0.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v1.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v2.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN vCtr.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; outPatch[0][0] _ p[0]; outPatch[0][1] _ v0; outPatch[0][2] _ vCtr; outPatch[0][3] _ v2; outPatch[1][0] _ p[1]; outPatch[1][1] _ v1; outPatch[1][2] _ vCtr; outPatch[1][3] _ v0; outPatch[2][0] _ p[2]; outPatch[2][1] _ v2; outPatch[2][2] _ vCtr; outPatch[2][3] _ v1; outPatch[0].props _ PutPropSafely[ outPatch[0].props, $Tangents, NEW[TangentQuad _ [t00, tm0, [tm2.t1, tm2.et1, tm2.t0, tm2.et0], t21] ] ]; outPatch[1].props _ PutPropSafely[ outPatch[1].props, $Tangents, NEW[TangentQuad _ [t10, tm1, [tm0.t1, tm0.et1, tm0.t0, tm0.et0], t01] ] ]; outPatch[2].props _ PutPropSafely[ outPatch[2].props, $Tangents, NEW[TangentQuad _ [t20, tm2, [tm1.t1, tm1.et1, tm1.t0, tm1.et0], t11] ] ]; FOR i: NAT IN [0..3) DO G3dShadeClipXfm.GetPatchClipState[ outPatch[i] ]; ENDLOOP; --bad!! outPatch.length _ 3; FOR i: NAT IN [0..3) DO TnsrQuadDisplay[ context, outPatch[i], 0, tol ]; ENDLOOP; }; SubdivideTnsrQuad: PROC[context: REF Context, p: REF Patch, level: NAT, tol: REAL] RETURNS[REF PatchSequence] ~ { shape: REF ShapeInstance _ NARROW[ GetProp[p.props, $Shape] ]; v0, v1, v2, v3, vCtr, vCtr2: VertexInfo; flat0, flat1, flat2, flat3: BOOLEAN; t: REF TangentQuad _ NARROW[GetProp[p.props, $Tangents] ]; t00, t01, t10, t11, t20, t21, t30, t31, tm0, tm1, tm2, tm3: TangentSet; outPatch: REF PatchSequence _ NEW[PatchSequence[4]]; [v0, t00, t01, flat0] _ CurveDivideTan[ context, p[0], p[1], t[0].et0, t[0].et1, GetNmlVec[t[0].et0, p[0]], GetNmlVec[t[0].et1, p[1], TRUE], 0.5, tol ]; [v1, t10, t11, flat1] _ CurveDivideTan[ context, p[1], p[2], t[1].et0, t[1].et1, GetNmlVec[t[1].et0, p[1]], GetNmlVec[t[1].et1, p[2], TRUE], 0.5, tol]; [v2, t20, t21, flat2] _ CurveDivideTan[ context, p[2], p[3], t[2].et0, t[2].et1, GetNmlVec[t[2].et0, p[2]], GetNmlVec[t[2].et1, p[3], TRUE], 0.5, tol]; [v3, t30, t31, flat3] _ CurveDivideTan[ context, p[3], p[0], t[3].et0, t[3].et1, GetNmlVec[t[3].et0, p[3]], GetNmlVec[t[3].et1, p[0], TRUE], 0.5, tol]; IF flat0 AND flat1 AND flat2 AND flat3 AND stopIfStraight -- all straight? then done THEN RETURN[NIL]; tm0.et0 _ GetSlopeVec[ [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], Add[t10.et0, t31.et1] ]; tm0.et0 _ ScaleTangent[ tm0.et0, DiffPosns[v2.coord, v0.coord, $Eye] ]; tm0.et1 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], Add[t11.et1, t30.et0] ]; tm0.et1 _ ScaleTangent[ tm0.et1, DiffPosns[v0.coord, v2.coord, $Eye] ]; tm1.et0 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], Add[t20.et0, t01.et1] ]; tm1.et0 _ ScaleTangent[ tm1.et0, DiffPosns[v3.coord, v1.coord, $Eye] ]; tm1.et1 _ GetSlopeVec[ [v3.shade.exn, v3.shade.eyn, v3.shade.ezn], Add[t21.et1, t00.et0] ]; tm1.et1 _ ScaleTangent[ tm1.et1, DiffPosns[v1.coord, v3.coord, $Eye] ]; [vCtr, tm0, tm2, ] _ CurveDivideTan[ context, v0, v2, tm0.et0, tm0.et1, GetNmlVec[tm0.et0, v0], GetNmlVec[tm0.et1, v2, TRUE], 0.5, tol ]; [vCtr2, tm1, tm3, ] _ CurveDivideTan[ context, v1, v3, tm1.et0, tm1.et1, GetNmlVec[tm1.et0, v1], GetNmlVec[tm1.et1, v3, TRUE], 0.5, tol ]; [[vCtr.coord.ex, vCtr.coord.ey, vCtr.coord.ez]] _ Div[ -- average Add[ [vCtr.coord.ex, vCtr.coord.ey, vCtr.coord.ez], [vCtr2.coord.ex, vCtr2.coord.ey, vCtr2.coord.ez] ], 2 ]; [[vCtr.shade.exn, vCtr.shade.eyn, vCtr.shade.ezn]] _ Nmlize[ Cross[ tm2.et0 , tm3.et0 ] ]; FOR i: NAT IN [0..4) DO outPatch[i] _ G3dShadeClipXfm.GetPatch[4]; -- 4 point patch, released by display action outPatch[i].type _ p.type; outPatch[i].oneSided _ p.oneSided; outPatch[i].nVtces _ 4; outPatch[i].clipState _ p.clipState; outPatch[i].dir _ p.dir; outPatch[i].props _ p.props; ENDLOOP; { OPEN v0.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v1.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v2.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v3.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN vCtr.coord; clip _ G3dShadeClipXfm.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dShadeClipXfm.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; outPatch[0][0] _ p[0]; outPatch[0][1] _ v0; outPatch[0][2] _ vCtr; outPatch[0][3] _ v3; outPatch[1][0] _ p[1]; outPatch[1][1] _ v1; outPatch[1][2] _ vCtr; outPatch[1][3] _ v0; outPatch[2][0] _ p[2]; outPatch[2][1] _ v2; outPatch[2][2] _ vCtr; outPatch[2][3] _ v1; outPatch[3][0] _ p[3]; outPatch[3][1] _ v3; outPatch[3][2] _ vCtr; outPatch[3][3] _ v2; IF t # NIL THEN { outPatch[0].props _ PutPropSafely[ outPatch[0].props, $Tangents, NEW[TangentQuad _ [t00, tm0, tm3, t31] ] ]; outPatch[1].props _ PutPropSafely[ outPatch[1].props, $Tangents, NEW[TangentQuad _ [t10, tm1, [tm0.t1, tm0.et1, tm0.t0, tm0.et0], t01] ] ]; outPatch[2].props _ PutPropSafely[ outPatch[2].props, $Tangents, NEW[TangentQuad _ [t20, [tm2.t1, tm2.et1, tm2.t0, tm2.et0], [tm1.t1, tm1.et1, tm1.t0, tm1.et0], t11] ] ]; outPatch[3].props _ PutPropSafely[ outPatch[3].props, $Tangents, NEW[TangentQuad _ [t30, [tm3.t1, tm3.et1, tm3.t0, tm3.et0], tm2, t21] ] ]; }; FOR i: NAT IN [0..4) DO G3dShadeClipXfm.GetPatchClipState[ outPatch[i] ]; ENDLOOP; --bad!! outPatch.length _ 4; RETURN[ outPatch ]; -- return four sub-patches }; TnsrQuadBackFacing: PROC[p: REF Patch] RETURNS [BOOLEAN] ~ { p.dir _ undetermined; -- don't know how to do this yet IF p.dir = back THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; GetTangents: ShapeProc ~ { GetSlope: PROC[normal, edge1, edge2: Triple, reverse: BOOL] RETURNS[slope: Triple] ~ { cornerNorm: Triple _ Cross[edge1, edge2]; IF reverse THEN cornerNorm _ Negate[ cornerNorm ]; slope _ Cross[ Cross[normal, edge2], normal ]; IF Length[slope] < .0001 THEN IF Dot[normal, edge2] < 0.0 -- catch degenerate cases THEN slope _ cornerNorm ELSE slope _ Negate[ cornerNorm ]; IF Dot[ normal, cornerNorm ] < 0.0 THEN IF Dot[ Nmlize[edge2], Nmlize[normal] ] > Dot[ Nmlize[edge1], Nmlize[normal] ] THEN slope _ Negate[ slope ]; -- wrong side of plane, high-curvature on one side slope _ ScaleTangent[ slope, edge2 ]; }; nullTriple: Triple _ [0.0, 0.0, 0.0]; surface: REF PtrPatchSequence _ NARROW[shape.surface]; corners: REF CornerSeqSeq; corners _ GetNormalsAndDirections[shape]; -- Get robust vertex normals and edge directions FOR i: NAT IN [0..corners.length) DO -- sort corners to clockwise order by matching edges IF corners[i] # NIL THEN corners[i] _ SortVertex[ corners[i] ]; ENDLOOP; FOR i: NAT IN [0..corners.length) DO nCorners: NAT _ IF corners[i] # NIL THEN corners[i].length ELSE 0; outDirs: REF TripleSequence _ NEW[TripleSequence[nCorners]]; -- temp tangents inDirs: REF TripleSequence _ NEW[TripleSequence[nCorners]]; FOR this: NAT IN [0..nCorners) DO last: NAT _ (this + nCorners-1) MOD nCorners; outDirs[this] _ GetSlope[ corners[i][this].normal, corners[i][this].inDir, corners[i][this].outDir, corners[i][this].concave ]; IF corners[i][last].inVtx = corners[i][this].outVtx -- matches previous corner THEN inDirs[last] _ outDirs[this] -- copy vector ELSE inDirs[last] _ GetSlope[ -- open edge, compute vector corners[i][last].normal, corners[i][last].outDir, corners[i][last].inDir, NOT corners[i][this].concave ]; ENDLOOP; FOR this: NAT IN [0..nCorners) DO -- store new tangents corners[i][this].outDir _ outDirs[this]; corners[i][this].inDir _ inDirs[this]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..corners.length) DO IF corners[i] # NIL THEN corners[i] _ FindContinuousEdges[ shape, i, corners[i] ]; ENDLOOP; { -- build tangent structure for polygons, using the vertex tangent structure just constructed tangents: REF TangentSeqSeq _ NEW[ TangentSeqSeq[shape.numSurfaces] ]; FOR i: NAT IN [0..shape.numSurfaces) DO IF surface[i] # NIL THEN { nVtces: NAT _ surface[i].nVtces; tangents[i] _ NEW[ TangentSeq[nVtces] ]; FOR cVtx: NAT IN [0..nVtces) DO -- get direction vectors for each edge at vtx. vtx: NAT _ surface[i].vtxPtr[cVtx]; -- current vertex nVtx: NAT _ surface[i].vtxPtr[(cVtx + 1) MOD nVtces]; -- other end of edge IF corners[vtx][corners[vtx].length-1].inVtx # corners[vtx][0].outVtx -- open edge? AND corners[vtx][corners[vtx].length-1].inVtx = nVtx -- check last inDir THEN tangents[i][cVtx].t0 _ corners[vtx][corners[vtx].length-1].inDir ELSE FOR j: NAT IN [0..corners[vtx].length) DO -- otherwise check all outDirs IF corners[vtx][j].outVtx = nVtx THEN { tangents[i][cVtx].t0 _ corners[vtx][j].outDir; EXIT; }; ENDLOOP; IF corners[nVtx][corners[nVtx].length-1].inVtx # corners[nVtx][0].outVtx -- open? AND corners[nVtx][corners[nVtx].length-1].inVtx = vtx -- last inDir THEN tangents[i][cVtx].t1 _ corners[nVtx][corners[nVtx].length-1].inDir ELSE FOR j: NAT IN [0..corners[nVtx].length) DO -- otherwise check all outDirs IF corners[nVtx][j].outVtx = vtx THEN { tangents[i][cVtx].t1 _ corners[nVtx][j].outDir; EXIT; }; ENDLOOP; IF tangents[i][cVtx].t0 = nullTriple OR tangents[i][cVtx].t1 = nullTriple THEN SIGNAL G3dRender.Error[[$Fatal, "Null tangent vector, bad topology?"]]; ENDLOOP; tangents[i].length _ nVtces; }; ENDLOOP; tangents.length _ shape.numSurfaces; shape.fixedProps _ PutProp[shape.fixedProps, $PatchTangents, tangents]; }; RETURN[shape]; }; GetNormalsAndDirections: PROC[shape: REF ShapeInstance] RETURNS[REF CornerSeqSeq] ~ { preNormaled: BOOLEAN _ GetProp[shape.fixedProps, $VertexNormalsInFile] # NIL; surface: REF PtrPatchSequence _ NARROW[shape.surface]; corners: REF CornerSeqSeq _ NEW[ CornerSeqSeq[shape.vertex.length] ]; IF NOT preNormaled THEN FOR i: NAT IN [0..shape.shade.length) DO IF shape.shade[i] # NIL THEN { OPEN shape.shade[i]; [xn, yn, zn] _ nullTriple; }; ENDLOOP; FOR i: NAT IN [0..shape.numSurfaces) DO IF surface[i] # NIL THEN { nVtces: NAT _ surface[i].nVtces; normal: Triple _ nullTriple; concave: REF BoolSequence _ NEW[ BoolSequence[nVtces] ]; cNmls: REF TripleSequence _ NEW[ TripleSequence[nVtces] ]; FOR cVtx: NAT IN [0..nVtces) DO -- get normal for each corner vtx: NAT _ surface[i].vtxPtr[cVtx]; nVtx: NAT _ surface[i].vtxPtr[(cVtx + 1) MOD nVtces]; lVtx: NAT _ surface[i].vtxPtr[(cVtx + nVtces - 1) MOD nVtces]; cNmls[cVtx] _ Cross[ -- in object space so do right-handed DiffPosns[ shape.vertex[lVtx]^, shape.vertex[vtx]^ ], DiffPosns[ shape.vertex[nVtx]^, shape.vertex[vtx]^ ] ]; IF unitizeNormals THEN cNmls[cVtx] _ Nmlize[cNmls[cVtx]]; normal _ Add[ normal, cNmls[cVtx] ]; -- sum normals ENDLOOP; FOR cVtx: NAT IN [0..nVtces) DO -- Get normals robustly IF Dot[ cNmls[cVtx], normal ] < 0.0 THEN { -- more than 90 degrees off poly norm. cNmls[cVtx] _ Negate[ cNmls[cVtx] ]; normal _ Add[ normal, cNmls[cVtx] ]; -- cancel 1st entry normal _ Add[ normal, cNmls[cVtx] ]; -- contribute to sum concave[cVtx] _ TRUE; -- put concave tag in here } ELSE concave[cVtx] _ FALSE; ENDLOOP; FOR cVtx: NAT IN [0..nVtces) DO vtx: NAT _ surface[i].vtxPtr[cVtx]; IF shape.shade[vtx] # NIL THEN { OPEN shape.shade[vtx]; IF preNormaled THEN cNmls[cVtx] _ [xn, yn, zn] -- Get predefined normal ELSE [[xn, yn, zn]] _ Add[[xn, yn, zn], cNmls[cVtx]]; -- sum normals }; ENDLOOP; FOR cVtx: NAT IN [0..nVtces) DO -- get direction vectors for each edge at vtx. vtx: NAT _ surface[i].vtxPtr[cVtx]; nVtx: NAT _ surface[i].vtxPtr[(cVtx + 1) MOD nVtces]; lVtx: NAT _ surface[i].vtxPtr[(cVtx + nVtces - 1) MOD nVtces]; corners[vtx] _ UpdateCornerVecSeq[ corners[vtx], [ lVtx, nVtx, -- store number of incoming vertex and outgoing vertex DiffPosns[shape.vertex[lVtx]^, shape.vertex[vtx]^], -- direction to incoming vertex DiffPosns[shape.vertex[nVtx]^, shape.vertex[vtx]^], -- outgoing direction cNmls[cVtx], -- normal at corner nullTriple, -- interior knot concave[cVtx] -- concave tag ] ]; ENDLOOP; }; ENDLOOP; corners.length _ shape.vertex.length; FOR i: NAT IN [0..shape.shade.length) DO -- normalize new vertex normals, store in corners IF shape.shade[i] # NIL THEN { OPEN shape.shade[i]; IF Length[ [xn, yn, zn] ] > shape.boundingRadius * .0001 THEN [[xn, yn, zn]] _ Nmlize[ [xn, yn, zn] ] ELSE [xn, yn, zn] _ nullTriple; -- likely unused, set default to stop trouble IF corners[i] # NIL THEN FOR j: NAT IN [0..corners[i].length) DO corners[i][j].normal _ [xn, yn, zn]; ENDLOOP; }; ENDLOOP; RETURN[ corners ]; }; UpdateCornerVecSeq: PROC[corner: REF CornerSeq, entry: Corner] RETURNS[REF CornerSeq] ~ { newSeq: REF CornerSeq; IF corner = NIL THEN newSeq _ NEW[ CornerSeq[6] ] -- get brand new sequence ELSE IF corner.length = corner.maxLength THEN { -- expand filled sequence newSeq _ NEW[ CornerSeq[ corner.maxLength * 2 ] ]; FOR i: NAT IN [0..corner.maxLength) DO newSeq[i] _ corner[i]; -- copy into new sequence ENDLOOP; } ELSE newSeq _ corner; -- no changes required corner _ newSeq; corner[corner.length] _ entry; corner.length _ corner.length + 1; RETURN[corner]; }; FindContinuousEdges: PROC[ shape: REF ShapeInstance, vtx: NAT, corner: REF CornerSeq ] RETURNS[ REF CornerSeq ] ~ { nCorners: NAT _ corner.length; acrossFrom: REF NatSequenceSequence _ NEW[NatSequenceSequence[nCorners]]; FOR this: NAT IN [0..nCorners) DO acrossFrom[this] _ NEW[NatSequence[nCorners]]; ENDLOOP; IF corner[nCorners-1].inVtx # corner[0].outVtx THEN { -- open edge (should only be one) cosAng: REAL _ Dot[Nmlize[corner[nCorners-1].inDir], Nmlize[ corner[0].outDir]]; IF cosAng < minCosToAlignOpen THEN { -- included angle over limit (aligned => -1.0) IF unitizeNormals THEN { -- normalize to give equal weight to direction corner[nCorners-1].inDir _ Nmlize[corner[nCorners-1].inDir]; corner[0].outDir _ Nmlize[corner[0].outDir]; }; corner[nCorners-1].inDir _ Add[ -- sum to get aligned direction corner[nCorners-1].inDir, Negate[corner[0].outDir] ]; corner[0].outDir _ Negate[corner[nCorners-1].inDir]; -- opposite dir corner[nCorners-1].inDir _ ScaleTangent[ -- scale inDir to Bezier length corner[nCorners-1].inDir, -- outdir done below DiffPosns[shape.vertex[corner[nCorners-1].inVtx]^, shape.vertex[vtx]^] ]; }; } ELSE SELECT nCorners FROM -- no open edge 1, 2 => SIGNAL G3dRender.Error[[$MisMatch, "too few edges at vertex"]]; 3 => {}; -- leave things alone if 3 4 => FOR i: NAT IN [0..2) DO -- align opposing vertices if 4 o: NAT _ i+2; o1: NAT _ i+1; i1: NAT _ (i+3) MOD 4; -- opposite, next, and prev. corner[o].outDir _ corner[o1].inDir _ Add[ -- sum for aligned dir corner[o].outDir, Negate[corner[i].outDir] ]; corner[i].outDir _ corner[i1].inDir _ Negate[corner[o].outDir]; ENDLOOP; ENDCASE => { }; IF nCorners > 50 THEN { -- for corners over 4 FOR this: NAT IN [0..nCorners) DO -- tag nearly continuous pairs FOR other: NAT IN (this+1..nCorners) DO -- don't test adjacent edges, musn't be aligned cosAng: REAL _ Dot[Nmlize[corner[other].outDir], Nmlize[corner[this].outDir]]; IF this = 0 AND other = nCorners-1 AND corner[other].inVtx = corner[this].outVtx THEN EXIT; -- skip if adjacent edge wrapping around zero IF cosAng < minCosToAlign THEN { -- included angle over limit, mark both edges acrossFrom[this][acrossFrom[this].length] _ other; acrossFrom[this].length _ acrossFrom[this].length + 1; acrossFrom[other][acrossFrom[other].length] _ this; acrossFrom[other].length _ acrossFrom[other].length + 1; }; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..nCorners) DO -- align paired tangents Sum: PROC[ k: NAT ] ~ { FOR j: NAT IN [1..acrossFrom[k].length) DO -- sum tangents across from this one corner[acrossFrom[k][0]].outDir _ Add[ corner[acrossFrom[k][0]].outDir, corner[acrossFrom[k][j]].outDir ]; ENDLOOP; }; Copy: PROC[ k: NAT ] ~ { FOR j: NAT IN [1..acrossFrom[k].length) DO -- copy sum to other tangents corner[acrossFrom[k][j]].outDir _ corner[acrossFrom[k][0]].outDir; ENDLOOP; }; Kill: PROC[ k, other: NAT ] ~ { FOR j: NAT IN [1..acrossFrom[k].length) DO -- kill pointers to prevent further use IF j # other THEN acrossFrom[acrossFrom[k][j]].length _ 0; ENDLOOP; acrossFrom[k].length _ 0; }; this, last, bigOne: NAT _ i; biggest: BOOLEAN _ FALSE; WHILE NOT biggest DO -- chain through to largest set of paired tangents biggestFound: NAT _ acrossFrom[this].length; biggest _ TRUE; bigOne _ this; FOR j: NAT IN [0..acrossFrom[this].length) DO -- check all edges paired with this one IF acrossFrom[acrossFrom[this][j]].length >= biggestFound THEN { bigOne _ acrossFrom[this][j]; -- found one the same size at least IF acrossFrom[acrossFrom[this][j]].length > biggestFound THEN {-- a bigger one biggestFound _ acrossFrom[acrossFrom[this][j]].length; biggest _ FALSE; }; }; ENDLOOP; last _ this; this _ bigOne; ENDLOOP; IF acrossFrom[bigOne].length > 1 AND acrossFrom[last].length > 1 THEN { FOR j: NAT IN [0..acrossFrom[bigOne].length) DO -- cull any neighboring edges IF (acrossFrom[bigOne][j] = (last + 1) MOD nCorners) OR (acrossFrom[bigOne][j] = (last + nCorners-1) MOD nCorners) THEN { FOR k: NAT IN [j..acrossFrom[bigOne].length) DO acrossFrom[bigOne][k] _ acrossFrom[bigOne][k+1]; ENDLOOP; acrossFrom[bigOne].length _ acrossFrom[bigOne].length - 1; }; ENDLOOP; }; Sum[bigOne]; Sum[last]; -- sum all directions in set IF bigOne # last THEN { corner[acrossFrom[last][0]].outDir _ Add[ corner[acrossFrom[last][0]].outDir, -- get aligned direction Negate[corner[acrossFrom[bigOne][0]].outDir] ]; corner[acrossFrom[bigOne][0]].outDir _ Negate[ corner[acrossFrom[last][0]].outDir ]; }; Copy[bigOne]; Copy[last]; -- copy directions to others in sets Kill[bigOne, last]; Kill[last, bigOne]; -- kill sets ENDLOOP; }; FOR this: NAT IN [0..nCorners) DO -- go back over tangents and scale properly last: NAT _ (this + nCorners -1) MOD nCorners; corner[this].outDir _ ScaleTangent[ corner[this].outDir, DiffPosns[shape.vertex[corner[this].outVtx]^, shape.vertex[vtx]^] ]; IF corner[last].inVtx = corner[this].outVtx THEN corner[last].inDir _ corner[this].outDir; ENDLOOP; RETURN[ corner ]; }; SortVertex: PROC[corner: REF CornerSeq] RETURNS[REF CornerSeq] ~ { nCrnrs: NAT _ corner.length; someLeft, someFound: BOOLEAN _ TRUE; newSeq: REF CornerSeq _ NEW[CornerSeq[nCrnrs]]; newSeq[0] _ corner[0]; newSeq.length _ 1; WHILE someLeft DO someLeft _ someFound _ FALSE; FOR j: NAT IN [1..nCrnrs) DO IF newSeq[newSeq.length-1].inVtx = corner[j].outVtx -- match last incoming edge THEN { -- next corner in clockwiser order newSeq[newSeq.length] _ corner[j]; newSeq.length _ newSeq.length+1; corner[j].outVtx _ corner[j].inVtx _ LAST[NAT]; -- no further use someFound _ TRUE; } ELSE IF newSeq[0].outVtx = corner[j].inVtx -- match 1st outgoing edge THEN { -- previous corner in clockwiser order FOR k: NAT DECREASING IN [0..newSeq.length) DO newSeq[k+1] _ newSeq[k]; ENDLOOP; newSeq[0] _ corner[j]; newSeq.length _ newSeq.length+1; corner[j].outVtx _ corner[j].inVtx _ LAST[NAT]; -- no further use someFound _ TRUE; } ELSE IF corner[j].outVtx # LAST[NAT] THEN someLeft _ TRUE; -- not done ENDLOOP; IF NOT someFound AND someLeft THEN -- more than one chain, apparently SIGNAL G3dRender.Error[[$Fatal, "Multiple open edges at a vertex not allowed"]]; ENDLOOP; IF newSeq.length > 0 THEN corner _ newSeq; RETURN[ corner ]; }; InitClasses[]; END. %΄PatchFromPolyProcs.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last Edited by: Crow, May 3, 1989 5:33:50 pm PDT Bloomenthal, September 14, 1988 1:06:44 pm PDT Types RECORD[coord: Vertex, shade: ShadingValue, props: Atom.PropList, aux: REF ANY]; Represents a corner of a polygon, - inVtx is the vertex at the other end of the incoming edge (previous vertex in order), - outVtx is other vertex on outgoing edge, - inDir, outDir are the corresponding outward direction vectors, - normal is the carefully determined normal vector - concave = TRUE indicates corner is concave vertex needing reversed cross product. Represents the tangents at the endpoints of an edge (in object space and eyespace), the edge is ordered in the vertex order of the polygon (t0 at leading endpoint, t1 at trailing endpoint) Renamed Procedures Global Variables Utility Procedures Put property on new property list, to avoid clobbering other lists inherited from same place Remove property from new property list, to avoid clobbering lists inherited from same place Difference f - g, returns zero if less than 3 decimal places Screen space f - g, returns zero if less than noticeable Subtracts vtx2 from vtx1 in selected space Returns a vector normal to the 1st vector and in the plane defined by both vectors Magnitude is scaled to equal that of second vector ("edge") Magnitude is scaled to to proper length for Bezier inner control pt approximating circular arc THEN slope _ G3dVector.Mul[ ScaleTangent[ slope, edge ], 3.0 ] Returns a normalized vector normal to the 1st vector and in the plane defined by 2nd Scale tangent vector to proper length for Bezier inner control pt approximating circular arc Find max distance from screen edge on inside Tests for slopes within n pixels of edge on screen PROC[context: REF Context, shape: REF ShapeInstance, data: REF] RETURNS[REF ShapeInstance]; Update shading and transform matrices Transform vertices to eyespace, get clip state of vertices and whole shape Transform normals to eyespace Display Procedures PROC[ context: REF Context, patch: REF Patch, data: REF ANY _ NIL ] RETURNS[REF Patch] Initialization of Classes Utilities for expansion of non-planar polygons Subdivide polygon to triangular patches, depth sort, then call TriangleDisplay on each one Find central point in polygon for use in triangulation In following, t0 toward center at vertex, t1 away from center at center Recursively divide triangular patches to straight edges, then display Divide triangular patch into four subtriangles get inner edge tangents based on midpoints { -- Reverse mid-normals if on wrong side of triangle plane by > 30 deg. normal: Triple _ Nmlize[ Cross[ DiffPosns[ p[1].coord, p[0].coord, $Eye ], DiffPosns[ p[2].coord, p[0].coord, $Eye ] ] ]; check: REAL; check _ Dot[[v0.shade.exn, v0.shade.eyn, v0.shade.ezn], normal ]; IF check < -0.5 THEN [[v0.shade.exn, v0.shade.eyn, v0.shade.ezn]] _ Negate[[v0.shade.exn, v0.shade.eyn, v0.shade.ezn]]; check _ Dot[[v1.shade.exn, v1.shade.eyn, v1.shade.ezn], normal ]; IF check < -0.5 THEN [[v1.shade.exn, v1.shade.eyn, v1.shade.ezn]] _ Negate[[v1.shade.exn, v1.shade.eyn, v1.shade.ezn]]; check _ Dot[[v2.shade.exn, v2.shade.eyn, v2.shade.ezn], normal ]; IF check < -0.5 THEN [[v2.shade.exn, v2.shade.eyn, v2.shade.ezn]] _ Negate[[v2.shade.exn, v2.shade.eyn, v2.shade.ezn]]; }; outPatch[3].props _ RemPropSafely[ outPatch[3].props, $Tangents]; outPatch[3].type _ $PolygonWithNormals; Procedures for expansion of non-planar polygons using normals at vertices Do we have texture? Use slopes to make corner triangles and internal hexagon, evaluate at corners of hexagon Returns parametric midpoint of curve given by v0-v1 Ensure consistent evaluation for arithmetic stability Do with both ends to get consistent mid normal, curve is likely to be non planar Do with both ends to get stable mid normal, must be a better way Procedures for expansion of non-planar polygons using tangent vectors at vertices PROC[context: REF Context, shape: REF ShapeInstance, data: REF] RETURNS[REF ShapeInstance]; Convert endpoints with tangents to Bezier knots Use slopes to make corner triangles and internal hexagon, evaluate at corners of hexagon Convert endpoints with tangents to Properly scaled tangents Get screen distance of inner knots from edge formed by outer knots Returns point on curve given by v0-v1 and tangents and parameter IF shape.shadingClass.texture # NIL THEN { -- for solid textures lerpProc: VertexInfoProc _ shape.shadingClass.lerpVtxAux; pos1 _ [v0.coord.x, v0.coord.y, v0.coord.z]; pos2 _ [v1.coord.x, v1.coord.y, v1.coord.z]; SELECT shape.class.type FROM $PolygonWithTangents => { edge: Triple _ DiffPosns[v1.coord, v0.coord, $Object]; slope1 _ IF tan.t0 # nullTriple THEN tan.t0 ELSE GetSlopeVec[ [v0.shade.xn, v0.shade.yn, v0.shade.zn], edge ]; slope2 _ IF tan.t1 # nullTriple THEN tan.t1 ELSE GetSlopeVec[ [v1.shade.xn, v1.shade.yn, v1.shade.zn], Negate[edge] ]; }; ENDCASE => SIGNAL G3dRender.Error[[$Unimplemented, "Unknown surface type"]]; pt.shade.xn _ (v0.shade.xn + v1.shade.xn) / 2; pt.shade.yn _ (v0.shade.yn + v1.shade.yn) / 2; pt.shade.zn _ (v0.shade.zn + v1.shade.zn) / 2; IF flat THEN { -- straight edge, average endpoints for midpoint pt.coord.x _ (pos1.x + pos2.x) / 2; pt.coord.y _ (pos1.y + pos2.y) / 2; pt.coord.z _ (pos1.z + pos2.z) / 2; } ELSE { -- not straight, evaluate curve b0: Triple _ pos1; b3: Triple _ pos2; b1: Triple _ Add[pos1, slope1]; b2: Triple _ Add[pos2, slope2]; m01, m12, m23, mm0, mm1: Triple; m01.x _ (b0.x + b1.x)/2; m01.y _ (b0.y + b1.y)/2; m01.z _ (b0.z + b1.z)/2; m12.x _ (b1.x + b2.x)/2; m12.y _ (b1.y + b2.y)/2; m12.z _ (b1.z + b2.z)/2; m23.x _ (b2.x + b3.x)/2; m23.y _ (b2.y + b3.y)/2; m23.z _ (b2.z + b3.z)/2; mm0.x _ (m01.x+m12.x)/2; mm0.y _ (m01.y+m12.y)/2; mm0.z _ (m01.z+m12.z)/2; mm1.x _ (m23.x+m12.x)/2; mm1.y _ (m23.y+m12.y)/2; mm1.z _ (m23.z+m12.z)/2; pt.coord.x _ (mm1.x + mm0.x) / 2; pt.coord.y _ (mm1.y + mm0.y) / 2; pt.coord.z _ (mm1.z + mm0.z) / 2; t0.t0 _ [ slope1.x / 2, slope1.y / 2, slope1.z / 2 ]; t0.t1 _ [ mm0.x - pt.coord.x, mm0.y - pt.coord.y, mm0.z - pt.coord.z ]; t1.t0 _ [ mm1.x - pt.coord.x, mm1.y - pt.coord.y, mm1.z - pt.coord.z ]; t1.t1 _ [ slope2.x / 2, slope2.y / 2, slope2.z / 2 ]; }; IF lerpProc # NIL AND v0.aux # NIL THEN { -- get auxiliary info from supplied proc data: LORA _ LIST[ v0.aux, v1.aux, NEW[REAL _ 0.5], NEW[REAL _ 0.5] ]; pt _ lerpProc[ NIL, pt, data]; -- average, for lack of better strategy }; }; Procedures for tensor product quadrilaterals using tangent vectors at vertices Divide polygon into quadrilateral and triangular patches, depth sort, divide triangles into quadrilaterals then call QuadrilateralDisplay on each one Recursively divide quadrilateral patches to straight edges, then display Divide triangular patch into three subquadrilaterals and send to display Find midpoints and midslopes for each side Get inner edges from midpoint to opposite vertex curves Split inner edges at 1/3 point and average for ctr. point Divide quadrangular patch into four subquadrangles Find midpoints and midslopes for each side Get inner edge endpoint tangents from opposite midpoints - first project direction given by endpoint cross-tangents on plane given by normal - Then scale direction by distance to opposite midpoint tm0.et0 _ GetSlopeVec[ [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], DiffPosns[v2.coord, v0.coord, $Eye] ]; tm0.et1 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], DiffPosns[v0.coord, v2.coord, $Eye] ]; tm1.et0 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], DiffPosns[v3.coord, v1.coord, $Eye] ]; tm1.et1 _ GetSlopeVec[ [v3.shade.exn, v3.shade.eyn, v3.shade.ezn], DiffPosns[v1.coord, v3.coord, $Eye] ]; Get center point, normal and cross tangents Procedures for computing tangent vectors at vertices Build endpoint tangents for each edge of a polygon Returns a vector normal to the 1st vector and in the plane defined by both vectors SIGNAL G3dRender.Error[[$Unimplemented, "surface ambiguously curved"]]; Pass through polygon structure, getting vertex normals robustly while building corner structure. Order contiguous polygons to clockwise around vertex, seen from outside Build tangents at corners around each vertex. By projecting edge directions on plane given by normal Make tangents continuous if within 30 degrees of being so. Always make continuous if open edge Find outgoing direction vector by pointer to opposing vertex Find direction vector at other end of edge by looking back from opposing vertex Get vertex normals robustly ( using robust polygon normal ) Find incoming and outgoing directions for each polygon at a vertex Clear vertex normals Get polygon normal robustly ( allowing slightly concave polygons ). Sum of normals given at corners is assumed to be the dominant direction. Normals over 90 degrees off that are assumed to come from concave vertices and are therefore reversed. Build corner structure Make tangents continuous if within 30 degrees of being so. Always make continuous if open edge, adjacent edges not allowed to be Check for open edge, if there, align tangents unless included angle less than 45 degrees This will attempt to align edges in the more complex cases Sort polygon entries to clockwise order at each vertex, ensure discontinuities are at extremes Build chain of corners by checking both ends of chain against all remaining unattached corners, only one chain allowed, since only one open edge allowed at vertex ΚJš œžœžœ˜>Jš œžœžœžœ˜>Jš œžœžœžœ˜8Jš œžœ žœžœ˜9Jš œžœ žœ˜;Jš œžœ žœ˜>Jš œžœžœ˜;Jš œžœ!žœžœžœžœžœ.˜uJš œžœ!žœžœžœžœž œ&˜x—™NšœžœΟc*˜JNšœžœ ‘'˜KNšœžœ ‘A˜aNšœžœ ‘˜;Nšœžœ ‘,˜LNšœžœ ‘0œ˜SNšœžœžœ‘,˜PNšœžœžœ‘7˜XNšœ žœ ‘˜8—™š   œžœ%žœžœžœ˜ZJš‘\™\Jšœžœ˜š žœ+žœžœžœ‘˜QJšœžœ$˜BJšœ žœ˜#Jšžœ˜—Idefaultšžœ#˜)Ošœ ˜—š   œžœ!žœžœžœ˜XJš‘[™[Jšœžœ˜š žœ+žœžœžœ‘˜QJšœžœ$˜BJšœ žœ˜#Jšžœ˜—Ošžœ+˜1Ošœ ˜—š œž œ žœžœžœžœžœ˜SO˜—š  œžœžœžœžœ˜'Jš‘=™=Jš žœ žœžœžœžœ˜EJ˜—š  œžœžœžœžœ˜*Jšœ‘8™9Jšœžœ ˜Jš žœžœ*žœžœžœžœ ˜VJ˜—š   œžœžœžœžœ ˜JJ™*šžœž˜Jšœ žœI˜YJšœ žœK˜[šžœ ‘˜JšžœC˜I——J˜—š   œžœ žœžœžœ˜YJ™RJšΟy;™;J™^J˜-šžœ˜ Jšžœ5˜9Jšžœ:™>Jšžœ%˜)—J˜—š   œžœ&žœžœž œ˜cJ™TJ˜:šœ žœ ˜Jšžœ˜"Jšžœ˜"—J˜—š  œžœžœ ˜@J™\Jšœžœ˜Jšœ žœ˜#Jšœ žœ˜#Jšžœžœžœžœ˜FJšœ žœ%˜2šœ žœ˜#Jšžœ˜ Jšžœ7˜;—šœžœ˜$šžœF˜JJšžœ#˜'——Jšžœ$˜*Jšœ ˜—š  œžœ žœ,žœžœžœ˜fJ™,Jšœ žœ˜Jš žœ žœžœžœ‘˜Fšžœ˜Jšžœ žœ&˜8—šžœ˜Jšžœ žœ;˜M—šžœ˜Jšžœ žœ%˜7—šžœ˜Jšžœ žœ:˜L—Jšžœ!žœžœžœžœžœžœ˜HJ˜—š   œžœ žœ;žœžœžœ˜vJ™2šœ:‘˜UJ˜6J˜—šœ:‘˜TJ˜6J˜—Jš œ žœžœžœžœžœ˜…Jšœ‘5˜RJšœ‘˜8Jšžœžœžœžœžœžœžœ˜8J˜—š  œžœ žœ žœžœžœ˜hOšœžœžœ˜2Ošœžœžœ˜9š žœžœžœžœ‘$˜HJ˜Jš žœžœžœžœ#žœ˜MJ˜J˜Jšžœ˜—šžœžœžœžœ˜š žœžœž œžœ žœ‘(˜Ošžœžœ˜Jšœžœžœ˜/J˜%J˜J˜—Jšžœ˜—Jšžœ˜—šžœžœžœžœžœžœžœ‘˜\Jšœžœ˜Jšœžœ:˜CJšžœ˜—J˜Jšžœ˜ J˜—š œ˜!Jš žœ žœžœžœžœžœ™[Ošœ žœžœžœžœžœžœžœ˜7šžœ1žœ˜7Jšžœ/‘#˜V—O™%šžœžœ˜J˜@J™KšœB˜BJ™—š žœžœžœžœžœžœ˜Gšžœ‘˜8J˜FJšžœ˜——šžœžœžœ‘ ˜@Jšœžœžœ˜4šžœžœžœžœ˜(šžœ žœ˜šžœ˜Jšžœ ‘˜8šžœžœ!‘˜AJšžœ:˜>Ošžœ˜———Jšžœ˜—J˜—Jšœžœ˜J˜—šžœžœžœ ˜@Jšžœ0˜4—Jšœžœ˜Jšžœ˜J˜——™š œ‘!˜DJšœžœžœ!˜BJšžœ žœžœ)‘˜TNšžœžœ˜N˜—š œ˜ Nšžœ žœžœžœžœžœžœžœ™VJšœžœžœ!˜BJš œ žœžœ"žœžœ˜BJšœ žœžœ.˜RJšœ žœžœ-˜OJ˜@Jšœžœ˜$Jšœžœžœžœžœžœžœžœžœ˜=šžœžœžœž˜"Jšœžœ žœ˜ Jšœžœ(˜1J˜:J˜šžœ ž˜J˜G˜HJ˜)J˜—Jšžœžœ;˜L—šžœžœžœ žœ˜Jšžœ˜Jšœžœ˜J˜,JšœP˜PJšœA˜Ašžœ˜JšžœH˜L—Jš œž œ žœžœ žœ˜MJš œž œ žœžœ žœ ˜FJšœ?‘˜QJšžœ˜—J˜J˜J˜šœžœ˜'Jšžœ˜Jšžœžœžœžœ ˜1—Jšœ:˜:Jšžœ˜—Jšžœžœ˜ J˜J˜——™š  œžœ ‘.˜IO˜EO˜J˜)J˜+J˜.J˜CJ˜*J˜/J˜.J˜DJ˜)J˜.J˜CJ˜%Jšœ>˜>J˜,J˜>J˜——™.š œ˜$J™ZNš œžœžœžœžœ˜NJšœ žœžœ ˜*Jšžœž œ5˜Ušžœž˜šžœ˜šžœ#žœ˜FJ˜šžœ˜J˜5J˜5J˜4J˜—J˜—J˜*Jšœžœ‘2˜AJ˜—šžœ˜Jšœ žœžœ˜AJ˜:šœ žœžœ˜&J˜ J˜—šžœžœžœž˜"Jšœžœ žœ‘˜2Jšœ,‘˜IO˜J˜&J˜J˜(J˜J˜ J˜Jšœžœ˜/J˜šžœ#žœ#˜LJ˜šžœ˜JšœA‘ ˜NJšœ‘!˜9Jšœ3‘œ3˜ƒJ˜—J˜—Jšžœžœ2˜TJšžœ˜—J˜Jšœ3‘˜Išžœžœžœžœ˜#O˜/Ošžœ˜—J˜——Jšžœ˜J˜—š  œžœ žœžœžœžœžœ˜|J™6Jšœžœžœ!˜BNš œžœžœžœžœ˜NJšžœžœžœ5˜V˜šœ žœžœ#ž œ"˜qJšžœžœžœžœ˜.—Oš œžœ žœžœžœžœ˜VJšœ žœ˜"Jš œ žœžœžœžœ˜dJ˜Dšžœžœžœž‘>˜[Jšœžœ‘2˜UJ˜Jšœžœžœ‘#˜Ošžœ#žœ!˜Hšžœ‘M˜SJ˜Jšœžœžœ‘˜MJ˜J˜0Jšœžœ‘!˜LJ˜J˜0J˜.˜$J˜E—J™GJ˜JšœD‘˜TJ˜—šžœ˜J˜IJ˜1J˜1J˜1J˜——J˜.J˜.J˜.J˜+J˜+J˜+J˜+šžœžœžœ‘˜=J˜+J˜+J˜+J˜.J˜.J˜.š žœ žœžœ žœžœžœ ‘ ˜TNšžœ(žœ˜?šžœ˜Nš œžœžœžœžœ žœžœ ˜INšœžœ˜$N˜——J˜—Jšžœ˜—Jšœ,‘'˜SJ˜*J˜*šžœ#žœ"˜Išžœ‘6˜?šžœžœžœž˜˜Jšœ.žœ˜6Jšœžœžœ˜™B—šœ žœ™ Jšžœ™ JšžœF™J—J™—Jšžœžœ<™M—J™.J™.J™/šžœ™šžœ ‘0™?J™#J™#J™#J™—šžœ ‘™.J™(J™#J™J™ J™TJ™TJ™TJ™NJ™NJ™#J™#J™$J™5J™GJ™GJ™5J™——š žœ žœžœ žœžœ‘(™RNš œžœžœžœžœ žœžœ ™FNšœžœ‘'™JN™—J™—J˜J˜——™Nš œ˜Jšœžœžœ!˜Bšžœ žœ˜,Jšœ8žœžœ˜HJšžœ˜—J˜!šžœžœ˜šžœ˜Jš œ žœžœ"žœžœ˜BJšœ žœžœ.˜RNšœ=‘˜ON˜—šžœ˜Jšœ žœžœ ˜*Nšœ4‘˜FN˜——Jšžœ žœžœ)‘˜TNšžœžœ˜N˜—š  œ˜J™•Nš œžœžœžœžœ˜NJšœ žœžœ ˜*Jšžœž œ5˜Ušžœž˜šžœ˜˜J˜šžœ˜J˜5J˜5J˜4J˜—J˜—J˜*J˜—šžœžœžœ˜Jšœ žœžœ˜AJ˜:šœ žœžœ˜&J˜ J˜—šžœžœžœž˜"Jšœžœ žœ‘˜2Jšœ,‘˜IO˜J˜&J˜J˜(J˜J˜ J˜Jšœžœ˜/J˜˜"J˜šžœ˜JšœA‘ ˜NJšœ‘!˜9Jšœ3‘œ3˜ƒJ˜—J˜—Jšžœžœ2˜TJšžœ˜—J˜Jšœ3‘˜Išžœžœžœžœ˜#O˜/Ošžœ˜—J˜—šžœ˜˜J˜šžœ˜J˜5J˜5J˜5J˜4J˜—J˜—J˜(J˜——Jšœžœ‘;˜JJšžœ˜J˜—š  œžœ žœ žœžœžœ˜UJ™HJšœžœž˜Jšžœžœžœ‘$˜GJšžœžœ‘˜Jš žœžœžœ žœžœžœ˜JJšžœžœ2˜Nšžœžœ˜šžœ ‘,˜JšœIžœ˜QJšœžœžœ˜JšœFžœ˜NJšœžœžœ˜:O˜GOšœ žœžœ˜5J™*˜(J˜)Jšœ5žœ˜G—˜'J˜)Jšœ5žœ ˜F—˜'J˜)Jšœ5žœ ˜F—˜'J˜)Jšœ5žœ˜G—š žœžœžœžœžœ‘˜UJšžœžœžœ˜—š‘8™8J™SJ™7—J˜[J˜GJ˜[J˜GJ˜[J˜GJ˜[J˜GJ™qJ™rJ™qJ™rJ™J™+˜%J˜#Jšœ/žœ˜A—˜&J˜#Jšœ/žœ˜A—J˜šœ>‘ ˜H˜J˜/J˜0J˜—J˜J˜—J˜ZJ˜šžœžœžœž˜Jšœ+‘,˜WO˜J˜"J˜J˜$J˜J˜Jšžœ˜—šœžœN˜Ušžœ˜JšžœW˜[——šœžœN˜Ušžœ˜JšžœW˜[——šœžœN˜Ušžœ˜JšžœW˜[——šœžœN˜Ušžœ˜JšžœW˜[——šœžœP˜Wšžœ˜JšžœW˜[——J˜]J˜]J˜]J˜]šžœžœžœ˜šœAžœ˜RJ˜—šœAžœ˜RJ˜8—šœAžœ˜RJ˜W—šœAžœ˜RJ˜8—J˜—Oš žœžœžœžœ5žœ‘˜\J˜Jšžœ‘˜.J˜—š  œžœžœ žœžœ˜=Jšœ‘ ˜=Jšžœžœžœžœžœžœžœ˜5J˜—J˜—™4š  œ˜O™2š œžœ(žœžœ˜VJ™RJ˜)Jšžœ žœ#˜2J˜.šžœžœžœ‘˜TJšžœžœ˜:—šžœ!žœ˜(šžœM˜OJšžœ‘2˜P—JšžœA™G—J™J˜%J˜—O˜%Ošœ žœžœ˜6Jšœ žœ˜J™`Jšœ+‘1˜\J™Gš žœžœžœžœ‘4˜ZJšžœžœžœ'˜?Jšžœ˜ —J™dšžœžœžœžœ˜%Jš œ žœžœžœžœžœ˜BJšœ žœžœ‘˜MJšœžœžœ˜;šžœžœžœž˜!Jšœžœžœ ˜-˜J˜IJ˜J˜—šžœ4‘˜PJšžœ#‘˜5šžœ ‘˜@J˜JJšžœ˜J˜——Jšžœ˜—š žœžœžœžœ‘ œ˜;Jšœžœ ž˜(Jšœžœ ž˜&Jšžœ˜—Jšžœ˜ —J™_šžœžœžœžœ˜%Jšžœžœžœ:˜RJšžœ˜—J™šœ‘\˜^Jšœ žœžœ%˜Fšžœžœžœžœžœžœžœ˜BJšœžœ˜ Jšœžœ˜(š žœžœžœ žœ‘.˜OJšœžœ"‘˜;Jšœžœ žœ ‘˜JJš‘<™<šžœD‘ ˜SJšžœ2‘˜IJšžœA˜Eš žœžœžœžœžœ‘˜Ošžœžœ˜&Jšœ5žœ˜>—Jšžœ˜——Jš‘O™OšžœG‘˜QJšžœ2‘˜JJšžœC˜Gš žœžœžœžœ‘ ˜Pšžœž˜%Jšœ6žœ˜?—Jšžœ˜——šžœ#žœ#ž˜NJšœžœB˜I—Jšžœ˜—J˜J˜Jšžœ˜—J˜$J˜GJ˜—Jšžœ˜O˜—š  œžœžœžœžœ˜UJ™;J™BJšœ žœ5žœ˜MOšœ žœžœ˜6Jšœ žœžœ'˜FJ™š žœ ž œžœžœžœ˜AJšžœžœžœžœ1˜UJšžœ˜ —šžœžœžœžœžœžœžœ˜BJšœžœ˜ J˜Jšœ žœžœ˜8Jšœžœžœ˜;J™υš žœžœžœ žœ‘˜>Jšœžœ˜#Jšœžœ žœ ˜5Jšœžœ)žœ ˜>šœ‘%˜;J˜5J˜5J˜—Jšžœžœ&˜˜#J˜šœ‘6˜IJšœ4‘˜SJšœ6‘˜KJšœ ‘!˜-Jšœ‘˜)Jšœ‘˜(J˜—J˜—Jšžœ˜—J˜Jšžœ˜—J˜&š žœžœžœžœ‘1˜Zšžœžœžœ˜Jšžœ˜šžœ6˜8Jšžœ(˜,Jšžœ‘/˜N—šžœžœžœžœžœžœžœ˜BJ˜&Jšžœ˜—J˜—Jšžœ˜ —Jšžœ ˜J˜—š  œžœ žœžœžœ˜dJšœžœ ˜šžœ žœ˜Jšžœ žœ‘˜>šžœžœ"˜)šžœ ‘˜*Jšœ žœ&˜2šžœžœžœž˜&Jšœ‘˜3Jšžœ˜—J˜—Jšžœ‘˜1——O˜J˜J˜"Ošžœ ˜O˜—š œžœ žœžœ žœžœžœ˜{J™‚Jšœ žœ˜Jšœ žœžœ ˜Išžœžœžœž˜!Jšœžœ˜.Jšžœ˜ —J™Xšžœ-˜/šžœ‘!˜(JšœžœD˜Pšžœžœ‘.˜Sšžœžœ‘.˜IJ˜—šžœžœ‘-˜NJšœ1ž˜2J˜6Jšœ2ž˜3J˜8J˜—Jšžœ˜—Jšžœ˜ —š žœžœžœžœ ‘˜@š œžœžœ˜š žœžœžœžœ‘$˜P˜&J˜@J˜—Jšžœ˜—J˜—š œžœžœ˜š žœžœžœžœ‘˜KJ˜BJšžœ˜—J˜—š œžœ žœ˜š žœžœžœžœ‘'˜SJšžœ žœ)˜:Jšžœ˜—J˜J˜—Jšœžœ˜Jšœ žœžœ˜šžœžœ žœ‘2˜KJšœžœ˜0Jšœ žœ˜!šžœžœžœž‘(˜Ušžœ8žœ‘˜AJšœ"‘#˜Ešžœ7žœ‘˜NJšœDžœ˜JJ˜—J˜—Jšžœ˜ J˜—Jšžœ˜ —šžœžœžœ˜Gš žœžœžœ žœ‘˜Nšžœ(žœ žœ.žœ ˜wšžœ˜šžœžœžœ žœ˜0J˜0Jšžœ˜—J˜:J˜——Jšžœ˜—J˜—Jšœ‘˜9šžœžœ˜˜*Jšœ,‘˜DJ˜,J˜—J˜TJ˜—Jšœ"‘$˜FJšœ1‘ ˜=Jšžœ˜—J˜—š žœžœžœžœ‘+˜PJšœžœžœ ˜.˜$J˜J˜AJ˜—Jšžœ*žœ*˜ZJšžœ˜ —Jšžœ ˜Jšœ ˜—š   œžœ žœ žœžœ˜BJ™^J™’Jšœžœ˜Jšœžœžœ˜$Jšœžœ žœ˜/J˜,šžœ ž˜Jšœžœ˜ šžœžœžœ ž˜šžœ2‘˜Pšžœ‘"˜5J˜FJšœ%žœžœ‘˜AJšœ žœ˜J˜—šžœžœ'‘˜Hšžœ ‘&˜7š žœžœž œžœžœ˜/J˜Jšžœ˜—J˜:Jšœ%žœžœ‘˜AJšœ žœ˜J˜—Jš žœžœžœžœžœ žœ‘ ˜F——Jšžœ˜—š žœžœ žœ žœ‘"˜FJšžœK˜Q—Jšžœ˜ —Jšžœžœ˜*Jšžœ ˜J˜J˜——˜J˜—Jšžœ˜—…— ²n±