G3dControlImpl.mesa
Copyright © 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
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
Type Declarations
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;
Global Variables
lastAdjustedCamera: CameraControl ← NIL;
alwaysUpdate:   BOOLTRUE;  -- = TRUE: attempt to fix sync problem
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.
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 ANYNIL,
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]]];
To get back the color, use ImagerColor.NarrowToOpConstantColor.
};
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: BOOLTRUE,
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 ~ {
Ignore: PROC RETURNS [BOOLFALSE] ~ {
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.
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 always update then we don't need initval (set on mouse down, used on mouse up):
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 {
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):
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
camera.initval ← 0.;    -- so Delta is properly computed (simulate down mouse)
};
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,
switch $EyeZMv and $EyeYMv so the user won't get confused:
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,
switch $EyeZRot and $EyeYRot so the user won't get confused:
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] ~ {
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.
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 {
Shift incremental transform into standard parameters:
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] ~ {
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.
rotateMode: {body, world} ← world; -- former default was body
construct old rotate matrix:
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];
merge incremental transformation into standard rotate and translate
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
compute Tx ← XTiX (incremental translate vector pre- and post-multiplied by swap X):
eyeMov ← [eyeMov.x, eyeMov.z, eyeMov.y];
compute new globalMove and set the controls:
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;
form new standard rotate (R' ← RXRiX, with R= standard rotate, Ri= eye rotate, X=swap)
using the identity RXRi = RXRiXX = (RXRiX)X = R'X
r: Matrix ← G3dMatrix.ObtainMatrix[];
swap: Matrix ← G3dMatrix.ObtainMatrix[];
make a "swap" matrix (to swap y and z axes)
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: BOOLFALSE;
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"]];
};
Slice Procedures
InitSlice: PUBLIC PROC [data: REF ANYNIL, 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];
};
Hold Procedures
InitHold: PUBLIC PROC [proc: ControlProc ← NIL, data: REF ANYNIL] 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]];
};
Interaction with the Display
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: NATIF 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];
The following plane is centered on the selected point and directed at the origin;
not sure it'll work for views with translation.
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];
};
};
Miscellaneous Procedures
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: BOOLTRUE]
~ {
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.