DIRECTORY Controls, Draw2d, G3dBasic, G3dControl, G3dDraw, G3dMatrix, G3dPlane, G3dShape, G3dVector, G3dView, Imager, ImagerColor, IO, KeyNames, KeyTypes, MessageWindow, Real, Rope, UserInputOps, ViewerOps, ViewersWorld, ViewersWorldInstance, ViewersWorldRefType, ViewerTools; G3dControlImpl: CEDAR PROGRAM IMPORTS Controls, Draw2d, G3dControl, G3dDraw, G3dMatrix, G3dPlane, G3dVector, G3dView, Imager, ImagerColor, IO, KeyNames, MessageWindow, Real, Rope, UserInputOps, ViewerOps, ViewersWorld, ViewersWorldInstance, ViewerTools EXPORTS G3dControl ~ BEGIN Viewer: TYPE ~ Controls.Viewer; Mouse: TYPE ~ Controls.Mouse; Control: TYPE ~ Controls.Control; ControlProc: TYPE ~ Controls.ControlProc; ControlType: TYPE ~ Controls.ControlType; ControlList: TYPE ~ Controls.ControlList; DrawType: TYPE ~ Draw2d.DrawType; Triple: TYPE ~ G3dBasic.Triple; TripleSequence: TYPE ~ G3dBasic.TripleSequence; Pair: TYPE ~ G3dBasic.Pair; PairSequence: TYPE ~ G3dBasic.PairSequence; ArcBall: TYPE ~ G3dControl.ArcBall; Method: TYPE ~ G3dControl.Method; ProxyMode: TYPE ~ G3dControl.ProxyMode; CameraControl: TYPE ~ G3dControl.CameraControl; CameraControlRep: TYPE ~ G3dControl.CameraControlRep; Hold: TYPE ~ G3dControl.Hold; HoldRep: TYPE ~ G3dControl.HoldRep; Slice: TYPE ~ G3dControl.Slice; SliceRep: TYPE ~ G3dControl.SliceRep; Pick: TYPE ~ G3dControl.Pick; Matrix: TYPE ~ G3dMatrix.Matrix; MatrixRep: TYPE ~ G3dMatrix.MatrixRep; Viewport: TYPE ~ G3dMatrix.Viewport; Plane: TYPE ~ G3dPlane.Plane; ScreenSequence: TYPE ~ G3dShape.ScreenSequence; Camera: TYPE ~ G3dView.Camera; CameraRep: TYPE ~ G3dView.CameraRep; Context: TYPE ~ Imager.Context; ROPE: TYPE ~ Rope.ROPE; xAxis: Triple ~ G3dBasic.xAxis; yAxis: Triple ~ G3dBasic.yAxis; zAxis: Triple ~ G3dBasic.zAxis; lastAdjustedCamera: CameraControl _ NIL; alwaysUpdate: BOOL _ TRUE; -- = TRUE: attempt to fix sync problem SetCameraControlMethod: PUBLIC PROC [camera: CameraControl, method: Method] ~ { camera.method _ method; }; CameraControlOrientation: PUBLIC PROC [ camera: CameraControl, eyePoint, lookAt, up: Triple, roll: REAL] ~ { camera.method _ geometry; camera.eyePoint _ eyePoint; camera.lookAt _ lookAt; camera.roll _ roll; }; InitCameraControl: PUBLIC PROC [ proc: ControlProc _ NIL, clientData: REF ANY _ NIL, proxyMode: ProxyMode _ wor, fieldOfView: REAL _ 60.0, scale: REAL _ 1.0, move: Triple _ [0.0, 2.0, 0.0], rotate: Triple _ [0.0, 0.0, 0.0], moveRange: REAL _ 10.0] RETURNS [c: CameraControl] ~ { NewControl: PROC [ name: Rope.ROPE, type: ControlType, taper: Controls.SliderTaper _ lin, min, max, init: REAL _ 0.0, col: Triple ¬ [0, 0, 1]] RETURNS [ctrl: Control] ~ { ctrl _ Controls.NewControl[ name: name, type: type, clientData: c, min: min, max: max, init: init, proc: CameraControlProc, taper: taper, color: ImagerColor.ColorFromRGB[[col.x, col.y, col.z]]]; }; r: REAL _ moveRange; c _ NEW[CameraControlRep]; IF move = [0.0, 2.0, 0.0] THEN move _ [0.0, 0.0, 2.0]; c.proc _ proc; c.clientData _ clientData; c.matrix _ NEW[MatrixRep]; c.view _ NEW[MatrixRep]; c.save _ NEW[CameraRep]; c.incr _ NEW[MatrixRep]; c.proxyMode _ proxyMode; c.proxySelect _ Controls.NewControl[NameFromProxyMode[proxyMode],, c,, 2, SELECT proxyMode FROM par => 0, wor => 1, ENDCASE => 2, ProxyProc, FALSE, 0,,,,,,,, LIST[[1, .5]]]; c.par.xMov _ NewControl["X", vSlider, , -r, r, move.x]; c.par.yMov _ NewControl["Y", vSlider, , -r, r, move.y]; c.par.zMov _ NewControl["Z", vSlider, , -r, r, move.z]; c.par.xRot _ NewControl["Xrot", dial, , 0.0, 360.0, rotate.x]; c.par.yRot _ NewControl["Yrot", dial, , 0.0, 360.0, rotate.y]; c.par.zRot _ NewControl["Zrot", dial, , 0.0, 360.0, rotate.z]; c.wor.xMov _ NewControl["WX", vSlider, , -r, r, 0.0]; c.wor.yMov _ NewControl["WY", vSlider, , -r, r, 0.0]; c.wor.zMov _ NewControl["WZ", vSlider, , -r, r, 0.0]; c.wor.xRot _ NewControl["WXrot", dial, , 0.0, 360.0, 0.0]; c.wor.yRot _ NewControl["WYrot", dial, , 0.0, 360.0, 0.0]; c.wor.zRot _ NewControl["WZrot", dial, , 0.0, 360.0, 0.0]; c.eye.xMov _ NewControl["EX", vSlider, , -r, r, 0.0]; c.eye.yMov _ NewControl["EY", vSlider, , -r, r, 0.0]; c.eye.zMov _ NewControl["EZ", vSlider, , -r, r, 0.0]; c.eye.xRot _ NewControl["Pitch", dial, , 0.0, 360.0, 0.0]; c.eye.yRot _ NewControl["Roll", dial, , 0.0, 360.0, 0.0]; c.eye.zRot _ NewControl["Yaw", dial, , 0.0, 360.0, 0.0]; c.proxy.xMov _ NewControl["X", vSlider, , -r, r, move.x]; c.proxy.yMov _ NewControl["Y", vSlider, , -r, r, move.y]; c.proxy.zMov _ NewControl["Z", vSlider, , -r, r, move.z]; c.proxy.xRot _ NewControl["Xrot", dial, , 0.0, 360.0, rotate.x]; c.proxy.yRot _ NewControl["Yrot", dial, , 0.0, 360.0, rotate.y]; c.proxy.zRot _ NewControl["Zrot", dial, , 0.0, 360.0, rotate.z]; c.scale _ NewControl["Scale", vSlider, exp, 0.0, 100.0, scale]; c.fieldOfView _ NewControl["Fov", vSlider, , 0.0, 180.0, fieldOfView, [1, 0, 0]]; c.hScreen _ NewControl["Xscr", vSlider, , -2.0, 2.0, 0.0]; c.vScreen _ NewControl["Yscr", vSlider, , -2.0, 2.0, 0.0]; c.arcBall _ G3dControl.InitArcBall[c, proc, clientData]; UpdateCameraControl[c]; }; AddCameraControl: PUBLIC PROC [ controls: ControlList, camera: CameraControl, useArcBalls: BOOL] RETURNS [ret: ControlList] ~ { ret _ CONS[camera.fieldOfView, controls]; IF useArcBalls THEN { ret _ CONS[camera.scale, ret]; ret _ CONS[camera.arcBall.translate.control, ret]; ret _ CONS[camera.arcBall.rotate.control, ret]; camera.fieldOfView.h _ camera.scale.h _ camera.arcBall.translate.control.h; camera.proxyMode _ par; } ELSE { ret _ CONS[camera.scale, ret]; ret _ CONS[camera.proxy.zMov, ret]; ret _ CONS[camera.proxy.yMov, ret]; ret _ CONS[camera.proxy.xMov, ret]; ret _ CONS[camera.proxy.zRot, ret]; ret _ CONS[camera.proxy.yRot, ret]; ret _ CONS[camera.proxy.xRot, ret]; ret _ CONS[camera.proxySelect, ret]; }; }; NameFromProxyMode: PROC [proxyMode: ProxyMode] RETURNS [r: ROPE] ~ { r _ SELECT proxyMode FROM par => "Par", wor => "Wor", ENDCASE => "Cam"; }; ProxyProc: ControlProc ~ { c: CameraControl ~ NARROW[control.clientData]; c.proxyMode _ SELECT control.value FROM 0 => par, 1 => wor, ENDCASE => eye; ViewerTools.SetContents[c.proxySelect.title, NameFromProxyMode[c.proxyMode]]; SELECT c.proxyMode FROM par => { Controls.SetSliderDialValue[c.proxy.xMov, c.par.xMov.value]; Controls.SetSliderDialValue[c.proxy.yMov, c.par.yMov.value]; Controls.SetSliderDialValue[c.proxy.zMov, c.par.zMov.value]; Controls.SetSliderDialValue[c.proxy.xRot, c.par.xRot.value]; Controls.SetSliderDialValue[c.proxy.yRot, c.par.yRot.value]; Controls.SetSliderDialValue[c.proxy.zRot, c.par.zRot.value]; }; ENDCASE => FOR l: ControlList _ LIST[c.proxy.xMov, c.proxy.yMov, c.proxy.zMov, c.proxy.xRot, c.proxy.yRot, c.proxy.zRot], l.rest WHILE l # NIL DO Benign[l.first]; ENDLOOP; }; InitContext: PUBLIC PROC [ context: Context, camera: CameraControl, viewer: Viewer _ NIL, clear: BOOL _ TRUE, out: Matrix _ NIL] RETURNS [Matrix] ~ { IF clear THEN Draw2d.Clear[context]; SetViewMatrix[camera]; IF viewer # NIL THEN camera.view _ G3dView.TransformByViewport[viewer, camera.view, camera.view]; RETURN[G3dMatrix.CopyMatrix[camera.view, out]]; }; CameraControlProc: ControlProc ~ { c: CameraControl ~ NARROW[control.clientData]; IF control = c.par.yRot AND (control.value = 90.0 OR control.value = 180.0 OR control.value = 270.0) THEN RETURN; -- this year's great kluge IF -- NOT Ignore[] -- control.mouse.state # up THEN { -- else user surprised by change c.current _ control; lastAdjustedCamera _ c; IF control.whatChanged = $InputKilled THEN MessageWindow.Append["InputKilled..."]; IF NOT alwaysUpdate THEN { IF control.mouse.state = down THEN SELECT CameraAction[c] FROM $Par, $Ignore => NULL; ENDCASE => { c.initval _ c.current.value; -- just zero the eye or wor control RETURN; -- maybe returning causes the update problem? }; }; UpdateCameraControl[c]; -- update the camera parameters and camera.matrix IF c.graphics # NIL THEN ViewerOps.PaintViewer[c.graphics, client, FALSE, control.whatChanged]; }; IF control.mouse.state = up THEN c.inverse _ G3dMatrix.Invert[c.matrix, c.inverse]; IF c.proc # NIL THEN c.proc[control, c.clientData]; -- call client proc, if any IF EverythingUp[] THEN SELECT CameraAction[c] FROM -- if missed up during draw $Par, $Proxy, $Ignore => NULL; ENDCASE => Benign[control]; }; UpdateCameraControl: PUBLIC PROC [camera: CameraControl] ~ { IF alwaysUpdate THEN DoUpdateCamera[camera] -- update based on current control ELSE { curr: Control _ camera.current; cleanupPrev: BOOL _ camera.prev # NIL AND camera.prev.mouse.state # up AND curr # camera.prev; slidIntoCurrent: BOOL _ camera.prev # curr AND curr.mouse.state # down; IF cleanupPrev THEN { -- enter new slider with no prev up, simulate prev up camera.prev.mouse.state _ up; -- camera.prev has its own mouse record camera.current _ camera.prev; DoUpdateCamera[camera]; -- clean up prev camera.current _ curr; }; IF slidIntoCurrent THEN curr.mouse.state _ down; -- simulate current down DoUpdateCamera[camera]; -- update for current control camera.prev _ curr; }; }; UpdateControl: PUBLIC PROC [camera: CameraControl, control: Control, value: REAL] ~ { IF control = NIL THEN RETURN; IF control.sliderDialRef = NIL THEN control.value _ value ELSE Controls.SetSliderDialValue[control, value]; IF camera.proxyMode = par THEN Controls.SetSliderDialValue[ProxyFromPar[camera, control], value]; IF NOT alwaysUpdate THEN { control.mouse.state _ up; -- so incremental controls will be absorbed into camera.par camera.current _ control; -- so Delta is properly computed }; DoUpdateCamera[camera]; -- as opposed to UpdateCameraControl }; ProxyFromPar: PROC [camera: CameraControl, control: Control] RETURNS [Control] ~ { RETURN[SELECT control FROM camera.par.xMov => camera.proxy.xMov, camera.par.yMov => camera.proxy.yMov, camera.par.zMov => camera.proxy.zMov, camera.par.xRot => camera.proxy.xRot, camera.par.yRot => camera.proxy.yRot, camera.par.zRot => camera.proxy.zRot, ENDCASE => NIL]; }; viewersWorld: ViewersWorldRefType.Ref ¬ ViewersWorldInstance.GetWorld[]; userInput: UserInputOps.Handle ¬ ViewersWorld.GetInputHandle[viewersWorld]; leftMouse: KeyTypes.KeySym ¬ KeyNames.KeySymFromName["LeftMouse"]; middleMouse: KeyTypes.KeySym ¬ KeyNames.KeySymFromName["MiddleMouse"]; rightMouse: KeyTypes.KeySym ¬ KeyNames.KeySymFromName["RightMouse"]; EverythingUp: PROC RETURNS [allUp: BOOL] ~ { allUp ¬ UserInputOps.GetLatestKeySymState[userInput, leftMouse] = up AND UserInputOps.GetLatestKeySymState[userInput, middleMouse] = up AND UserInputOps.GetLatestKeySymState[userInput, rightMouse] = up; }; Benign: PROC [control: Control] ~ { -- make control seem 'benign.' Controls.SetSliderDialValue[control, 0.0]; }; SetCameraControlGraphics: PUBLIC PROC [camera: CameraControl, graphics: Viewer] ~ { camera.graphics _ graphics; }; CameraAction: PROC [c: CameraControl] RETURNS [ATOM] ~ { mode: ProxyMode _ c.proxyMode; RETURN[SELECT c.current FROM -- meaning of c.current c.hScreen, c.vScreen => $Ignore, c.wor.xMov => $WorXMv, c.wor.yMov => $WorYMv, c.wor.zMov => $WorZMv, c.wor.xRot => $WorXRot, c.wor.yRot => $WorYRot, c.wor.zRot => $WorZRot, c.eye.xMov => $EyeXMv, c.eye.yMov => $EyeZMv, -- switch y and z so the user won't get confused c.eye.zMov => $EyeYMv, -- i.e., we're in eye-space (LHS), but pretend otherwise to user c.eye.xRot => $EyeXRot, c.eye.yRot => $EyeZRot, -- switch y and z so the user won't get confused c.eye.zRot => $EyeYRot, -- i.e., we're in eye-space (LHS), but pretend otherwise to user c.proxy.xMov => SELECT mode FROM par=>$Proxy, eye=>$EyeXMv, ENDCASE=>$WorXMv, c.proxy.yMov => SELECT mode FROM par=>$Proxy, eye=>$EyeZMv, ENDCASE=>$WorYMv, c.proxy.zMov => SELECT mode FROM par=>$Proxy, eye=>$EyeYMv, ENDCASE=>$WorZMv, c.proxy.xRot => SELECT mode FROM par=>$Proxy, eye=>$EyeXRot, ENDCASE=>$WorXRot, c.proxy.yRot => SELECT mode FROM par=>$Proxy, eye=>$EyeZRot, ENDCASE=>$WorYRot, c.proxy.zRot => SELECT mode FROM par=>$Proxy, eye=>$EyeYRot, ENDCASE=>$WorZRot, ENDCASE => $Par]; }; DoUpdateCamera: PROC [camera: CameraControl] ~ { Delta: PROC RETURNS [REAL] ~ { IF alwaysUpdate THEN RETURN[Controls.GetSliderDialDeltaValue[c.current]] ELSE RETURN[c.current.value-c.initval]; }; c: CameraControl _ camera; incrSpace: {world, eye, none} _ none; -- which space are incremental xforms in? action: ATOM _ CameraAction[c]; SELECT action FROM $WorXMv => {incrSpace _ world; [] _ G3dMatrix.MakeTranslate[[Delta[], 0, 0], c.incr]}; $WorYMv => {incrSpace _ world; [] _ G3dMatrix.MakeTranslate[[0, Delta[], 0], c.incr]}; $WorZMv => {incrSpace _ world; [] _ G3dMatrix.MakeTranslate[[0, 0, Delta[]], c.incr]}; $WorXRot => {incrSpace _ world; [] _ G3dMatrix.MakeRotate[xAxis, Delta[],, c.incr]}; $WorYRot => {incrSpace _ world; [] _ G3dMatrix.MakeRotate[yAxis, Delta[],,c.incr]}; $WorZRot => {incrSpace _ world; [] _ G3dMatrix.MakeRotate[zAxis, Delta[],, c.incr]}; $EyeXMv => {incrSpace _ eye; [] _ G3dMatrix.MakeTranslate[[Delta[], 0, 0], c.incr]}; $EyeYMv => {incrSpace _ eye; [] _ G3dMatrix.MakeTranslate[[0, Delta[], 0], c.incr]}; $EyeZMv => {incrSpace _ eye; [] _ G3dMatrix.MakeTranslate[[0, 0, Delta[]], c.incr]}; $EyeXRot => {incrSpace _ eye; [] _ G3dMatrix.MakeRotate[xAxis, Delta[],, c.incr]}; $EyeYRot => {incrSpace _ eye; [] _ G3dMatrix.MakeRotate[yAxis, Delta[],, c.incr]}; $EyeZRot => {incrSpace _ eye; [] _ G3dMatrix.MakeRotate[zAxis, Delta[],, c.incr]}; $Ignore => RETURN; ENDCASE; IF action # $Par AND action # $Proxy AND (alwaysUpdate OR c.current.mouse.state = up) THEN { ParameterizeIncrementalTransform[c, action]; incrSpace _ none; }; IF action = $Proxy THEN { -- update the corresponding par control parControl: Control _ SELECT c.current FROM c.proxy.xMov => c.par.xMov, c.proxy.yMov => c.par.yMov, c.proxy.zMov => c.par.zMov, c.proxy.xRot => c.par.xRot, c.proxy.yRot => c.par.yRot, ENDCASE => c.par.zRot; Controls.SetSliderDialValue[parControl, c.current.value]; }; c.matrix _ G3dView.MakeCameraMatrix[ IF incrSpace = world THEN c.incr ELSE NIL, c.scale.value, [c.par.xMov.value, c.par.yMov.value, c.par.zMov.value], [c.par.xRot.value, c.par.yRot.value, c.par.zRot.value], IF incrSpace = eye THEN c.incr ELSE NIL, IF c.use = shape THEN 0.0 ELSE c.fieldOfView.value, FALSE, -- c.use = view, FAREWELL TO FRANK's COORDINATE SYSTEM c.matrix]; IF c.mouse.state = up OR c.mouse.state = none THEN [c.eyePoint, c.lookAt, c.up] _ G3dView.FromScaleMovesRots[ c.scale.value, [c.par.xMov.value, c.par.yMov.value, c.par.zMov.value], [c.par.xRot.value, c.par.yRot.value, c.par.zRot.value] ! G3dMatrix.singular => CONTINUE]; }; ParameterizeIncrementalTransform: PROC [c: CameraControl, action: ATOM] ~ { rotateMode: {body, world} _ world; -- former default was body oldrot: Matrix _ G3dMatrix.Identity[G3dMatrix.ObtainMatrix[]]; oldrot _ G3dMatrix.Rotate[oldrot, xAxis, c.par.xRot.value,,, oldrot]; oldrot _ G3dMatrix.Rotate[oldrot, yAxis, c.par.yRot.value,,, oldrot]; oldrot _ G3dMatrix.Rotate[oldrot, zAxis, c.par.zRot.value,,, oldrot]; SELECT action FROM $WorXMv, $WorYMv, $WorZMv => { -- merge world move into standard move t: Triple _ [c.par.xMov.value, c.par.yMov.value, c.par.zMov.value]; ti: Triple _ G3dVector.Mul[ [c.incr[3][0], c.incr[3][1], c.incr[3][2]], c.scale.value ]; IF rotateMode = world THEN { rInv: Matrix _ G3dMatrix.Transpose[oldrot, G3dMatrix.ObtainMatrix[]]; -- = inverse t _ G3dVector.Add[t, G3dMatrix.Transform[ti, rInv]]; G3dMatrix.ReleaseMatrix[rInv]; } ELSE t _ G3dVector.Add[t, ti]; Controls.SetSliderDialValue[c.par.xMov, t.x]; Controls.SetSliderDialValue[c.par.yMov, t.y]; Controls.SetSliderDialValue[c.par.zMov, t.z]; }; $WorXRot, $WorYRot, $WorZRot => { -- merge world rotate into standard rotate and move r: Matrix _ G3dMatrix.ObtainMatrix[]; rRot: Matrix _ G3dMatrix.ObtainMatrix[]; parMov: Triple _ [c.par.xMov.value, c.par.yMov.value, c.par.zMov.value]; t: Triple; IF rotateMode = world THEN [] _ G3dMatrix.Mul[oldrot, c.incr, r] -- newer rotate matrix ELSE [] _ G3dMatrix.Mul[c.incr, oldrot, r]; -- new rotate matrix t _ G3dMatrix.ExtractRotate[r]; Controls.SetSliderDialValue[c.par.xRot, t.x]; Controls.SetSliderDialValue[c.par.yRot, t.y]; Controls.SetSliderDialValue[c.par.zRot, t.z]; IF rotateMode = world THEN { rRot: Matrix _ G3dMatrix.Transpose[r, G3dMatrix.ObtainMatrix[]]; t _ G3dMatrix.Transform[parMov, oldrot]; t _ G3dMatrix.Transform[t, rRot]; G3dMatrix.ReleaseMatrix[rRot]; } ELSE { [] _ G3dMatrix.Transpose[c.incr, r]; -- r now holds inverse of incremental rotate t _ G3dMatrix.Transform[parMov, r]; }; Controls.SetSliderDialValue[c.par.xMov, t.x]; Controls.SetSliderDialValue[c.par.yMov, t.y]; Controls.SetSliderDialValue[c.par.zMov, t.z]; G3dMatrix.ReleaseMatrix[r]; }; $EyeXMv, $EyeYMv, $EyeZMv => { -- merge eye move (dolly) into standard move rInv: Matrix _ G3dMatrix.Transpose[oldrot, G3dMatrix.ObtainMatrix[]]; -- = inverse parMov: Triple _ [c.par.xMov.value, c.par.yMov.value, c.par.zMov.value]; eyeMov: Triple _ [c.incr[3][0], c.incr[3][1], c.incr[3][2]]; -- 2 out of 3 are zero eyeMov _ [eyeMov.x, eyeMov.z, eyeMov.y]; parMov _ G3dVector.Add[parMov, G3dMatrix.Transform[eyeMov, rInv]]; Controls.SetSliderDialValue[c.par.xMov, parMov.x]; Controls.SetSliderDialValue[c.par.yMov, parMov.y]; Controls.SetSliderDialValue[c.par.zMov, parMov.z]; G3dMatrix.ReleaseMatrix[rInv]; }; $EyeXRot, $EyeYRot, $EyeZRot => { -- merge eye rotate (gimbal) into standard rotate t: Triple; r: Matrix _ G3dMatrix.ObtainMatrix[]; swap: Matrix _ G3dMatrix.ObtainMatrix[]; swap^ _ [[1., 0., 0., 0.], [0., 0., 1., 0.], [0., 1., 0., 0.], [0., 0., 0., 1.]]; [] _ G3dMatrix.Mul[oldrot, swap, r]; [] _ G3dMatrix.Mul[r, c.incr, r]; [] _ G3dMatrix.Mul[r, swap, r]; -- RXRiX t _ G3dMatrix.ExtractRotate[r]; Controls.SetSliderDialValue[c.par.xRot, t.x]; Controls.SetSliderDialValue[c.par.yRot, t.y]; Controls.SetSliderDialValue[c.par.zRot, t.z]; G3dMatrix.ReleaseMatrix[r]; G3dMatrix.ReleaseMatrix[swap]; }; ENDCASE => NULL; G3dMatrix.ReleaseMatrix[oldrot]; }; GetCameraControlMatrix: PUBLIC PROC [camera: CameraControl] RETURNS [Matrix] ~ { RETURN[camera.matrix]; }; GetViewMatrix: PUBLIC PROC [camera: CameraControl, out: Matrix] RETURNS [Matrix] ~ { SetViewMatrix[camera]; RETURN[G3dMatrix.CopyMatrix[camera.view, out]]; }; SetViewMatrix: PROC [c: CameraControl] ~ { c.view _ G3dMatrix.Translate[c.matrix, [c.hScreen.value, c.vScreen.value, 0.0], c.view]; }; EyeViewFromCameraControl: PUBLIC PROC [camera: CameraControl] RETURNS [Triple] ~ { eyePoint, lookAt: Triple; [eyePoint, lookAt, []] _ G3dView.FromScaleMovesRots[ camera.scale.value, [camera.par.xMov.value, camera.par.yMov.value, camera.par.zMov.value], [camera.par.xRot.value, camera.par.yRot.value, camera.par.zRot.value]]; RETURN[G3dVector.Sub[lookAt, eyePoint]]; }; SaveState: PUBLIC PROC [camera: CameraControl, state: Camera] ~ { ab: ArcBall _ camera.arcBall; state^ _ [ move: [camera.par.xMov.value, camera.par.yMov.value, camera.par.zMov.value], rotate: [camera.par.xRot.value, camera.par.yRot.value, camera.par.zRot.value], scale: camera.scale.value, fieldOfView: camera.fieldOfView.value, roll: camera.roll, eyePoint: camera.eyePoint, lookAt: camera.lookAt, up: camera.up, matrix: G3dMatrix.CopyMatrix[camera.matrix, state.matrix]]; }; RestoreState: PUBLIC PROC [camera: CameraControl, state: Camera] ~ { ab: ArcBall _ camera.arcBall; Controls.SetSliderDialValue[camera.par.xMov, state.move.x]; Controls.SetSliderDialValue[camera.par.yMov, state.move.y]; Controls.SetSliderDialValue[camera.par.zMov, state.move.z]; Controls.SetSliderDialValue[camera.par.xRot, state.rotate.x]; Controls.SetSliderDialValue[camera.par.yRot, state.rotate.y]; Controls.SetSliderDialValue[camera.par.zRot, state.rotate.z]; Controls.SetSliderDialValue[camera.scale, state.scale]; Controls.SetSliderDialValue[camera.fieldOfView, state.fieldOfView]; camera.roll _ state.roll; camera.eyePoint _ state.eyePoint; camera.lookAt _ state.lookAt; camera.up _ state.up; camera.matrix _ G3dMatrix.CopyMatrix[state.matrix, camera.matrix]; }; LastAdjustedCameraControl: PUBLIC PROC RETURNS [CameraControl] ~ { RETURN[lastAdjustedCamera]; }; CameraControlMessage: PUBLIC PROC [camera: CameraControl] RETURNS [out: ROPE] ~ { Add: PROC [r: ROPE] ~ {IF r#NIL THEN out_Rope.Cat[out,IF out=NIL THEN NIL ELSE "; ",r]}; Ok: PROC [c: Control] RETURNS [b: BOOL] ~ {b _ c.viewer # NIL AND c.value # 0.0}; Val: PROC [c: Control, id: ROPE] RETURNS [o: ROPE] ~ { IF Ok[c] THEN o _ IO.PutFR["%g%g", IO.real[Real.Round[1000*c.value]/1000], IO.rope[id]]; }; Val3: PROC [header: ROPE, c1, c2, c3: Control, id1, id2, ig3d: ROPE] RETURNS [out: ROPE] ~ { Inner: PROC [c: Control, id: ROPE] ~ { IF NOT Ok[c] THEN RETURN; IF once THEN out _ Rope.Concat[out, ", "]; once _ TRUE; out _ IO.PutFR["%g%g", IO.rope[out], IO.rope[Val[c, id]]]; }; once: BOOL _ FALSE; Inner[c1, id1]; Inner[c2, id2]; Inner[c3, ig3d]; IF once THEN out _ Rope.Cat[header, ": ", out]; }; c: CameraControl _ camera; Add[Val[c.scale, "scale"]]; Add[Val3["move", c.par.xMov, c.par.yMov, c.par.zMov, "x", "y", "z"]]; Add[Val3["rot", c.par.xRot, c.par.yRot, c.par.zRot, "x", "y", "z"]]; Add[Val[c.fieldOfView, "fov"]]; }; InitSlice: PUBLIC PROC [data: REF ANY _ NIL, proc: Controls.ControlProc _ NIL] RETURNS [slice: Slice] ~ { slice _ NEW[SliceRep]; slice.x _ Controls.NewControl["x", vSlider, data, -1.0, 1.0, 0.0, proc]; slice.y _ Controls.NewControl["y", vSlider, data, -1.0, 1.0, 0.0, proc]; slice.z _ Controls.NewControl["z", vSlider, data, -1.0, 1.0, 0.0, proc]; slice.phi _ Controls.NewControl["phi", dial, data, 0.0, 360.0, 90.0, proc]; slice.theta _ Controls.NewControl["theta", dial, data, 0.0, 360.0, 0.0, proc]; slice.size _ Controls.NewControl["size", vSlider, data, 0.0, 1.0, 0.1, proc]; UpdateSlice[slice]; }; SetSlice: PUBLIC PROC [slice: Slice, plane: Plane, size: REAL _ 1.0] ~ { o: Triple _ G3dPlane.CenterOfPlane[plane]; t: Triple _ G3dVector.PolarFromCartesian[[plane.x, plane.y, plane.z]]; SetTripleControls[slice.x, slice.y, slice.z, o]; SetTripleControls[slice.phi, slice.theta, slice.size, [t.x, t.y, size]]; UpdateSlice[slice]; }; UpdateSlice: PUBLIC PROC [slice: Slice] ~ { o: Triple ~ [slice.x.value, slice.y.value, slice.z.value]; z: Triple ~ G3dVector.CartesianFromPolar[[slice.phi.value, slice.theta.value, 1.0]]; x: Triple ~ G3dVector.Unit[G3dVector.Cross[IF z # zAxis THEN zAxis ELSE yAxis, z]]; y: Triple ~ G3dVector.Unit[G3dVector.Cross[x, z]]; slice.plane _ G3dPlane.FromPointAndNormal[o, z]; slice.matrix _ G3dMatrix.MakeFromTriad[x, y, z, o, FALSE, slice.matrix]; slice.matrix _ G3dMatrix.LocalScale[slice.matrix, slice.size.value, slice.matrix]; }; DrawSlice: PUBLIC PROC [ context: Context, slice: Slice, view: Matrix, viewport: Viewport _ [], drawType: DrawType _ solid] ~ { zip: Draw2d.Zip _ Draw2d.GetZip[context]; IF slice # NIL AND slice.matrix # NIL THEN { m: Matrix ~ G3dMatrix.Mul[slice.matrix, view]; p: Triple ~ [-slice.plane.x, -slice.plane.y, -slice.plane.z]; q: ARRAY [0..4) OF Pair _ [[-1.0, 1.0], [1.0, 1.0], [1.0, -1.0], [-1.0, -1.0]]; a: ARRAY [0..4) OF Pair; c: Triple _ G3dMatrix.TransformPair[[0.0, 0.0], slice.matrix]; FOR i: NAT IN [0..4) DO a[i] _ G3dMatrix.TransformPairD[q[i], m]; ENDLOOP; FOR i: NAT IN [0..4) DO Draw2d.Line[context, a[i], a[(i+1) MOD 4], drawType, zip]; ENDLOOP; G3dDraw.Vector[context, c, p, view, viewport,, 0.1]; }; }; MoveSliceInY: PUBLIC PROC [slice: Slice, amount: REAL] ~ { v: Triple ~ G3dMatrix.TransformVec[yAxis, slice.matrix]; o: Triple _ G3dVector.Combine[[slice.x.value, slice.y.value, slice.z.value], 1., v, amount]; Controls.SetSliderDialValue[slice.x, o.x]; Controls.SetSliderDialValue[slice.y, o.y]; Controls.SetSliderDialValue[slice.z, o.z]; UpdateSlice[slice]; }; MoveSliceInZ: PUBLIC PROC [slice: Slice, amount: REAL] ~ { v: Triple ~ G3dMatrix.TransformVec[zAxis, slice.matrix]; o: Triple _ G3dVector.Combine[[slice.x.value, slice.y.value, slice.z.value], 1., v, amount]; Controls.SetSliderDialValue[slice.x, o.x]; Controls.SetSliderDialValue[slice.y, o.y]; Controls.SetSliderDialValue[slice.z, o.z]; UpdateSlice[slice]; }; InitHold: PUBLIC PROC [proc: ControlProc _ NIL, data: REF ANY _ NIL] RETURNS [h: Hold] ~ { h _ NEW[HoldRep]; h.x _ Controls.NewControl["ptx", vSlider, data, -1.0, 1.0, 0.0, proc]; h.y _ Controls.NewControl["pty", vSlider, data, -1.0, 1.0, 0.0, proc]; h.z _ Controls.NewControl["ptz", vSlider, data, -1.0, 1.0, 0.0, proc]; h.pitch _ Controls.NewControl["pitch", dial, data, 0.0, 360.0, 0.0, proc]; h.yaw _ Controls.NewControl["yaw", dial, data, 0.0, 360.0, 0.0, proc]; h.roll _ Controls.NewControl["roll", dial, data, 0.0, 360.0, 0.0, proc]; h.mag _ Controls.NewControl["mag", vSlider, data, -2.0, 2.0, 1.0, proc]; }; SetHold: PUBLIC PROC [hold: Hold, position, tangent: Triple _ [0.0, 0.0, 0.0]] ~ { polar: Triple _ G3dVector.PolarFromCartesian[tangent]; Controls.SetSliderDialValue[hold.pitch, polar.x]; Controls.SetSliderDialValue[hold.yaw, polar.y]; Controls.SetSliderDialValue[hold.mag, polar.z]; Controls.SetSliderDialValue[hold.x, position.x]; Controls.SetSliderDialValue[hold.y, position.y]; Controls.SetSliderDialValue[hold.z, position.z]; }; PositionFromHold: PUBLIC PROC [hold: Hold] RETURNS [Triple] ~ { RETURN[[hold.x.value, hold.y.value, hold.z.value]]; }; VectorFromHold: PUBLIC PROC [hold: Hold] RETURNS [t: Triple] ~ { t _ G3dVector.CartesianFromPolar[[hold.yaw.value, hold.pitch.value, hold.mag.value]]; }; ScreenPick: PUBLIC PROC [screens: ScreenSequence, screen: G3dBasic.IntegerPair] RETURNS [picked: INTEGER _ -1] ~ { IF screens # NIL THEN { dist, max: INTEGER _ 1000000; FOR n: NAT IN [0..screens.length) DO p: G3dBasic.IntegerPair _ screens[n].intPos; d: G3dBasic.IntegerPair _ [screen.x-p.x, screen.y-p.y]; IF (dist _ d.x*d.x+d.y*d.y) < max THEN {max _ dist; picked _ n}; ENDLOOP; }; }; PointPick: PUBLIC PROC [ points: TripleSequence, pairs: PairSequence, view: Matrix, mouse: Mouse] RETURNS [pointPicked: INTEGER] ~ { ln: NAT _ IF points # NIL THEN points.length ELSE IF pairs # NIL THEN pairs.length ELSE 0; dist, max: REAL _ 100000.0; IF pairs = NIL THEN RETURN[-1]; IF points # NIL THEN { IF pairs.maxLength < points.length THEN RETURN[-2]; IF mouse.state = down OR mouse.state = held THEN FOR n: NAT IN [0..points.length) DO pairs[n] _ G3dMatrix.TransformD[points[n], view]; ENDLOOP; }; pointPicked _ 0; FOR i: NAT IN [0..ln) DO dx: REAL _ mouse.pos.x-pairs[i].x; dy: REAL _ mouse.pos.y-pairs[i].y; IF (dist _ dx*dx+dy*dy) < max THEN {max _ dist; pointPicked _ i}; ENDLOOP; }; PickMovePoint: PUBLIC PROC [ points: TripleSequence, pairs: PairSequence _ NIL, view: Matrix, viewport: Viewport, mouse: Mouse, pick: Pick] RETURNS [Pick] ~ { persp: BOOL _ G3dMatrix.HasPerspective[view]; SELECT mouse.state FROM down => { min: REAL _ 1000000.0; FOR n: NAT IN [0..points.length) DO screen: Pair _ G3dMatrix.GetScreen[points[n], view, persp, viewport].pos; dif: Pair _ [mouse.pos.x-screen.x, mouse.pos.y-screen.y]; r: REAL _ dif.x*dif.x+dif.y*dif.y; IF r < min THEN {min _ r; pick _ [dif, n]}; ENDLOOP; }; held => { screen: Pair _ [mouse.pos.x-pick.offset.x, mouse.pos.y-pick.offset.y]; plane: Plane _ IF persp THEN G3dPlane.FromPointAndNormal[ points[pick.selected], G3dVector.Negate[points[pick.selected]]] ELSE [0.0, 0.0, 1.0, -points[pick.selected].z]; points[pick.selected] _ G3dMatrix.TripleFromScreenAndPlane[ screen, [plane.x, plane.y, plane.z, plane.w], view ! G3dPlane.Error => GOTO BadView]; IF pairs # NIL THEN pairs[pick.selected] _ [mouse.pos.x, mouse.pos.y]; }; ENDCASE; RETURN[pick]; EXITS BadView => { MessageWindow.Append[" bad view-"]; RETURN[pick]; }; }; PaintControls: PUBLIC PROC [c1, c2, c3, c4, c5, c6: Control _ NIL] ~ { IF c1 # NIL THEN ViewerOps.PaintViewer[c1.viewer, client, FALSE, c1]; IF c2 # NIL THEN ViewerOps.PaintViewer[c2.viewer, client, FALSE, c2]; IF c3 # NIL THEN ViewerOps.PaintViewer[c3.viewer, client, FALSE, c2]; IF c4 # NIL THEN ViewerOps.PaintViewer[c3.viewer, client, FALSE, c2]; IF c5 # NIL THEN ViewerOps.PaintViewer[c3.viewer, client, FALSE, c2]; IF c6 # NIL THEN ViewerOps.PaintViewer[c3.viewer, client, FALSE, c2]; }; SetTripleControls: PUBLIC PROC [x, y, z: Control _ NIL, triple: Triple, repaint: BOOL _ TRUE] ~ { IF x # NIL THEN Controls.SetSliderDialValue[x, triple.x, repaint]; IF y # NIL THEN Controls.SetSliderDialValue[y, triple.y, repaint]; IF z # NIL THEN Controls.SetSliderDialValue[z, triple.z, repaint]; }; END. Θ G3dControlImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Bloomenthal, February 26, 1993 12:32 pm PST Heckbert, June 18, 1988 1:14:46 pm PDT Ken Shoemake, April 5, 1989 3:08:45 pm PDT Type Declarations Global Variables CameraControl Procedures Note: Controls.KillInputFocus has changed so that ActivateControl is called ONLY if the user typed a value followed by a CR. This is as advertised in Controls. The previous behavior, wherein ActivateControl was called upon any kill of input focus, would result in serious problems in controlling the input focus, namely: the control proc called by ActivateControl might change the input focus and cause an endless propagation of KillInputFocus; one solution was to save the input focus and restore it after the control proc is called, but what if the user actually wanted to change the input focus? So . . . the present solution is to ActivateControl only upon a typed CR; this means that clients must read the values of controls (and buttons) whenever a new function is executed. To get back the color, use ImagerColor.NarrowToOpConstantColor. Ignore: PROC RETURNS [BOOL _ FALSE] ~ { IF control.mouse.state = up THEN RETURN; IF NOT ControlsPrivate.CalledFromQueue[control] THEN RETURN; If event was queued, service only last event if it is held: IF ControlsPrivate.GetQueue[control] = NIL AND control.mouse.state = held THEN RETURN; RETURN[TRUE]; }; Update the camera params when a control is adjusted by the user. If always update then we don't need initval (set on mouse down, used on mouse up): This method requires initval to be right: In event user slides off a control without an up mouse event occurring, or slides into a control without a down event (we'd like an enter/exit notifier, if not too hard to use): camera.initval _ 0.; -- so Delta is properly computed (simulate down mouse) switch $EyeZMv and $EyeYMv so the user won't get confused: switch $EyeZRot and $EyeYRot so the user won't get confused: This presumes the change has occured in camera.current only. If NOT alwaysUpdate, event presumed to begin with mouse down and end with mouse up. Shift incremental transform into standard parameters: DON'T FUCK WITH THIS CODE, PLEASE!!!!!!!! Absorb current incremental move/rotate into standard transform (see WorldToViewMatrix) See the G3dControl CameraControl Model notes by Paul Heckbert. construct old rotate matrix: merge incremental transformation into standard rotate and translate compute Tx _ XTiX (incremental translate vector pre- and post-multiplied by swap X): compute new globalMove and set the controls: form new standard rotate (R' _ RXRiX, with R= standard rotate, Ri= eye rotate, X=swap) using the identity RXRi = RXRiXX = (RXRiX)X = R'X make a "swap" matrix (to swap y and z axes) Slice Procedures Hold Procedures Interaction with the Display The following plane is centered on the selected point and directed at the origin; not sure it'll work for views with translation. Miscellaneous Procedures Κ'>•NewlineDelimiter ™™Jšœ Οmœ1™J˜>J˜>J˜8J˜8J˜8J˜>J˜=J˜Jšœžœ˜šžœ˜ Jšœ£#˜AJšžœ£2˜;J˜——J˜—Jšœ£2˜Mšžœž˜Jšžœ+žœ˜K—J˜—Jšžœžœ3˜SJšžœ žœžœ#£˜Rš žœžœžœžœ£˜NJšœžœ˜Jšžœ˜—J˜J˜—–(Postfix0.320 0.931 0.362 textColor)š¦œž œ˜<š ‘ ˜Jšžœ£#˜@šžœ˜J™)J™XJ™XJ˜šœ žœ˜Jšœ žœžœžœ˜J—Jšœžœžœ˜Gšžœ žœ£€£€£€£€£€£€£€£€£€£˜NJšœ £œ£˜GJ˜Jšœ£˜+J˜J˜—Jšžœžœ£˜IJšœ£˜7J˜J˜——J˜J˜—–( Postfix0.320 0.931 0.362 textColorHš¦ œžœžœ2žœ˜UJšžœ žœžœžœ˜šžœž˜Jšžœ˜Jšžœ-˜1—šžœ˜JšžœC˜G—š ‘ ‘ ‘˜Jšœ£;˜VJšœ£ ˜;Jšœ£6™NJ˜—Jšœ£$˜>J˜J˜—–( Postfix0.320 0.931 0.362 textColorFš¦ œžœ+žœ ˜Ršžœžœ ž˜J˜&J˜%J˜%J˜&J˜&J˜&Jšžœžœ˜—J˜J˜—J˜HJ˜LJ˜CJ˜F˜EJ˜—–( Postfix0.320 0.931 0.362 textColor š¦ œžœžœ žœ˜,šœEž˜HJšœCž˜FJ˜B—J˜J˜—–(Postfix0.320 0.931 0.362 textColor<š¦œžœ£˜BJ˜*J˜J˜—–(Postfix0.320 0.931 0.362 textColor;š¦œž œ.˜SJ˜J˜J˜—–( Postfix0.320 0.931 0.362 textColor,š¦ œžœžœžœ˜8J˜šžœžœ žœ£˜5J˜ J˜J˜J˜J˜J˜J˜J˜Jšœ£0˜GJšœ£@˜WJ˜Jšœ£0˜HJšœ£@˜XJšœžœžœžœ ˜MJ™:Jšœžœžœžœ ˜MJšœžœžœžœ ˜MJšœžœžœžœ ˜OJ™Jšœ#£˜=™J˜>J˜EJ˜EJ˜E—Jš£C™Cšžœž˜šœ£&˜EJ˜CJ˜Xšžœ˜šžœ˜JšœF£ ˜RJ˜4J˜J˜—Jšžœ˜—J˜-J˜-J˜-J˜—šœ"£3˜UJ˜%J˜(J˜HJ˜ šžœ˜Jšžœ)£˜CJšžœ*£˜B—J˜J˜-J˜-J˜-šžœ˜šžœ˜J˜@J˜(J˜!J˜J˜—šžœ˜Jšœ%£,˜QJ˜#J˜——J˜-J˜-J˜-J˜J˜—šœ£-˜KJšœF£ ˜RJ˜HJšœ=£˜Sšœ ΟdœͺœD™TJ˜(—™-J˜BJ˜2J˜2J˜2—J˜J˜—šœ"£1˜SJ˜ šœ£Πcd£œͺœ™VJšœͺœͺœ ͺœ ™1J˜%J˜(™+J˜Q—J˜%J˜!Jšœ&£«£˜.J˜J˜-J˜-J˜-J˜J˜—J˜—Jšžœžœ˜—J˜ J˜J˜—–(Postfix0.320 0.931 0.362 textColor:š¦œžœžœžœ ˜PJšžœ˜J˜J˜—–( Postfix0.320 0.931 0.362 textColorGš¦ œžœžœ&žœ ˜TJ˜Jšžœ)˜/J˜J™—–( Postfix0.320 0.931 0.362 textColorš¦ œžœ˜*J˜XJ˜J™—–(Postfix0.320 0.931 0.362 textColor:š¦œžœžœžœ ˜RJ˜˜4J˜J˜FJ˜G—Jšžœ"˜(J˜J˜—–( Postfix0.320 0.931 0.362 textColor8š¦ œžœžœ+˜AJ˜˜ J˜LJ˜NJšœ˜Jšœ&˜&Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜;—J˜J˜—–( Postfix0.320 0.931 0.362 textColor8š¦ œžœžœ+˜DJ˜J˜;J˜;J˜;J˜=J˜=J˜=Jšœ7˜7JšœC˜CJšœ˜Jšœ!˜!Jšœ˜Jšœ˜J˜BJ˜J˜—–(Postfix0.320 0.931 0.362 textColor)š¦œž œžœ˜BJšžœ˜J˜J˜—–(Postfix0.320 0.931 0.362 textColor=š ¦œžœžœžœžœ˜QJ–(Postfix0.320 0.931 0.362 textColorUš¦œžœžœžœžœžœžœžœžœžœžœ ˜XJ–(Postfix0.320 0.931 0.362 textColorOš ¦œžœžœžœžœžœ˜Q–(Postfix0.320 0.931 0.362 textColor3š ¦œžœžœžœžœ˜6Jš žœžœžœžœ%žœ ˜XJ˜—–(Postfix0.320 0.931 0.362 textColorXš ¦œžœ žœ'žœžœžœ˜\–(Postfix0.320 0.931 0.362 textColor!š¦œžœžœ˜&Jšžœžœžœžœ˜Jšžœžœ˜*Jšœžœ˜ Jšœžœžœ žœ˜:J˜—Jšœžœžœ˜J˜0Jšžœžœ#˜/J˜—J˜J˜J˜EJ˜DJ˜J˜——–0.0 24 .div 1 1 textColorš’™–( Postfix0.320 0.931 0.362 textColorEš¦ œžœžœžœžœžœžœ˜NJšžœ˜J˜Jšœžœ ˜J˜IJ˜HJ˜IJ˜KJ˜NJ˜MJ˜J˜J˜—–(Postfix0.320 0.931 0.362 textColor@š¦œžœžœ$žœ ˜HJ˜*J˜FJ˜0J˜HJ˜J˜J˜—–( Postfix0.320 0.931 0.362 textColor š¦ œž œ˜+J˜:J˜TJšœ+žœ žœžœ ˜SJ˜2J˜0Jšœ3žœ˜HJ˜RJ˜J˜—–( Postfix0.320 0.931 0.362 textColorš¦ œžœžœ˜J˜J˜ J˜ J˜Jšœ˜J˜Jšœ)˜)š žœ žœžœžœžœ˜,J˜.Jšœ=˜=Jšœžœžœ=˜OJšœžœžœ˜Jšœ>˜>Jš žœžœžœžœ+žœ˜Jšžœžœžœž˜Jšœ#žœ˜:Jšžœ˜—J˜4J˜—J˜J˜—–( Postfix0.320 0.931 0.362 textColor.š¦ œž œžœ˜:J˜8J˜\J˜*J˜*J˜*J˜J˜J˜—–( Postfix0.320 0.931 0.362 textColor.š¦ œž œžœ˜:J˜8J˜\J˜*J˜*J˜*J˜J˜——–0.0 24 .div 1 1 textColorš’™–(Postfix0.320 0.931 0.362 textColorRš¦œžœžœžœžœžœžœžœ˜ZJšœžœ ˜J˜FJ˜FJ˜FJ˜JJ˜FJ˜HJ˜HJ˜J™—–(Postfix0.320 0.931 0.362 textColorKš‘œžœžœ>˜RJ˜6J˜1J˜/J˜/J˜0J˜0J˜0J˜J˜—–(Postfix0.320 0.931 0.362 textColor/š¦œž œžœ ˜?Jšžœ-˜3J˜J˜—–(Postfix0.320 0.931 0.362 textColor2š¦œžœžœžœ˜@J˜UJ˜——–0.0 24 .div 1 1 textColorš’™–( Postfix0.320 0.931 0.362 textColorEš¦ œž œ8˜OJšžœ žœ˜Jšœ˜šžœ žœžœ˜Jšœ žœ ˜šžœžœžœž˜$Jšœ,˜,Jšœ7˜7Jšžœ žœ˜@Jšžœ˜—J˜—J˜J˜—–( Postfix0.320 0.931 0.362 textColorš¦ œžœžœ˜J˜HJšžœžœ˜J˜Jšœžœžœ žœžœžœžœ žœžœžœ˜ZJšœ žœ žœ˜Jšžœ žœžœžœ˜šžœ žœžœ˜Jšžœ!žœžœ˜3šžœžœ˜+š žœžœžœžœž˜(J˜1Jšžœ˜——J˜—J˜šžœžœžœ ž˜Jšœžœ˜"Jšœžœ˜"Jšžœžœ˜AJšžœ˜—J˜J˜—–( Postfix0.320 0.931 0.362 textColorš¦ œž œ˜J˜Jšœžœ˜J˜ J˜J˜ J˜ Jšžœ˜J˜Jšœžœ"˜-šžœ ž˜˜ Jšœžœ ˜šžœžœžœž˜#J˜IJ˜9Jšœžœ˜"Jšžœ žœ˜+Jšžœ˜—J˜—˜ J˜FJ™QJ™/šœžœ˜šžœ˜!J˜J˜(—Jšžœ+˜/—˜;˜2Jšœžœ ˜"——Jšžœ žœžœ3˜FJ˜—Jšžœ˜—Jšžœ˜ šžœ ˜J˜#Jšžœ˜ J˜—J˜——–0.0 24 .div 1 1 textColorš’™–( Postfix0.320 0.931 0.362 textColor9š¦ œžœžœ$žœ˜FKšžœžœžœ*žœ˜EKšžœžœžœ*žœ˜EKšžœžœžœ*žœ˜EKšžœžœžœ*žœ˜EKšžœžœžœ*žœ˜EKšžœžœžœ*žœ˜EK˜K˜—–(Postfix0.320 0.931 0.362 textColorLš ¦œž œžœžœžœ˜]K˜Kšžœžœžœ3˜BKšžœžœžœ3˜BKšžœžœžœ3˜BK˜K˜——Jšžœ˜J˜J˜—…—o"’(