DIRECTORY SVCoordSys, SVGraphics, FunctionCache, Imager, ImagerColor, ImagerColorPrivate, ImagerFont, ImagerPath, SVMatrix3d, SVShading, SVPolygon3d, Real, Rope, SV2d, SV3d, SVDraw, SVLines2d, SVVector3d, SVModelTypes, Vectors2d; SVGraphicsImpl: CEDAR PROGRAM IMPORTS SVCoordSys, FunctionCache, Imager, ImagerColor, ImagerColorPrivate, ImagerFont, ImagerPath, SVMatrix3d, Rope, SVShading, SVDraw, SVLines2d, SVPolygon3d, SVVector3d, Vectors2d EXPORTS SVGraphics = BEGIN Artwork: TYPE = SVModelTypes.Artwork; Camera: TYPE = REF CameraObj; CameraObj: TYPE = SVModelTypes.CameraObj; Color: TYPE = Imager.Color; CoordSystem: TYPE = SVModelTypes.CoordSystem; DisplayStyle: TYPE = SVModelTypes.DisplayStyle; DrawStyle: TYPE = SVModelTypes.DrawStyle; FrameBox: TYPE = SVModelTypes.FrameBox; LightSource: TYPE = SVModelTypes.LightSource; LightSourceList: TYPE = SVShading.LightSourceList; Matrix4by4: TYPE = SV3d.Matrix4by4; Plane: TYPE = SV3d.Plane; Point2d: TYPE = SV2d.Point2d; Point3d: TYPE = SV3d.Point3d; Poly3d: TYPE = SV3d.Poly3d; Projection: TYPE = SVModelTypes.Projection; -- {perspective, orthogonal} QualityMode: TYPE = SVModelTypes.QualityMode; Ray2d: TYPE = SV2d.Ray2d; StrokeEnd: TYPE = Imager.StrokeEnd; Vector3d: TYPE = SV3d.Vector3d; CreateCamera: PUBLIC PROC [viewName: Rope.ROPE, coordSys: CoordSystem, screenCS: CoordSystem, resolution: REAL, focalLength: REAL, projection: Projection, frame: FrameBox, clippingPlanes: LIST OF Plane, visibleAssemblies: LIST OF Rope.ROPE, style: DrawStyle, colorFilm: BOOL, useBoundBoxes: BOOL, useBoundSpheresForShadows: BOOL] RETURNS [camera: Camera] = { displayStyle: DisplayStyle _ print; camera _ NEW[CameraObj _ [viewName, coordSys, screenCS, resolution, focalLength, projection, frame, clippingPlanes, visibleAssemblies, style, colorFilm, fast, displayStyle, FALSE, useBoundBoxes, useBoundSpheresForShadows, [0,0,0]]]; }; -- end of CreateCamera PlaceCamera: PUBLIC PROC [camera: Camera, focus: Point3d, origin: Point3d, slant: REAL] = { zAxis: Vector3d _ SVVector3d.Sub[origin, focus]; SVCoordSys.SetMat[camera.coordSys, SVMatrix3d.MakeHorizontalMatFromZAxis[zAxis, origin]]; SVCoordSys.SetMat[camera.coordSys, SVMatrix3d.LocalRotateZ[SVCoordSys.GetMat[camera.coordSys], slant]]; }; SetFocalLengthCamera: PUBLIC PROC [camera: Camera, focalLength: REAL] = { camera.focalLength _ focalLength; }; SetQualityCamera: PUBLIC PROC [camera: Camera, qual: QualityMode] = { camera.quality _ qual; }; ColorFilmCamera: PUBLIC PROC [camera: Camera, colorFilm: BOOL] = { camera.colorFilm _ colorFilm; }; Clip: PUBLIC PROC [dc: Imager.Context, camera: Camera] = { downLeft, upRight: Point2d; IF camera.frame.fullScreen THEN RETURN; downLeft _ SVCoordSys.CameraToScreen[camera.frame.downLeft, camera.screenCS]; upRight _ SVCoordSys.CameraToScreen[camera.frame.upRight, camera.screenCS]; Imager.ClipRectangle[dc, [downLeft[1], downLeft[2], upRight[1] - downLeft[1], upRight[2] - downLeft[2]]]; }; DrawFrame: PUBLIC PROC [dc: Imager.Context, camera: Camera] = { downLeft, upRight: Point2d; IF camera.frame.fullScreen THEN RETURN; downLeft _ SVCoordSys.CameraToScreen[camera.frame.downLeft, camera.screenCS]; upRight _ SVCoordSys.CameraToScreen[camera.frame.upRight, camera.screenCS]; SVDraw.LineSandwich[dc, downLeft[1], downLeft[2], downLeft[1], upRight[2]]; SVDraw.LineSandwich[dc, downLeft[1], upRight[2], upRight[1], upRight[2]]; SVDraw.LineSandwich[dc, upRight[1], upRight[2], upRight[1], downLeft[2]]; SVDraw.LineSandwich[dc, upRight[1], downLeft[2], downLeft[1], downLeft[2]]; }; -- fast, low quality DoProjection: PUBLIC PROC [point3d: Point3d, camera: Camera] RETURNS [newPoint: Point2d] = { IF camera.projection = orthogonal THEN { newPoint[1] _ point3d[1]; newPoint[2] _ point3d[2]; RETURN; } ELSE RETURN[SVMatrix3d.PerspectiveTrans[point3d, camera.focalLength]]; }; LocalToCamera: PUBLIC PROC [localPoint: Point3d, localCS: CoordSystem, cameraCS: CoordSystem] RETURNS [cameraPoint: Point3d] = { cameraPoint _ SVMatrix3d.Update[localPoint, SVCoordSys.WRTCamera[localCS, cameraCS]]; }; LocalToCameraInternal: PROC [localPoint: Point3d, localCamera: Matrix4by4] RETURNS [cameraPoint: Point3d] = { cameraPoint _ SVMatrix3d.Update[localPoint, localCamera]; }; LocalToWorld: PUBLIC PROC [localPt: Point3d, localCS: CoordSystem] RETURNS [worldPt: Point3d] = { worldPt _ SVMatrix3d.Update[localPt, SVCoordSys.WRTWorld[localCS]]; }; VectorToWorld: PUBLIC PROC [vector: Vector3d, localCS: CoordSystem] RETURNS [worldVector: Vector3d] = { worldVector _ SVMatrix3d.UpdateVectorWithInverse[SVCoordSys.FindWorldInTermsOf[localCS], vector]; }; SetCP: PUBLIC PROC [dc: Imager.Context, point3d: Point3d, camera: Camera, localCamera: Matrix4by4] = { camera.lastPoint _ SVMatrix3d.Update[point3d, localCamera]; }; SetCPAbsolute: PUBLIC PROC [dc: Imager.Context, point3d: Point3d, camera: Camera] = { camera.lastPoint _ point3d; }; DrawTo: PUBLIC PROC [dc: Imager.Context, point3d: Point3d, camera: Camera, localCamera: Matrix4by4, strokeWidth: REAL _ 1.0] = { newP1, newP2, thisCameraPoint: Point3d; lastScreenPoint, thisScreenPoint: Point2d; nullSegment: BOOL; thisCameraPoint _ SVMatrix3d.Update[point3d, localCamera]; [newP1, newP2, ----, ----, nullSegment] _ SVPolygon3d.ClipLineSegmentToPlanes[camera.lastPoint, thisCameraPoint, camera.clippingPlanes]; IF nullSegment THEN {camera.lastPoint _ thisCameraPoint; RETURN}; lastScreenPoint _ DoProjection[newP1, camera]; lastScreenPoint _ SVCoordSys.CameraToScreen[lastScreenPoint, camera.screenCS]; thisScreenPoint _ DoProjection[newP2, camera]; thisScreenPoint _ SVCoordSys.CameraToScreen[thisScreenPoint, camera.screenCS]; IF camera.quality = quality THEN { -- use round-ended strokes Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, round]; } ELSE { -- not a quality camera. Draw it fast. Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, butt]; }; Imager.MaskVector[dc, [lastScreenPoint[1], lastScreenPoint[2]], [thisScreenPoint[1], thisScreenPoint[2]]]; camera.lastPoint _ thisCameraPoint; }; -- end of DrawTo DrawToAbsolute: PUBLIC PROC [dc: Imager.Context, point3d: Point3d, camera: Camera, strokeWidth: REAL _ 1.0] = { newP1, newP2, thisCameraPoint: Point3d; lastScreenPoint, thisScreenPoint: Point2d; newP1isP1, newP2isP2, nullSegment: BOOL; thisCameraPoint _ point3d; [newP1, newP2, newP1isP1, newP2isP2, nullSegment] _ SVPolygon3d.ClipLineSegmentToPlanes[camera.lastPoint, thisCameraPoint, camera.clippingPlanes]; IF nullSegment THEN {camera.lastPoint _ thisCameraPoint; RETURN}; lastScreenPoint _ DoProjection[newP1, camera]; lastScreenPoint _ SVCoordSys.CameraToScreen[lastScreenPoint, camera.screenCS]; thisScreenPoint _ DoProjection[newP2, camera]; thisScreenPoint _ SVCoordSys.CameraToScreen[thisScreenPoint, camera.screenCS]; IF camera.quality = quality THEN { -- use round-ended strokes Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, round]; } ELSE { -- not a quality camera. Use butt-ended strokes Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, butt]; }; Imager.MaskVector[dc, [lastScreenPoint[1], lastScreenPoint[2]], [thisScreenPoint[1], thisScreenPoint[2]]]; camera.lastPoint _ thisCameraPoint; }; -- end of DrawToAbsolute MoveTo: PUBLIC PROC [point3d: Point3d, camera: Camera, localCS: CoordSystem] RETURNS [path: ImagerPath.Trajectory] = { objPoint: Point2d; point3d _ LocalToCamera[point3d, localCS, camera.coordSys]; objPoint _ DoProjection[point3d, camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords path _ ImagerPath.MoveTo[[objPoint[1], objPoint[2]]]; }; MoveToAbsolute: PUBLIC PROC [point3d: Point3d, camera: Camera] RETURNS [path: ImagerPath.Trajectory] = { objPoint: Point2d; objPoint _ DoProjection[point3d, camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords path _ ImagerPath.MoveTo[[objPoint[1], objPoint[2]]]; }; LineTo: PUBLIC PROC [path: ImagerPath.Trajectory, point3d: Point3d, camera: Camera, localCS: CoordSystem] RETURNS [newPath: ImagerPath.Trajectory] = { objPoint: Point2d; point3d _ LocalToCamera[point3d, localCS, camera.coordSys]; -- puts in CAMERA objPoint _ DoProjection[point3d, camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords newPath _ ImagerPath.LineTo[path, [objPoint[1], objPoint[2]]]; }; LineToAbsolute: PUBLIC PROC [path: ImagerPath.Trajectory, point3d: Point3d, camera: Camera] RETURNS [newPath: ImagerPath.Trajectory] = { objPoint: Point2d; objPoint _ DoProjection[point3d, camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords newPath _ ImagerPath.LineTo[path, [objPoint[1], objPoint[2]]]; }; DrawStroke: PUBLIC PROC [dc: Imager.Context, path: ImagerPath.Trajectory, width: REAL _ 1, closed: BOOLEAN _ FALSE, ends: StrokeEnd _ butt] = { Imager.SetStrokeWidth[dc, width]; Imager.SetStrokeEnd[dc, ends]; Imager.MaskStrokeTrajectory[dc, path, closed]; }; DrawFilled: PUBLIC PROC[dc: Imager.Context, path: ImagerPath.Trajectory, parityFill: BOOLEAN _ FALSE] = { Imager.MaskFillTrajectory[dc, path, parityFill]; }; DrawPolygonAbsolute: PUBLIC PROC [dc: Imager.Context, poly: Poly3d, width: REAL _ 1, ends: StrokeEnd _ butt, camera: Camera] = { outline: ImagerPath.Trajectory; objPoint: Point2d; poly _ SVPolygon3d.ClipPolyToPlanes[poly, camera.clippingPlanes]; objPoint _ DoProjection[poly[0], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.MoveTo[[objPoint[1], objPoint[2]]]; FOR i: NAT IN[1..poly.len) DO objPoint _ DoProjection[poly[i], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.LineTo[outline, [objPoint[1], objPoint[2]]]; ENDLOOP; Imager.SetStrokeWidth[dc, width]; Imager.SetStrokeEnd[dc, ends]; Imager.MaskStrokeTrajectory[dc, outline, TRUE]; }; DrawLine: PUBLIC PROC [dc: Imager.Context, start: Point3d, end: Point3d, camera: Camera, localCS: CoordSystem] = { lastScreenPoint, thisScreenPoint: Point2d; newP1isP1, newP2isP2, nullSegment: BOOL; newP1, newP2: Point3d; start _ LocalToCamera[start, localCS, camera.coordSys]; -- puts in CAMERA end _ LocalToCamera[end, localCS, camera.coordSys]; -- puts in CAMERA [newP1, newP2, newP1isP1, newP2isP2, nullSegment] _ SVPolygon3d.ClipLineSegmentToPlanes[start, end, camera.clippingPlanes]; IF nullSegment THEN RETURN; lastScreenPoint _ DoProjection[newP1, camera]; lastScreenPoint _ SVCoordSys.CameraToScreen[lastScreenPoint, camera.screenCS]; -- puts into SCREEN coords thisScreenPoint _ DoProjection[newP2, camera]; thisScreenPoint _ SVCoordSys.CameraToScreen[thisScreenPoint, camera.screenCS]; -- puts into SCREEN coords IF camera.quality = quality THEN { -- use round-ended strokes line: ImagerPath.Trajectory; line _ ImagerPath.MoveTo[[lastScreenPoint[1], lastScreenPoint[2]]]; line _ ImagerPath.LineTo[line, [thisScreenPoint[1], thisScreenPoint[2]]]; Imager.SetStrokeWidth[dc, 1]; Imager.SetStrokeEnd[dc, round]; Imager.MaskStrokeTrajectory[dc, line, FALSE]; } ELSE { -- not a quality camera. Just draw line segment. Imager.MaskVector[dc, [lastScreenPoint[1], lastScreenPoint[2]], [thisScreenPoint[1], thisScreenPoint[2]]]; }; }; -- end of DrawLine DrawLineOnScreen: PUBLIC PROC [dc: Imager.Context, screenPoint1, screenPoint2: Point2d, camera: Camera] = { IF camera.quality = quality THEN { -- use round-ended strokes line: ImagerPath.Trajectory; line _ ImagerPath.MoveTo[[screenPoint1[1], screenPoint1[2]]]; line _ ImagerPath.LineTo[line, [screenPoint2[1], screenPoint2[2]]]; Imager.SetStrokeWidth[dc, 1]; Imager.SetStrokeEnd[dc, round]; Imager.MaskStrokeTrajectory[dc, line, FALSE]; } ELSE { -- not a quality camera. Just draw line segment. Imager.MaskVector[dc, [screenPoint1[1], screenPoint1[2]], [screenPoint2[1], screenPoint2[2]]]; }; }; DrawChar: PUBLIC PROC [dc: Imager.Context, c: CHARACTER, camera: Camera] = { screenPoint: Point2d; screenPoint _ DoProjection[camera.lastPoint, camera]; screenPoint _ SVCoordSys.CameraToScreen[screenPoint, camera.screenCS]; -- puts into SCREEN coords Imager.SetXY[dc, [screenPoint[1], screenPoint[2]]]; Imager.SetFont[dc, coordFont]; Imager.ShowChar[dc, c]; }; -- end of DrawChar CameraPolygon: PROC [poly3d: Poly3d, localCamera: Matrix4by4] RETURNS [cameraPoly: Poly3d] = { IF poly3d.len = 4 THEN {cameraPoly _ quadPoly; SVPolygon3d.ClearPoly[cameraPoly]} ELSE cameraPoly _ SVPolygon3d.CreatePoly[poly3d.len]; FOR i: NAT IN [0..poly3d.len) DO cameraPoly _ SVPolygon3d.AddPolyPoint[cameraPoly, LocalToCameraInternal[poly3d[i], localCamera]]; ENDLOOP; }; DrawArea: PUBLIC PROC [dc: Imager.Context, localNormal: Vector3d, poly3d: Poly3d, artwork: Artwork, lightSources: LightSourceList, camera: Camera, localCS: CoordSystem] = { worldPoint3d: Point3d; objPoint: Point2d; colorshade: Imager.Color; r, g, b, scaleFactor: REAL; cameraNormal: Vector3d; worldNormal: Vector3d; eyepoint: Point3d; cameraPolygon: Poly3d; outline: ImagerPath.Trajectory; cameraNormal _ SVMatrix3d.UpdateVectorWithInverse[SVCoordSys.FindCameraInTermsOf[localCS, camera.coordSys], localNormal]; worldNormal _ SVMatrix3d.UpdateVectorWithInverse[SVCoordSys.FindWorldInTermsOf[localCS], localNormal]; IF cameraNormal[3]<=0 THEN RETURN; worldPoint3d _ SVMatrix3d.Update[poly3d[0], SVCoordSys.WRTWorld[localCS]]; eyepoint _ LocalToWorld[[0,0,camera.focalLength], camera.coordSys]; SELECT artwork.material FROM plastic => [r,g,b] _ SVShading.DiffuseAndSpecularReflectance[eyepoint, worldNormal, worldPoint3d, artwork.color, lightSources]; chalk => [r,g,b] _ SVShading.DiffuseReflectance[worldNormal, worldPoint3d, artwork.color, lightSources]; ENDCASE => ERROR; cameraPolygon _ CameraPolygon[poly3d, SVCoordSys.WRTCamera[localCS, camera.coordSys]]; cameraPolygon _ SVPolygon3d.ClipPolyToPlanes[cameraPolygon, camera.clippingPlanes]; objPoint _ DoProjection[cameraPolygon[0], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.MoveTo[[objPoint[1], objPoint[2]]]; FOR i: NAT IN[1..cameraPolygon.len) DO objPoint _ DoProjection[cameraPolygon[i], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.LineTo[outline, [objPoint[1], objPoint[2]]]; ENDLOOP; scaleFactor _ MAX[r,g,b]; IF scaleFactor > 1.0 THEN { r _ r/scaleFactor; g _ g/scaleFactor; b _ b/scaleFactor; }; colorshade _ ImagerColor.ColorFromRGB[[r,g,b]]; SetColor[dc, camera, colorshade]; Imager.MaskFillTrajectory[dc, outline]; }; quadPoly: Poly3d _ SVPolygon3d.CreatePoly[4]; ComputeShading: PROC [cameraNormal: Vector3d, cameraPolygon: Poly3d, artwork: Artwork, lightSources: LightSourceList, camera: Camera, hiddenLine: BOOL] RETURNS [colorshade: Imager.Color, backfacing: BOOL _ FALSE] = { IF NOT hiddenLine THEN { r, g, b, scaleFactor: REAL; worldNormal: Vector3d; eyepoint, worldPoint3d: Point3d; worldNormal _ SVMatrix3d.UpdateVectorWithInverse[SVCoordSys.FindWorldInTermsOf[camera.coordSys], cameraNormal]; worldPoint3d _ LocalToWorld[cameraPolygon[0], camera.coordSys]; -- poly[0] in World eyepoint _ LocalToWorld[[0,0,camera.focalLength], camera.coordSys]; -- eyepoint in World BEGIN OPEN SVVector3d; IF DotProduct[Sub[worldPoint3d, eyepoint], worldNormal] >=0 THEN RETURN[NIL, TRUE]; END; IF artwork = NIL THEN RETURN[NIL, FALSE]; SELECT artwork.material FROM plastic => [r,g,b] _ SVShading.DiffuseAndSpecularReflectance[eyepoint, worldNormal, worldPoint3d, artwork.color, lightSources]; chalk => [r,g,b] _ SVShading.DiffuseReflectance[worldNormal, worldPoint3d, artwork.color, lightSources]; ENDCASE => ERROR; scaleFactor _ MAX[r,g,b]; IF scaleFactor > 1.0 THEN { r _ r/scaleFactor; g _ g/scaleFactor; b _ b/scaleFactor; }; colorshade _ ImagerColor.ColorFromRGB[[r,g,b]]; } ELSE colorshade _ Imager.black; }; DrawAreaNormalAbsolute: PUBLIC PROC [dc: Imager.Context, cameraNormal: Vector3d, poly3d: Poly3d, artwork: Artwork, lightSources: LightSourceList, camera: Camera, localCamera: Matrix4by4, hiddenLine: BOOL, strokeColor: Color _ NIL] = { objPoint: Point2d; colorshade: Imager.Color; cameraPolygon: Poly3d; outline: ImagerPath.Trajectory; backfacing: BOOL _ FALSE; cameraPolygon _ CameraPolygon[poly3d, localCamera]; [colorshade, backfacing] _ ComputeShading[cameraNormal, cameraPolygon, artwork, lightSources, camera, hiddenLine]; IF backfacing THEN RETURN; objPoint _ DoProjection[cameraPolygon[0], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.MoveTo[[objPoint[1], objPoint[2]]]; FOR i: NAT IN[1..cameraPolygon.len) DO objPoint _ DoProjection[cameraPolygon[i], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.LineTo[outline, [objPoint[1], objPoint[2]]]; ENDLOOP; DrawOutlineCached[dc, outline, colorshade, camera, hiddenLine, strokeColor]; }; DrawAreaAbsolute: PUBLIC PROC [dc: Imager.Context, cameraNormal: Vector3d, poly3d: Poly3d, artwork: Artwork, lightSources: LightSourceList, camera: Camera, hiddenLine: BOOL, strokeColor: Color _ NIL] = { outline: ImagerPath.Trajectory; objPoint: Point2d; colorshade: Color; backfacing: BOOL _ FALSE; [colorshade, backfacing] _ ComputeShading[cameraNormal, poly3d, artwork, lightSources, camera, hiddenLine]; IF backfacing THEN RETURN; objPoint _ DoProjection[poly3d[0], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.MoveTo[[objPoint[1], objPoint[2]]]; FOR i: NAT IN[1..poly3d.len) DO objPoint _ DoProjection[poly3d[i], camera]; objPoint _ SVCoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords outline _ ImagerPath.LineTo[outline, [objPoint[1], objPoint[2]]]; ENDLOOP; DrawOutlineCached[dc, outline, colorshade, camera, hiddenLine, strokeColor]; }; -- end of DrawAreaAbsolute CacheContents: TYPE = REF CacheContentsObj; CacheContentsObj: TYPE = RECORD [ lineObject: Imager.Object, fillObject: Imager.Object ]; AlmostZeroVec: PROC [vec: Imager.VEC] RETURNS [BOOL] = { epsilon: REAL = 0.5; -- half a pixel RETURN[ABS[vec.x] < epsilon AND ABS[vec.y] < epsilon]; }; LookUpOutline: PROC [a: ImagerPath.Trajectory] RETURNS [lineObject: Imager.Object, fillObject: Imager.Object] = { CompareShape: FunctionCache.CompareProc = { OPEN Vectors2d; b: ImagerPath.Trajectory _ NARROW[argument]; aPt, bPt, diff, thisDiff: Imager.VEC; trajA, trajB: ImagerPath.Trajectory; IF a.length # b.length THEN RETURN[FALSE]; trajA _ a; trajB _ b; aPt _ trajA.lp; bPt _ trajB.lp; diff _ Sub[aPt, bPt]; trajA _ trajA.prev; trajB _ trajB.prev; FOR i: INT IN [1..(a.length-1)] DO thisDiff _ Add[Sub[trajB.lp, trajA.lp], diff]; IF NOT AlmostZeroVec[thisDiff] THEN RETURN[FALSE]; trajA _ trajA.prev; trajB _ trajB.prev; ENDLOOP; RETURN[TRUE]; }; value: FunctionCache.Range; cacheContents: CacheContents; maxPixels: REAL _ 10000.0; ok: BOOL; [value, ok] _ FunctionCache.Lookup[x: cache, compare: CompareShape, clientID: $SVPolygonObject]; IF ok THEN cacheContents _ NARROW[value] ELSE RETURN[NIL, NIL]; lineObject _ cacheContents.lineObject; fillObject _ cacheContents.fillObject; }; showBox: BOOL _ FALSE; cache: FunctionCache.Cache; OutlineBox: PROC [outline: ImagerPath.Trajectory] RETURNS [rect: Imager.Rectangle] = { loX, loY, hiX, hiY: REAL; loX _ hiX _ outline.lp.x; loY _ hiY _ outline.lp.y; FOR thisTraj: ImagerPath.Trajectory _ outline.prev, thisTraj.prev UNTIL thisTraj = NIL DO loX _ MIN[thisTraj.lp.x, loX]; loY _ MIN[thisTraj.lp.y, loY]; hiX _ MAX[thisTraj.lp.x, hiX]; hiY _ MAX[thisTraj.lp.y, hiY]; ENDLOOP; loX _ loX - 2.0; loY _ loY - 2.0; hiX _ hiX + 2.0; hiY _ hiY + 2.0; rect _ [loX, loY, hiX-loX, hiY-loY]; }; DrawOutlineCached: PROC [dc: Imager.Context, outline: ImagerPath.Trajectory, color: Imager.Color, camera: Camera, hiddenLine: BOOL, strokeColor: Color _ NIL] = { lineObject, fillObject: Imager.Object; cacheContents: CacheContents; IF NOT hiddenLine THEN { OldDrawOutline[dc, outline, color, camera, strokeColor]; RETURN; }; [lineObject, fillObject] _ LookUpOutline[outline]; IF lineObject = NIL THEN { -- put it in the cache clipR: Imager.Rectangle _ OutlineBox[outline]; IF showBox THEN { Imager.SetGray[dc, 0.2]; Imager.MaskRectangle[dc, clipR]; }; lineObject _ NEW[Imager.ObjectRep _ [draw: OutlineDrawLines, clip: clipR, data: outline]]; fillObject _ NEW[Imager.ObjectRep _ [draw: OutlineDrawFill, clip: clipR, data: outline]]; cacheContents _ NEW[CacheContentsObj _ [lineObject: lineObject, fillObject: fillObject]]; FunctionCache.Insert[cache, outline, cacheContents, 60, $SVPolygonObject]; }; BEGIN traj: ImagerPath.Trajectory _ NARROW[lineObject.data]; interactive: BOOL _ camera.quality = fast; position: Imager.VEC _ Vectors2d.Sub[outline.lp, traj.lp]; Imager.SetColor[dc, Imager.white]; Imager.DrawObject[context: dc, object: fillObject, interactive: interactive, position: position]; Imager.SetColor[dc, Imager.black]; Imager.DrawObject[context: dc, object: lineObject, interactive: interactive, position: position]; END; }; OutlineDrawFill: PROC [self: Imager.Object, context: Imager.Context] = { traj: ImagerPath.Trajectory _ NARROW[self.data]; Imager.SetStrokeWidth[context, 2.0]; Imager.SetStrokeEnd[context, round]; -- needed to make object caching work Imager.SetStrokeJoint[context, round]; -- needed to make object caching work Imager.MaskFillTrajectory[context, traj]; }; OutlineDrawLines: PROC [self: Imager.Object, context: Imager.Context] = { traj: ImagerPath.Trajectory _ NARROW[self.data]; Imager.SetStrokeWidth[context, 2.0]; Imager.SetStrokeEnd[context, round]; -- needed to make object caching work Imager.SetStrokeJoint[context, round]; -- needed to make object caching work Imager.MaskStrokeTrajectory[context, traj, TRUE]; }; OldDrawOutline: PROC [dc: Imager.Context, outline: ImagerPath.Trajectory, color: Imager.Color, camera: Camera, strokeColor: Color _ NIL] = { IF color # NIL THEN { SetColor[dc, camera, color]; Imager.MaskFillTrajectory[dc, outline]; }; IF strokeColor # NIL THEN { Imager.SetStrokeWidth[dc, 2.0]; SetColor[dc, camera, strokeColor]; Imager.SetStrokeJoint[dc, bevel]; Imager.MaskStrokeTrajectory[dc, outline, TRUE]; }; }; DrawHorizonOfPlane: PUBLIC PROC [dc: Imager.Context, plane: Plane, camera: Camera, localCS: CoordSystem] = { plane _ SVMatrix3d.UpdatePlaneWithInverse[plane, SVCoordSys.FindCameraInTermsOf[localCS, camera.coordSys]]; DrawHorizonOfPlaneAbsolute[dc, plane, camera]; }; -- end of DrawHorizonOfPlane DrawHorizonOfPlaneAbsolute: PUBLIC PROC [dc: Imager.Context, plane: Plane, camera: Camera] = { almostZero: REAL _ 1.0e-12; t: REAL; vanishCamera1, vanishCamera2, vanishScreen1, vanishScreen2: Point2d; IF Abs[plane.A] <= almostZero AND Abs[plane.B] <= almostZero THEN RETURN; IF Abs[plane.B] > Abs[plane.A] THEN { y1, y2: REAL; y1 _ (plane.A+plane.C)/plane.B; y2 _ (plane.C-plane.A)/plane.B; t _ camera.focalLength; -- since we have chosen direction[3] to be -1 vanishCamera1[1] _ -t; vanishCamera1[2] _ t*y1; vanishScreen1 _ SVCoordSys.CameraToScreen[vanishCamera1, camera.screenCS]; vanishCamera2[1] _ t; vanishCamera2[2] _ t*y2; vanishScreen2 _ SVCoordSys.CameraToScreen[vanishCamera2, camera.screenCS]; } ELSE { x1, x2: REAL; x1 _ (plane.B+plane.C)/plane.A; x2 _ (plane.C-plane.B)/plane.A; t _ camera.focalLength; -- since we have chosen direction[3] to be -1 vanishCamera1[1] _ t*x1; vanishCamera1[2] _ -t; vanishScreen1 _ SVCoordSys.CameraToScreen[vanishCamera1, camera.screenCS]; vanishCamera2[1] _ t*x2; vanishCamera2[2] _ t; vanishScreen2 _ SVCoordSys.CameraToScreen[vanishCamera2, camera.screenCS]; }; DrawLineOnScreen[dc, vanishScreen1, vanishScreen2, camera]; }; -- end of DrawHorizonOfPlaneAbsolute DrawInfiniteLine: PUBLIC PROC [dc: Imager.Context, p1, p2: Point3d, camera: Camera, localCS: CoordSystem, clippedBy: Imager.Rectangle] = { cameraP1, cameraP2: Point3d; screenP1, screenP2, vanishCamera, vanishScreen: Point2d; almostZero: REAL _ 1.0e-12; count: NAT; ray: Ray2d; params: ARRAY[1..2] OF REAL; cameraP1 _ LocalToCamera[p1, localCS, camera.coordSys]; cameraP2 _ LocalToCamera[p2, localCS, camera.coordSys]; screenP1 _ DoProjection[cameraP1, camera]; screenP1 _ SVCoordSys.CameraToScreen[screenP1, camera.screenCS]; screenP2 _ DoProjection[cameraP2, camera]; screenP2 _ SVCoordSys.CameraToScreen[screenP2, camera.screenCS]; IF camera.projection = perspective AND Abs[cameraP2[3]-cameraP1[3]] > almostZero THEN { -- there is a vanishing point t: REAL; t _ -camera.focalLength/(cameraP2[3]-cameraP1[3]); vanishCamera[1] _ t*(cameraP2[1]-cameraP1[1]); vanishCamera[2] _ t*(cameraP2[2]-cameraP1[2]); vanishScreen _ SVCoordSys.CameraToScreen[vanishCamera, camera.screenCS]; IF cameraP2[3] > cameraP1[3] THEN ray _ SVLines2d.CreateRayFromPoints[vanishScreen, screenP2] ELSE ray _ SVLines2d.CreateRayFromPoints[vanishScreen, screenP1]; [count, params] _ SVLines2d.RayMeetsBox[ray, clippedBy.x, clippedBy.y, clippedBy.x + clippedBy.w, clippedBy.y + clippedBy.h]; IF count = 0 THEN RETURN; t _ params[count]; -- use the highest value of t. screenP2 _ SVLines2d.EvalRay[ray, t]; IF camera.quality = quality THEN { -- use round-ended strokes line: ImagerPath.Trajectory; line _ ImagerPath.MoveTo[[vanishScreen[1], vanishScreen[2]]]; line _ ImagerPath.LineTo[line, [screenP2[1], screenP2[2]]]; Imager.SetStrokeWidth[dc, 1]; Imager.SetStrokeEnd[dc, round]; Imager.MaskStrokeTrajectory[dc, line, FALSE]; } ELSE { -- not a quality camera. Just draw line segment. Imager.MaskVector[dc, [vanishScreen[1], vanishScreen[2]], [screenP2[1], screenP2[2]]]; }; } ELSE { ray _ SVLines2d.CreateRayFromPoints[screenP1, screenP2]; [count, params] _ SVLines2d.LineRayMeetsBox[ray, clippedBy.x, clippedBy.y, clippedBy.x + clippedBy.w, clippedBy.y + clippedBy.h]; IF count = 2 THEN { screenP1 _ SVLines2d.EvalRay[ray, params[1]]; screenP2 _ SVLines2d.EvalRay[ray, params[2]]; IF camera.quality = quality THEN { -- use round-ended strokes line: ImagerPath.Trajectory; line _ ImagerPath.MoveTo[[screenP1[1], screenP1[2]]]; line _ ImagerPath.LineTo[line, [screenP2[1], screenP2[2]]]; Imager.SetStrokeWidth[dc, 1]; Imager.SetStrokeEnd[dc, round]; Imager.MaskStrokeTrajectory[dc, line, FALSE]; } ELSE { -- not a quality camera. Just draw line segment. Imager.MaskVector[dc, [screenP1[1], screenP1[2]], [screenP2[1], screenP2[2]]]; }; }; }; }; -- end of DrawInfiniteLine Abs: PROC [r: REAL] RETURNS [REAL] = { RETURN[IF r >= 0 THEN r ELSE -r]; }; DrawInfiniteLineAbsolute: PUBLIC PROC [dc: Imager.Context, p1, p2: Point3d, camera: Camera, localCS: CoordSystem, clippedBy: Imager.Rectangle] = {}; DrawInfinitePlaneWireFrame: PUBLIC PROC [dc: Imager.Context, plane: Plane, camera: Camera, localCS: CoordSystem] = { }; DrawInfinitePlaneWireFrameAbsolute: PUBLIC PROC [dc: Imager.Context, plane: Plane, camera: Camera, localCS: CoordSystem] = {}; DrawInfinitePlaneShaded: PUBLIC PROC [dc: Imager.Context, plane: Plane, artwork: Artwork, lightSources: LightSourceList, camera: Camera, localCS: CoordSystem] = {}; DrawInfinitePlaneShadedAbsolute: PUBLIC PROC [dc: Imager.Context, plane: Plane, artwork: Artwork, lightSources: LightSourceList, camera: Camera] = {}; SetColor: PRIVATE PROC [dc: Imager.Context, camera: Camera, color: Color] = { IF camera.colorFilm THEN Imager.SetColor [dc, color] ELSE { intensity: REAL _ ImagerColorPrivate.IntensityFromColor[NARROW[color]]; Imager.SetColor[dc, ImagerColor.ColorFromGray[1.0-intensity]] }; }; DrawStyleToRope: PUBLIC PROC [drawStyle: DrawStyle] RETURNS [rope: Rope.ROPE] = { SELECT drawStyle FROM wire => rope _ "wireframe"; shaded => rope _ "shaded"; hiddenLine => rope _ "hiddenLine"; rayCast => rope _ "rayCast"; normals => rope _ "normals"; ENDCASE => ERROR UpdateThisCode; }; RopeToDrawStyle: PUBLIC PROC [rope: Rope.ROPE] RETURNS [drawStyle: DrawStyle, success: BOOL] = { success _ TRUE; SELECT TRUE FROM Rope.Equal[rope, "wireframe", FALSE] => drawStyle _ wire; Rope.Equal[rope, "shaded", FALSE] => drawStyle _ shaded; Rope.Equal[rope, "hiddenLine", FALSE] => drawStyle _ hiddenLine; Rope.Equal[rope, "rayCast", FALSE] => drawStyle _ rayCast; Rope.Equal[rope, "normals", FALSE] => drawStyle _ normals; ENDCASE => success _ FALSE; }; ProjectionToRope: PUBLIC PROC [projection: Projection] RETURNS [rope: Rope.ROPE] = { SELECT projection FROM perspective => rope _ "perspect"; orthogonal => rope _ "ortho"; ENDCASE => ERROR UpdateThisCode; }; RopeToProjection: PUBLIC PROC [rope: Rope.ROPE] RETURNS [projection: Projection, success: BOOL] = { success _ TRUE; SELECT TRUE FROM Rope.Equal[rope, "perspect", FALSE] => projection _ perspective; Rope.Equal[rope, "ortho", FALSE] => projection _ orthogonal; ENDCASE => success _ FALSE; }; UpdateThisCode: PUBLIC ERROR = CODE; coordFont: ImagerFont.Font; Init: PROC [] = { coordFont _ ImagerFont.Scale[ImagerFont.Find["xerox/TiogaFonts/Helvetica14B"], 1.0]; cache _ FunctionCache.Create[maxEntries: 100]; }; Init[]; END. bFile: SVGraphicsImpl.mesa Last edited by Bier on September 23, 1987 7:40:07 pm PDT Contents: Implementation of a simple graphics package for 3d renderings. xAxis should be normal to zAxis, parallel to the xz plane, and counter-clockwise from the projection of z onto the xz plane. If z is vertical, I arbitrarily chose x axis aligned with world x axis. Now, rotate the coordSys counter-clockwise slant degrees around its z axis. How you wish to make the speed/print quality trade off puts into SCREEN coords Perform a perspective or orthogonal projection on point3d. Find out how much of the line defined by point3d (in CAMERA) and lastCameraPoint is visible. If this section includes lastCameraPoint and camera.quality = qual and we are working on a visible section of polygon, then LineTo lastCameraPoint and LineTo point3d. If this is a new visible section, then MoveTo lastCameraPoint and LineTo point3d. Like DrawTo, except that point3d is assumed to be already in CAMERA coordinates. Assumes point3d is a point in CAMERA coordinate system. Draws the edges of poly assuming that the vertex coordinates are CAMERA coordinates. Assumes poly3d in camera coords. Clip the polygon against the camera's clipping planes to produce a new clipped polygon. Like DrawTo except that starting and ending points are given at the same time. Converts a polygon from local to CAMERA coordinates Given an ordered list (array) of Point3d's, defined in the current local coordinate system, find the corresponding WORLD points, and project the polygon they define onto the image plane with a perspective projection. Find the real normal. A reverse-facing surface is one which is not visible to the camera. In our current world of opaque surfaces, we need not draw reverse-facing surfaces. To detect a reverse-facing surface, take its normal N. Find the dot product of N with the vector from the a point on the surface to the camera lens. If the result is negative, the surface is reverse-facing. A simpler test which removes most reverse-facing surfaces is this one: Don't draw reverse-facing surfaces. Look at the first point. Use this point to estimate surface color Find the polygon in camera coordinates. Clip this polygon against the camera's clipping planes. Move to the first point. Create a path which forms the edges of this area. Use the first point point to estimate surface color. given a Poly3d, defined in local coordinates, find the WORLD points, and project the polygon onto the image plane with the specified projection. cameraPolygon _ SVPolygon3d.ClipPolyToPlanes[cameraPolygon, camera.clippingPlanes]; -- for now, suppress clipping Build an Imager Trajectory. Create a path which forms the edges of this area. Assumes poly3d in camera coords. poly3d _ SVPolygon3d.ClipPolyToPlanes[poly3d, camera.clippingPlanes]; -- for now, suppress clipping Build an Imager Trajectory. Imager.MaskFillTrajectory[dc, outline]; CompareProc: TYPE ~ PROC [argument: Domain] RETURNS [good: BOOL]; Allow for stroke width. Given a plane, in local coordinates, convert the plane to CAMERA coordinates and then use DrawHorizonOfPlaneAbsolute. Given a plane, in CAMERA coordinates, find two points, in CAMERA coordinates, which are on the projection of the horizon onto the screen. Convert to SCREEN coordinates and draw as an infinite line. If the plane is parallel to the screen, then there is no horizon. The plane is more nearly horizontal than vertical. Find two direction vectors which are parallel to the plane. Choose the first to be [-1,y,-1] and the second to be [1,y,-1]. If the plane is [A, B, C, D], then the first vector has the property that [A, B, C] dot [-1, y, -1] = 0. That is: -A + By -C = 0. y1 = (A+C)/B. Likewise, A + By -C = 0. y2 = (C-A)/B. Find the first vanishing point. Find the second vanishing point. The plane is more nearly vertical than horizontal. Proceed as above, but use the vectors [x, -1, -1] and [x, 1, -1]. The two equations are: Ax -B -C = 0. x1 = (B+C)/A. and Ax +B -C = 0. x2 = (C-B)/A. Find the first vanishing point. Find the second vanishing point. p1 and p2 are assumed to be in local coordinates. First find p1 and p2 in CAMERA coordinates: Next find the vanishing point. "Shoot a ray" with direction p2-p1 from the eyepoint and find its intersection with the screen. In CAMERA coordinates, the screen has equation z = 0. The eyepoint is at [0,0,focalLength]. Hence, our ray has equation R[t] = [0,0,focalLength] + t*(p2-p1). z(t) = focalLength+t*(p2[3]-p1[3]). So the intersection occurs where z=0, i.e. t = -focalLength/(p2[3]-p1[3]). If p2[3]=p1[3], then there is no vanishing point. There other point can be found by projecting p1 and p2 onto the screen and finding the intersection of the ray (from the vanishing point to whichever has the farther (less negative) z component) with the bounding box or our display context. There is no vanishing point. Find the projections of p1 and p2 onto the screen. Call the line determined by the resulting points L. Find the intersection of L with the bounding rectangle of our display context. Consider the ray r(t) = screenP1 + t*(screenP2-screenP1). p1 and p2 are assumed to be in CAMERA coordinates. plane is assumed to be in local coordinates. plane is assumed to be in CAMERA coordinates. plane is assumed to be in local coordinates. plane is assumed to be in CAMERA coordinates. Does the inverse of DrawStyleToRope. Does the inverse of ProjectionToRope. Κ(– "cedar" style˜Iheadšœ™Iprocšœ8™8LšœH™HL˜šΟk ˜ LšœΫ˜Ϋ—L˜šœ ˜Lšœ°˜·Lšœ ˜—Lš˜˜Lšœ œ˜%Lšœœœ ˜Lšœ œ˜)Lšœœ˜Lšœ œ˜-Lšœœ˜/Lšœ œ˜)Lšœ œ˜'Lšœ œ˜-Lšœœ˜2Lšœ œ˜#Lšœœ˜Lšœ œ˜Lšœ œ˜Lšœœ˜Lšœ œ8˜HLšœ œ˜-L˜Lšœ œ˜#Lšœ œ˜L˜L˜—šΟn œ œœ<œœ;œœœœœœœœœž˜ζL˜#Lšœ œ‘œ6˜θLšœΟc˜L˜—šž œœœ:œ˜[Lšœ0˜0LšœΕ™ΕLšœY˜YL™KLšœg˜gLšœ˜—šžœœœœ˜ILšœ!˜!Lšœ˜—šžœœœ(˜ELšœ6™6Lšœ˜Lšœ˜—šžœœœœ˜BLšœ˜Lšœ˜—L˜šžœœœ)˜:Lšœ˜šœœœ˜'Lšœ™—LšœM˜MLšœK˜KLšœi˜iLšœ˜—L˜šž œœœ*˜@Lšœ˜Lšœœœ˜'LšœM˜MLšœK˜KLšœK˜KLšœI˜ILšœI˜ILšœK˜KLšœŸ˜—L˜šž œœœ$œ˜\L™:šœ œ˜(Lšœ˜Lšœ˜Lšœ˜L˜—Lšœœ;˜FLšœ˜—šž œœœDœ˜€LšœU˜ULšœ˜—šžœœΟuœœ˜mLšœ1 œ˜9Lšœ˜—šž œœœ*œ˜aLšœC˜CLšœ˜—šž œœœ*œ˜gLšœa˜aLšœ˜—L˜šžœœœ= œ˜fLšœ3 œ˜;L˜L˜—šž œœœ;˜ULšœ˜Lšœ˜—š žœœœ= œœ ˜€LšœΧ™ΧLšœ'˜'Lšœ*˜*Lšœ œ˜L˜Lšœ2 œ˜:šœ)˜)Lšœ^˜^—Lšœ œ&œ˜ALšœ.˜.LšœN˜NLšœ.˜.šœN˜NL˜—šœœŸ˜=Lšœ'˜'Lšœ˜L˜—šœŸ'˜.Lšœ'˜'Lšœ˜L˜—Lšœj˜jLšœ#˜#LšœŸ˜L˜—šžœœœEœ ˜oLšœP™PLšœ'˜'Lšœ*˜*L˜Lšœ#œ˜(Lšœ˜Lšœ’˜’Lšœ œ&œ˜ALšœ.˜.LšœN˜NLšœ.˜.šœN˜NL˜—šœœŸ˜=Lšœ'˜'Lšœ˜L˜—šœŸ0˜7Lšœ'˜'Lšœ˜L˜—Lšœj˜jLšœ#˜#LšœŸ˜L˜—šžœœœ:œ"˜vLšœ˜Lšœ<˜Ÿ˜OLšœ)˜)LšœAŸ˜[Lšœ>˜>Lšœ˜—šžœœœAœ%˜ˆLšœ˜Lšœ)˜)LšœAŸ˜[Lšœ>˜>Lšœ˜—š ž œœœ:œœœ˜Lšœ!˜!Lšœ˜Lšœ.˜.Lšœ˜—šž œ œ>œœ˜iLšœ0˜0Lšœ˜—L˜šžœ œ+œ1˜€L™TLšœ ™ Lšœ˜Lšœ˜L™WLšœA˜AL˜Lšœ)˜)LšœAŸ˜[Lšœ9˜9šœœœ˜Lšœ)˜)LšœAŸ˜[LšœA˜A—Lšœ˜Lšœ!˜!Lšœ˜Lšœ/˜/L˜L˜—šžœœœ]˜rL™OLšœ*˜*Lšœ#œ˜(Lšœ˜Lšœ:Ÿ˜KLšœ5Ÿ˜FLšœ{˜{Lšœ œœ˜Lšœ.˜.LšœOŸ˜iLšœ.˜.šœOŸ˜iL˜—šœœŸ˜=L˜LšœC˜CLšœI˜ILšœ˜Lšœ˜Lšœ-˜-L˜—šœŸ1˜8Lšœj˜jL˜—LšœŸ˜—šžœœœN˜kšœœŸ˜=L˜Lšœ=˜=LšœC˜CLšœ˜Lšœ˜Lšœ-˜-L˜—šœŸ1˜8Lšœ^˜^Lšœ˜—L˜—šžœ œ œ˜LL˜Lšœ5˜5LšœGŸ˜aLšœ3˜3Lšœ˜Lšœ˜L˜—L˜šž œœ œœ˜^Lšœ!œ ™3Lšœœ;˜QLšœ1˜5šœœœ˜ LšœX œ˜aLšœ˜—˜L˜——šžœœœ—˜¬LšœΨ™ΨLšœ˜Lšœ˜Lšœ˜Lšœœ˜Lšœ˜Lšœ˜Lšœ˜L˜Lšœ˜L˜Lšœ™Lšœy˜yLšœf˜fLšœι™ιLšœG™GLšœœœ˜#Lšœ#™#L˜Lšœ™LšœJ˜JL˜Lšœ(™(LšœC˜Cšœ˜Lšœ˜Lšœh˜hLšœœ˜—Lšœ'™'LšœW˜WL™7LšœT˜TL™Lšœ2˜2LšœAŸ˜[Lšœ9˜9Lšœ1™1šœœœ˜&Lšœ2˜2LšœAŸ˜[LšœA˜A—Lšœ˜Lšœœ˜šœœ˜Lšœ8˜8L˜—Lšœ/˜/Lšœ!˜!Lšœ'˜'Lšœ˜L˜—Lšœ-˜-L˜š žœœ~œœ(œœ˜Ψšœœ œ˜Lšœ4™4Lšœœ˜Lšœ˜Lšœ ˜ Lšœo˜oLšœ@Ÿ˜SLšœDŸ˜Xš˜Lšœ ˜Lš œ:œœœœ˜SLšœ˜—Lš œ œœœœœ˜)šœ˜Lšœ˜Lšœh˜hLšœœ˜—Lšœœ˜šœœ˜Lšœ8˜8L˜—Lšœ/˜/L˜—šœ˜L˜—L˜L˜—š žœœœ„ œœœ˜κLšœ™Lšœ˜Lšœ˜L˜Lšœ˜Lšœ œœ˜L˜Lšœ+ œ˜3Lšœr˜rLšœ œœ˜L˜Lšœq™qL™L™Lšœ2˜2LšœAŸ˜[Lšœ8˜8L˜Lšœ1™1šœœœ˜&Lšœ2˜2LšœAŸ˜[LšœA˜ALšœ˜—LšœL˜LLšœ˜L˜—š žœœœ‹œœ˜ΛLšœ ™ Lšœ˜Lšœ˜Lšœ˜Lšœ œœ˜L˜Lšœk˜kLšœ œœ˜L˜LšœFŸ™cL˜L™Lšœ+˜+LšœAŸ˜[Lšœ9˜9šœœœ˜Lšœ+˜+LšœAŸ˜[LšœA˜ALšœ˜—Lšœ'™'LšœL˜LLšœŸ˜—Icode˜Mšœœœ˜+šœœœ˜!Mšœ˜Mšœ˜M˜M˜—š ž œœœœœ˜8Mšœ œŸ˜$Mšœœœœ˜6M˜M˜—šž œœœ;˜qšž œ˜+Mš œ œœœœ™AMšΠbkΟb ˜Mšœœ ˜,Mšœ!œ˜%Mšœ$˜$Mšœœœœ˜*Mšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ(˜(šœœœ˜"Mšœ.˜.Mš œœœœœ˜2Mšœ(˜(Mšœ˜—Mšœœ˜ M˜M˜—M•StartOfExpansionj[x: FunctionCache.Cache, compare: FunctionCache.CompareProc, clientID: FunctionCache.ClientID _ NIL]˜M˜Mšœ œ ˜Mšœœ˜ Mšœ`˜`Mšœœœœœœœ˜?Mšœ&˜&Mšœ&˜&M˜M˜—Lšœ œœ˜L˜L˜šž œœ"œ˜VLšœœ˜Lšœ˜Lšœ˜šœ?œ œ˜YLšœœ˜Lšœœ˜Lšœœ˜Lšœœ˜Lšœ˜—Lš’™L˜Lšœ˜Lšœ˜Lšœ˜Lšœ$˜$L˜L˜—šžœœgœœ˜‘Mšœ&˜&M˜šœœ œ˜Mšœ8˜8Mšœ˜Mšœ˜—Mšœ2˜2šœœœŸ˜2Mš’%œ’˜.šœ œ˜M˜Mšœ ˜ Mšœ˜—Mšœ œ’œ˜ZMšœ œ’œ˜YMšœœF˜YMšœJ˜JM˜—š˜Mšœœ˜6Mšœ œ˜*Mšœœ&˜:Mšœ"˜"Mšœa˜aMšœ"˜"Mšœa˜aMšœ˜—M˜M˜—š’œœ4˜IMšœœ ˜0Mšœ$˜$Mšœ%Ÿ%˜JMšœ'Ÿ%˜LMšœ)˜)M˜M˜—š’œœ4˜JMšœœ ˜0Mšœ$˜$Mšœ%Ÿ%˜JMšœ'Ÿ%˜LMšœ+œ˜1M˜M˜—šžœœpœ˜Œšœ œœ˜Lšœ˜Lšœ'˜'L˜—šœœœ˜Lšœ˜Lšœ"˜"Lšœ!˜!Lšœ)œ˜/L˜—L˜L˜—L˜šžœœœM˜lLšœu™uLšœk˜kLšœ.˜.šœŸ˜L˜—L˜—šžœœœ7˜^L™ΖL™ALšœ œ ˜Lšœœ˜LšœD˜DLš œ œœ œœœ˜Išœ œœœ˜%Lšœœ˜ L™2L™πL™L™'Lšœ œœœ˜Lšœ œœœ˜L™LšœŸ-˜ELšœ˜Lšœ˜LšœJ˜JL™ Lšœ˜Lšœ˜LšœJ˜JL˜—šœ˜Lšœœ˜ L™L™!L™Lšœ œœœ˜Lšœ œœœ˜L™LšœŸ-˜ELšœ˜Lšœ˜LšœJ˜JL™ Lšœ˜Lšœ˜LšœJ˜JL˜—Lšœ;˜;šœŸ$˜'L˜—L˜—šžœœœm˜ŠL™1L™+Lšœ˜Lšœ8˜8Lšœ œ ˜Lšœœ˜ L˜ Lšœœœœ˜Lšœ7˜7Lšœ7˜7Lšœ*˜*Lšœ@˜@Lšœ*˜*LšœA˜AL™Δšœ!œ+œŸ˜uLšœœ˜Lšœ2˜2Lšœ.˜.Lšœ.˜.LšœH˜HL™πLšœœ<˜]Lšœ=˜ALšœ}˜}Lšœ œœ˜LšœŸ˜1Lšœ%˜%šœœŸ˜=L˜Lšœ=˜=Lšœ;˜;Lšœ˜Lšœ˜Lšœ-˜-L˜—šœŸ1˜8LšœV˜VLšœ˜—L˜—šœ˜L™ΥLšœ9™9Lšœ8˜8Lšœ˜šœ œ˜Lšœ-˜-Lšœ-˜-šœœŸ˜=L˜Lšœ5˜5Lšœ;˜;Lšœ˜Lšœ˜Lšœ-˜-L˜—šœŸ1˜8LšœN˜NLšœ˜—L˜—L˜—L˜L˜—š žœœœœœ˜&Lšœœœœ˜!L˜—šžœ œo˜”L™3—šžœ œM˜tL™,L˜L˜—šž"œ œO˜~L™.—šžœ œ€˜€L™,—šžœ œj˜–Lšœ.™.—šžœœœ7˜MLšœœ˜4šœ˜Lšœ œ)œ ˜GLšœ=˜=Lšœ˜—Lšœ˜—L˜L˜š žœœœœ œ˜Qšœ ˜Lšœ˜Lšœ˜Lšœ"˜"Lšœ˜Lšœ˜Lšœœ˜ L˜——š žœœœ œœ!œ˜`Lšœ$™$Jšœ œ˜šœœ˜Jšœœ˜9Jšœœ˜8Jšœœ˜@Jšœœ˜:Jšœœ˜:Jšœœ˜—Jšœ˜—š žœœœœ œ˜Tšœ ˜Lšœ!˜!Lšœ˜Lšœœ˜ L˜——š žœœœ œœ#œ˜cLšœ%™%Jšœ œ˜šœœ˜Jšœœ˜@Jšœœ˜