DIRECTORY Controls, ControlsPrivate, Convert, Draw2d, G2dVector, G3dBasic, G3dControl, G3dMatrix, G3dQuaternion, G3dVector, G3dView, Imager, ImagerBackdoor, ImagerColor, ImagerFont, IO, MessageWindow, MouseTrap, Real, RealFns, Rope, ViewerClasses, ViewerOps, ViewersWorld, ViewersWorldInstance, ViewersWorldRefType, ViewerTools; G3dArcBallImpl: CEDAR PROGRAM IMPORTS Controls, Draw2d, G2dVector, G3dControl, G3dMatrix, G3dQuaternion, G3dVector, G3dView, Imager, ImagerColor, ImagerFont, IO, MessageWindow, MouseTrap, Real, RealFns, Rope, ViewerOps, ViewersWorld, ViewersWorldInstance, ViewerTools EXPORTS G3dControl ~ BEGIN Control: TYPE ~ Controls.Control; ControlProc: TYPE ~ Controls.ControlProc; Pair: TYPE ~ G2dVector.Pair; Triple: TYPE ~ G3dBasic.Triple; ArcBall: TYPE ~ G3dControl.ArcBall; ArcControl: TYPE ~ G3dControl.ArcControl; ArcControlRep: TYPE ~ G3dControl.ArcControlRep; CameraControl: TYPE ~ G3dControl.CameraControl; Matrix: TYPE ~ G3dMatrix.Matrix; Quaternion: TYPE ~ G3dQuaternion.Quaternion; Context: TYPE ~ Imager.Context; STREAM: TYPE ~ IO.STREAM; ROPE: TYPE ~ Rope.ROPE; Viewer: TYPE ~ ViewerClasses.Viewer; ViewerClass: TYPE ~ ViewerClasses.ViewerClass; ViewerClassRec: TYPE ~ ViewerClasses.ViewerClassRec; deg: REAL ~ 3.1415926535/180.0; stdFrame: Matrix ~ G3dMatrix.Identity[]; font: ImagerFont.Font ¬ ImagerFont.Find["Xerox/TiogaFonts/Gacha12B"]; SetValueFromRopeProc: TYPE ~ ControlsPrivate.SetValueFromRopeProc; RopeFromValueProc: TYPE ~ ControlsPrivate.RopeFromValueProc; viewersWorld: ViewersWorldRefType.Ref ¬ ViewersWorldInstance.GetWorld[]; InitArcBall: PUBLIC PROC [ camera: CameraControl, clientProc: ControlProc, clientData: REF ANY] RETURNS [a: ArcBall] ~ { NewArcControl: PROC [flavor: ATOM] RETURNS [ac: ArcControl] ~ { name: ROPE ¬ IF flavor = $ArcRot THEN "Rotate" ELSE "Translate"; proc: ControlProc ¬ IF flavor = $ArcRot THEN ArcRotControl ELSE ArcTranControl; ropeProc: RopeFromValueProc ¬ IF flavor = $ArcRot THEN RopeFromRotate ELSE RopeFromTranslate; valueProc: SetValueFromRopeProc ¬ IF flavor = $ArcRot THEN SetRotateFromRope ELSE SetTranslateFromRope; ac ¬ NEW[ArcControlRep ¬ [camera: camera, clientProc: clientProc, clientData: clientData, qAbs: qAbs, tAbs: tCam]]; ac.rope ¬ IF flavor = $ArcRot THEN "0.00 0.00 0.00 1.00" ELSE "0.000 0.000 0.000"; ac.frame ¬ G3dMatrix.Identity[]; ac.control ¬ Controls.NewControl[name: name, proc: proc, w: 136, h: 136, textLocation: [up, left], flavor: flavor, clientData: a, color: ImagerColor.ColorFromRGB[[0.0, 0.7, 0.0]]]; ac.control.contourRef ¬ NEW[RopeFromValueProc ¬ ropeProc]; ac.control.sketchRef ¬ NEW[SetValueFromRopeProc ¬ valueProc]; }; rCam: Triple ¬ [camera.par.xRot.value, camera.par.yRot.value, camera.par.zRot.value]; tCam: Triple ¬ [camera.par.xMov.value, camera.par.yMov.value, camera.par.zMov.value]; qAbs: Quaternion ¬ G3dQuaternion.FromXYZAngles[rCam.x*deg, rCam.y*deg, rCam.z*deg]; a ¬ NEW[G3dControl.ArcBallRep]; a.rotate ¬ NewArcControl[$ArcRot]; a.translate ¬ NewArcControl[$ArcTran]; }; SetArcBall: PUBLIC PROC [arcBall: ArcBall, rotate, translate: Triple ¬ []] ~ { SetArcControl: PROC [ac: ArcControl] ~ { ac.firstPlace ¬ ac.lastPlace ¬ ac.firstPrev ¬ ac.lastPrev ¬ [0, 0, 1]; ac.frame ¬ G3dMatrix.Identity[ac.frame]; ac.ropeBasis ¬ [0, 0, 0]; }; SetArcControl[arcBall.rotate]; SetArcControl[arcBall.translate]; arcBall.rotate.qAbs ¬ G3dQuaternion.FromXYZAngles[rotate.x*deg, rotate.y*deg, rotate.z*deg]; arcBall.rotate.qInc ¬ [0, 0, 0, 1]; arcBall.rotate.qBasis ¬ arcBall.rotate.ropeBasis ¬ [0, 0, 0]; arcBall.translate.tAbs ¬ translate; arcBall.translate.tInc ¬ [0, 0, 0]; }; SetArcBallSize: PUBLIC PROC [arcBall: ArcBall, size: INTEGER] ~ { arcBall.rotate.control.w ¬ arcBall.rotate.control.h ¬ size; arcBall.translate.control.w ¬ arcBall.translate.control.h ¬ size; }; MakeClass: PROC [flavor: ATOM] RETURNS [ViewerClass] ~ { dialClassRec: ViewerClassRec ¬ ViewerOps.FetchViewerClass[$ControlsSliderDial]­; dialClassRec.paint ¬ IF flavor = $ArcRot THEN PaintArcRotViewer ELSE PaintArcTranViewer; RETURN[NEW[ViewerClassRec ¬ dialClassRec]]; }; ArcRotControl: ControlProc ~ { arcBall: ArcBall ¬ NARROW[control.clientData]; arcRot: ArcControl ¬ arcBall.rotate; cam: CameraControl ¬ arcRot.camera; viewer: Viewer ¬ control.viewer; center: Pair ¬ [viewer.cx+(viewer.cw-2)/2.0, viewer.cy+(viewer.ch-2)/2.0]; radius: REAL ¬ (viewer.cw-1)/2.0-1.5; place: Triple ¬ MouseOnSphere[[control.mouse.pos.x, control.mouse.pos.y], center, radius]; qInc: Quaternion; tAbs: Triple ¬ [cam.par.xMov.value, cam.par.yMov.value, cam.par.zMov.value]; mouse: Pair ¬ [control.mouse.pos.x-center.x, control.mouse.pos.y-center.y]; cam.current ¬ control; IF mouse.x*mouse.x+mouse.y*mouse.y > radius*radius THEN { SELECT control.mouse.state FROM down => arcRot.inCorner ¬ TRUE; up => { arcRot.inCorner ¬ FALSE; IF mouse.y > 0 AND mouse.x < 0 THEN { arcBall.originBody ¬ NOT arcBall.originBody; ViewerOps.PaintViewer[viewer, client, FALSE, $BodyOrigin]; }; IF mouse.y > 0 AND mouse.x > 0 THEN { control.whatChanged ¬ $ArcBallReset; IF arcRot.clientProc # NIL THEN arcRot.clientProc[control, arcRot.clientData]; }; }; ENDCASE; RETURN; }; IF arcRot.inCorner THEN { IF control.mouse.state = up THEN arcRot.inCorner ¬ FALSE; RETURN; }; IF control.whatChanged = $TypedIn THEN RETURN; SELECT control.mouse.state FROM down => { MouseTrap.TrapTillMouseUp[control.viewer, TRUE, TRUE]; arcRot.qAbs ¬ G3dQuaternion.FromXYZAngles[ cam.par.xRot.value*deg, cam.par.yRot.value*deg, cam.par.zRot.value*deg]; arcRot.frame ¬ G3dQuaternion.ToMatrix[ [arcRot.qAbs.x, arcRot.qAbs.z, -arcRot.qAbs.y, arcRot.qAbs.w], arcRot.frame]; IF control.mouse.controlKey OR control.mouse.shiftKey THEN { place ¬ ForceToAxis[place, IF control.mouse.controlKey THEN arcRot.frame ELSE NIL]; SetMousePosition[place, center, radius, viewer]; }; arcRot.firstPlace ¬ arcRot.lastPlace ¬ place; qInc ¬ G3dQuaternion.Id[]; arcRot.qInc ¬ arcRot.qAbs; arcRot.inhibitPrint ¬ TRUE; }; held => { arcRot.lastPlace ¬ SELECT TRUE FROM control.mouse.controlKey => ForceToPlane[place, arcRot.firstPlace, arcRot.frame], control.mouse.shiftKey => ForceToPlane[place, arcRot.firstPlace, NIL], ENDCASE => place; qInc ¬ QuatFromArc[arcRot.firstPlace, arcRot.lastPlace]; arcRot.qInc ¬ G3dQuaternion.Mul[qInc, arcRot.qAbs]; IF NOT arcBall.originBody THEN tAbs ¬ G3dQuaternion.Rot[qInc, tAbs]; arcRot.inhibitPrint ¬ TRUE; }; up => { arcRot.lastPlace ¬ SELECT TRUE FROM control.mouse.controlKey => ForceToPlane[place, arcRot.firstPlace, arcRot.frame], control.mouse.shiftKey => ForceToPlane[place, arcRot.firstPlace, NIL], ENDCASE => place; qInc ¬ QuatFromArc[arcRot.firstPlace, arcRot.lastPlace]; arcRot.qInc ¬ arcRot.qAbs ¬ G3dQuaternion.Mul[qInc, arcRot.qAbs]; IF NOT arcBall.originBody THEN tAbs ¬ G3dQuaternion.Rot[qInc, tAbs]; UpdateCameraValues[cam, arcRot.qInc, tAbs]; arcRot.inhibitPrint ¬ FALSE; ViewerTools.SetContents[control.status, RopeFromRotate[control].valueRope]; ViewerOps.PaintViewer[control.status, client]; RepaintTranslate[control.outerData.controls]; }; ENDCASE; ViewerOps.PaintViewer[viewer, client, FALSE, $Arc]; -- may prevent feedback lag cam.matrix ¬ MakeCameraMatrix[ cam.scale.value, tAbs, arcRot.qInc, IF cam.use = view THEN cam.fieldOfView.value ELSE 0.0, cam.matrix]; IF control.mouse.state = up THEN cam.inverse ¬ G3dMatrix.Invert[cam.matrix, cam.inverse]; IF arcRot.clientProc # NIL THEN arcRot.clientProc[control, arcRot.clientData]; }; ArcTranControl: ControlProc ~ { arcTran: ArcControl ¬ NARROW[control.clientData, ArcBall].translate; cam: CameraControl ¬ arcTran.camera; viewer: Viewer ¬ control.viewer; center: Pair ¬ [viewer.cx+(viewer.cw-2)/2.0, viewer.cy+(viewer.ch-2)/2.0]; radius: REAL ¬ (viewer.cw-1)/2.0-1.5; place: Triple ¬ MouseOnSphere[[control.mouse.pos.x, control.mouse.pos.y], center, radius]; rCam: Triple ¬ [cam.par.xRot.value, cam.par.yRot.value, cam.par.zRot.value]; cam.current ¬ control; IF control.whatChanged = $TypedIn THEN RETURN; arcTran.tInc ¬ [0, 0, 0]; IF arcTran.qBasis # rCam THEN { arcTran.qAbs ¬ G3dQuaternion.FromXYZAngles[rCam.x*deg, rCam.y*deg, rCam.z*deg]; arcTran.qBasis ¬ rCam; }; SELECT control.mouse.state FROM down => { tCam: Triple ¬ [cam.par.xMov.value, cam.par.yMov.value, cam.par.zMov.value]; MouseTrap.TrapTillMouseUp[control.viewer, FALSE, FALSE]; arcTran.frame ¬ G3dQuaternion.ToMatrix[ [arcTran.qAbs.x, arcTran.qAbs.z, -arcTran.qAbs.y, arcTran.qAbs.w], arcTran.frame]; SELECT TRUE FROM control.mouse.controlKey => place ¬ ForceToAxis[place, arcTran.frame]; control.mouse.shiftKey => place ¬ ForceToAxis[place, NIL]; ENDCASE; arcTran.firstPlace ¬ place; arcTran.lastPlace ¬ [0, 0, 1]; arcTran.qTan ¬ G3dQuaternion.FromComponents[place.y, -place.x, 0, place.z+1]; arcTran.qTan ¬ G3dQuaternion.Unit[arcTran.qTan]; arcTran.frame ¬ G3dQuaternion.ToMatrix[ G3dQuaternion.Mul[arcTran.qTan, [arcTran.qAbs.x, arcTran.qAbs.z, -arcTran.qAbs.y, arcTran.qAbs.w]], arcTran.frame]; arcTran.tAbs ¬ arcTran.tInc ¬ tCam; SetMousePosition[[0, 0, 1], center, radius, viewer]; arcTran.inhibitPrint ¬ TRUE; }; held => { arcTran.lastPlace ¬ SELECT TRUE FROM control.mouse.controlKey => place ¬ ForceToPlane[place, [0, 0, 1], arcTran.frame], control.mouse.shiftKey => place ¬ ForceToPlane[place, [0, 0, 1], NIL], ENDCASE => place; arcTran.tInc ¬ G3dVector.Add[arcTran.tAbs, TransFromArc[arcTran.qTan, place]]; arcTran.inhibitPrint ¬ TRUE; }; up => { arcTran.lastPlace ¬ SELECT TRUE FROM control.mouse.controlKey => ForceToPlane[place, [0, 0, 1], arcTran.frame], control.mouse.shiftKey => ForceToPlane[place, [0, 0, 1], NIL], ENDCASE => place; arcTran.tInc ¬ G3dVector.Add[arcTran.tAbs, TransFromArc[arcTran.qTan, place]]; arcTran.tAbs ¬ arcTran.tInc; UpdateCameraTranslate[cam]; arcTran.inhibitPrint ¬ FALSE; ViewerTools.SetContents[control.status, RopeFromTranslate[control].valueRope]; ViewerOps.PaintViewer[control.status, client]; }; ENDCASE; ViewerOps.PaintViewer[viewer, client, FALSE, $Line]; -- may prevent feedback lag cam.matrix ¬ MakeCameraMatrix[ cam.scale.value, arcTran.tInc, arcTran.qAbs, IF cam.use = view THEN cam.fieldOfView.value ELSE 0.0, cam.matrix]; IF control.mouse.state = up THEN cam.inverse ¬ G3dMatrix.Invert[cam.matrix, cam.inverse]; IF arcTran.clientProc # NIL THEN arcTran.clientProc[control, arcTran.clientData]; }; FrameToStd: PROC [frame: Matrix ¬ NIL, p: Triple, w: REAL ¬ 0.0] RETURNS [pp: Triple] ~ { IF frame = NIL THEN RETURN[p]; SELECT p.x FROM 0.0 => pp ¬ [0.0, 0.0, 0.0]; 1.0 => pp ¬ [frame[0][0], frame[0][1], frame[0][2]]; ENDCASE => pp ¬ [p.x*frame[0][0], p.x*frame[0][1], p.x*frame[0][2]]; SELECT p.y FROM 0.0 => NULL; 1.0 => pp ¬ G3dVector.Add[pp, [frame[1][0], frame[1][1], frame[1][2]]]; ENDCASE => pp ¬ G3dVector.Add[pp, [p.y*frame[1][0], p.y*frame[1][1], p.y*frame[1][2]]]; SELECT p.z FROM 0.0 => NULL; 1.0 => pp ¬ G3dVector.Add[pp, [frame[2][0], frame[2][1], frame[2][2]]]; ENDCASE => pp ¬ G3dVector.Add[pp, [p.z*frame[2][0], p.z*frame[2][1], p.z*frame[2][2]]]; SELECT w FROM 0.0 => NULL; 1.0 => pp ¬ G3dVector.Add[pp, [frame[3][0], frame[3][1], frame[3][2]]]; ENDCASE => pp ¬ G3dVector.Add[pp, [w*frame[3][0], w*frame[3][1], w*frame[3][2]]]; }; StdToFrame: PROC [frame: Matrix ¬ NIL, p: Triple, w: REAL ¬ 0.0] RETURNS [pp: Triple] ~ { IF frame = NIL THEN RETURN[p]; SELECT w FROM 0.0 => NULL; 1.0 => p ¬ G3dVector.Sub[p, [frame[3][0], frame[3][1], frame[3][2]]]; ENDCASE => p ¬ G3dVector.Sub[p, [w*frame[3][0], w*frame[3][1], w*frame[3][2]]]; pp ¬ [ p.x*frame[0][0]+p.y*frame[0][1]+p.z*frame[0][2], p.x*frame[1][0]+p.y*frame[1][1]+p.z*frame[1][2], p.x*frame[2][0]+p.y*frame[2][1]+p.z*frame[2][2]]; }; MaxAxisIndex: PROC [frame: Matrix ¬ NIL, p: Triple] RETURNS [index: CARDINAL] ~ { pRelF: Triple ¬ StdToFrame[frame, p]; flip: ARRAY [0..3) OF CARDINAL ¬ ALL[0]; IF frame = NIL THEN frame ¬ stdFrame; IF (frame[0][2] < 0) OR (frame[0][2] = 0 AND pRelF.x < 0) THEN {pRelF.x ¬ -pRelF.x; flip[0] ¬ 4}; IF (frame[1][2] < 0) OR (frame[1][2] = 0 AND pRelF.y < 0) THEN {pRelF.y ¬ -pRelF.y; flip[1] ¬ 4}; IF (frame[2][2] < 0) OR (frame[2][2] = 0 AND pRelF.z < 0) THEN {pRelF.z ¬ -pRelF.z; flip[2] ¬ 4}; RETURN[IF pRelF.y > pRelF.z THEN IF pRelF.x > pRelF.y THEN 0+flip[0] ELSE 1+flip[1] ELSE IF pRelF.x > pRelF.z THEN 0+flip[0] ELSE 2+flip[2]]; }; ForceToAxis: PROC [p: Triple, frame: Matrix ¬ NIL] RETURNS [pp: Triple] ~ { index: CARDINAL ¬ MaxAxisIndex[frame, p]; i: CARDINAL ¬ index MOD 4; IF frame = NIL THEN frame ¬ stdFrame; pp ¬ [frame[i][0], frame[i][1], frame[i][2]]; IF index >= 4 THEN pp ¬ G3dVector.Negate[pp]; }; ForceToPlane: PROC [p, first: Triple, frame: Matrix ¬ NIL] RETURNS [pp: Triple] ~ { Norm: PROC [a, b: REAL] RETURNS [REAL] ~ INLINE {RETURN[RealFns.SqRt[a*a+b*b]]}; firstIndex: CARDINAL ¬ MaxAxisIndex[frame, first] MOD 4; pRelF: Triple ¬ StdToFrame[frame, p]; minIndex: INT ¬ SELECT firstIndex FROM 0 => IF ABS[pRelF.y] < ABS[pRelF.z] THEN 1 ELSE 2, 1 => IF ABS[pRelF.x] < ABS[pRelF.z] THEN 0 ELSE 2, 2 => IF ABS[pRelF.x] < ABS[pRelF.y] THEN 0 ELSE 1, ENDCASE => -1; SELECT minIndex FROM 0 => {s: REAL ¬ 1.0/Norm[pRelF.y, pRelF.z]; pp ¬ [0.0, s*pRelF.y, s*pRelF.z]}; 1 => {s: REAL ¬ 1.0/Norm[pRelF.x, pRelF.z]; pp ¬ [s*pRelF.x, 0.0, s*pRelF.z]}; 2 => {s: REAL ¬ 1.0/Norm[pRelF.x, pRelF.y]; pp ¬ [s*pRelF.x, s*pRelF.y, 0.0]}; ENDCASE; pp ¬ FrameToStd[frame, pp]; IF pp.z < 0 THEN pp ¬ G3dVector.Negate[pp]; }; SetCameraToIdentity: PROC [camera: CameraControl] ~ { G3dControl.SetTripleControls[camera.par.xRot, camera.par.yRot, camera.par.zRot, []]; G3dControl.SetTripleControls[camera.par.xMov, camera.par.yMov, camera.par.zMov, []]; G3dControl.UpdateControl[camera, camera.scale, 1.0]; }; UpdateCameraValues: PROC [cam: CameraControl, qAbs: Quaternion, tAbs: Triple] ~ { matTmp: Matrix ¬ G3dMatrix.ObtainMatrix[]; rCam: Triple ¬ G3dMatrix.ExtractRotate[G3dQuaternion.ToMatrix[qAbs, matTmp]]; tCam: Triple ¬ tAbs; Controls.SetSliderDialValue[cam.par.xRot, rCam.x]; Controls.SetSliderDialValue[cam.par.yRot, rCam.y]; Controls.SetSliderDialValue[cam.par.zRot, rCam.z]; Controls.SetSliderDialValue[cam.par.xMov, tCam.x]; Controls.SetSliderDialValue[cam.par.yMov, tCam.y]; Controls.SetSliderDialValue[cam.par.zMov, tCam.z]; UpdateCameraVectors[cam]; G3dMatrix.ReleaseMatrix[matTmp]; }; UpdateCameraTranslate: PROC [cam: CameraControl] ~ { Controls.SetSliderDialValue[cam.par.xMov, cam.arcBall.translate.tAbs.x]; Controls.SetSliderDialValue[cam.par.yMov, cam.arcBall.translate.tAbs.y]; Controls.SetSliderDialValue[cam.par.zMov, cam.arcBall.translate.tAbs.z]; UpdateCameraVectors[cam]; }; UpdateCameraVectors: PROC [cam: CameraControl] ~ { [cam.eyePoint, cam.lookAt, cam.up] ¬ G3dView.FromScaleMovesRots[ cam.scale.value, [cam.par.xMov.value, cam.par.yMov.value, cam.par.zMov.value], [cam.par.xRot.value, cam.par.yRot.value, cam.par.zRot.value] ! G3dMatrix.singular => CONTINUE]; }; MakeCameraMatrix: PROC [ scale: REAL ¬ 1.0, move: Triple ¬ [0.0, 0.0, 0.0], rotate: Quaternion ¬ [0.0, 0.0, 0.0, 1.0], fieldOfView: REAL ¬ 0.0, out: Matrix ¬ NIL] RETURNS [Matrix] ~ { out ¬ G3dQuaternion.ToMatrix[rotate, out]; [] ¬ G3dMatrix.Scale[out, scale, out]; [] ¬ G3dMatrix.Translate[out, move, out]; IF fieldOfView # 0.0 THEN { t: Matrix ¬ G3dMatrix.ObtainMatrix[]; [] ¬ G3dMatrix.Mul[out, G3dMatrix.MakePerspective[10., 0., fieldOfView, t], out]; G3dMatrix.ReleaseMatrix[t]; }; RETURN[out]; }; BlinkMessage: PROC [message: ROPE ¬ NIL] ~ { IF message # NIL THEN MessageWindow.Append[message, TRUE]; MessageWindow.Blink[]; }; RopeFromQuaternion: PROC [q: Quaternion] RETURNS [rope: ROPE ¬ NIL] ~ { rope ¬ IO.PutFLR["%.2f %.2f %.2f %.2f", LIST[IO.real[q.x], IO.real[q.y], IO.real[q.z], IO.real[q.w]]]; }; RopeFromRotate: ControlsPrivate.RopeFromValueProc ~ { arcRot: ArcControl ¬ NARROW[control.clientData, ArcBall].rotate; cam: CameraControl ¬ arcRot.camera; rCam: Triple ¬ [cam.par.xRot.value, cam.par.yRot.value, cam.par.zRot.value]; valueRope ¬ arcRot.rope; IF G3dVector.Equal[arcRot.ropeBasis, rCam] THEN RETURN[arcRot.rope, FALSE] ELSE { IF (repaint ¬ NOT arcRot.inhibitPrint) THEN { q: Quaternion ¬ G3dQuaternion.FromXYZAngles[rCam.x*deg,rCam.y*deg,rCam.z*deg]; valueRope ¬ arcRot.rope ¬ RopeFromQuaternion[q]; arcRot.ropeBasis ¬ rCam; }; }; }; SetRotateFromRope: ControlsPrivate.SetValueFromRopeProc ~ { stream: STREAM ¬ IO.RIS[valueRope]; viewer: Viewer ¬ control.viewer; arcRot: ArcControl ¬ NARROW[control.clientData, ArcBall].rotate; cam: CameraControl ¬ arcRot.camera; rCam: Triple ¬ [cam.par.xRot.value, cam.par.yRot.value, cam.par.zRot.value]; tCam: Triple ¬ [cam.par.xMov.value, cam.par.yMov.value, cam.par.zMov.value]; qAbs: Quaternion ¬ G3dQuaternion.FromXYZAngles[rCam.x*deg, rCam.y*deg, rCam.z*deg]; tAbs: Triple ¬ G3dQuaternion.Rot[qAbs, tCam]; { ENABLE IO.Error, IO.EndOfStream => GOTO BadScan; qAbs.x ¬ IO.GetReal[stream]; qAbs.y ¬ IO.GetReal[stream]; qAbs.z ¬ IO.GetReal[stream]; qAbs.w ¬ IO.GetReal[stream]; arcRot.qAbs ¬ arcRot.qInc ¬ qAbs ¬ G3dQuaternion.Unit[qAbs]; [arcRot.firstPlace, arcRot.lastPlace] ¬ ArcFromQuat[qAbs]; ViewerOps.PaintViewer[viewer, client, TRUE]; UpdateCameraValues[cam, qAbs, tAbs]; arcRot.rope ¬ RopeFromQuaternion[qAbs]; arcRot.ropeBasis ¬ rCam; ViewerTools.SetContents[control.status, arcRot.rope]; ViewerOps.PaintViewer[control.status, client]; RepaintTranslate[control.outerData.controls]; control.valuePrev ¬ Real.Floor[control.valuePrev]; control.value ¬ 1.0 - control.valuePrev; -- nonsensical value, but force change IF cam.proc # NIL THEN cam.proc[control, cam.clientData]; -- call client proc, if any EXITS BadScan => BlinkMessage["Type x y z w."]; }; }; RopeFromTriple: PROC [t: Triple] RETURNS [rope: ROPE ¬ NIL] ~ { rope ¬ IO.PutFR["%.3f %.3f %.3f", IO.real[t.x], IO.real[t.y], IO.real[t.z]]; }; RopeFromTranslate: ControlsPrivate.RopeFromValueProc ~ { arcTran: ArcControl ¬ NARROW[control.clientData, ArcBall].translate; cam: CameraControl ¬ arcTran.camera; tCam: Triple ¬ [cam.par.xMov.value, cam.par.yMov.value, cam.par.zMov.value]; rCam: Triple ¬ [cam.par.xRot.value, cam.par.yRot.value, cam.par.zRot.value]; valueRope ¬ arcTran.rope; IF arcTran.qBasis = rCam THEN { IF arcTran.ropeBasis = tCam THEN RETURN[valueRope, FALSE]; } ELSE { arcTran.qAbs ¬ G3dQuaternion.FromXYZAngles[rCam.x*deg, rCam.y*deg, rCam.z*deg]; arcTran.qBasis ¬ rCam; }; IF (repaint ¬ NOT arcTran.inhibitPrint) THEN { arcTran.ropeBasis ¬ tCam; tCam ¬ G3dQuaternion.Rot[arcTran.qAbs, tCam]; valueRope ¬ arcTran.rope ¬ RopeFromTriple[tCam]; }; }; SetTranslateFromRope: ControlsPrivate.SetValueFromRopeProc ~ { stream: STREAM ¬ IO.RIS[valueRope]; viewer: Viewer ¬ control.viewer; arcTran: ArcControl ¬ NARROW[control.clientData, ArcBall].translate; cam: CameraControl ¬ arcTran.camera; rCam: Triple ¬ [cam.par.xRot.value, cam.par.yRot.value, cam.par.zRot.value]; tScan: Triple; IF arcTran.qBasis # rCam THEN { arcTran.qAbs ¬ G3dQuaternion.FromXYZAngles[rCam.x*deg, rCam.y*deg, rCam.z*deg]; arcTran.qBasis ¬ rCam; }; { ENABLE IO.Error, IO.EndOfStream => GOTO BadScan; tScan.x ¬ IO.GetReal[stream]; tScan.y ¬ IO.GetReal[stream]; tScan.z ¬ IO.GetReal[stream]; arcTran.tAbs ¬ arcTran.tInc ¬ G3dQuaternion.Rot[G3dQuaternion.Conjugate[arcTran.qAbs], tScan]; UpdateCameraTranslate[cam]; [arcTran.firstPlace, arcTran.lastPlace, arcTran.qTan] ¬ ArcFromTrans[arcTran.tAbs]; ViewerOps.PaintViewer[viewer, client, TRUE]; arcTran.rope ¬ RopeFromTriple[tScan]; arcTran.ropeBasis ¬ tScan; ViewerTools.SetContents[control.status, arcTran.rope]; ViewerOps.PaintViewer[control.status, client]; control.valuePrev ¬ Real.Floor[control.valuePrev]; control.value ¬ 1.0 - control.valuePrev; -- nonsensical value, but force change IF cam.proc # NIL THEN cam.proc[control, cam.clientData]; -- call client proc, if any EXITS BadScan => BlinkMessage["Type x y z."]; }; }; RepaintTranslate: PROC [controlList: Controls.ControlList] ~ { FOR cl: Controls.ControlList ¬ controlList, cl.rest WHILE cl # NIL DO IF cl.first.type = dial AND Rope.Equal[cl.first.name, "Translate"] THEN {Controls.SetSliderDialValue[cl.first, cl.first.value, TRUE]; EXIT}; ENDLOOP; }; MouseOnSphere: PROC [mouse: Pair, center: Pair ¬ [0, 0], radius: REAL ¬ 1.0] RETURNS [sphere: Triple] ~ { dSq: REAL; sphere.x ¬ (mouse.x-center.x)/radius; sphere.y ¬ (mouse.y-center.y)/radius; IF (dSq ¬ sphere.x*sphere.x+sphere.y*sphere.y) > 1.0 THEN { d: REAL ¬ RealFns.SqRt[dSq]; sphere.x ¬ sphere.x/d; sphere.y ¬ sphere.y/d; dSq ¬ 1.0; }; sphere.z ¬ RealFns.SqRt[1.0-dSq]; }; MouseFromSphere: PROC [sphere: Triple, center: Pair ¬ [0, 0], radius: REAL ¬ 1.0] RETURNS [mouse: Pair] ~ { mouse.x ¬ radius*sphere.x + center.x; mouse.y ¬ radius*sphere.y + center.y; }; QuatFromArc: PROC [p0, p1: Triple] RETURNS [q: Quaternion] ~ { q ¬ G3dQuaternion.Mul[G3dQuaternion.FromVector[p1], G3dQuaternion.Conjugate[G3dQuaternion.FromVector[p0]]]; q ¬ G3dQuaternion.FromComponents[x: q.x, y: -q.y, z: -q.z, w: q.w]; }; ArcFromQuat: PROC [q: Quaternion] RETURNS [p0, p1: Triple] ~ { q ¬ G3dQuaternion.FromComponents[x: q.x, y: -q.y, z: -q.z, w: q.w]; IF q.x = 0.0 AND q.y = 0.0 THEN p0 ¬ [0.0, 1.0, 0.0] ELSE {norm: REAL ¬ RealFns.SqRt[q.x*q.x+q.y*q.y]; p0 ¬ [-q.y/norm, q.x/norm, 0.0]}; p1 ¬ G3dQuaternion.Vector[G3dQuaternion.Mul[q, G3dQuaternion.FromVector[p0]]]; IF q.w < 0.0 THEN p0 ¬ [-p0.x, -p0.y, 0.0]; }; TransFromArc: PROC [q: Quaternion, p1: Triple] RETURNS [tInc: Triple] ~ { scale: REAL; IF p1.z = 0.0 THEN p1.z ¬ Real.FScale[MAX[ABS[p1.x], ABS[p1.y]]/RealFns.SqRt[Real.LargestNumber], 1]; scale ¬ 2.0/p1.z; tInc ¬ [p1.x*scale, p1.y*scale, 0]; tInc ¬ G3dQuaternion.Rot[G3dQuaternion.Conjugate[q], tInc]; tInc ¬ [tInc.x, tInc.y, tInc.z]; }; ArcFromTrans: PROC [tArg: Triple] RETURNS [p0, p1: Triple, q: Quaternion] ~ { tAbs: Triple ¬ [tArg.x, tArg.y, tArg.z]; xySqrs: REAL ¬ tAbs.x*tAbs.x+tAbs.y*tAbs.y; len: REAL ¬ RealFns.SqRt[xySqrs+tAbs.z*tAbs.z]; scale: REAL ¬ RealFns.SqRt[xySqrs]; IF scale = 0 THEN p0 ¬ [0, 1, 0] ELSE p0 ¬ [-tAbs.y/scale, tAbs.x/scale, 0]; q ¬ G3dQuaternion.Unit[G3dQuaternion.FromComponents[p0.y, -p0.x, 0, p0.z+1]]; tAbs ¬ G3dVector.Add[G3dQuaternion.Rot[q, tAbs], [0, 0, 1]]; p1 ¬ G3dVector.Unit[[tAbs.x/2.0, tAbs.y/2.0, tAbs.z]]; }; SetMousePosition: PROC [place: Triple, center: Pair ¬ [0, 0], radius: REAL ¬ 1.0, v: Viewer] ~ { mPlace: Pair ¬ MouseFromSphere[place, center, radius]; pos: MouseTrap.Position ¬ MouseTrap.UserToMouseCoords[v, Real.Round[mPlace.x], Real.Round[mPlace.y]]; ViewersWorld.SetMousePosition[viewersWorld, pos.x, pos.y]; }; PaintArcRotViewer: ViewerClasses.PaintProc ~ { ShowTopLeftChar: PROC [char: CHAR] ~ { Imager.SetFont[context, font]; Imager.SetXY[context, [self.cx+4, self.cy+self.ch-16]]; Imager.ShowChar[context, char]; }; arcBall: ArcBall ¬ NARROW[NARROW[self.data, Control].clientData]; arcRot: ArcControl ¬ arcBall.rotate; radius: REAL ¬ (self.cw-1)/2.0-1.5; center: Pair ¬ [self.cx+radius, self.cy+radius]; SELECT whatChanged FROM $BodyOrigin => { Imager.SetColor[context, Imager.white]; ShowTopLeftChar[IF arcBall.originBody THEN 'C ELSE 'B]; Imager.SetColor[context, arcRot.control.color]; ShowTopLeftChar[IF arcBall.originBody THEN 'B ELSE 'C]; }; $ClearArc => { Imager.SetColor[context, Imager.white]; FastArc[context, arcRot.firstPrev, arcRot.lastPrev, center, radius]; Imager.SetColor[context, arcRot.control.color]; Draw2d.Square[context, center, 2]; }; $Arc => { Imager.SetColor[context, Imager.white]; FastArc[context, arcRot.firstPrev, arcRot.lastPrev, center, radius]; Imager.SetColor[context, arcRot.control.color]; FastArc[context, arcRot.firstPlace, arcRot.lastPlace, center, radius]; arcRot.firstPrev ¬ arcRot.firstPlace; arcRot.lastPrev ¬ arcRot.lastPlace; }; NIL => { Imager.SetColor[context, arcRot.control.color]; Draw2d.Circle[context, center, radius]; FastArc[context, arcRot.firstPlace, arcRot.lastPlace, center, radius]; ShowTopLeftChar[IF arcBall.originBody THEN 'B ELSE 'C]; Imager.SetXY[context, [self.cx+self.cw-16, self.cy+self.ch-16]]; Imager.ShowRope[context, "UN"]; }; ENDCASE; }; PaintArcTranViewer: ViewerClasses.PaintProc ~ { ToScreen: PROC [p: Triple] RETURNS [Pair] ~ INLINE { RETURN[G2dVector.Add[G2dVector.Mul[[p.x, p.y], radius], center]]; }; control: Control ¬ NARROW[self.data]; arcTran: ArcControl ¬ NARROW[control.clientData, ArcBall].translate; center: Pair ¬ [self.cx+(self.cw-2)/2.0, self.cy+(self.ch-2)/2.0]; radius: REAL ¬ (self.cw-1)/2.0-1.5; firstPrev: Pair ¬ ToScreen[arcTran.firstPrev]; lastPrev: Pair ¬ ToScreen[arcTran.lastPrev]; firstPlace: Pair ¬ ToScreen[arcTran.firstPlace]; lastPlace: Pair ¬ ToScreen[arcTran.lastPlace]; IF arcTran.lastPlace = [0, 0, 1] THEN { Imager.SetColor[context, Imager.white]; Draw2d.Square[context, firstPrev, 2]; Imager.SetColor[context, arcTran.control.color]; Draw2d.Circle[context, center, radius]; }; SELECT whatChanged FROM $ClearArc => { Imager.SetColor[context, Imager.white]; Draw2d.Line[context, center, lastPrev]; Draw2d.Square[context, firstPlace, 2]; Imager.SetColor[context, arcTran.control.color]; Draw2d.Square[context, center, 2]; }; $Line => { Imager.SetColor[context, Imager.white]; Draw2d.Line[context, center, lastPrev]; Imager.SetColor[context, arcTran.control.color]; Draw2d.Line[context, center, lastPlace]; arcTran.firstPrev ¬ arcTran.firstPlace; arcTran.lastPrev ¬ arcTran.lastPlace; }; NIL => { Imager.SetColor[context, arcTran.control.color]; Draw2d.Circle[context, center, radius]; Draw2d.Line[context, center, lastPlace]; }; ENDCASE; Draw2d.Square[context, firstPlace, 2]; }; DrawBelt: PROC [context: Context, n: Triple, center: Pair, radius: REAL] ~ { p: Triple ¬ IF n.z = 1.0 THEN [0, 1, 0] ELSE G3dVector.Unit[[n.y, -n.x, 0]]; m: Triple ¬ G3dVector.Cross[p, n]; FastBelly[context, p, m, G3dVector.Negate[p], center, radius, solid]; }; Bisect: PROC [a, b: Triple] RETURNS [Triple] ~ INLINE { RETURN[G3dVector.Unit[G3dVector.Add[a, b]]]; }; FastArc: PROC [context: Context, p000, p200: Triple, center: Pair, radius: REAL] ~ { p100: Triple ¬ Bisect[p000, p200]; FastBelly[context, p000, p100, p200, center, radius, solid, TRUE]; }; FastBelly: PROC [ context: Context, p000, p100, p200: Triple, center: Pair, radius: REAL, drawType: Draw2d.DrawType, navel: BOOL ¬ FALSE] ~ { Double: PROC [a, b: Pair] RETURNS [Pair] ~ INLINE {RETURN[G2dVector.Sub[G2dVector.Mul[b, aDotB2], a]]}; Project: PROC [p: Triple] RETURNS [Pair] ~ INLINE {RETURN[[p.x, p.y]]}; Screen: PROC [p: Pair] RETURNS [Pair] ~ INLINE {RETURN[G2dVector.Add[G2dVector.Mul[[p.x, p.y], radius], center]]}; p010: Triple ¬ Bisect[p000, p100]; p001: Triple ¬ Bisect[p000, p010]; aDotB2: REAL ¬ Real.FScale[G3dVector.Dot[p000, p001], 1]; pp000: Pair ¬ Project[p000]; pp200: Pair ¬ Project[p200]; pp100: Pair ¬ Project[p100]; pp010: Pair ¬ Project[p010]; pp001: Pair ¬ Project[p001]; pp011: Pair ¬ Double[pp001, pp010]; pp101: Pair ¬ Double[pp011, pp100]; pp110: Pair ¬ Double[pp100, pp101]; pp111: Pair ¬ Double[pp101, pp110]; s000: Pair ¬ Screen[pp000]; s001: Pair ¬ Screen[pp001]; s010: Pair ¬ Screen[pp010]; s011: Pair ¬ Screen[pp011]; s100: Pair ¬ Screen[pp100]; s101: Pair ¬ Screen[pp101]; s110: Pair ¬ Screen[pp110]; s111: Pair ¬ Screen[pp111]; s200: Pair ¬ Screen[pp200]; DoArc: PROC ~ { zip: Draw2d.Zip ¬ Draw2d.GetZip[context]; Draw2d.Line[context, s000, s001, drawType, zip]; Draw2d.Line[context, s001, s010, drawType, zip]; Draw2d.Line[context, s010, s011, drawType, zip]; Draw2d.Line[context, s011, s100, drawType, zip]; Draw2d.Line[context, s100, s101, drawType, zip]; Draw2d.Line[context, s101, s110, drawType, zip]; Draw2d.Line[context, s110, s111, drawType, zip]; Draw2d.Line[context, s111, s200, drawType, zip]; Draw2d.ReleaseZip[zip]; IF navel THEN Draw2d.Square[context, [s000.x-1, s000.y-1], 2]; }; Imager.DoSave[context, DoArc]; }; ViewerOps.RegisterViewerClass[$ArcRot, MakeClass[$ArcRot]]; ViewerOps.RegisterViewerClass[$ArcTran, MakeClass[$ArcTran]]; END.. Φ G3dArcBallImpl.mesa Copyright Σ 1985, 1992 by Xerox Corporation. All rights reserved. Ken Shoemake, November 7, 1989 11:36:01 pm PST Bloomenthal, April 9, 1993 12:54 pm PDT Imported Type Declarations Local Types, Constants and Data Quaternion Control of a Camera ControlsPrivate.RegisterRopeFromValueProc[a.rotate, RopeFromRotate]; ControlsPrivate.RegisterValueFromRopeProc[a.rotate, SetRotateFromRope]; tAbs _ G3dQuaternion.Rot[arcRot.qAbs, tAbs]; -- originally G3dView mov before rot tAbs _ G3dQuaternion.Rot[arcRot.qAbs, tAbs]; -- originally G3dView mov before rot tAbs _ G3dQuaternion.Rot[arcRot.qAbs, tAbs]; -- originally G3dView mov before rot TRUE, -- cam.use = view, arcTran.tAbs _ arcTran.tInc _ G3dQuaternion.Rot[arcTran.qAbs, tCam]; -- originally ... arcTran.tAbs _ G3dQuaternion.Rot[G3dQuaternion.Conjugate[ arcTran.qAbs], arcTran.tInc]; -- originally G3dView mov before rot TRUE, -- cam.use = view, Constraints Convert coords of p relative to frame to be relative to stdFrame. NIL frame => standard axes. Convert coords of p relative to stdFrame to be relative to frame. NIL frame => standard axes. This picks the axis with the largest component in p. Adds 4 if axis is flipped. Ensure puncture points on front hemisphere. This dots p with each axis and picks the largest. NIL frame => standard axes. Dot p with axes and eliminate the smallest other than first. NIL frame => standard axes. Connect to Camera Camera thinks Mov preceeds Rot. Set t=t0 R-1. -- no longer tCam: Triple _ G3dQuaternion.Rot[G3dQuaternion.Conjugate[qAbs], tAbs]; swapYZ: BOOL _ TRUE, Like G3dView.MakeCameraMatrix but rotate is a Matrix (not Triple) and eyeIncr isn't used. Notice out is initialized to the rotate matrix before scale and translate (as in G3dView). At this point we're in WORLD SPACE IF swapYZ THEN FOR n: NAT IN [0..4) DO Go from right-handed world space to left-handed view space (swap y and z): temp: REAL _ out[n][1]; out[n][1] _ out[n][2]; out[n][2] _ temp; ENDLOOP; At this point we're in EYE SPACE At this point we're in VIEW SPACE Read and Print qDiff: Quaternion _ G3dQuaternion.Sub[arcRot.qAbs, arcRot.qInc]; repaint _ qDiff.x = 0 AND qDiff.y = 0 AND qDiff.z = 0 AND qDiff.w = 0; repaint _ G3dVector.Equal[arcTran.tAbs, tCam]; Find a control named "Translate" and make it redraw itself. Sense and Draw Convert mouse x-y coordinates to sphere x-y-z coordinates. Convert sphere x-y-z coordinates to mouse x-y coordinates. Return quaternion for rotation determined by arc endpoints p0 and p1, in that order. Return arc endpoints p0 and p1, in that order, determined by quaternion for rotation. Return vector for translation determined by arc with final endpoint p1. tInc _ G3dQuaternion.Rot[q, p1]; tInc _ [tInc.x/tInc.z, tInc.y/tInc.z, 0]; Return arc with endpoints p0, p1 determined by vector for translation. Move mouse to given place on sphere. Draw an elliptical arc approximation in context, quickly. Draw an elliptical arc approximation with midpoint in context, quickly. Initialization Κ'n•NewlineDelimiter –"cedarcode" style™šœ™Jšœ Οeœ6™BJšœ.™.J™'J™—šΟk œ­žœ˜ΘJ˜—–0.320 0.931 0.362 textColoršΡblnœΠbkΟb ˜Jšžœyžœk˜νJšžœ ˜J˜—Jšœž˜head–0.0 24 .div 1 1 textColoršΟl™J• CharProps'Postfix16.0 24 .div 1 1 textColorš‘œžœ˜&J–'Postfix16.0 24 .div 1 1 textColorš‘žœ˜,J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜"J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜$J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜(J–'Postfix16.0 24 .div 1 1 textColorš‘žœ˜,J–'Postfix16.0 24 .div 1 1 textColor š‘žœ˜1J–'Postfix16.0 24 .div 1 1 textColor š‘žœ˜1J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜%J–'Postfix16.0 24 .div 1 1 textColor š‘žœ˜/J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜$J–' Postfix16.0 24 .div 1 1 textColorš ‘žœžœžœ˜J–' Postfix16.0 24 .div 1 1 textColorš ‘žœžœ˜J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜)J–'Postfix16.0 24 .div 1 1 textColor!š‘žœ˜1J–'Postfix16.0 24 .div 1 1 textColor$š‘žœ ˜6—–0.0 24 .div 1 1 textColorš’™J–' Postfix16.0 24 .div 1 1 textColorš‘ žœ˜%J–'Postfix16.0 24 .div 1 1 textColorš‘œ˜,–' Postfix16.0 24 .div 1 1 textColor@š‘ œ@˜KJ˜—J–'Postfix16.0 24 .div 1 1 textColor,š‘žœ(˜B–'Postfix16.0 24 .div 1 1 textColor)š‘žœ%˜Jšžœ ˜—J˜Nšœ9™9JšœB™B—J˜J˜Jšœžœ˜JšœN˜NJšœ.˜.Jšœ˜—Jšžœ˜—Jšœ&žœ ₯˜P˜J˜J˜ Jšœ ˜ Jšžœžœžœ˜6Jšžœ₯™J˜ —Jšžœžœ9˜YJšžœžœžœ1˜QJ˜——–0.0 24 .div 1 1 textColorš’ ™ –( Postfix0.320 0.931 0.362 textColorOš £ œžœžœžœžœ˜YJšœCžœ™^Jšžœ žœžœžœ˜šžœž˜J˜J˜4Jšžœ=˜D—šžœž˜Jšœžœ˜ J˜GJšžœP˜W—šžœž˜Jšœžœ˜ J˜GJšžœP˜W—šžœž˜ Jšœžœ˜ J˜GJšžœJ˜Q—J˜J˜—–( Postfix0.320 0.931 0.362 textColorOš £ œžœžœžœžœ˜YJšœCžœ™^Jšžœ žœžœžœ˜šžœž˜ Jšœžœ˜ J˜EJšžœH˜O—˜J˜0J˜0J˜1—J˜J˜—–( Postfix0.320 0.931 0.362 textColorEš £ œžœžœ žœ žœ˜QJšœP™PJ˜%Jš œžœžœžœžœ˜(Jšžœ žœžœ˜%J™+šžœžœžœ ˜9Jšžœ#˜'—šžœžœžœ ˜9Jšžœ#˜'—šžœžœžœ ˜9Jšžœ#˜'—šžœžœ˜Jšžœžœžœ žœ ˜7Jšžœžœžœ žœ ˜9—J˜J˜—–( Postfix0.320 0.931 0.362 textColor@š£ œžœžœžœ˜KJšœ3žœ™NJšœžœ˜)Jšœžœ žœ˜Jšžœ žœžœ˜%J˜-Jšžœ žœ˜-J˜J˜—–( Postfix0.320 0.931 0.362 textColorGš£ œžœ$žœžœ˜SJšœ>žœ™YJ–(Postfix0.320 0.931 0.362 textColorLš£œžœžœžœžœžœžœ˜PJšœ žœžœ˜8J˜%šœ žœžœ ž˜&Jš œžœžœ žœ žœžœ˜2Jš œžœžœ žœ žœžœ˜2Jš œžœžœ žœ žœžœ˜2Jšžœ˜—šžœ ž˜Jšœ žœA˜NJšœ žœA˜NJšœ žœA˜NJšžœ˜—J˜Jšžœ žœ˜+J˜——–0.0 24 .div 1 1 textColorš’™–(Postfix0.320 0.931 0.362 textColor"š£œžœ˜5J˜TJ˜TJšœ4˜4J˜J˜—–(Postfix0.320 0.931 0.362 textColor?š£œžœ9˜QJ˜*J˜MJš‘(Πbd‘ΠbuΡbou‘™;JšœF™FJ˜Jšœ2˜2Jšœ2˜2Jšœ2˜2Jšœ2˜2Jšœ2˜2Jšœ2˜2Jšœ˜Jšœ ˜ J˜J˜—–(Postfix0.320 0.931 0.362 textColorš£œžœ˜4JšœH˜HJšœH˜HJšœH˜HJšœ˜J˜J˜—–(Postfix0.320 0.931 0.362 textColorš£œžœ˜2˜@J˜J˜=J˜—J˜J˜—–(Postfix0.320 0.931 0.362 textColor'š£œ'˜5Jšœžœ%˜@J˜#J˜LJ˜šžœ(˜*Jšžœžœžœ˜šžœ˜Jšœ@™@Jšœžœ žœ žœ ™Fšžœ žœžœ˜-J˜NJ˜0J˜Jšœ˜—J˜——J˜J˜—–(Postfix0.320 0.931 0.362 textColor*š£œ*˜;Jšœžœžœžœ ˜#J˜ Jšœžœ%˜@J˜#J˜LJ˜LJ˜SJ˜-šœ˜Jšžœžœžœžœ ˜0Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜J˜Jšœžœžœžœ ˜#J˜ Jšœžœ(˜DJ˜$J˜LJšœ˜šžœžœ˜J˜OJ˜J˜—šœ˜Jšžœžœžœžœ ˜0Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜˜Jšœ@˜@—J˜J˜SJšœ&žœ˜,J˜%J˜Jšœ6˜6Jšœ.˜.J˜2Jšœ)₯&˜OJšžœ žœžœ$₯˜Ušž˜Jšœ'˜'—Jšœ˜—J˜J˜—–(Postfix0.320 0.931 0.362 textColor.š£œžœ(˜>Jšœ;™;šžœ1žœžœž˜Ešžœžœ'˜BJšžœ8žœžœ˜I—Jšžœ˜—J˜——–0.0 24 .div 1 1 textColorš’™–( Postfix0.320 0.931 0.362 textColor?š£ œžœ.žœ˜LJšžœ˜Jšœ˜J™:Jšœžœ˜ J˜%J˜%šžœ3žœ˜;Jšœžœ˜J˜J˜J˜ Jšœ˜—J˜!J˜J™—–(Postfix0.320 0.931 0.362 textColorBš£œžœ1žœ˜QJšžœ˜Jšœ˜J™:J˜%J˜%J˜J™—–( Postfix0.320 0.931 0.362 textColor3š£ œžœžœ˜>J™TJ˜kJ˜CJ˜J™—–( Postfix0.320 0.931 0.362 textColor3š£ œžœžœ˜>J™UJ˜Cšžœ žœ ˜Jšžœ˜JšžœžœC˜S—J˜NJšžœ žœ˜+J˜J™—–( Postfix0.320 0.931 0.362 textColor=š£ œžœžœ˜IJ™GJšœžœ˜ Jšœ ™ Jšœ)™)šžœ ˜ Jšžœžœžœžœ-˜W—J˜J˜#J˜;J˜ J˜J™—–( Postfix0.320 0.931 0.362 textColorAš£ œžœžœ$˜MJ™FJ˜(Jšœžœ˜+Jšœžœ&˜/Jšœžœ˜#Jšžœ žœžœ'˜LJ˜MJ˜žœ˜TJ™9J˜"Jšœ<žœ˜BJ˜J˜—–( Postfix0.320 0.931 0.362 textColor–y -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]š£ œžœ˜Jšœ˜Jšœ˜Jšœ ˜ Jšœžœ˜ Jšœ˜Jšœžœžœ˜Jšœ˜J™G–(Postfix0.320 0.931 0.362 textColor"š£œžœžœ˜(Jšœžœžœ.˜>—J–(Postfix0.320 0.931 0.362 textColor@š £œžœ žœ žœžœ˜G–(Postfix0.320 0.931 0.362 textColorš£œžœ žœ˜%Jšœžœžœ<˜L—J˜"J˜"Jšœžœ-˜9J˜J˜J˜J˜J˜J˜#J˜#J˜#J˜#J˜J˜J˜J˜J˜J˜J˜J˜J˜–(Postfix0.320 0.931 0.362 textColor š£œžœ˜J˜)Jšœ0˜0Jšœ0˜0Jšœ0˜0Jšœ0˜0Jšœ0˜0Jšœ0˜0Jšœ0˜0Jšœ0˜0J˜Jšžœžœ1˜>J˜—J˜J˜——–0.0 24 .div 1 1 textColorš’™Jšœ;˜;Jšœ=˜=J˜—Jšžœ˜J˜J˜—…—lnŸ²