DIRECTORY Atom, Basics, Convert, G3dBasic, G3dMatrix, G3dRender, G3dScanConvert, G3dClipXfmShade, G3dShape, G3dSortandDisplay, G3dSpline, G3dVector, Real, RealFns, Rope; G3dBezierFromPolyProcs: CEDAR MONITOR IMPORTS Atom, Basics, Convert, G3dMatrix, G3dRender, G3dClipXfmShade, G3dSortandDisplay, G3dSpline, G3dVector, Real, RealFns, Rope = BEGIN ROPE: TYPE ~ Rope.ROPE; NatSequence: TYPE ~ G3dRender.NatSequence; NatSequenceRep: TYPE ~ G3dRender.NatSequenceRep; Pair: TYPE ~ G3dRender.Pair; Triple: TYPE ~ G3dRender.Triple; TripleSequence: TYPE ~ G3dRender.TripleSequence; TripleSequenceRep: TYPE ~ G3dBasic.TripleSequenceRep; RGB: TYPE ~ G3dRender.RGB; RealSequence: TYPE ~ G3dRender.RealSequence; RealSequenceRep: TYPE ~ G3dBasic.RealSequenceRep; Context: TYPE ~ G3dRender.Context; SixSides: TYPE ~ G3dRender.SixSides; Matrix: TYPE ~ G3dRender.Matrix; Shape: TYPE ~ G3dRender.Shape; Patch: TYPE ~ G3dRender.Patch; PatchSequence: TYPE ~ G3dRender.PatchSequence; PatchSequenceRep: TYPE ~ G3dRender.PatchSequenceRep; PatchProc: TYPE ~ G3dRender.PatchProc; Vertex: TYPE ~ G3dShape.Vertex; CtlPoint: TYPE ~ G3dRender.CtlPoint; CtlPtInfo: TYPE ~ G3dRender.CtlPtInfo; CtlPtInfoSequence: TYPE ~ G3dRender.CtlPtInfoSequence; CtlPtInfoProc: TYPE ~ G3dRender.CtlPtInfoProc; Shading: TYPE ~ G3dRender.Shading; RenderData: TYPE ~ G3dRender.RenderData; RenderStyle: TYPE ~ G3dRender.RenderStyle; 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 CtlPtInfo] RETURNS[REF CtlPtInfo]; nullTriple: Triple ~ [0.0, 0.0, 0.0]; Corner: TYPE ~ RECORD [ inVtx, outVtx, ptr: NAT _ 0, inDir, outDir, normal, interiorKnot, interiorKnot2: Triple _ nullTriple, double: 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; TangentSeqSeq: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF REF TangentSeq]; Triangle: TYPE ~ RECORD [ v: ARRAY[0..3) OF CtlPtInfo, t: ARRAY[0..3) OF TangentSet ]; NatSequenceSequence: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF NatSequence ]; Add: PROC[v1, v2: Triple] RETURNS[Triple] ~ G3dVector.Add; Cross: PROC[v1, v2: Triple] RETURNS[Triple] ~ G3dVector.Cross; 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; Normalize: PROC[v: Triple] RETURNS[Triple] ~ G3dVector.Normalize; Sub3: PROC[v1, v2: Triple] RETURNS[Triple] ~ G3dVector.Sub; ReleasePatch: PROC [p: REF Patch] ~ G3dClipXfmShade.ReleasePatch; GetPatch: PROC [size: NAT] RETURNS [REF Patch] ~ G3dClipXfmShade.GetPatch; 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; minCosToAlignOpen: REAL _ 0.707; -- Align open edges if they meet at > 45 degrees cosMaxDihedral: REAL _ 0.707; -- max dihedral angle between control surfaces unitizeNormals: BOOLEAN _ FALSE; -- treat all polygons equally when making vertex normal fixDihedrals: BOOLEAN _ TRUE; -- allows dihedral fixing to be turned off ref1third: REF REAL _ NEW[REAL _ 1.0/3.0]; ref2thirds: REF REAL _ NEW[REAL _ 2.0/3.0]; refOne: REF REAL _ NEW[REAL _ 1.0]; refMinusOne: REF REAL _ NEW[REAL _ -1.0]; 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: CtlPoint, 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]]]; }; ExtendCornerSeq: PROC[corners: REF CornerSeq] RETURNS[newCorners: REF CornerSeq] ~ { newCorners _ NEW[CornerSeq[corners.maxLength+4]]; FOR i: NAT IN [0..corners.maxLength) DO newCorners[i] _ corners[i]; ENDLOOP; newCorners.length _ corners.length; }; ShowCorner: PROC[corner: REF CornerSeq, keys: LIST OF ROPE] RETURNS[ROPE] ~ { AddNat: PROC[number: NAT] ~ { msg _ Rope.Cat[msg, ", ", Convert.RopeFromInt[number] ]; }; AddTriple: PROC[trio: Triple] ~ { msg _ Rope.Cat[ msg, ", ", Convert.RopeFromReal[Real.Fix[trio.x*100.0] / 100.0 ] ]; msg _ Rope.Cat[ msg, ", ", Convert.RopeFromReal[ Real.Fix[trio.y*100.0] / 100.0 ], ", ", Convert.RopeFromReal[ Real.Fix[trio.z*100.0] / 100.0 ] ]; }; msg: ROPE; FOR i: NAT IN [0..corner.length) DO FOR list: LIST OF ROPE _ keys, list.rest UNTIL list = NIL DO SELECT TRUE FROM Rope.Equal[list.first, "inVtx", FALSE] => AddNat[corner[i].inVtx]; Rope.Equal[list.first, "outVtx", FALSE] => AddNat[corner[i].outVtx]; Rope.Equal[list.first, "ptr", FALSE] => AddNat[corner[i].ptr]; Rope.Equal[list.first, "inDir", FALSE] => AddTriple[corner[i].inDir]; Rope.Equal[list.first, "outDir", FALSE] => AddTriple[corner[i].outDir]; Rope.Equal[list.first, "normal", FALSE] => AddTriple[corner[i].normal]; Rope.Equal[list.first, "interiorKnot", FALSE] => AddTriple[corner[i].interiorKnot]; Rope.Equal[list.first, "interiorKnot2", FALSE] => AddTriple[corner[i].interiorKnot2]; Rope.Equal[list.first, "double", FALSE] => msg _ Rope.Cat[ msg, ", ", IF corner[i].double THEN "TRUE" ELSE "FALSE" ]; ENDCASE; ENDLOOP; msg _ Rope.Concat[msg, "\n"]; ENDLOOP; RETURN[ msg ]; }; ShowCoords: PROC[poly: REF Patch, space: ATOM _ $Screen] RETURNS[list: LIST OF REF Pair] ~ { FOR i: CARD16 DECREASING IN [0..poly.nVtces) DO p: REF Pair; SELECT space FROM $Screen => p _ NEW[ Pair _ [ poly[i].coord.sx, poly[i].coord.sy ] ]; $Eye => p _ NEW[ Pair _ [ poly[i].coord.ex, poly[i].coord.ey ] ]; $Object => p _ NEW[ Pair _ [ poly[i].coord.x, poly[i].coord.y ] ]; ENDCASE; list _ CONS[p, list]; ENDLOOP; RETURN[list]; }; GetSlopeVec: PROC[normal, edge: Triple, hermite: BOOL _ FALSE] RETURNS[slope: Triple] ~ { slope _ Cross[ Cross[normal, edge], normal ]; slope _ ScaleTangent[ slope, edge ]; }; 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 ] ]; }; PatchSort: PROC[ context: Context, p: PatchSequence, c: REF CornerSeqSeq _ NIL] RETURNS[PatchSequence, REF CornerSeqSeq] ~ { z: RealSequence _ NEW[RealSequenceRep[p.length]]; pOut: PatchSequence _ NEW [PatchSequenceRep[p.length]]; cOut: REF CornerSeqSeq _ NEW [CornerSeqSeq[p.length]]; FOR i: NAT IN [0..p.length) DO 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]; cOut[i] _ c[i]; ENDLOOP; FOR i: NAT IN [1..p.length) DO FOR j: NAT DECREASING IN [0..i) DO -- sort to increasing depth order IF z[j+1] < z[j] THEN { t: REAL _ z[j+1]; p: REF Patch _ pOut[j+1]; c: REF CornerSeq _ cOut[j+1]; z[j+1] _ z[j]; pOut[j+1] _ pOut[j]; cOut[j+1] _ cOut[j]; z[j] _ t; pOut[j] _ p; cOut[j] _ c; }; 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]; tmpC: REF CornerSeq _ cOut[i]; pOut[i] _ pOut[j]; cOut[i] _ cOut[j]; pOut[j] _ tmpP; cOut[j] _ tmpC; ENDLOOP; pOut.length _ p.length; RETURN[pOut, cOut]; }; Intersection: PROC[pos1, pos2, dir1, dir2, normal: Triple] RETURNS [pt: Triple] ~ { Intersect2d: PROC[ pos1, dir1, pos2, dir2: Pair ] RETURNS[ pos: Pair ]~ { l1: Triple _ G3dVector.Line2d[ pos1, [pos1.x + dir1.x, pos1.y + dir1.y] ]; l2: Triple _ G3dVector.Line2d[ pos2, [pos2.x + dir2.x, pos2.y + dir2.y] ]; pos _ G3dVector.IntersectTwoLines2d[l1, l2]; }; n: Triple _ normal; pr: Pair; SELECT TRUE FROM ABS[n.x] >= ABS[n.y] AND ABS[n.x] >= ABS[n.z] => { pr _ Intersect2d[ [pos1.y, pos1.z], [dir1.y, dir1.z], [pos2.y, pos2.z], [dir2.y, dir2.z] ]; pt _ [ -(n.y*pr.x + n.z*pr.y)/n.x, pr.x, pr.y ]; }; ABS[n.y] >= ABS[n.z] AND ABS[n.y] >= ABS[n.x] => { pr _ Intersect2d[ [pos1.z, pos1.x], [dir1.z, dir1.x], [pos2.z, pos2.x], [dir2.z, dir2.x] ]; pt _ [ pr.y, -(n.z*pr.x + n.x*pr.y)/n.y, pr.x ]; }; ABS[n.z] >= ABS[n.x] AND ABS[n.z] >= ABS[n.y] => { pr _ Intersect2d[ [pos1.x, pos1.y], [dir1.x, dir1.y], [pos2.x, pos2.y], [dir2.x, dir2.y] ]; pt _ [ pr.x, pr.y, -(n.x*pr.x + n.y*pr.y)/n.z ]; }; ENDCASE; }; DisplayPatchEdges: PROC[context: Context, patch: REF Patch, corner: REF CornerSeq] ~ { shape: Shape _ NARROW[GetProp[patch.props, $Shape]]; xfm: Matrix _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; clr: RGB _ patch.renderData.shadingClass.color; npts: NAT _ 8; FOR i: NAT IN [0..patch.nVtces) DO j: NAT _ (i+1) MOD patch.nVtces; outP: REF Patch _ GetPatch[npts]; outState: OutCode _ AllOut; inState: OutCode _ NoneOut; coeffs: G3dSpline.Coeffs _ GetEdgeCurveBez[ -- vtx1, vtx2, outdir1, indir2 patch[i], patch[j], corner[i], corner[j] ]; 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] _ G3dClipXfmShade.XfmPtToEyeSpace[context, [x, y, z], xfm]; clip _ G3dClipXfmShade.GetClipCodeForPt[ context, [ex, ey, ez] ]; IF clip = NoneOut THEN [[sx, sy, sz]] _ G3dClipXfmShade.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.renderData _ patch.renderData; 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]; ReleasePatch[outP]; ENDLOOP; }; InitClasses: PROC[] ~ { -- register procedures for basic surface types standardClass: ShapeClass _ G3dRender.GetShapeClass[$ConvexPolygon]; standardClass.type _ $PolygonToBezier; standardClass.validate _ ValidatePolyToBezier; standardClass.displayPatch _ DisplayPatchBezier; G3dRender.RegisterShapeClass[standardClass, $PolygonToBezier]; }; DisplayPatchBezier: PatchProc ~ { patchNo: NAT _ NARROW[ Atom.GetPropFromList[patch.props, $PatchNo], REF NAT ]^; corners: REF CornerSeqSeq _ NARROW[ GetProp[patch.renderData.props, $PatchCorners] ]; patch.type _ $PolygonToBezier; ConvertPolygonToBezier[context, patch, corners[patchNo] ]; -- convert to patch, display RETURN[ NIL ]; }; ValidatePolyToBezier: ShapeProc ~ { shape _ G3dSortandDisplay.ValidatePolyhedron[context, shape]; -- Do shading and matrices IF GetProp[G3dRender.RenderDataFrom[shape].props, $PatchCorners] = NIL THEN shape _ GetTangents[context, shape]; -- calculate tangent vectors if not read in RETURN[ shape ]; }; GetEdgeCurveBez: PROC[p1, p2: CtlPtInfo, c1, c2: Corner] RETURNS[coeffs: G3dSpline.Coeffs] ~ { b0, b1, b2, b3: Triple; [b0, b1, b2, b3] _ CrnrsToBezKnots[ p1, p2, c1, c2 ]; coeffs _ G3dSpline.CoeffsFromBezier[ [b0, b1, b2, b3] ]; }; CrnrsToBezKnots: PROC[p1, p2: CtlPtInfo, c1, c2: Corner] 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, c1.outDir ]; b2 _ Add[ b3, c2.inDir ]; }; XfmAndClip: PROC[context: Context, patch: REF Patch] ~ { shape: Shape _ NARROW[ Atom.GetPropFromList[patch.props, $Shape] ]; xfm: Matrix _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; FOR i: NAT IN [0..patch.nVtces) DO OPEN patch[i].coord; [[ ex, ey, ez]] _ G3dMatrix.Transform[ [x, y, z], xfm ]; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; ENDLOOP; }; ConvertPolygonToBezier: PROC[ context: Context, patch: REF Patch, corner: REF CornerSeq ] ~ { renderStyle: RenderStyle; IF corner.length > 4 THEN { SliceUpBezier[ context, patch, corner ]; RETURN[]; }; WITH patch.renderData.shadingClass.renderMethod SELECT FROM style: REF RenderStyle => { renderStyle _ style^; SELECT renderStyle FROM lines => { [] _ DisplayPatchEdges[context, patch, corner]; RETURN[]; }; ENDCASE; }; ENDCASE; IF patch.nVtces = 3 THEN DisplayTriangleAsBezier[ context, patch, corner ] ELSE DisplayQuadrilateralAsBezier[ context, patch, corner ]; }; SliceUpBezier: PROC[ context: Context, patch: REF Patch, corners: REF CornerSeq ] ~ { outPatch: PatchSequence _ NEW [PatchSequenceRep[(corners.length)/2]]; -- upper bound outCorner: REF CornerSeqSeq _ NEW [CornerSeqSeq[(corners.length)/2]]; lVtx: NAT _ patch.nVtces-1; newStart: LIST OF NAT _ LIST[0]; WHILE newStart # NIL DO cVtx, startVtx, lVtx, cCrnr, vtxCnt: NAT; onPath: BOOLEAN _ TRUE; pCnt: NAT _ outPatch.length; outPatch[pCnt] _ GetPatch[4]; -- released by display action outCorner[pCnt] _ NEW[CornerSeq[4]]; outPatch[pCnt].type _ patch.type; outPatch[pCnt].oneSided _ patch.oneSided; outPatch[pCnt].clipState _ patch.clipState; outPatch[pCnt].dir _ unknown; outPatch[pCnt].props _ patch.props; cCrnr _ newStart.first; newStart _ newStart.rest; startVtx _ cVtx _ corners[cCrnr].ptr; lVtx _ corners[cCrnr].inVtx; vtxCnt _ 0; WHILE TRUE DO IF corners[cCrnr].ptr # cVtx OR corners[cCrnr].inVtx # lVtx THEN { IF onPath THEN newStart _ CONS[cCrnr, newStart]; -- not part of current chain onPath _ FALSE; cCrnr _ cCrnr + 1; -- stack for later processing } ELSE { outPatch[pCnt][vtxCnt] _ patch[cVtx]; outCorner[pCnt][vtxCnt] _ corners[ cCrnr ]; vtxCnt _ vtxCnt + 1; onPath _ TRUE; lVtx _ cVtx; cVtx _ corners[cCrnr].outVtx; -- step to next vertex cCrnr _ cCrnr + 1; -- step to next corner IF cVtx = startVtx THEN EXIT; }; ENDLOOP; outPatch[pCnt].nVtces _ vtxCnt; outCorner[pCnt].length _ vtxCnt; outPatch.length _ outPatch.length + 1; ENDLOOP; [outPatch, outCorner] _ PatchSort[context, outPatch, outCorner]; -- sort to depth order FOR i: NAT IN [0..outPatch.length) DO ConvertPolygonToBezier[ context, outPatch[i], outCorner[i] ]; -- convert to patch, display ReleasePatch[outPatch[i]]; ENDLOOP; }; DisplayTriangleWithTangents: PROC[ context: Context, patch: REF Patch, corner: REF CornerSeq ] ~ { tanClass: ShapeClass; tangents: REF TangentSeq _ NEW[TangentSeq[3]]; shape: Shape _ NARROW[ GetProp[patch.props, $Shape] ]; xfm: Matrix _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; FOR i: NAT IN [0..3) DO tangents[i].t0 _ corner[i].outDir; tangents[i].t1 _ corner[(i+1) MOD 3].inDir; [ tangents[i].et0 ] _ G3dMatrix.TransformVec[ tangents[i].t0, xfm]; [ tangents[i].et1 ] _ G3dMatrix.TransformVec[ tangents[i].t1, xfm]; ENDLOOP; XfmAndClip[context, patch]; -- transform to eyespace and get clip state tanClass _ G3dRender.GetShapeClass[ $PolygonWithTangents ]; patch _ tanClass.displayPatch[ context, patch, tangents ]; }; DisplayTriangleAsBezier: PROC[ context: Context, patch: REF Patch, corner: REF CornerSeq ] ~ { doubleCornerFound: BOOLEAN _ FALSE; p: REF Patch _ GetPatch[4]; c: REF CornerSeq _ NEW[CornerSeq[4]]; cVtx: NAT _ 0; FOR i: NAT IN [0..3) DO IF NOT corner[i].double AND (i < 2 OR doubleCornerFound) THEN { p[cVtx] _ patch[i]; c[cVtx] _ corner[i]; cVtx _ cVtx+1; } ELSE { edge: Triple _ Sub3[ corner[i].outDir, corner[i].inDir ]; IF i = 2 AND NOT corner[i].double THEN SIGNAL G3dRender.Error[$MisMatch, "triangle without doubled vertex"]; p[cVtx+1] _ p[cVtx] _ patch[i]; c[cVtx+1] _ c[cVtx] _ corner[i]; c[cVtx].outVtx _ LAST[NAT]; c[cVtx+1].inVtx _ LAST[NAT]; c[cVtx].outDir _ [0.0, 0.0, 0.0]; c[cVtx+1].inDir _ [0.0, 0.0, 0.0]; c[cVtx].double _ FALSE; c[cVtx+1].double _ FALSE; c[cVtx+1].interiorKnot _ c[cVtx].interiorKnot2; cVtx _ cVtx+2; IF NOT doubleCornerFound THEN doubleCornerFound _ TRUE ELSE SIGNAL G3dRender.Error[$MisMatch, "extra doubled vertex"]; }; ENDLOOP; p.type _ patch.type; p.oneSided _ patch.oneSided; p.nVtces _ 4; p.clipState _ patch.clipState; p.dir _ unknown; p.renderData _ patch.renderData; p.props _ patch.props; DisplayQuadrilateralAsBezier[ context, p, c ]; ReleasePatch[p]; }; DisplayQuadrilateralAsBezier: PROC[ context: Context, patch: REF Patch, corner: REF CornerSeq ] ~ { b: REF Patch _ GetPatch[16]; bezClass: ShapeClass; FOR i: NAT IN [0..4) DO IF corner[i].double THEN SIGNAL G3dRender.Error[$MisMatch, "extra doubled vertex"]; ENDLOOP; [b[0], b[4], b[8], b[12]] _ BoundaryRow[ context, patch[0], patch[1], corner[0], corner[1] ]; [b[12],b[13],b[14],b[15]] _ BoundaryRow[ context, patch[1], patch[2], corner[1], corner[2] ]; [b[15],b[11],b[7], b[3]] _ BoundaryRow[ context, patch[2], patch[3], corner[2], corner[3] ]; [b[3], b[2], b[1], b[0]] _ BoundaryRow[ context, patch[3], patch[0], corner[3], corner[0] ]; b[5] _ InteriorPt[ context, b[0], b[1], b[4], corner[0] ]; b[9] _ InteriorPt[ context, b[12], b[13], b[8], corner[1] ]; b[10] _ InteriorPt[ context, b[15], b[14], b[11], corner[2] ]; b[6] _ InteriorPt[ context, b[3], b[2], b[7], corner[3] ]; b.type _ $Bezier; b.oneSided _ patch.oneSided; b.nVtces _ 16; b.clipState _ patch.clipState; b.dir _ unknown; b.renderData _ patch.renderData; b.props _ patch.props; XfmAndClip[context, b]; -- transform to eyespace and get clip state bezClass _ G3dRender.GetShapeClass[ $Bezier ]; b _ bezClass.displayPatch[ context, b ]; ReleasePatch[b]; }; BoundaryRow: PROC[ context: Context, p1, p2: CtlPtInfo, c1, c2: Corner ] RETURNS[v0, v1, v2, v3: CtlPtInfo] ~ { InnerShades: PROC[ s0, s3: Shading ] RETURNS[ s1, s2: Shading ] ~ { s1.r _ (2*s0.r + s3.r)/3; s1.g _ (2*s0.g + s3.g)/3; s1.b _ (2*s0.b + s3.b)/3; s2.r _ (s0.r + 2*s3.r)/3; s2.g _ (s0.g + 2*s3.g)/3; s2.b _ (s0.b + 2*s3.b)/3; s1.t _ (2*s0.t + s3.t)/3; s2.t _ (s0.t + 2*s3.t)/3; s1.txtrX _ (2*s0.txtrX + s3.txtrX)/3; s2.txtrX _ (s0.txtrX + 2*s3.txtrX)/3; s1.txtrY _ (2*s0.txtrY + s3.txtrY)/3; s2.txtrY _ (s0.txtrY + 2*s3.txtrY)/3; }; b0, b1, b2, b3: Triple; [b0, b1, b2, b3] _ CrnrsToBezKnots[ p1, p2, c1, c2 ]; v0 _ p1; v3 _ p2; v1.coord.x _ b1.x; v1.coord.y _ b1.y; v1.coord.z _ b1.z; v2.coord.x _ b2.x; v2.coord.y _ b2.y; v2.coord.z _ b2.z; [v1.shade, v2.shade] _ InnerShades[v0.shade, v3.shade]; v1.data _ v2.data _ p1.data; }; InteriorPt: PROC[ context: Context, cornerPt, adj1, adj2: CtlPtInfo, corner: Corner ] RETURNS[ interiorPt: CtlPtInfo ] ~ { interiorPt.coord.x _ cornerPt.coord.x + corner.interiorKnot.x; interiorPt.coord.y _ cornerPt.coord.y + corner.interiorKnot.y; interiorPt.coord.z _ cornerPt.coord.z + corner.interiorKnot.z; interiorPt.shade.r _ adj1.shade.r + adj2.shade.r - cornerPt.shade.r; interiorPt.shade.g _ adj1.shade.g + adj2.shade.g - cornerPt.shade.g; interiorPt.shade.b _ adj1.shade.b + adj2.shade.b - cornerPt.shade.b; interiorPt.shade.t _ adj1.shade.t + adj2.shade.t - cornerPt.shade.t; interiorPt.shade.txtrX _ adj1.shade.txtrX + adj2.shade.txtrX - cornerPt.shade.txtrX; interiorPt.shade.txtrY _ adj1.shade.txtrY + adj2.shade.txtrY - cornerPt.shade.txtrY; interiorPt.data _ cornerPt.data; }; BuildBezierData: PROC[ shape: Shape, corners: REF CornerSeqSeq ] ~ { polyCorners: REF CornerSeqSeq _ NEW[ CornerSeqSeq[shape.surfaces.length] ]; renderData: REF RenderData _ G3dRender.RenderDataFrom[shape]; FOR i: NAT IN [0..corners.length) DO -- Add inner knots at corners IF corners[i] # NIL THEN BuildInteriorKnots[ corners[i] ]; ENDLOOP; FOR i: NAT IN [0..corners.length) DO IF corners[i] # NIL THEN -- zero for use in next loop FOR j: NAT IN [0..corners[i].length) DO corners[i][j].ptr _ 0; ENDLOOP; ENDLOOP; IF fixDihedrals THEN CheckDihedrals[ shape, corners ]; -- Fix mid-rectangle dihedral angle on each edge FOR i: NAT IN [0..shape.surfaces.length) DO IF shape.surfaces[i] # NIL THEN { vtxCnt: NAT _ 0; nVtces: NAT _ shape.surfaces[i].length; polyCorners[i] _ NEW[CornerSeq[nVtces+2]]; -- allow for one split in polygon FOR cVtx: NAT IN [0..nVtces) DO -- current vertex in polygon nextVtx: NAT _ (cVtx + 1) MOD nVtces; prevVtx: NAT _ (cVtx + nVtces-1) MOD nVtces; vtx: NAT _ shape.surfaces[i][cVtx]; -- current vertex in shape nVtx: NAT _ shape.surfaces[i][nextVtx]; -- at far end of edge lVtx: NAT _ shape.surfaces[i][prevVtx]; -- previous vertex FOR j: NAT IN [0..corners[vtx].length) DO IF corners[vtx][j].inVtx = lVtx THEN { -- is incoming edge from previous vertex? jc: NAT _ j; IF vtxCnt >= polyCorners[i].maxLength-2 THEN polyCorners[i] _ ExtendCornerSeq[polyCorners[i]]; polyCorners[i][vtxCnt] _ corners[vtx][jc]; -- copy corner polyCorners[i][vtxCnt].inVtx _ prevVtx; -- change to polygon vertex numbers polyCorners[i][vtxCnt].outVtx _ nextVtx; polyCorners[i][vtxCnt].ptr _ cVtx; -- change to shape vertex number vtxCnt _ vtxCnt + 1; IF corners[vtx][j].outVtx # nVtx THEN { -- must be at a sliced corner otherVtx: NAT _ corners[vtx][j].outVtx; jc _ (j + corners[vtx].length-1) MOD corners[vtx].length; FOR k: NAT IN [0..nVtces) DO IF otherVtx = shape.surfaces[i][k] THEN { otherVtx _ k; EXIT; }; ENDLOOP; polyCorners[i][vtxCnt] _ corners[vtx][jc]; polyCorners[i][vtxCnt].inVtx _ polyCorners[i][vtxCnt-1].outVtx _ otherVtx; polyCorners[i][vtxCnt].outVtx _ nextVtx; polyCorners[i][vtxCnt].ptr _ cVtx; vtxCnt _ vtxCnt + 1; }; IF corners[vtx][jc].outVtx # nVtx THEN SIGNAL G3dRender.Error[$MisMatch, "bad corner structure"]; EXIT; }; ENDLOOP; ENDLOOP; polyCorners[i].length _ vtxCnt; }; ENDLOOP; renderData.props _ PutProp[renderData.props, $PatchCorners, polyCorners]; }; CheckDihedrals: PROC[ shape: Shape, corners: REF CornerSeqSeq ] ~ { GetKnots: PROC[rCrnr, lCrnr: Corner, pos: Triple] RETURNS[left, mid, right: Triple] ~ { mid _ Add[rCrnr.outDir, pos]; right _ Add[ IF rCrnr.double THEN rCrnr.interiorKnot2 ELSE rCrnr.interiorKnot, pos ]; left _ Add[ lCrnr.interiorKnot, pos ]; }; FixKnots: PROC[ corner: REF CornerSeq, i: NAT, scale: REAL, clockWise: BOOL ] ~ { o: NAT _ IF clockWise THEN (i + 1) MOD corner.length -- next edge (to right) in order around vtx ELSE (i + corner.length-1) MOD corner.length; -- previous edge (to left) in order knot: Triple _ IF NOT clockWise OR NOT corner[i].double THEN corner[i].interiorKnot ELSE corner[i].interiorKnot2; midDir: Triple _ IF NOT clockWise THEN corner[i].inDir ELSE corner[i].outDir; dist: Triple _ Sub3[ knot, midDir ]; IF NOT ( corner[i].double -- not double corner or open edge OR (NOT clockWise AND corner[o].inVtx # corner[i].outVtx) OR (clockWise AND corner[o].outVtx # corner[i].inVtx) ) THEN { -- Scale outDir of next edge to keep line connecting knots straight nextKnot: Triple _ IF NOT clockWise OR NOT corner[o].double THEN corner[o].interiorKnot ELSE corner[o].interiorKnot2; sideDir: Triple _ IF NOT clockWise THEN corner[o].inDir ELSE corner[o].outDir; prtDist: Triple _ Sub3[ knot, sideDir ]; whlDist: Triple _ Sub3[ knot, nextKnot ]; ratio: REAL _ Length[prtDist] / Length[whlDist]; sideDir _ G3dVector.Mul[ sideDir, scale + (1.0 - scale) * ratio ]; IF NOT clockWise THEN corner[i].outDir _ corner[o].inDir _ sideDir -- update matching directions ELSE corner[i].inDir _ corner[o].outDir _ sideDir; }; knot _ Add[ G3dVector.Mul[dist, scale], midDir ]; IF NOT corner[i].double -- store modified knot position THEN corner[i].interiorKnot _ knot ELSE IF NOT clockWise THEN corner[i].interiorKnot _ knot ELSE corner[i].interiorKnot2 _ knot; }; FOR v: NAT IN [0..shape.vertices.length) DO IF corners[v] # NIL THEN {-- check each vtx. vtx: Vertex _ shape.vertices[v]; FOR i: NAT IN [0..corners[v].length) DO -- find inner knots for each outgoing edge left, leftNear, leftFar, mid, midNear, midFar, right, rightNear, rightFar: Triple; cosang: REAL; oppIndx: NAT; last: NAT _ (i + corners[v].length-1) MOD corners[v].length; oppVtx: NAT _ corners[v][i].outVtx; IF oppVtx # corners[v][last].inVtx THEN LOOP; -- skip if open edge IF corners[v][i].ptr = oppVtx THEN LOOP; -- skip if touched from other end [leftNear, midNear, rightNear] _ GetKnots[ -- get middle knots closest to this end corners[v][i], corners[v][last], vtx.point ]; IF leftNear = midNear OR rightNear = midNear THEN LOOP; -- ignore creases FOR j: NAT IN [0..corners[oppVtx].length) DO -- find other end of edge IF corners[oppVtx][j].outVtx = v THEN { last: NAT _ (j + corners[oppVtx].length-1) MOD corners[oppVtx].length; [rightFar, midFar, leftFar] _ GetKnots[ -- get knots at other end corners[oppVtx][j], corners[oppVtx][last], vtx.point ]; corners[oppVtx][j].ptr _ v; corners[v][i].ptr _ oppVtx; -- tag edge as touched oppIndx _ j; EXIT; }; ENDLOOP; IF leftFar = midFar OR rightFar = midFar THEN LOOP; -- ignore creases left _ G3dVector.Mul[ Add[leftNear, leftFar], 0.5 ]; mid _ G3dVector.Mul[ Add[midNear, midFar], 0.5 ]; right _ G3dVector.Mul[ Add[rightNear, rightFar], 0.5 ]; cosang _ Dot[ Normalize[ Sub3[mid, left] ], Normalize[ Sub3[right, mid] ] ]; IF cosang < .999 THEN { -- cosine of dihedral angle shows its not zero nearRatio: REAL _ -- ratio of edge lengths formed by far knots Length[Sub3[rightNear, midNear]] / Length[Sub3[midNear, leftNear]]; farRatio: REAL _ -- ratio of edge lengths formed by near knots Length[ Sub3[rightFar, midFar] ] / Length[ Sub3[midFar, leftFar] ]; targetRatio: REAL _ (nearRatio + farRatio)/ 2.0; -- try to adjust both to this ratio IF Sub[nearRatio, farRatio] # 0.0 THEN IF targetRatio > 1.0 -- not almost equal THEN { -- left side smaller index: NAT _ (i + corners[v].length-1) MOD corners[v].length; FixKnots[corners[v], index, nearRatio/targetRatio, FALSE]; -- near left knots FixKnots[corners[oppVtx], oppIndx, farRatio/targetRatio, TRUE]; -- far left } ELSE { -- right side smaller oppIndx _ (oppIndx + corners[oppVtx].length-1) MOD corners[oppVtx].length; FixKnots[corners[v], i, targetRatio/nearRatio, TRUE]; -- near right knots FixKnots[corners[oppVtx], oppIndx, targetRatio/farRatio, FALSE]; -- far right }; }; ENDLOOP; }; ENDLOOP; }; BuildInteriorKnots: PROC[ corner: REF CornerSeq ] ~ { FOR i: NAT IN [0..corner.length) DO cosAng: REAL _ Dot[corner[i].outDir, corner[i].inDir]; -- dot product cosAng _ cosAng / Length[corner[i].outDir]; -- normalize cosAng _ cosAng / Length[corner[i].inDir]; -- and you get cosine SELECT TRUE FROM cosAng > 0.99 => { -- same direction, set everything to the same point corner[i].interiorKnot _ corner[i].inDir _ corner[i].outDir _ G3dVector.Mul[ Add[ corner[i].inDir, corner[i].outDir ], 0.5 ]; corner[(i + corner.length-1) MOD corner.length].inDir _ corner[i].outDir; -- prev. corner[(i + 1) MOD corner.length].outDir _ corner[i].inDir; -- next IF corner[i].double THEN corner[i].interiorKnot2 _ corner[i].interiorKnot; }; cosAng < -0.99 => IF corner[i].double -- opposite directions THEN { j: NAT _ (i+1) MOD corner.length; WHILE corner[j].inDir = corner[i].inDir DO -- search clockwise for another dir. j _ (j+1) MOD corner.length; -- should go to right ENDLOOP; corner[i].interiorKnot _ Sub3[ -- subtract to get interior knots corner[i].inDir, G3dVector.Mul[corner[j].inDir, 0.5] ]; corner[i].interiorKnot2 _ Sub3[ corner[i].outDir, G3dVector.Mul[corner[j].inDir, 0.5] ]; } ELSE SIGNAL G3dRender.Error[$MisMatch, "corner should be double"]; ENDCASE => IF corner[i].double -- other angles THEN { corner[i].interiorKnot2 _ corner[i].interiorKnot _ Add[ corner[i].inDir, corner[i].outDir ]; } ELSE corner[i].interiorKnot _ Add[ -- sum directions corner[i].inDir, corner[i].outDir ]; ENDLOOP; }; GetTangents: ShapeProc ~ { corners: REF CornerSeqSeq; corners _ GetNormalsAndDirections[context, shape]; -- Get vertex normals, edge directions ForceFourCorners[corners, shape]; FOR i: NAT IN [0..corners.length) DO -- sort corners to clockwise order by matching edges IF corners[i] # NIL THEN corners[i] _ SortCtlPoint[ corners[i] ]; ENDLOOP; FOR i: NAT IN [0..corners.length) DO nCorners: NAT _ IF corners[i] # NIL THEN corners[i].length ELSE 0; FOR this: NAT IN [0..nCorners) DO last: NAT _ (this + nCorners-1) MOD nCorners; corners[i][this].outDir _ GetSlopeVec[ corners[i][this].normal, corners[i][this].outDir ]; IF corners[i][last].inVtx = corners[i][this].outVtx -- matches previous corner THEN corners[i][last].inDir _ corners[i][this].outDir -- copy vector ELSE corners[i][last].inDir _ GetSlopeVec[ -- open edge, compute vector corners[i][last].normal, corners[i][last].inDir ]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..corners.length) DO IF corners[i] # NIL THEN corners[i] _ FindContinuousEdges[ shape, i, corners[i] ]; ENDLOOP; BuildBezierData[shape, corners]; -- convert patches to Bezier form RETURN[shape]; }; GetNormalsAndDirections: PROC[context: Context, shape: Shape] RETURNS[REF CornerSeqSeq] ~ { corners: REF CornerSeqSeq _ NEW[ CornerSeqSeq[shape.vertices.length] ]; angles: RealSequence _ NEW[RealSequenceRep[8]]; IF NOT shape.vertices.valid.normal THEN FOR i: NAT IN [0..shape.vertices.length) DO IF shape.vertices[i] # NIL THEN shape.vertices[i].normal _ nullTriple; -- Clear vtx nmls ENDLOOP; FOR i: NAT IN [0..shape.surfaces.length) DO IF shape.surfaces[i] # NIL THEN { nVtces: NAT _ shape.surfaces[i].length; -- do for all polys smallest: NAT _ LAST[NAT]; -- smallest angle in triangle (default value harmless) IF angles.maxLength < nVtces THEN angles _ NEW[RealSequenceRep[nVtces]]; IF NOT shape.vertices.valid.normal THEN GetPolyNormals[shape, shape.surfaces[i]]; IF nVtces # 4 THEN { smallestSize: REAL; -- Find smallestSize angle within non-quadrilateral FOR cVtx: NAT IN [0..nVtces) DO vtx: NAT _ shape.surfaces[i][cVtx]; nVtx: NAT _ shape.surfaces[i][(cVtx + 1) MOD nVtces]; lVtx: NAT _ shape.surfaces[i][(cVtx + nVtces - 1) MOD nVtces]; angles[cVtx] _ GetAngle[ shape, vtx, lVtx, nVtx ]; IF angles[cVtx] < smallestSize OR cVtx = 0 THEN { smallestSize _ angles[cVtx]; smallest _ cVtx; }; ENDLOOP; }; FOR cVtx: NAT IN [0..nVtces) DO -- get direction vectors for each edge at vtx. vtx: NAT _ shape.surfaces[i][cVtx]; nVtx: NAT _ shape.surfaces[i][(cVtx + 1) MOD nVtces]; lVtx: NAT _ shape.surfaces[i][(cVtx + nVtces - 1) MOD nVtces]; corners[vtx] _ UpdateCornerVecSeq[ corners[vtx], [ inVtx: lVtx, outVtx: nVtx, -- store number of incoming vertex and outgoing vertex ptr: i, -- store polygon number, in case needed inDir: G3dVector.Sub[ shape.vertices[lVtx].point, shape.vertices[vtx].point ], -- dir. to incoming vertex outDir: G3dVector.Sub[ shape.vertices[nVtx].point, shape.vertices[vtx].point ], -- outgoing direction double: IF cVtx = smallest THEN TRUE ELSE FALSE -- doubled (degenerate edge) ] ]; ENDLOOP; IF nVtces > 4 THEN SplitBigPoly[shape, i, corners, angles]; }; ENDLOOP; corners.length _ shape.vertices.length; FOR i: NAT IN [0..shape.vertices.length) DO -- normalize new vertex normals, store in corners IF shape.vertices[i] # NIL THEN { OPEN shape.vertices[i]; IF Length[ normal ] > shape.sphereExtent.radius * .0001 THEN normal _ Normalize[ normal ] ELSE normal _ 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 _ normal; ENDLOOP; }; ENDLOOP; RETURN[ corners ]; }; GetAngle: PROC[ shape: Shape, vtx, lVtx, nVtx: NAT] RETURNS[REAL] ~ { e1: Triple _ Normalize[G3dVector.Sub[shape.vertices[lVtx].point, shape.vertices[vtx].point]]; e2: Triple _Normalize[G3dVector.Sub[shape.vertices[nVtx].point, shape.vertices[vtx].point]]; angle: REAL _ Length[ Cross[e1, e2] ]; -- sine dot: REAL _ Dot[e1, e2]; -- cosine angle IF dot < 0 THEN angle _ 1.0 + (1.0 - angle); -- angle 0-180 => 0.0-2.0 RETURN[angle]; }; SplitBigPoly: PROC[ shape: Shape, polyNo: NAT, corners: REF CornerSeqSeq, angles: RealSequence ] ~ { poly: REF Patch _ G3dRender.PatchesFrom[shape][polyNo]; nVtces: NAT _ poly.nVtces; largest: REAL _ 0.0; vtx, lVtx, nVtx, oVtx, increment, remainingVtces: NAT; FOR i: NAT IN [0..nVtces) DO -- find largest angle for split point IF angles[i] > largest THEN { largest _ angles[i]; vtx _ i; }; ENDLOOP; nVtx _ (vtx + 1) MOD nVtces; lVtx _ (vtx + nVtces - 1) MOD nVtces; increment _ IF nVtces > 5 THEN 2 ELSE 1; oVtx _ IF angles[nVtx] < angles[lVtx] -- get smaller adjacent angle THEN (nVtx + increment) MOD nVtces ELSE (lVtx + nVtces - increment) MOD nVtces; InsertEdge[shape, polyNo, corners, vtx, oVtx]; InsertEdge[shape, polyNo, corners, oVtx, vtx]; remainingVtces _ nVtces - increment; WHILE remainingVtces > 4 DO remainingVtces _ remainingVtces - increment; ENDLOOP; poly.props _ PutProp[poly.props, $Split, $ItIs]; }; InsertEdge: PROC[ shape: Shape, polyNo: NAT, corners: REF CornerSeqSeq, cVtx, oVtx: NAT ] ~ { poly: NatSequence _ shape.surfaces[polyNo]; nVtces: NAT _ poly.length; vtx: NAT _ poly[cVtx]; oppVtx: NAT _ poly[oVtx]; nVtx: NAT _ poly[(cVtx + 1) MOD nVtces]; lVtx: NAT _ poly[(cVtx + nVtces - 1) MOD nVtces]; FOR i: NAT IN [0..corners[vtx].length) DO IF corners[vtx][i].outVtx = nVtx AND corners[vtx][i].inVtx = lVtx THEN { corners[vtx] _ UpdateCornerVecSeq[ -- add new vtx to end of sequence corners[vtx], [ inVtx: lVtx, outVtx: oppVtx, ptr: corners[vtx][i].ptr, normal: corners[vtx][i].normal, inDir: G3dVector.Sub[ -- dir to incoming Vtx shape.vertices[lVtx].point, shape.vertices[vtx].point ], outDir: G3dVector.Sub[ -- outgoing direction shape.vertices[oppVtx].point, shape.vertices[vtx].point ] ] ]; corners[vtx][i].inVtx _ oppVtx; -- fix up vtx already in sequence corners[vtx][i].inDir _ G3dVector.Sub[ shape.vertices[oppVtx].point, shape.vertices[vtx].point ]; EXIT; }; ENDLOOP; IF (cVtx + 2) MOD nVtces = oVtx THEN FOR i: NAT IN [0..corners[nVtx].length) DO IF corners[nVtx][i].outVtx = oppVtx AND corners[nVtx][i].inVtx = vtx THEN { corners[nVtx][i].double _ TRUE; EXIT; }; ENDLOOP; }; GetPolyNormals: PROC[ shape: Shape, poly: NatSequence ] ~ { normal: Triple _ nullTriple; nVtces: NAT _ poly.length; cNmls: TripleSequence _ NEW[TripleSequenceRep[nVtces]]; FOR cVtx: NAT IN [0..nVtces) DO -- get normal for each corner vtx: NAT _ poly[cVtx]; nVtx: NAT _ poly[(cVtx + 1) MOD nVtces]; lVtx: NAT _ poly[(cVtx + nVtces - 1) MOD nVtces]; cNmls[cVtx] _ Cross[ -- in object space so do right-handed G3dVector.Sub[ shape.vertices[lVtx].point, shape.vertices[vtx].point ], G3dVector.Sub[ shape.vertices[nVtx].point, shape.vertices[vtx].point ] ]; IF unitizeNormals THEN cNmls[cVtx] _ Normalize[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 { cNmls[cVtx] _ Negate[ cNmls[cVtx] ]; normal _ Add[ normal, cNmls[cVtx] ]; -- cancel 1st entry normal _ Add[ normal, cNmls[cVtx] ]; -- contribute to sum }; ENDLOOP; FOR cVtx: NAT IN [0..nVtces) DO -- Store robust vertex normals vtx: NAT _ poly[cVtx]; IF shape.vertices[vtx] # NIL THEN shape.vertices[vtx].normal _ Add[shape.vertices[vtx].normal, cNmls[cVtx]]; ENDLOOP; }; 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; newSeq.length _ corner.length; } ELSE newSeq _ corner; -- no changes required corner _ newSeq; corner[corner.length] _ entry; corner.length _ corner.length + 1; RETURN[corner]; }; ForceFourCorners: PROC[ corners: REF CornerSeqSeq, shape: Shape ] ~ { patch: PatchSequence _ G3dRender.PatchesFrom[shape]; FOR i: NAT IN [0..corners.length) DO IF corners[i] # NIL AND corners[i].length = 3 THEN { ok: BOOLEAN _ FALSE; FOR j: NAT IN [0..3) DO -- if a double edge included then all is ok IF corners[i][j].double = TRUE THEN { ok _ TRUE; EXIT; }; ENDLOOP; IF NOT ok THEN { -- better split a polygon cVtx, oppVtx: NAT _ LAST[NAT]; -- this vertex, opposing vertex to split to smallestCorner, vertexNo, oppVtxNo, polyToSplit: NAT _ LAST[NAT]; FOR j: NAT IN [0..3) DO k: NAT _ corners[i][j].ptr; -- find a participating polygon IF patch[k].nVtces > 3 AND GetProp[patch[k].props, $Split] = NIL THEN { nVtces: NAT _ patch[k].nVtces; -- only triangles have doubled vertices FOR v: NAT IN [0..nVtces) DO IF patch[k][v].vtxPtr = i THEN { cVtx _ v; oppVtx _ (v+2) MOD nVtces; -- get opposing vtx. EXIT; }; ENDLOOP; IF corners[patch[k][oppVtx].vtxPtr].length = 3 -- find a 3-poly vertex THEN { InsertEdge[shape, k, corners, cVtx, oppVtx]; InsertEdge[shape, k, corners, oppVtx, cVtx]; patch[k].props _ PutProp[patch[k].props, $Split, $ItIs]; EXIT; } ELSE IF corners[patch[k][oppVtx].vtxPtr].length < smallestCorner THEN { smallestCorner _ corners[patch[k][oppVtx].vtxPtr].length; polyToSplit _ k; vertexNo _ cVtx; oppVtxNo _ oppVtx; }; }; IF j = 2 THEN { InsertEdge[shape, polyToSplit, corners, vertexNo, oppVtxNo]; InsertEdge[shape, polyToSplit, corners, oppVtxNo, vertexNo]; patch[polyToSplit].props _ Atom.PutPropOnList[ patch[polyToSplit].props, $Split, $ItIs ]; }; ENDLOOP; }; }; ENDLOOP; }; FindContinuousEdges: PROC[ shape: Shape, 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[NatSequenceRep[nCorners]]; ENDLOOP; IF corner[nCorners-1].inVtx # corner[0].outVtx THEN { -- open edge (should only be one) cosAng: REAL _ Dot[ Normalize[ corner[nCorners-1].inDir ], Normalize[ 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 _ Normalize[corner[nCorners-1].inDir]; corner[0].outDir _ Normalize[corner[0].outDir]; }; corner[nCorners-1].inDir _ Add[ -- sum to get aligned direction corner[nCorners-1].inDir, G3dVector.Negate[corner[0].outDir] ]; corner[0].outDir _ G3dVector.Negate[corner[nCorners-1].inDir]; -- opposite dir }; } ELSE SELECT nCorners FROM -- no open edge 1, 2 => SIGNAL G3dRender.Error[$MisMatch, "too few edges at vertex"]; 3 => FOR i: NAT IN [0..3) DO -- find a doubled corner and set its angle to 180 degrees IF corner[i].double THEN { corner[i].outDir _ Sub3[ corner[i].outDir, corner[i].inDir ]; corner[(i + 2) MOD 3].inDir _ corner[i].outDir; corner[i].inDir _ G3dVector.Negate[corner[i].outDir]; corner[(i + 1) MOD 3].outDir _ corner[i].inDir; EXIT; }; IF i = 2 THEN SIGNAL G3dRender.Error[$MisMatch, "Three edges at vertex"]; ENDLOOP; 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, G3dVector.Negate[corner[i].outDir] ]; corner[i].outDir _ corner[i1].inDir _ G3dVector.Negate[corner[o].outDir]; ENDLOOP; ENDCASE => { }; IF nCorners > 4 THEN AlignBigCtlPoint[ corner, nCorners ]; 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, G3dVector.Sub[shape.vertices[corner[this].outVtx].point, shape.vertices[vtx].point] ]; IF corner[last].inVtx = corner[this].outVtx THEN corner[last].inDir _ corner[this].outDir ELSE corner[last].inDir _ ScaleTangent[ corner[last].inDir, G3dVector.Sub[shape.vertices[corner[last].inVtx].point, shape.vertices[vtx].point] ]; ENDLOOP; RETURN[ corner ]; }; AlignBigCtlPoint: PROC[ corner: REF CornerSeq, nCorners: NAT ] ~ { sumDirections: NAT _ nCorners; FOR i: NAT IN [0..nCorners) DO corner[i].ptr _ 0; ENDLOOP; WHILE sumDirections > 4 DO smallestVtx, next, last, start: NAT; smallestAng: REAL _ 0.0; -- actually largest cosine of the angle FOR i: NAT IN [0..nCorners) DO IF corner[i].ptr = 0 THEN { start _ i; EXIT; }; -- find non-collapsed angle ENDLOOP; FOR i: NAT IN [0..nCorners) DO -- get angle between outDir and following outDir cosAng: REAL; this: NAT _ (i + start) MOD nCorners; next _ (this + 1) MOD nCorners; cosAng _ Dot[ Normalize[ corner[next].outDir ], Normalize[ corner[this].outDir ] ]; IF cosAng > smallestAng AND cosAng < 0.9999 THEN { -- ignore collapsed angles smallestAng _ cosAng; smallestVtx _ this; -- smallest angle (largest cosine }; ENDLOOP; corner[smallestVtx].ptr _ 1; -- tag first of pair forming smallest angle last _ (smallestVtx + nCorners-1) MOD nCorners; -- step back past other tagged angles WHILE corner[last].ptr # 0 DO last _ (last + nCorners-1) MOD nCorners; ENDLOOP; last _ (last + 1) MOD nCorners; -- first tagged corner of collapsed sequence next _ last; WHILE corner[next].ptr # 0 DO -- now sum all the directions involved next _ (next + 1) MOD nCorners; corner[last].outDir _ Add[ corner[last].outDir, corner[next].outDir ]; ENDLOOP; next _ last; WHILE corner[next].ptr # 0 DO -- now set all collapsed corners with direction next _ (next + 1) MOD nCorners; corner[next].outDir _ corner[last].outDir; ENDLOOP; sumDirections _ 0; FOR i: NAT IN [0..nCorners) DO -- count unpaired and leading edges IF corner[i].ptr = 0 THEN sumDirections _ sumDirections + 1; ENDLOOP; ENDLOOP; IF corner[0].outVtx # corner[nCorners-1].inVtx THEN { -- open edge, everything aligned with open edge and one other dir. i: NAT _ 0; WHILE corner[i].ptr # 0 DO -- align anything paired with first edge to open edge corner[i].outDir _ G3dVector.Negate[ corner[nCorners-1].inDir ]; IF i > 0 THEN corner[i-1].inDir _ corner[i].outDir; i _ i + 1; ENDLOOP; FOR j: NAT IN (i..nCorners) DO -- align everything else to single direction corner[i].outDir _ Add[ corner[i].outDir, corner[j].outDir ]; ENDLOOP; FOR j: NAT IN (i..nCorners) DO corner[j].outDir _ corner[i].outDir; corner[j-1].inDir _ corner[j].outDir; ENDLOOP; } ELSE { -- no open edge, find 4 groups, align directions, reload directions directions: ARRAY [0..4) OF Triple _ ALL[nullTriple]; weights: ARRAY [0..4) OF NAT _ ALL[0]; j, start: NAT _ 0; FOR i: NAT IN [0..nCorners) DO -- find non-collapsed corner to start with IF corner[i].ptr = 0 THEN { start _ i; EXIT; }; ENDLOOP; start _ (start + 1) MOD nCorners; -- guaranteed not paired with last corner directions[0] _ corner[start].outDir; weights[0] _ 1; -- therefore 1st direction FOR k: NAT IN [1..nCorners) DO i: NAT _ (k + start) MOD nCorners; IF corner[(i + nCorners-1) MOD nCorners].ptr = 0 -- not paired with last corner THEN { j _ j + 1; directions[j] _ corner[i].outDir; weights[j] _ 1; } ELSE weights[j] _ weights[j] + 1; ENDLOOP; directions[0] _ Sub3[ -- align 4 directions G3dVector.Mul[ directions[0], weights[0] ], G3dVector.Mul[ directions[2], weights[2] ] ]; directions[2] _ G3dVector.Negate[ directions[0] ]; directions[1] _ Sub3[ G3dVector.Mul[ directions[1], weights[1] ], G3dVector.Mul[ directions[3], weights[3] ] ]; directions[3] _ G3dVector.Negate[ directions[1] ]; j _ 0; corner[start].outDir _ directions[0]; corner[(start + nCorners-1) MOD nCorners].inDir _ corner[start].outDir; FOR k: NAT IN [1..nCorners) DO -- reload directions i: NAT _ (k + start) MOD nCorners; IF corner[(i + nCorners-1) MOD nCorners].ptr = 0 THEN j _ j + 1; corner[i].outDir _ directions[j]; corner[(i + nCorners-1) MOD nCorners].inDir _ corner[i].outDir; ENDLOOP; }; }; SortCtlPoint: 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. ΜG3dBezierFromPolyProcs.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last Edited by: Crow, May 17, 1989 11:12:07 am PDT Bloomenthal, September 14, 1988 1:06:06 pm PDT Types RECORD[coord: CtlPoint, shade: Shading, 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 - ptr is multipurpose marker, sometimes the vtx number, sometimes the patch number - inDir, outDir are the corresponding outward direction vectors, inDir lies clockwise of outDir (later used as Bezier knots) - normal is the carefully determined normal vector - interiorKnot is the interior Bezier patch control point between inDir and outDir - interiorKnot2 is the 2nd interior ctl. pt. for a doubled corner, lies between interiorKnot and outDir - Double = TRUE indicates corner is treated as a degenerate edge needing a double interior control point when treated as a Bezier patch. 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 and Constants Utility Procedures 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 to proper length for Bezier inner control pt approximating circular arc Scale tangent vector to proper length for Bezier inner control pt approximating circular arc Get average of depths at corners Display Procedures Initialization of Classes Procedures for conversion of non-planar polygons to Bezier patches PROC[context: Context, shape: Shape, data: REF] RETURNS[Shape]; Convert endpoints with corners to Bezier knots b1 _ Add[ b0, ScaleTangent[c1.outDir, edgeDir] ]; b2 _ Add[ b3, ScaleTangent[c2.inDir, G3dVector.Negate[edgeDir]] ]; Transform to eyespace and get clip state Converts polygon with corner tangents and interior points into bezier patch THEN DisplayTriangleWithTangents[ context, patch, corner ] Build patch boundary by evaluating polygon edges with tangents Polygons taken clockwise: Patches taken rowwise: 3 -> 0 3 -> 0 3 2 1 0 ^ ^ 7 6 5 4 2 <- 1 15 <- 12 11 10 9 8 15 14 13 12 Build interior knots by completing parallelograms formed by boundary knots at corners Calculates four vertices to use as knots along boundary of patch Gets interior point from corner data derives shading data - Adjust rectangles with excessive angles, recheck effected neighbors. Build Polygon corner structure from vertex corner structure CtlPoint Corner structure - outVtx matches previous inVtx Polygon corner structure - outVtx matches next inVtx Find upper bound on dihedral angles at patch boundaries via midpoint analysis, adjust inner knot positions to zero angle where necessary Clockwise refers to direction from vector out along edge to vector to interiorKnot Modulate effect of scale to keep knot -> sideDir -> nextKnot linear ELSE IF corner[i].interiorKnot2 = corner[i].interiorKnot THEN corner[i].interiorKnot2 _ corner[i].interiorKnot _ knot corner[i].interiorKnot _ Add[ corner[i].inDir, ScaleTangent[ -- scale to arc from inDir to outDir corner[i].outDir, Sub3[corner[i].outDir, corner[i].inDir] ] ]; corner[i].interiorKnot2 _ Add[ corner[i].outDir, ScaleTangent[ -- scale to arc from outDir to inDir corner[i].inDir, Sub3[corner[i].inDir, corner[i].outDir] ] ]; Procedures for computing tangent vectors at vertices Build endpoint tangents for each edge of a polygon Pass through polygon structure building corner structure. Check for vertices with only three edges. If not open edges, split a polygon to make 4 edges Order contiguous polygons around vertex Build tangents at corners around each vertex. By projecting edge directions on plane given by normal Make opposing pairs of tangents continuous. Always make open edges continuous. Reduce larger vertices to 4 directions by collapsing smaller angles Get vertex normals robustly ( using robust polygon normal ) Find incoming and outgoing directions for each polygon at a vertex Get normals robustly(?) Build corner structure Add extra edges for big polygons Split up big polygon, add extra edges to corners Need to complete this to handle polygons with more than 6 sides!!!! If triangle formed, mark corner opposite new edge as double 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. Add to corners[surface[k][cVtx]] and corners[surface[k][oppVtx]] All edges must be forced into 2 sets of opposing directions. If there are more than 4 edges, those forming the smallest angles will be collapsed Check for open edge, if there, align tangents unless included angle less than 45 degrees Collapse smallest angles until only 4 directions Found start of sequence of collapsed angles Now, align four directions Find 4 directions 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 Κ-r˜Ihead™šœ Οmœ1™Jš œžœžœžœ˜8Jš œžœ žœžœ˜9Jš œžœ žœ˜;Jš  œžœ žœ˜AJš œžœžœ˜;Jš  œžœžœ'˜AJš  œžœžœžœžœ#˜JJš œžœ!žœžœžœžœžœ.˜uJš œžœ!žœžœžœžœž œ&˜x—™Nšœžœ Οc0œ˜SNšœžœ ‘/˜ONšœžœžœ‘7˜XNšœžœžœ‘*œ˜JJš œ žœžœžœžœ ˜*Jš œ žœžœžœžœ ˜+Jš œžœžœžœžœ˜#Jš œ žœžœžœžœ ˜)—™defaultš œž œ žœžœžœžœžœ˜SO˜—š  œžœžœžœžœ˜'Jš‘=™=Jš žœ žœžœžœžœ˜EJ˜—š  œžœžœžœžœ˜*Jšœ‘8™9Jšœžœ ˜Jš žœžœ*žœžœžœžœ ˜VJ˜—š   œžœžœžœžœ ˜LJ™*šžœž˜Jšœ žœI˜YJšœ žœK˜[šžœ ‘˜JšžœC˜I——J˜—š  œžœ žœ žœ žœ˜TJšœ žœ!˜1Jš žœžœžœžœžœ˜NJ˜#J˜—š  œžœ žœžœžœžœžœžœ˜Mš œžœ žœ˜J˜:J˜—š  œžœ˜#J˜S˜J˜=J˜6J˜—J˜—Jšœžœ˜ šžœžœžœž˜#š žœžœžœžœžœžœž˜<šžœžœž˜Jšœ!žœ˜CJšœ#žœ˜FJšœ!žœ˜AJšœ"žœ ˜GJšœ#žœ"˜JJšœ#žœ!˜IJšœ'žœ(˜TJšœ(žœ(˜Ušœ#žœ˜=Jš œžœžœžœžœžœ˜7J˜—Jšžœ˜—Jšžœ˜—J˜Jšžœ˜—Jšžœ˜J˜—š  œžœžœžœ ž œžœžœžœ ˜aš žœžœž œžœž˜/Jšœžœ˜ šžœž˜Jšœžœ3˜FJšœžœ2˜DJšœžœ0˜CJšžœ˜—Jšœžœ ˜Jšžœ˜ —Jšžœ˜ J˜—š   œžœ žœžœžœ˜YJ™RJ™^J˜-J˜$J˜—š  œžœžœ ˜@J™\Jšœžœ˜Jšœ žœ˜#Jšœ žœ˜#Jšžœžœžœžœ˜FJšœ žœ%˜2šœ žœ˜#Jšžœ˜ Jšžœ7˜;—šœžœ˜$šžœF˜JJšžœ#˜'——Jšžœ$˜*Jšœ ˜—š   œžœ*žœžœ žœžœ˜„Ošœžœ˜1Ošœžœ˜8šœžœžœ˜7J™ —šžœžœžœž˜J˜ Jš žœžœžœžœ#žœ˜LJ˜J˜"Jšžœ˜—šžœžœžœžœ˜š žœžœž œžœ žœ‘!˜Išžœžœ˜Jšœžœžœžœ˜JJ˜;J˜+J˜—Jšžœ˜—Jšžœ˜—šžœžœžœžœžœžœžœ‘˜\Jšœžœ˜Jšœžœžœ˜‘˜XšžœAžœ˜GJšžœ'‘+˜V—Ošžœ ˜O˜—š œžœ$žœ˜hJ˜J˜5J˜8J˜J˜—š œžœ-žœ˜fJ™.J˜J˜,J˜,J˜J˜J˜J™1™ J™J™2J™—Jšœ ˜—š  œžœžœ ˜8J™(Jšœžœ.˜CJ˜?šžœžœžœž˜"Jšžœ˜J˜8JšœA˜Ašžœ˜JšžœT˜X—Jšžœ˜—J˜—š œžœžœžœ˜jN™KJšœ˜Jšžœžœ0žœ˜Xšžœ,žœžœ˜=šœžœ˜Jšœ˜šžœ ž˜Jšœ?žœ˜LJšžœ˜—J˜—Jšžœ˜—O˜šžœ˜Ošžœ2˜6Ošžœ6™:Ošžœ8˜<—O˜—š  œžœžœžœ˜^Jšœžœ*‘˜UJšœ žœ žœ#˜EJšœžœ˜Jš œ žœžœžœžœ˜ šžœ žœž˜Jšœ%žœ˜)Jšœžœžœ˜Jšœžœ˜Jšœ‘˜‘˜ZNšœ˜Ošžœ˜—J˜—š œžœžœ žœ˜rJ˜Jšœ žœžœ˜.Jšœžœ!˜6J˜?šžœžœžœž˜J˜"Jšœžœ ˜+J˜GJ˜GJšžœ˜—Jšœ ‘+˜KJšœ;˜;Jšœžœ)˜:O˜—š œžœžœžœ˜kJšœžœžœ˜#Jšœžœ˜Jšœžœ žœ˜%Ošœžœ˜šžœžœžœž˜šžœžœžœžœ˜8OšžœC˜Gšžœ˜O˜9šžœžœžœ˜"Ošžœžœ@˜K—O˜$O˜%Oš œžœžœžœžœ˜O™3O™$O™O™$O™Jšœžœ˜J˜š žœžœžœžœžœ˜,Ošžœžœ4˜?Ošžœ˜—J˜^J˜]J˜]J˜_N™UN˜:N˜N˜:O˜J˜J˜J˜J˜J˜ J˜Jšœ‘+˜GJšœ.˜.Jšœ žœ-˜:O˜—š  œžœ=žœ˜tJ™@š  œžœžœ˜CJ˜WJ˜WJ˜6J˜NJ˜NJ˜—J˜J˜5J˜J˜>J˜>J˜7J˜J˜—š  œžœFž œ˜N™9N˜>N˜>N˜?N˜DN˜DN˜DN˜DN˜TN˜TJ˜ J˜—š œžœžœ˜DJšœ žœžœ(˜KJšœ žœ.˜=J˜š žœžœžœžœ‘˜HJšžœžœžœ"˜:Jšžœ˜ —šžœžœžœžœžœžœžœ‘˜[Jš žœžœžœžœž œ˜IJšžœ˜J˜—Jšžœžœ$‘0˜hJ™J™G™;J™9J™4—šžœžœžœžœžœžœžœ˜MJšœžœ˜Jšœžœ˜'Jšœžœ‘!˜Mš žœžœžœ žœ ‘œ˜EJšœ žœžœ˜%Jšœ žœžœ˜,Jšœžœ"‘˜DJšœžœ%‘˜CJšœžœ%‘˜@šžœžœžœž˜)šžœžœ‘)˜PJšœžœ˜ šžœ&˜(Jšžœ2˜6—Jšœ-‘˜;Jšœ*‘%˜OJ˜(Jšœ&‘ ˜FJ˜šžœžœ‘˜EJšœ žœ˜'Jšœ!žœ˜9šžœžœžœ ž˜Jšžœ!žœžœ˜EJšžœ˜—J˜.J˜JJ˜(J˜"J˜J˜—šžœ ˜"Jšžœžœ8˜C—Jšžœ˜J˜—Jšžœ˜—Jšžœ˜—J˜J˜Jšžœ˜—J˜IJ˜š œžœžœ˜CJ™ˆš œžœ$žœ˜WJ˜˜ Jšžœžœžœ˜BJ˜J˜—J˜&J˜—š  œžœ žœžœ žœ žœ˜QJ™Ršœžœžœ ˜Jšžœ žœ‘+˜MJšžœžœ‘#˜S—š œžœžœ žœžœ˜8Jšžœ˜Jšžœ˜—Jš œžœžœ žœžœ˜NJ˜$šžœžœ‘!œžœžœ žœ)žœ žœ&˜»šžœ‘C˜Lš œžœžœ žœžœ˜J˜2šžœžœ ˜+Jšžœ7˜;—Jšžœ˜—J˜J˜—J™š žœžœžœ žœ‘.˜OJšœžœ˜#Jšœžœ žœ ˜5Jšœžœ)žœ ˜>˜#J˜šœ‘6˜TJšœ‘'˜5šœ˜Jšœ9‘˜S—šœ˜Jšœ:‘˜P—Jš œžœžœžœžœžœ‘*˜]J˜—J˜—Jšžœ˜ —J™ Jšžœ žœ)˜;J˜Jšžœ˜—Jšœ(˜(š žœžœžœžœ‘1˜]šžœžœžœ˜!Jšžœ˜šžœ5˜7Jšžœ˜!Jšžœ‘/˜H—šžœžœžœžœžœžœžœ˜BJ˜ Jšžœ˜—J˜—Jšžœ˜ —Jšžœ ˜J˜š  œžœ!žœžœžœ˜EJ˜]J˜\Jšœžœ‘˜.Jšœžœ‘˜/Jšžœ žœ"‘˜JOšžœ˜O˜—š  œžœžœžœ)˜kO™0Jšœžœ.˜7Jšœžœ˜Ošœ žœ;žœ˜Lš žœžœžœ žœ‘%˜HOšžœžœ(˜COšžœ˜ —Jšœžœ˜Jšœžœ˜%Ošœ žœ žœžœ˜(šœžœ"‘˜HOšžœžœ˜"Ošžœžœ˜-—O˜1O˜.O˜$šžœž˜O™COšœžœžœ ž˜,O˜Ošžœ˜—J˜0O˜—š   œžœžœžœžœ˜fJšœ+˜+Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœžœ ˜(Jšœžœžœ ˜1šžœžœžœž˜)šžœžœžœ˜Hšœ'‘!˜HJ˜˜;J˜šœ ‘˜6Jšœ8˜8—šœ‘!˜7Jšœ:˜:—J˜—J˜—Jšœ$‘$˜Hšœ&˜&Jšœ:˜:—Jšžœ˜J˜—Jšžœ˜—J™;š žœ žœžœžœžœžœž˜Ošžœ"žœ˜EJšžœžœžœ˜4—Jšžœ˜—J˜—š œžœ'˜;J™υJ˜Jšœžœ˜Jšœžœ˜8š žœžœžœ žœ‘˜>Jšœžœ˜Jšœžœžœ ˜(Jšœžœžœ ˜1šœ‘%˜;JšœG˜GJšœG˜GJ˜—Jšžœžœ)˜?Jšœ&‘˜4Jšžœ˜—š žœžœžœ žœ‘˜7šžœ"žœ˜*J˜$Jšœ$‘˜9Jšœ%‘˜9J˜—Jšžœ˜—š žœžœžœ žœ‘˜>Jšœžœ˜šžœžœžœ˜$JšœJ˜J—Jšž˜—O˜—š  œžœ žœžœžœ˜dJšœžœ ˜šžœ žœ˜Jšžœ žœ‘˜>šžœžœ"˜)šžœ ‘˜*Jšœ žœ&˜2šžœžœžœž˜&Jšœ‘˜3Jšžœ˜—J˜J˜—Jšžœ‘˜1——O˜J˜J˜"Ošžœ ˜O˜—O˜—š œžœ žœ!˜EJšœ4˜4šžœžœžœžœžœžœžœžœ˜YJšœžœžœ˜š žœžœžœžœ‘+˜FJš žœžœžœžœžœ˜=Jšžœ˜—šžœžœžœ ‘˜1Jšœžœžœžœ‘+˜JJšœ1žœžœžœ˜Ašžœžœžœžœ˜Jšœžœ‘ ˜?šžœžœ#žœžœ˜GJšœžœ‘'˜Gš žœžœžœ žœžœžœ˜=Jšœžœ ‘œ˜?Jšžœ˜J˜Jšžœ˜—šžœ-‘˜Jšžœ˜J™@Ošœ0˜0Ošœ,˜,Jšœ8˜8Jšžœ˜J˜—šžœžœ:žœ˜GJšœ9˜9J˜6J˜——J˜—šžœžœ˜Ošœ@˜@Ošœ<˜<šœ.˜.Jšœ'˜'J˜—J˜—Jšžœ˜—J˜—J˜Jšžœ˜—J˜—š  œžœžœ žœžœžœ˜oJ™’Jšœ žœ˜Jšœ žœžœ ˜Išžœžœžœž˜!Jšœžœ˜1Jšžœ˜ —J™Xšžœ-˜/šžœ‘!˜(šœžœ˜J˜&J˜J˜—šžœžœ‘.˜Sšžœžœ‘.˜IJ˜?J˜2J˜—šœ ‘˜?J˜