DIRECTORY Atom, Basics, G3dBasic, G3dMappedAndSolidTexture, G3dMatrix, G3dRender, G3dScanConvert, G3dClipXfmShade, G3dShape, G3dSortandDisplay, G3dSpline, G3dVector, RealFns; G3dPatchFromPolyProcs: CEDAR MONITOR IMPORTS Atom, Basics, G3dMappedAndSolidTexture, G3dMatrix, G3dRender, G3dClipXfmShade, G3dSortandDisplay, G3dSpline, G3dVector, RealFns = BEGIN Ray: TYPE ~ G3dBasic.Ray; NatSequence: TYPE ~ G3dRender.NatSequence; NatSequenceRep: TYPE ~ G3dBasic.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; Vertex: TYPE ~ G3dShape.Vertex; Patch: TYPE ~ G3dRender.Patch; PatchSequence: TYPE ~ G3dRender.PatchSequence; PatchSequenceRep: TYPE ~ G3dRender.PatchSequenceRep; PatchProc: TYPE ~ G3dRender.PatchProc; CtlPoint: TYPE ~ G3dRender.CtlPoint; CtlPtInfo: TYPE ~ G3dRender.CtlPtInfo; CtlPtInfoSequence: TYPE ~ G3dRender.CtlPtInfoSequence; CtlPtInfoProc: TYPE ~ G3dRender.CtlPtInfoProc; Shading: TYPE ~ G3dRender.Shading; RenderStyle: TYPE ~ G3dRender.RenderStyle; RenderData: TYPE ~ G3dRender.RenderData; 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: 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 CtlPtInfo, t: ARRAY[0..3) OF TangentSet ]; NatSequenceSequence: TYPE ~ RECORD [ length: NAT _ 0, s: SEQUENCE maxLength: NAT OF 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; 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; 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 ] ]; }; 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]; }; DiffPosnsVtx: PROC[vtx1, vtx2: Vertex, space: ATOM _ NIL] RETURNS[Triple] ~ { RETURN[ [ Sub[vtx1.point.x, vtx2.point.x], Sub[vtx1.point.y, vtx2.point.y], Sub[vtx1.point.z, vtx2.point.z] ] ]; }; DiffPosnsCtlPt: 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] ] ]; }; 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: CtlPtInfo, 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: Context, v: CtlPoint, 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: Context, v0, v1: CtlPtInfo, slope1, slope2: Triple, tol: REAL] RETURNS[BOOL] ~ { pos1: Triple _ G3dClipXfmShade.XfmPtToDisplay[ context, -- near slope plus near end Add[[v0.coord.ex, v0.coord.ey, v0.coord.ez], slope1 ] ]; pos2: Triple _ G3dClipXfmShade.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: Context, p: PatchSequence] RETURNS[PatchSequence] ~ { z: RealSequence _ NEW[RealSequenceRep[p.length]]; pOut: PatchSequence _ NEW [PatchSequenceRep[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]; }; DisplayNothing: PatchProc ~ { -- dummy routine for timing tests RETURN[ patch ]; }; DisplayPatchEdges: PatchProc ~ { shape: Shape _ NARROW[ GetProp[patch.props, $Shape] ]; patchNo: NAT _ NARROW[ GetProp[patch.props, $PatchNo], REF NAT ]^; renderData: REF RenderData _ patch.renderData; tangents: REF TangentSeqSeq _ NARROW[ GetProp[renderData.props, $PatchTangents] ]; corners: REF CornerSeqSeq _ NARROW[ GetProp[renderData.props, $PatchCorners] ]; xfm: Matrix _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; clr: RGB _ renderData.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 _ 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] _ 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.nVtces _ npts; outP.clipState _ IF inState = NoneOut THEN in ELSE IF outState # NoneOut THEN out ELSE clipped; outP.renderData _ patch.renderData; outP.props _ patch.props; [outP] _ G3dSortandDisplay.OutputPolygon[context, outP]; ENDLOOP; RETURN[NIL]; }; InitClasses: PROC[] ~ { -- register procedures for basic surface types standardClass: ShapeClass _ G3dRender.GetShapeClass[$ConvexPolygon]; standardClass.type _ $PolygonWithNormals; standardClass.displayPatch _ DisplayPatchNmls; G3dRender.RegisterShapeClass[standardClass, $PolygonWithNormals]; standardClass.type _ $PolygonWithTangents; standardClass.validate _ ValidatePolyWTangents; standardClass.displayPatch _ DisplayPatchTngs; G3dRender.RegisterShapeClass[standardClass, $PolygonWithTangents]; standardClass.type _ $PolygonToTnsrPatch; standardClass.displayPatch _ DisplayPatchTnsr; G3dRender.RegisterShapeClass[standardClass, $PolygonToTnsrPatch]; standardClass.type _ $PolygonNoImage; standardClass.validate _ G3dSortandDisplay.ValidatePolyhedron; standardClass.displayPatch _ DisplayNothing; G3dRender.RegisterShapeClass[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]; } ELSE { outPatch: PatchSequence _ NEW [PatchSequenceRep[patch.nVtces]]; midPt: CtlPtInfo _ GetCenterPt[context, patch, tangents]; midTangents: REF TangentSeq _ NARROW[ midPt.data ]; FOR i: NAT IN [0..patch.nVtces) DO j: NAT _ (i + 1) MOD patch.nVtces; -- next vertex outPatch[i] _ 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 _ unknown; outPatch[i].renderData _ patch.renderData; 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 G3dClipXfmShade.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]; ReleasePatch[outPatch[i]]; ENDLOOP; }; RETURN[patch]; }; GetCenterPt: PROC[context: Context, patch: REF Patch, tangents: REF TangentSeq _ NIL] RETURNS[midPt: CtlPtInfo] ~ { 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; 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: CtlPtInfo; 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], patch.renderData, 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 patch.renderData.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.txtrX _ midPt.shade.txtrX + pt.shade.txtrX; midPt.shade.txtrY _ midPt.shade.txtrY + pt.shade.txtrY; 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; }; 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], DiffPosnsCtlPt[midPt.coord, patch[i].coord, $Eye] ]; midTangents[i].et1 _ GetSlopeVec[ [midPt.shade.exn, midPt.shade.eyn, midPt.shade.ezn], DiffPosnsCtlPt[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 patch.renderData.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; midPt.shade.txtrX _ midPt.shade.txtrX / loopEnd; midPt.shade.txtrY _ midPt.shade.txtrY / 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], DiffPosnsCtlPt[midPt.coord, patch[i].coord] ]; midTangents[i].t1 _ GetSlopeVec[ [midPt.shade.xn, midPt.shade.yn, midPt.shade.zn], DiffPosnsCtlPt[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; }; }; { OPEN midPt.coord; shape: Shape _ NARROW[ GetProp[patch.props, $Shape] ]; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; midPt.data _ midTangents; }; }; TriangleDisplay: PROC[ context: Context, p: REF Patch, level: NAT, tol: REAL] ~ { subP: PatchSequence _ NIL; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received IF p.dir = unknown 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; G3dClipXfmShade.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]; ReleasePatch[subP[0]]; TriangleDisplay[context, subP[1], level+1, tol]; ReleasePatch[subP[1]]; TriangleDisplay[context, subP[2], level+1, tol]; ReleasePatch[subP[2]]; TriangleDisplay[context, subP[3], level+1, tol]; ReleasePatch[subP[3]]; }; }; }; SubdivideTriangle: PROC[context: Context, p: REF Patch, level: NAT, tol: REAL] RETURNS[PatchSequence] ~ { shape: Shape _ NARROW[ GetProp[p.props, $Shape] ]; v0, v1, v2: CtlPtInfo; flat0, flat1, flat2: BOOLEAN; t: REF TangentTriple _ NARROW[GetProp[p.props, $Tangents] ]; t00, t01, t10, t11, t20, t21, tm0, tm1, tm2: TangentSet; outPatch: PatchSequence _ NEW[PatchSequenceRep[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], DiffPosnsCtlPt[v1.coord, v0.coord, $Eye] ]; tm0.et1 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], DiffPosnsCtlPt[v0.coord, v1.coord, $Eye] ]; tm1.et0 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], DiffPosnsCtlPt[v2.coord, v1.coord, $Eye] ]; tm1.et1 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], DiffPosnsCtlPt[v1.coord, v2.coord, $Eye] ]; tm2.et0 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], DiffPosnsCtlPt[v0.coord, v2.coord, $Eye] ]; tm2.et1 _ GetSlopeVec[ [v0.shade.exn, v0.shade.eyn, v0.shade.ezn], DiffPosnsCtlPt[v2.coord, v0.coord, $Eye] ]; } ELSE { [v0, flat0] _ TriangleCurveDivideNml[context, p[0], p[1], p.renderData, level, tol]; [v1, flat1] _ TriangleCurveDivideNml[context, p[1], p[2], p.renderData, level, tol]; [v2, flat2] _ TriangleCurveDivideNml[context, p[2], p[0], p.renderData, 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] _ GetPatch[3]; -- allocate 3 point patch 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].renderData _ p.renderData; outPatch[i].props _ p.props; ENDLOOP; { OPEN v0.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v1.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v2.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.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 G3dClipXfmShade.GetPatchClipState[ outPatch[i] ]; ENDLOOP; --bad!! outPatch.length _ 4; RETURN[ outPatch ]; -- return four sub-patches }; DisplayPatchNmls: PatchProc ~ { renderStyle: RenderStyle; patch.type _ $PolygonWithNormals; WITH patch.renderData.shadingClass.renderMethod SELECT FROM style: REF RenderStyle => { renderStyle _ style^; SELECT renderStyle FROM lines => { [] _ DisplayPatchEdges[context, patch]; RETURN[patch]; }; ENDCASE; }; ENDCASE; IF patch.renderData.shadingClass.texture # NIL THEN { txtrRange: REF Pair _ NARROW[ GetProp[patch.renderData.shadingProps, $TxtrCoordRange] ]; IF txtrRange # NIL THEN G3dMappedAndSolidTexture.AdjustTexture[ -- fix texture seams patch, patch.renderData.shadingClass.texture, txtrRange^ ] }; patch _ TriangulateAndDisplay[context, patch]; -- make triangles, recursively subdivide RETURN[ patch ]; }; GetEdgeCurveNmls: PROC[p1, p2: CtlPtInfo] RETURNS[coeffs: G3dSpline.Coeffs] ~ { edge: Triple _ DiffPosnsCtlPt[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 unknown _ TRUE; }; back, front, unknown: 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 unknown THEN p.dir _ unknown ELSE IF back THEN p.dir _ back ELSE IF front THEN p.dir _ front ELSE p.dir _ unknown; IF p.dir = back THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; TriangleCurveDivideNml: PROC[ context: Context, vtx0, vtx1: CtlPtInfo, renderData: REF RenderData, level: NAT, tol: REAL ] RETURNS[pt: CtlPtInfo, 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; v0: CtlPtInfo _ IF vtx0.coord.x < vtx1.coord.x THEN vtx0 ELSE vtx1; v1: CtlPtInfo _ 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 _ DiffPosnsCtlPt[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 renderData.shadingClass.texture # NIL THEN { -- for solid textures pos1 _ [v0.coord.x, v0.coord.y, v0.coord.z]; pos2 _ [v1.coord.x, v1.coord.y, v1.coord.z]; SELECT renderData.class.type FROM $PolygonWithNormals => { edge: Triple _ DiffPosnsCtlPt[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] ]; }; pt.shade.txtrX _ (v0.shade.txtrX + v1.shade.txtrX) / 2; -- average texture coords pt.shade.txtrY _ (v0.shade.txtrY + v1.shade.txtrY) / 2; }; pt.data _ vtx0.data; }; DisplayPatchTngs: PatchProc ~ { renderStyle: RenderStyle; WITH patch.renderData.shadingClass.renderMethod SELECT FROM style: REF RenderStyle => { renderStyle _ style^; SELECT renderStyle FROM lines => { [] _ DisplayPatchEdges[context, patch]; RETURN[patch]; }; ENDCASE; }; ENDCASE; patch.type _ $PolygonWithTangents; IF data = NIL THEN { patchNo: NAT _ NARROW[ GetProp[patch.props, $PatchNo], REF NAT ]^; tangents: REF TangentSeqSeq _ NARROW[ GetProp[patch.renderData.props, $PatchTangents] ]; patch _ TriangulateAndDisplay[ context, patch, tangents[patchNo] ]; -- subdivide proc. } ELSE { tangents: REF TangentSeq _ NARROW[ data ]; patch _ TriangulateAndDisplay[ context, patch, tangents ]; -- subdivide proc. }; RETURN[ patch ]; }; ValidatePolyWTangents: ShapeProc ~ { shape _ G3dSortandDisplay.ValidatePolyhedron[context, shape]; -- Update shading, xfm IF GetProp[G3dRender.RenderDataFrom[shape].props, $PatchTangents] = NIL THEN shape _ GetTangents[context, shape]; -- calculate tangent vectors if not read in IF shape.clipState # out THEN { xfm: Matrix _ G3dMatrix.Mul[shape.matrix, context.eyeSpaceXfm]; tangent: REF TangentSeqSeq _ NARROW[ GetProp[G3dRender.RenderDataFrom[shape].props, $PatchTangents] ]; FOR i: NAT IN [0..shape.surfaces.length) 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: CtlPtInfo, 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: CtlPtInfo, 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 unknown _ TRUE; }; back, front, unknown: 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 unknown THEN p.dir _ unknown ELSE IF back THEN p.dir _ back ELSE IF front THEN p.dir _ front ELSE p.dir _ unknown; IF p.dir = back THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; NmlizeTangentSet: PROC[v0, v1: CtlPtInfo, 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: Context, p0, p1, s0, s1: Triple, tol: REAL] RETURNS[BOOLEAN] ~{ DistOffEdge: PROC[v: Triple, line: Ray] RETURNS[REAL] ~ { ptOnEdge: Triple _ G3dClipXfmShade.XfmPtToDisplay[ context, G3dVector.NearestToLine[line, v] ]; vs: Triple _ G3dClipXfmShade.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: Context, vtx0, vtx1: CtlPtInfo, slope1, slope2, offst1, offst2: Triple, parameter, tol: REAL ] RETURNS[ pt: CtlPtInfo, t0, t1: TangentSet, flat: BOOLEAN ] ~ { p1: REAL _ 1.0 - parameter; p2: REAL _ parameter; pos1, pos2, edge, oPt: Triple; v0: CtlPtInfo _ vtx0; v1: CtlPtInfo _ 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 _ DiffPosnsCtlPt[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.data _ v0.data }; DisplayPatchTnsr: PatchProc ~ { renderStyle: RenderStyle; WITH patch.renderData.shadingClass.renderMethod SELECT FROM style: REF RenderStyle => { renderStyle _ style^; SELECT renderStyle FROM lines => { [] _ DisplayPatchEdges[context, patch]; RETURN[patch]; }; ENDCASE; }; ENDCASE; patch.type _ $PolygonToTnsrPatch; IF data = NIL THEN { patchNo: NAT _ NARROW[ GetProp[patch.props, $PatchNo], REF NAT ]^; tangents: REF TangentSeqSeq _ NARROW[ GetProp[patch.renderData.props, $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 G3dClipXfmShade.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: PatchSequence _ NEW [PatchSequenceRep[patch.nVtces]]; midPt: CtlPtInfo _ GetCenterPt[context, patch, tangents]; midTangents: REF TangentSeq _ NARROW[ midPt.data ]; FOR i: NAT IN [0..patch.nVtces) DO j: NAT _ (i + 1) MOD patch.nVtces; -- next vertex outPatch[i] _ 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 _ unknown; outPatch[i].renderData _ patch.renderData; 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 G3dClipXfmShade.GetPatchClipState[ outPatch[i] ]; ENDLOOP; outPatch.length _ patch.nVtces; outPatch _ PatchDepthSort[ context, outPatch ]; -- sort to depth order FOR i: NAT IN [0..patch.nVtces) DO -- subdivide and display TnsrTriangleDivide[context, outPatch[i], tol]; ReleasePatch[outPatch[i]]; 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]; }; RETURN[patch]; }; TnsrQuadDisplay: PROC[ context: Context, p: REF Patch, level: NAT, tol: REAL] ~ { subP: PatchSequence _ NIL; IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received IF p.dir = unknown 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; G3dClipXfmShade.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]; ReleasePatch[subP[0]]; TnsrQuadDisplay[context, subP[1], level+1, tol]; ReleasePatch[subP[1]]; TnsrQuadDisplay[context, subP[2], level+1, tol]; ReleasePatch[subP[2]]; TnsrQuadDisplay[context, subP[3], level+1, tol]; ReleasePatch[subP[3]]; }; }; }; TnsrTriangleDivide: PROC[ context: Context, p: REF Patch, tol: REAL] ~ { shape: Shape _ NARROW[ GetProp[p.props, $Shape] ]; v0, v1, v2, vCtr, vCtr1, vCtr2, vCtr3: CtlPtInfo; flat0, flat1, flat2: BOOLEAN; t: REF TangentTriple _ NARROW[GetProp[p.props, $Tangents] ]; t00, t01, t10, t11, t20, t21, tm0, tm1, tm2: TangentSet; outPatch: PatchSequence _ NEW[PatchSequenceRep[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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] _ GetPatch[4]; -- allocate 4 point patch 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].renderData _ p.renderData; outPatch[i].props _ p.props; ENDLOOP; { OPEN v0.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v1.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v2.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN vCtr.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.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 G3dClipXfmShade.GetPatchClipState[ outPatch[i] ]; ENDLOOP; --bad!! outPatch.length _ 3; FOR i: NAT IN [0..3) DO TnsrQuadDisplay[ context, outPatch[i], 0, tol ]; ReleasePatch[outPatch[i]]; ENDLOOP; }; SubdivideTnsrQuad: PROC[context: Context, p: REF Patch, level: NAT, tol: REAL] RETURNS[PatchSequence] ~ { shape: Shape _ NARROW[ GetProp[p.props, $Shape] ]; v0, v1, v2, v3, vCtr, vCtr2: CtlPtInfo; 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: PatchSequence _ NEW[PatchSequenceRep[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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, DiffPosnsCtlPt[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] _ 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].renderData _ p.renderData; outPatch[i].props _ p.props; ENDLOOP; { OPEN v0.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v1.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v2.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN v3.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.XfmPtToDisplay[ context, [ex, ey, ez], shape ]; }; { OPEN vCtr.coord; clip _ G3dClipXfmShade.GetClipCodeForPt[context, [ ex, ey, ez] ]; IF clip = NoneOut THEN [ [sx, sy, sz] ] _ G3dClipXfmShade.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 G3dClipXfmShade.GetPatchClipState[ outPatch[i] ]; ENDLOOP; --bad!! outPatch.length _ 4; RETURN[ outPatch ]; -- return four sub-patches }; TnsrQuadBackFacing: PROC[p: REF Patch] RETURNS [BOOLEAN] ~ { p.dir _ unknown; -- 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]; 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] _ SortCtlPoint[ corners[i] ]; ENDLOOP; FOR i: NAT IN [0..corners.length) DO nCorners: NAT _ IF corners[i] # NIL THEN corners[i].length ELSE 0; outDirs: TripleSequence _ NEW[TripleSequenceRep[nCorners]]; -- temp tangents inDirs: TripleSequence _ NEW[TripleSequenceRep[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 renderData: REF RenderData _ G3dRender.RenderDataFrom[shape]; tangents: REF TangentSeqSeq _ NEW[ TangentSeqSeq[shape.surfaces.length] ]; FOR i: NAT IN [0..shape.surfaces.length) DO nVtces: NAT _ shape.surfaces[i].length; tangents[i] _ NEW[ TangentSeq[nVtces] ]; FOR cVtx: NAT IN [0..nVtces) DO -- get direction vectors for each edge at vtx. vtx: NAT _ shape.surfaces[i][cVtx]; -- current vertex nVtx: NAT _ shape.surfaces[i][(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.surfaces.length; renderData.props _ PutProp[renderData.props, $PatchTangents, tangents]; }; RETURN[shape]; }; GetNormalsAndDirections: PROC[shape: Shape] RETURNS[REF CornerSeqSeq] ~ { corners: REF CornerSeqSeq _ NEW[ CornerSeqSeq[shape.vertices.length] ]; IF NOT shape.vertices.valid.normal THEN FOR i: NAT IN [0..shape.vertices.length) DO shape.vertices[i].normal _ nullTriple; ENDLOOP; FOR i: NAT IN [0..shape.surfaces.length) DO nVtces: NAT _ shape.surfaces[i].length; normal: Triple _ nullTriple; concave: REF BoolSequence _ NEW[ BoolSequence[nVtces] ]; cNmls: TripleSequence _ NEW[ TripleSequenceRep[nVtces] ]; FOR cVtx: NAT IN [0..nVtces) DO -- get normal for each corner 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]; cNmls[cVtx] _ Cross[ -- in object space so do right-handed DiffPosnsVtx[ shape.vertices[lVtx], shape.vertices[vtx] ], DiffPosnsVtx[ shape.vertices[nVtx], shape.vertices[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 _ shape.surfaces[i][cVtx]; IF shape.vertices[vtx] # NIL THEN { OPEN shape.vertices[vtx]; IF shape.vertices.valid.normal THEN cNmls[cVtx] _ normal -- Get predefined normal ELSE normal _ Add[normal, cNmls[cVtx]]; -- sum normals }; 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], [ lVtx, nVtx, -- store number of incoming vertex and outgoing vertex DiffPosnsVtx[shape.vertices[lVtx], shape.vertices[vtx]], -- dir to incoming vtx DiffPosnsVtx[shape.vertices[nVtx], shape.vertices[vtx]], -- outgoing direction cNmls[cVtx], -- normal at corner nullTriple, -- interior knot concave[cVtx] -- concave tag ] ]; ENDLOOP; ENDLOOP; corners.length _ shape.vertices.length; FOR i: NAT IN [0..shape.vertices.length) DO -- normalize new vertex normals, store in corners OPEN shape.vertices[i]; IF Length[ normal ] > shape.sphereExtent.radius * .0001 THEN normal _ Nmlize[ 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 ]; }; 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: 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[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 DiffPosnsVtx[shape.vertices[corner[nCorners-1].inVtx], shape.vertices[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, DiffPosnsVtx[shape.vertices[corner[this].outVtx], shape.vertices[vtx]] ]; IF corner[last].inVtx = corner[this].outVtx THEN corner[last].inDir _ corner[this].outDir; ENDLOOP; RETURN[ corner ]; }; 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. $ G3dPatchFromPolyProcs.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last Edited by: Crow, May 19, 1989 4:03:00 pm PDT Bloomenthal, September 14, 1988 1:06:44 pm PDT Types RECORD[coord: CtlPoint, shade: Shading, vtxPtr: NAT, data: REF]; 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 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 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 Display Procedures PROC[ context: 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[ DiffPosnsCtlPt[p[1].coord, p[0].coord, $Eye], DiffPosnsCtlPt[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]]; }; 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: Context, shape: Shape, data: REF] RETURNS[Shape]; 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: CtlPtInfoProc _ 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 _ DiffPosnsCtlPt[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], DiffPosnsCtlPt[v2.coord, v0.coord, $Eye] ]; tm0.et1 _ GetSlopeVec[ [v2.shade.exn, v2.shade.eyn, v2.shade.ezn], DiffPosnsCtlPt[v0.coord, v2.coord, $Eye] ]; tm1.et0 _ GetSlopeVec[ [v1.shade.exn, v1.shade.eyn, v1.shade.ezn], DiffPosnsCtlPt[v3.coord, v1.coord, $Eye] ]; tm1.et1 _ GetSlopeVec[ [v3.shade.exn, v3.shade.eyn, v3.shade.ezn], DiffPosnsCtlPt[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 ส9*˜Ihead™šœ ฯmœ1™Jš œžœžœ˜>Jš œžœžœžœ˜>Jš œžœžœžœ˜8Jš œžœ žœžœ˜9Jš œžœ žœ˜;Jš œžœ žœ˜>Jš œžœžœ˜;Jš  œžœžœ'˜AJš  œžœžœžœžœ#˜JJš œžœ!žœžœžœžœžœ.˜uJš œžœ!žœžœžœžœž œ&˜x—™Nšœžœฯc*˜JNšœžœ ก'˜KNšœžœ กA˜aNšœžœ ก˜;Nšœžœ ก,˜LNšœžœ ก0œ˜SNšœžœžœก,˜PNšœžœžœก7˜XNšœ žœ ก˜8—™š   œžœ%žœžœžœ˜ZJšก\™\Jšœžœ˜š žœ+žœžœžœก˜QJšœžœ$˜BJšœ žœ˜#Jšžœ˜—Idefaultšžœ#˜)Ošœ ˜—š œž œ žœžœžœžœžœ˜SO˜—š  œžœžœžœžœ˜'Jšก=™=Jš žœ žœžœžœžœ˜EJ˜—š  œžœžœžœžœ˜*Jšœก8™9Jšœžœ ˜Jš žœžœ*žœžœžœžœ ˜VJ˜—š   œžœžœžœžœ ˜MJ™šžœ˜ Jšœ!˜!Jšœ!˜!Jšœ˜Jšœ˜—J˜—š  œžœžœžœžœ ˜QJ™*šžœž˜Jšœ žœI˜YJšœ žœK˜[šžœ ก˜JšžœG˜M——J˜—š   œžœ žœžœžœ˜YJ™RJšฯy;™;J™^J˜-šžœ˜ Jšžœ5˜9Jšžœ:™>Jšžœ%˜)—J˜—š   œžœ%žœžœž œ˜bJ™TJ˜:šœ žœ ˜Jšžœ˜"Jšžœ˜"—J˜—š  œžœžœ ˜@J™\Jšœžœ˜Jšœ žœ˜#Jšœ žœ˜#Jšžœžœžœžœ˜FJšœ žœ%˜2šœ žœ˜#Jšžœ˜ Jšžœ7˜;—šœžœ˜$šžœF˜JJšžœ#˜'——Jšžœ$˜*Jšœ ˜—š  œžœ8žœžœžœ˜dJ™,Jšœ žœ˜Jš žœ žœžœžœก˜Fšžœ˜Jšžœ žœ&˜8—šžœ˜Jšžœ žœ;˜M—šžœ˜Jšžœ žœ%˜7—šžœ˜Jšžœ žœ:˜L—Jšžœ!žœžœžœžœžœžœ˜HJ˜—š   œžœCžœžœžœ˜qJ™2šœ:ก˜UJ˜6J˜—šœ:ก˜TJ˜6J˜—Jš œ žœžœžœžœžœ˜…Jšœก5˜RJšœก˜8Jšžœžœžœžœžœžœžœ˜8J˜—š œžœ.žœ˜\Ošœžœ˜1Ošœžœ˜8š žœžœžœžœก$˜HJ˜Jš žœžœžœžœ#žœ˜MJ˜J˜Jšžœ˜—šžœžœžœžœ˜š žœžœž œžœ žœก(˜Ošžœžœ˜Jšœžœžœ˜/J˜%J˜J˜—Jšžœ˜—Jšžœ˜—šžœžœžœžœžœžœžœก˜\Jšœžœ˜Jšœžœ:˜CJšžœ˜—J˜Jšžœ˜ J˜——™š œก!˜DNšžœ ˜N˜—š œ˜ Nšžœžœžœžœžœžœžœ™RJšœžœ!˜6Jš œ žœžœ"žœžœ˜BJšœ žœ˜.Jšœ žœžœ.˜RJšœ žœžœ-˜OJšœ?˜?Jšœžœ!˜)Jšœžœžœžœžœžœžœžœžœ˜=šžœžœžœž˜"Jšœžœ žœ˜ Jšœžœ˜!J˜:J˜šžœ ž˜J˜G˜HJ˜)J˜—Jšžœžœ9˜J—šžœžœžœ žœ˜Jšžœ˜Jšœžœ˜J˜,JšœP˜PJšœA˜Ašžœ˜JšžœH˜L—Jš œž œ žœžœ žœ˜MJš œž œ žœžœ žœ ˜FJšœ?ก˜QJšžœ˜—J˜J˜šœžœ˜'Jšžœ˜Jšžœžœžœžœ ˜1—J˜#J˜Jšœ:˜:Jšžœ˜—Jšžœžœ˜ J˜J˜——™š  œžœ ก.˜IOšœD˜DO˜J˜)J˜.JšœB˜BJ˜*J˜/J˜.JšœC˜CJ˜)J˜.JšœB˜BJ˜%Jšœ>˜>J˜,Jšœ=˜=J˜——™.š œ˜$J™ZNš œžœžœžœžœ˜NJšœ žœžœ ˜*Jšžœž œ3˜Sšžœž˜šžœ˜šžœ#žœ˜FJ˜šžœ˜J˜5J˜5J˜4J˜—J˜—J˜*J˜—šžœ˜Jšœžœ"˜@Jšœ9˜9Jšœ žœžœ˜3šžœžœžœž˜"Jšœžœ žœก˜2Jšœก˜9O˜J˜&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šœK˜KOšžœ˜—J˜——Jšžœ˜J˜—š   œžœžœžœžœžœ˜wJ™6Nš œžœžœžœžœ˜NJšžœžœžœ3˜T˜šœ žœžœ#ž œ"˜qJšžœžœžœžœ˜.—Jšœ žœ˜"Jš œ žœžœžœžœ˜dJ˜Dšžœžœžœžก>˜[Jšœžœก2˜TJ˜Jšœžœžœก#˜Ošžœ#žœ!˜HšžœกM˜SJ˜Jšœžœžœก˜MJ˜J˜0Jšœžœก!˜LJ˜J˜0J˜.˜$J˜E—J™GJ˜JšœDก˜TJ˜—šžœ˜šœ$˜$Jšœ5˜5Jšœ˜—J˜1J˜1J˜1J˜——J˜.J˜.J˜.J˜+J˜+J˜+J˜+šžœ)žœžœก˜HJ˜+J˜+J˜+J˜7J˜7J˜.J˜.J˜.J˜—Jšžœ˜—Jšœ,ก'˜SJ˜*J˜*šžœ#žœ"˜Išžœก6˜?šžœžœžœž˜˜ก˜TšžœBžœ˜HJšžœ'ก+˜V—šžœžœ˜J˜?šœ žœžœ˜$Jšœ>˜>Jšœ˜—š žœžœžœžœก˜Mš žœžœžœžœžœ˜;J˜/J˜/Jšžœ˜—Jšžœ˜—J˜—Ošžœ ˜O˜—š œžœ)žœ˜nJ˜J˜5J˜8J˜J˜—š œžœ5žœ˜mJ™/J˜J˜,J˜,J˜J˜2J˜:Jšœ ˜—š  œžœžœ žœžœ˜AJ™Yš œžœ˜,˜N˜)N˜(N˜—Nšœžœ˜$šžœ˜Pšžœžœ˜Pš žœžœžœ žœžœ žœ˜;—J˜—Jšœžœžœ˜&Jšœžœžœ ˜=Jšœžœžœ˜šžœžœžœžœ˜J˜=Jšžœ˜—šžœžœžœž˜Jšœžœžœ ˜%J˜&Jšœžœ˜4Jšžœ˜—šžœžœžœž˜Jš œžœžœžœ žœ˜=J˜+J˜.J˜.Jšžœ˜—šžœžœžœ ˜Jšžœ˜šžœžœ˜ Jšžœ˜šžœžœ˜Jšžœ˜Jšžœ˜———Jšžœžœžœžœžœžœžœ˜5J˜—š œžœ2žœ˜`J™;J˜8J˜8J˜4J˜4J˜J˜1J˜9J˜J˜J˜+J˜3Jšžœ˜Jšœ ˜—š   œžœ0žœžœžœ˜Yš  œžœžœžœ˜9šœ3˜3J˜*J˜—Jšœ:˜:JšžœD˜JJ˜—J˜!J™BJšœžœ"˜-Jšœžœ"˜-Jšžœ žœžœžœžœžœžœžœ˜DJ˜—š  œžœožœžœ+žœ˜ิJ™@J˜Jšœžœžœ ˜4J˜Jšœ˜Jšœ˜Jšœžœžœ˜)šžœžœก˜NJšžœž˜šžœžœ˜ Jšžœ?˜Cšžœžœ˜ JšžœA˜E———J˜0Jšžœ/žœ ž˜FJ˜/Jšœ/ž˜0šžœžœ žœ7˜Ušžœ ก0˜?J˜QJ˜TJ˜8J˜EJšœžœ˜ J˜—šžœ ก1˜@J˜(J˜BJ˜ J˜&J˜&J˜$J˜PJ˜QJ˜\J˜]Jšœ3ก˜IJ˜0J˜&J˜&J˜$J˜PJ˜)Jšœžœ˜ J˜——˜6J˜EJ˜—J˜/J˜/J˜/J˜1šžœžœžœก™COšœ8™8J™,J™,šžœž™™J™;šœ žœ™ Jšžœ™ Jšžœ>™B—šœ žœ™ Jšžœ™ JšžœF™J—J™—Jšžœžœ:™K—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šœ˜šžœ,žœžœ˜=šœžœ˜Jšœ˜šžœ ž˜Jšœ7žœ ˜IJšžœ˜—J˜—Jšžœ˜—J˜!šžœžœ˜šžœ˜Jš œ žœžœ"žœžœ˜Bšœ žœžœ˜&Jšœ0˜0Jšœ˜—Nšœ=ก˜ON˜—šžœ˜Jšœ žœžœ ˜*Nšœ4ก˜FN˜——Jšžœ žœžœ)ก˜TNšžœžœ˜N˜—š  œ˜J™•Nš œžœžœžœžœ˜NJšœ žœžœ ˜*Jšžœž œ3˜Sšžœž˜šžœ˜˜J˜šžœ˜J˜5J˜5J˜4J˜—J˜—J˜*J˜—šžœžœžœ˜Jšœžœ"˜@Jšœ9˜9Jšœ žœžœ˜3šžœžœžœž˜"Jšœžœ žœก˜2Jšœก˜9O˜J˜&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˜JOšžœ˜—J˜—šžœ˜˜J˜šžœ˜J˜5J˜5J˜5J˜4J˜—J˜—J˜*J˜——Jšžœ˜J˜—š  œžœžœžœžœ˜QJ™HJšœž˜Jšžœžœžœก$˜GJšžœžœก˜Eš žœžœžœ žœžœžœ˜JJšžœžœ2˜Nšžœžœ˜šžœ ก,˜ก ˜H˜J˜/J˜0J˜—J˜J˜—J˜ZJ˜šžœžœžœž˜Jšœก,˜GO˜J˜"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šœก ˜8Jšžœžœžœžœžœžœžœ˜5J˜—J˜—™4š  œ˜O™2š œžœ(žœžœ˜VJ™RJ˜)Jšžœ žœ#˜2J˜.šžœžœžœก˜TJšžœžœ˜:—šžœ!žœ˜(šžœM˜OJšžœก2˜P—Jšžœ?™E—J™J˜%J˜—O˜%Jšœ žœ˜J™`Jšœ+ก1˜\J™Gš žœžœžœžœก4˜ZJšžœžœžœ)˜AJšžœ˜ —J™dšžœžœžœžœ˜%Jš œ žœžœžœžœžœ˜BJšœžœก˜LJšœžœ˜:šžœžœžœž˜!Jšœžœžœ ˜-˜J˜IJ˜J˜—šžœ4ก˜PJšžœ#ก˜5šžœ ก˜@J˜JJšžœ˜J˜——Jšžœ˜—š žœžœžœžœก œ˜;Jšœžœ ž˜(Jšœžœ ž˜&Jšžœ˜—Jšžœ˜ —J™_šžœžœžœžœ˜%Jšžœžœžœ:˜RJšžœ˜—J™šœก\˜^Jšœ žœ.˜=Jšœ žœžœ)˜Jšžœžœžœžœ˜,Jšœžœ˜'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šœžœ@˜G—Jšžœ˜—J˜Jšžœ˜—J˜(JšœG˜GJ˜—Jšžœ˜O˜—š œžœžœžœ˜IJ™;J™BJšœ žœžœ)˜HJ™š žœž œžœžœžœ˜TJšœ'˜'Jšžœ˜ —šžœžœžœž˜+Jšœžœ˜'J˜Jšœ žœžœ˜8Jšœžœ˜:J™๕š žœžœžœ žœก˜>Jšœžœ˜#Jšœžœ žœ ˜5Jšœžœ)žœ ˜>šœก%˜;J˜:J˜:J˜—Jšžœžœ&˜˜#J˜šœก6˜IJšœ9ก˜OJšœ:ก˜OJšœ ก!˜-Jšœก˜)Jšœก˜(J˜—J˜—Jšžœ˜—Jšžœ˜—Jšœ(˜(š žœžœžœžœก1˜]Jšžœ˜šžœ5˜7Jšžœ˜Jšžœก/˜H—šžœžœžœžœžœžœžœ˜BJ˜ Jšžœ˜—Jšžœ˜ —Jšžœ ˜J˜—š  œžœ žœžœžœ˜dJšœžœ ˜šžœ žœ˜Jšžœ žœก˜>šžœžœ"˜)šžœ ก˜*Jšœ žœ&˜2šžœžœžœž˜&Jšœก˜3Jšžœ˜—J˜—Jšžœก˜1——O˜J˜J˜"Ošžœ ˜O˜—š  œžœžœ žœžœžœ˜oJ™‚Jšœ žœ˜Jšœ žœžœ ˜Išžœžœžœž˜!Jšœžœ˜1Jšžœ˜ —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˜FJ˜—Jšžœ*žœ*˜ZJšžœ˜ —Jšžœ ˜Jšœ ˜—š   œžœ žœ žœžœ˜DJ™^J™ขJšœžœ˜Jšœžœžœ˜$Jšœžœ žœ˜/J˜,šžœ ž˜Jšœžœ˜ šžœžœžœ ž˜šžœ2ก˜Pšžœก"˜5J˜FJšœ%žœžœก˜AJšœ žœ˜J˜—šžœžœ'ก˜Hšžœ ก&˜7š žœžœž œžœžœ˜/J˜Jšžœ˜—J˜:Jšœ%žœžœก˜AJšœ žœ˜J˜—Jš žœžœžœžœžœ žœก ˜F——Jšžœ˜—š žœžœ žœ žœก"˜FJšžœI˜O—Jšžœ˜ —Jšžœžœ˜*Jšžœ ˜J˜J˜——˜J˜—Jšžœ˜—…—Dax