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;
Imported Type Declarations
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;
Quaternion Control of a Camera
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];
ControlsPrivate.RegisterRopeFromValueProc[a.rotate, RopeFromRotate];
ControlsPrivate.RegisterValueFromRopeProc[a.rotate, SetRotateFromRope];
};
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;
tAbs ← G3dQuaternion.Rot[arcRot.qAbs, tAbs]; -- originally G3dView mov before rot
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];
tAbs ← G3dQuaternion.Rot[arcRot.qAbs, tAbs]; -- originally G3dView mov before rot
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];
tAbs ← G3dQuaternion.Rot[arcRot.qAbs, tAbs]; -- originally G3dView mov before rot
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,
TRUE, -- cam.use = view,
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;
arcTran.tAbs ← arcTran.tInc ← G3dQuaternion.Rot[arcTran.qAbs, tCam]; -- originally ...
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 ← G3dQuaternion.Rot[G3dQuaternion.Conjugate[
arcTran.qAbs], arcTran.tInc]; -- originally G3dView mov before rot
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,
TRUE, -- cam.use = view,
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];
};
Constraints
FrameToStd:
PROC [frame: Matrix ¬
NIL, p: Triple, w:
REAL ¬ 0.0]
RETURNS [pp: Triple] ~ {
Convert coords of p relative to frame to be relative to stdFrame. NIL frame => standard axes.
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] ~ {
Convert coords of p relative to stdFrame to be relative to frame. NIL frame => standard axes.
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] ~ {
This picks the axis with the largest component in p. Adds 4 if axis is flipped.
pRelF: Triple ¬ StdToFrame[frame, p];
flip: ARRAY [0..3) OF CARDINAL ¬ ALL[0];
IF frame = NIL THEN frame ¬ stdFrame;
Ensure puncture points on front hemisphere.
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] ~ {
This dots p with each axis and picks the largest. NIL frame => standard axes.
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] ~ {
Dot p with axes and eliminate the smallest other than first. NIL frame => standard axes.
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];
};
Connect to Camera
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]];
Camera thinks Mov preceeds Rot. Set t=t0 R-1. -- no longer
tCam: Triple ← G3dQuaternion.Rot[G3dQuaternion.Conjugate[qAbs], tAbs];
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,
swapYZ: BOOL ← TRUE,
out: Matrix ¬ NIL]
RETURNS [Matrix]
~ {
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).
out ¬ G3dQuaternion.ToMatrix[rotate, out];
At this point we're in WORLD SPACE
[] ¬ G3dMatrix.Scale[out, scale, out];
[] ¬ G3dMatrix.Translate[out, move, out];
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
IF fieldOfView # 0.0
THEN {
t: Matrix ¬ G3dMatrix.ObtainMatrix[];
[] ¬ G3dMatrix.Mul[out, G3dMatrix.MakePerspective[10., 0., fieldOfView, t], out];
G3dMatrix.ReleaseMatrix[t];
};
At this point we're in VIEW SPACE
RETURN[out];
};
Read and Print
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 {
qDiff: Quaternion ← G3dQuaternion.Sub[arcRot.qAbs, arcRot.qInc];
repaint ← qDiff.x = 0 AND qDiff.y = 0 AND qDiff.z = 0 AND qDiff.w = 0;
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;
};
repaint ← G3dVector.Equal[arcTran.tAbs, tCam];
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] ~ {
Find a control named "Translate" and make it redraw itself.
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;
};
Sense and Draw
MouseOnSphere:
PROC [mouse: Pair, center: Pair ¬ [0, 0], radius:
REAL ¬ 1.0]
RETURNS [sphere: Triple]
~ {
Convert mouse x-y coordinates to sphere x-y-z coordinates.
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]
~ {
Convert sphere x-y-z coordinates to mouse x-y coordinates.
mouse.x ¬ radius*sphere.x + center.x;
mouse.y ¬ radius*sphere.y + center.y;
};
QuatFromArc:
PROC [p0, p1: Triple]
RETURNS [q: Quaternion] ~ {
Return quaternion for rotation determined by arc endpoints p0 and p1, in that order.
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] ~ {
Return arc endpoints p0 and p1, in that order, determined by quaternion for rotation.
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] ~ {
Return vector for translation determined by arc with final endpoint p1.
scale: REAL;
tInc ← G3dQuaternion.Rot[q, p1];
tInc ← [tInc.x/tInc.z, tInc.y/tInc.z, 0];
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] ~ {
Return arc with endpoints p0, p1 determined by vector for translation.
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]
~
{
Move mouse to given place on sphere.
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] ~ {
Draw an elliptical arc approximation in context, quickly.
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]
~ {
Draw an elliptical arc approximation with midpoint in context, quickly.
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];
};