Skip to main content

Rotation around an arbitrary point

4 replies [Last post]
jaume_aau
Offline
Joined: 2009-10-19
Points: 0

Hi list.

I have create a mouse behavior that composes MouseRotation,
MouseTranslation, MouseZoom and MouseWheelZoom into the same. It is the
class DefaultMouseNavigationBehavior. It works fine as it is.

it basically works capturing the AWT events as usual, and the events
are processed at doNavigation() method in two steps. The first one
computes the rotation (doProcessRotation()), translationXY
(doProcessTranslation()) and translationZ (doProcessWheel() and
doProcessZoom()) transforms, and the second applies the transforms
calculated (applyTransforms())

In particular, the rotation transform is applied like this (fragment
from the applyTransform() method)
{
// apply rotation
Matrix4d mat = new Matrix4d();
// Remember old matrix
currXform.get(mat);

// Translate to origin
System.out.println(rotationCenter+" "+invert);
currXform.setTranslation(rotationCenter);
if (invert) {
currXform.mul(currXform, transformX);
currXform.mul(currXform, transformY);
} else {
currXform.mul(transformX, currXform);
currXform.mul(transformY, currXform);
}

tmpRotationOldTranslation.set(mat.m03, mat.m13, mat.m23);
currXform.setTranslation(tmpRotationOldTranslation);

if (callback!=null)
callback.transformChanged(
MouseBehaviorCallback.ROTATE, currXform );

}
}

Now I wanted to go a step further and make a new behavior that allows
rotating around an arbitrary point that is calculated using an picking
canvas. I created a LocalOrbitNavigationBehavior which is a subclass of
DefaultMouseBehavior. LocalOrbitNavigationBehavior overloads the
doProcess() method to introduce another step before navigation occurs.
This step detects where the user has clicked in world coordinates. Here
is the method

protected void doProcessPick(MouseEvent event) {
if (event.getID() == MouseEvent.MOUSE_PRESSED) {
clickedPoint = event.getPoint();
pickCanvas.setShapeLocation(xpos, ypos);
PickResult pickResult = pickCanvas.pickClosest();

if (pickResult != null) {
try {
PickIntersection intersection =
pickResult.getIntersection(0);

Point3d pickedPoint =
intersection.getPointCoordinatesVW();
if (pickedPoint != null) {
rotationCenter.set(pickedPoint.x, pickedPoint.y,
pickedPoint.z);

}
} catch (ArrayIndexOutOfBoundsException e) {

}
}
}
if (event.getID() == MouseEvent.MOUSE_RELEASED) {
clickedPoint = null;
}

if (debug)
System.out.println("got mouse event");
xpos = ((MouseEvent) event).getPoint().x;
ypos = ((MouseEvent) event).getPoint().y;
}

The point is calculated correctly because I insert a ball in the
clicked point and the ball is in the right place.

And here comes the question.

To my understanding, in order to rotate around a point that is not the
origin, I need to translate that point to the origin, then perform the
transforms and finally move the point back to the original position (at
least that is how I used to do it in 2D). Thus, I thought that by
capturing the point in the rotationCenter parameter and let the
DefaultMouseBehavior perform normally would solve it. But it doesn't
and I really don't know why. I think I've tried all possible
combinations. That's why I'm starting to wonder. Could you please give
me some tips?

I have include a self-contained program. You just need to run Human1
as a standalone application.
in Human1.java line 439 you can choose which behavior to use by
commenting out and enabling the corresponding line.

Below is the code. Thank you very much for your help.

package arbitrary_point_rotation;
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.text.NumberFormat;

import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Material;
import javax.media.j3d.Screen3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.geometry.Text2D;
import com.sun.j3d.utils.universe.SimpleUniverse;

public class Human1 extends Applet implements ChangeListener,
ActionListener {

SimpleUniverse u;

boolean isApplication;

Canvas3D canvas;

OffScreenCanvas3D offScreenCanvas;

View view;

// These names correspond to the H-Anim names
TransformGroup Human_body;

TransformGroup Human_r_shoulder;

TransformGroup Human_r_elbow;

TransformGroup Human_l_shoulder;

TransformGroup Human_l_elbow;

TransformGroup Human_skullbase;

// these set up the transformations
int rShoulderRot = 0;

AxisAngle4f rShoulderAA = new AxisAngle4f(0.0f, 0.0f, -1.0f,
0.0f);

JSlider rShoulderSlider;

JLabel rShoulderSliderLabel;

int rElbowRot = 0;

AxisAngle4f rElbowAA = new AxisAngle4f(0.0f, 0.0f, -1.0f,
0.0f);

JSlider rElbowSlider;

JLabel rElbowSliderLabel;

int lShoulderRot = 0;

AxisAngle4f lShoulderAA = new AxisAngle4f(0.0f, 0.0f, 1.0f,
0.0f);

JSlider lShoulderSlider;

JLabel lShoulderSliderLabel;

int lElbowRot = 0;

AxisAngle4f lElbowAA = new AxisAngle4f(0.0f, 0.0f, 1.0f, 0.0f);

JSlider lElbowSlider;

JLabel lElbowSliderLabel;

String snapImageString = "Snap Image";

String outFileBase = "human";

int outFileSeq = 0;

float offScreenScale = 1.5f;

// GUI elements
JTabbedPane tabbedPane;

// Temporaries that are reused
Transform3D tmpTrans = new Transform3D();

Vector3f tmpVector = new Vector3f();

AxisAngle4f tmpAxisAngle = new AxisAngle4f();

// These will be created, attached the scene graph and then the
variable
// will be reused to initialize other sections of the scene
graph.
Cylinder tmpCyl;

Sphere tmpSphere;

TransformGroup tmpTG;

// colors for use in the cones
Color3f red = new Color3f(1.0f, 0.0f, 0.0f);

Color3f black = new Color3f(0.0f, 0.0f, 0.0f);

Color3f white = new Color3f(1.0f, 1.0f, 1.0f);

// geometric constant
Point3f origin = new Point3f();

Vector3f yAxis = new Vector3f(0.0f, 1.0f, 0.0f);

// NumberFormat to print out floats with only two digits
NumberFormat nf;

// Returns the TransformGroup we will be editing to change the
tranform
// on the cone
void createHuman() {
Human_body = new TransformGroup();

// center the body
tmpVector.set(0.0f, -1.5f, 0.0f);
tmpTrans.set(tmpVector);
Human_body.setTransform(tmpTrans);

// Set up an appearance to make the body with red
ambient,
// black emmissive, red diffuse and white specular
coloring
Material material = new Material(red, black, red,
white, 64);
Appearance appearance = new Appearance();
appearance.setMaterial(material);

// offset and place the cylinder for the body
tmpTG = new TransformGroup();
// offset the shape
tmpVector.set(0.0f, 1.5f, 0.0f);
tmpTrans.set(tmpVector);
tmpTG.setTransform(tmpTrans);
tmpCyl = new Cylinder(0.75f, 3.0f, appearance);
tmpTG.addChild(tmpCyl);

// add the shape to the body
Human_body.addChild(tmpTG);

// create the r_shoulder TransformGroup
Human_r_shoulder = new TransformGroup();

Human_r_shoulder.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

Human_r_shoulder.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
// translate from the waist
tmpVector.set(-0.95f, 2.9f, -0.2f);
tmpTrans.set(tmpVector);
Human_r_shoulder.setTransform(tmpTrans);

// place the sphere for the r_shoulder
tmpSphere = new Sphere(0.22f, appearance);
Human_r_shoulder.addChild(tmpSphere);

// offset and place the cylinder for the r_shoulder
tmpTG = new TransformGroup();
// offset the shape
tmpVector.set(0.0f, -0.5f, 0.0f);
tmpTrans.set(tmpVector);
tmpTG.setTransform(tmpTrans);
tmpCyl = new Cylinder(0.2f, 1.0f, appearance);
tmpTG.addChild(tmpCyl);

// add the shape to the r_shoulder
Human_r_shoulder.addChild(tmpTG);

// add the shoulder to the body group
Human_body.addChild(Human_r_shoulder);

// create the r_elbow TransformGroup
Human_r_elbow = new TransformGroup();

Human_r_elbow.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

Human_r_elbow.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
tmpVector.set(0.0f, -1.054f, 0.0f);
tmpTrans.set(tmpVector);
Human_r_elbow.setTransform(tmpTrans);

// place the sphere for the r_elbow
tmpSphere = new Sphere(0.22f, appearance);
Human_r_elbow.addChild(tmpSphere);

// offset and place the cylinder for the r_shoulder
tmpTG = new TransformGroup();
// offset the shape
tmpVector.set(0.0f, -0.5f, 0.0f);
tmpTrans.set(tmpVector);
tmpTG.setTransform(tmpTrans);
tmpCyl = new Cylinder(0.2f, 1.0f, appearance);
tmpTG.addChild(tmpCyl);

// add the shape to the r_shoulder
Human_r_elbow.addChild(tmpTG);

// add the elbow to the shoulder group
Human_r_shoulder.addChild(Human_r_elbow);

// create the l_shoulder TransformGroup
Human_l_shoulder = new TransformGroup();

Human_l_shoulder.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

Human_l_shoulder.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
tmpVector.set(0.95f, 2.9f, -0.2f);
tmpTrans.set(tmpVector);
Human_l_shoulder.setTransform(tmpTrans);

// place the sphere for the l_shoulder
tmpSphere = new Sphere(0.22f, appearance);
Human_l_shoulder.addChild(tmpSphere);

// offset and place the cylinder for the l_shoulder
tmpTG = new TransformGroup();
// offset the shape
tmpVector.set(0.0f, -0.5f, 0.0f);
tmpTrans.set(tmpVector);
tmpTG.setTransform(tmpTrans);
tmpCyl = new Cylinder(0.2f, 1.0f, appearance);
tmpTG.addChild(tmpCyl);

// add the shape to the l_shoulder
Human_l_shoulder.addChild(tmpTG);

// add the shoulder to the body group
Human_body.addChild(Human_l_shoulder);

// create the r_elbow TransformGroup
Human_l_elbow = new TransformGroup();

Human_l_elbow.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

Human_l_elbow.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
tmpVector.set(0.0f, -1.054f, 0.0f);
tmpTrans.set(tmpVector);
Human_l_elbow.setTransform(tmpTrans);

// place the sphere for the l_elbow
tmpSphere = new Sphere(0.22f, appearance);
Human_l_elbow.addChild(tmpSphere);

// offset and place the cylinder for the l_elbow
tmpTG = new TransformGroup();
// offset the shape
tmpVector.set(0.0f, -0.5f, 0.0f);
tmpTrans.set(tmpVector);
tmpTG.setTransform(tmpTrans);
tmpCyl = new Cylinder(0.2f, 1.0f, appearance);
tmpTG.addChild(tmpCyl);

// add the shape to the l_elbow
Human_l_elbow.addChild(tmpTG);

// add the shoulder to the body group
Human_l_shoulder.addChild(Human_l_elbow);

// create the skullbase TransformGroup
Human_skullbase = new TransformGroup();
tmpVector.set(0.0f, 3.632f, 0.0f);
tmpTrans.set(tmpVector);
Human_skullbase.setTransform(tmpTrans);

// offset and place the sphere for the skull
tmpSphere = new Sphere(0.5f, appearance);

// add the shape to the l_shoulder
Human_skullbase.addChild(tmpSphere);

// add the shoulder to the body group
Human_body.addChild(Human_skullbase);

}

public void actionPerformed(ActionEvent e) {
String action = e.getActionCommand();
Object source = e.getSource();
if (action == snapImageString) {
Point loc = canvas.getLocationOnScreen();
offScreenCanvas.setOffScreenLocation(loc);
Dimension dim = canvas.getSize();
dim.width *= offScreenScale;
dim.height *= offScreenScale;
nf.setMinimumIntegerDigits(3);
offScreenCanvas.snapImageFile(
outFileBase +
nf.format(outFileSeq++), dim.width,
dim.height);
nf.setMinimumIntegerDigits(0);
}
}

public void setRShoulderRot(int rotation) {
rShoulderRot = rotation;
rShoulderAA.angle = (float)
Math.toRadians(rShoulderRot);
Human_r_shoulder.getTransform(tmpTrans);
tmpTrans.setRotation(rShoulderAA);
Human_r_shoulder.setTransform(tmpTrans);
}

public void setRElbowRot(int rotation) {
float angle = (float) Math.toRadians(rotation);
rElbowRot = rotation;
rElbowAA.angle = (float) Math.toRadians(rElbowRot);
Human_r_elbow.getTransform(tmpTrans);
tmpTrans.setRotation(rElbowAA);
Human_r_elbow.setTransform(tmpTrans);
}

public void setLShoulderRot(int rotation) {
lShoulderRot = rotation;
lShoulderAA.angle = (float)
Math.toRadians(lShoulderRot);
Human_l_shoulder.getTransform(tmpTrans);
tmpTrans.setRotation(lShoulderAA);
Human_l_shoulder.setTransform(tmpTrans);
}

public void setLElbowRot(int rotation) {
float angle = (float) Math.toRadians(rotation);
lElbowRot = rotation;
lElbowAA.angle = (float) Math.toRadians(lElbowRot);
Human_l_elbow.getTransform(tmpTrans);
tmpTrans.setRotation(lElbowAA);
Human_l_elbow.setTransform(tmpTrans);
}

public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
int value = source.getValue();
if (source == rShoulderSlider) {
setRShoulderRot(value);

rShoulderSliderLabel.setText(Integer.toString(value));
} else if (source == rElbowSlider) {
setRElbowRot(value);

rElbowSliderLabel.setText(Integer.toString(value));
} else if (source == lShoulderSlider) {
setLShoulderRot(value);

lShoulderSliderLabel.setText(Integer.toString(value));
} else if (source == lElbowSlider) {
setLElbowRot(value);

lElbowSliderLabel.setText(Integer.toString(value));
}
}

BranchGroup createSceneGraph() {
// Create the root of the branch graph
BranchGroup objRoot = new BranchGroup();

objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

// Create a TransformGroup to scale the scene down by
3.5x
// TODO: move view platform instead of scene using
orbit behavior
TransformGroup objScale = new TransformGroup();
Transform3D scaleTrans = new Transform3D();
scaleTrans.set(1 / 3.5f); // scale down by 3.5x
objScale.setTransform(scaleTrans);
objRoot.addChild(objScale);

// Create a TransformGroup and initialize it to the
// identity. Enable the TRANSFORM_WRITE capability so
that
// the mouse behaviors code can modify it at runtime.
Add it to the
// root of the subgraph.
TransformGroup objTrans = new TransformGroup();

objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
objScale.addChild(objTrans);

// Add the primitives to the scene
createHuman(); // the human

BoundingSphere bounds = new BoundingSphere(new
Point3d(), 100.0);

{
// Create a TransformGroup object
TransformGroup TGR = new TransformGroup();
// Set the proper capability

TGR.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

objScale.addChild(TGR);

BranchGroup tree = new BranchGroup();
{
// offset and place the cylinder for the body
TransformGroup tmpTG = new
TransformGroup();
// offset the shape
Vector3f tmpVector= new Vector3f(-1.0f,
1.5f, -1.0f);
Transform3D tmpTrans = new
Transform3D();
tmpTrans.set(tmpVector);
tmpTG.setTransform(tmpTrans);
// ColorCube c = new ColorCube();
tmpTG.addChild(new Text2D("Example of
text that does not move", new Color3f(0f, 0f, 1f), "Serif", 40,
Font.BOLD));
tree.addChild(tmpTG);
}
TGR.addChild(tree);
}

objTrans.addChild(Human_body);

Background bg = new Background(new Color3f(1.0f, 1.0f,
1.0f));
bg.setApplicationBounds(bounds);
objTrans.addChild(bg);

// set up the mouse rotation behavior
// DefaultMouseNavigationBehavior mr = new
DefaultMouseNavigationBehavior();
LocalOrbitNavigationBehavior mr = new
LocalOrbitNavigationBehavior(canvas, objRoot, bounds );
mr.setTransformGroup(objTrans);
mr.setSchedulingBounds(bounds);
mr.setFactor(0.007);
objTrans.addChild(mr);

// Set up the ambient light
Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
AmbientLight ambientLightNode = new
AmbientLight(ambientColor);
ambientLightNode.setInfluencingBounds(bounds);
objRoot.addChild(ambientLightNode);

// Set up the directional lights
Color3f light1Color = new Color3f(1.0f, 1.0f, 1.0f);
Vector3f light1Direction = new Vector3f(0.0f, -0.2f,
-1.0f);

DirectionalLight light1 = new
DirectionalLight(light1Color,
light1Direction);
light1.setInfluencingBounds(bounds);
objRoot.addChild(light1);

return objRoot;
}

public Human1() {
this(false, 1.0f);
}

public Human1(boolean isApplication, float initOffScreenScale)
{
this.isApplication = isApplication;
this.offScreenScale = initOffScreenScale;
}

public void init() {

// set up a NumFormat object to print out float with
only 3 fraction
// digits
nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(3);

setLayout(new BorderLayout());
GraphicsConfiguration config = SimpleUniverse
.getPreferredConfiguration();

canvas = new Canvas3D(config);

add("Center", canvas);

u = new SimpleUniverse(canvas);

if (isApplication) {
offScreenCanvas = new OffScreenCanvas3D(config,
true);
// set the size of the off-screen canvas based
on a scale
// of the on-screen size
Screen3D sOn = canvas.getScreen3D();
Screen3D sOff = offScreenCanvas.getScreen3D();
Dimension dim = sOn.getSize();
dim.width *= offScreenScale;
dim.height *= offScreenScale;
sOff.setSize(dim);

sOff.setPhysicalScreenWidth(sOn.getPhysicalScreenWidth()
* offScreenScale);

sOff.setPhysicalScreenHeight(sOn.getPhysicalScreenHeight()
* offScreenScale);

// attach the offscreen canvas to the view

u.getViewer().getView().addCanvas3D(offScreenCanvas);
}

// Create a simple scene and attach it to the virtual
universe
BranchGroup scene = createSceneGraph();

// This will move the ViewPlatform back a bit so the
// objects in the scene can be viewed.
u.getViewingPlatform().setNominalViewingTransform();
u.addBranchGraph(scene);

view = u.getViewer().getView();

add("East", guiPanel());
}

// create a panel with a tabbed pane holding each of the edit
panels
JPanel guiPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 1));

// Human_r_shoulder rotation
panel.add(new JLabel("Right Shoulder rotation"));
rShoulderSlider = new JSlider(JSlider.HORIZONTAL, 0,
180, rShoulderRot);
rShoulderSlider.addChangeListener(this);
rShoulderSliderLabel = new
JLabel(Integer.toString(rShoulderRot));
panel.add(rShoulderSlider);
panel.add(rShoulderSliderLabel);

// Human_r_elbow rotation
panel.add(new JLabel("Right Elbow rotation"));
rElbowSlider = new JSlider(JSlider.HORIZONTAL, 0, 180,
rElbowRot);
rElbowSlider.addChangeListener(this);
rElbowSliderLabel = new
JLabel(Integer.toString(rElbowRot));
panel.add(rElbowSlider);
panel.add(rElbowSliderLabel);

// Human_l_shoulder rotation
panel.add(new JLabel("Left Shoulder rotation"));
lShoulderSlider = new JSlider(JSlider.HORIZONTAL, 0,
180, lShoulderRot);
lShoulderSlider.addChangeListener(this);
lShoulderSliderLabel = new
JLabel(Integer.toString(lShoulderRot));
panel.add(lShoulderSlider);
panel.add(lShoulderSliderLabel);

// Human_l_elbow rotation
panel.add(new JLabel("Left Elbow rotation"));
lElbowSlider = new JSlider(JSlider.HORIZONTAL, 0, 180,
lElbowRot);
lElbowSlider.addChangeListener(this);
lElbowSliderLabel = new
JLabel(Integer.toString(lElbowRot));
panel.add(lElbowSlider);
panel.add(rElbowSliderLabel);

if (isApplication) { JButton snapButton = new
JButton(snapImageString);
snapButton.setActionCommand(snapImageString);
snapButton.addActionListener(this);
panel.add(snapButton);
}

return panel;
}

public void destroy() {
u.removeAllLocales();
}

// The following allows Human to be run as an application
// as well as an applet
//
public static void main(String[] args) {
float initOffScreenScale = 2.5f;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-s")) {
if (args.length >= (i + 1)) {
initOffScreenScale =
Float.parseFloat(args[i + 1]);
i++;
}
}
}
new MainFrame(new Human1(true, initOffScreenScale),
950, 600);
}
}

class OffScreenCanvas3D extends Canvas3D {

OffScreenCanvas3D(GraphicsConfiguration graphicsConfiguration,
boolean offScreen) {

super(graphicsConfiguration, offScreen);
}

private BufferedImage doRender(int width, int height) {

BufferedImage bImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);

ImageComponent2D buffer = new ImageComponent2D(
ImageComponent.FORMAT_RGB, bImage);
//buffer.setYUp(true);

setOffScreenBuffer(buffer);
renderOffScreenBuffer();
waitForOffScreenRendering();
bImage = getOffScreenBuffer().getImage();
return bImage;
}

void snapImageFile(String filename, int width, int height) {
BufferedImage bImage = doRender(width, height);

/*
* JAI: RenderedImage fImage = JAI.create("format",
bImage,
* DataBuffer.TYPE_BYTE); JAI.create("filestore",
fImage, filename +
* ".tif", "tiff", null);
*/

/* No JAI: */
try {
FileOutputStream fos = new
FileOutputStream(filename + ".jpg");
BufferedOutputStream bos = new
BufferedOutputStream(fos);

JPEGImageEncoder jie =
JPEGCodec.createJPEGEncoder(bos);
JPEGEncodeParam param =
jie.getDefaultJPEGEncodeParam(bImage);
param.setQuality(1.0f, true);
jie.setJPEGEncodeParam(param);
jie.encode(bImage);

bos.flush();
fos.close();
} catch (Exception e) {
System.out.println(e);
}
}
}

package arbitrary_point_rotation;

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.Enumeration;

import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOnBehaviorPost;
import javax.vecmath.Matrix4d;
import javax.vecmath.Vector3d;

import com.sun.j3d.utils.behaviors.mouse.MouseBehavior;
import com.sun.j3d.utils.behaviors.mouse.MouseBehaviorCallback;

public class DefaultMouseNavigationBehavior extends MouseBehavior {
private double x_angle, y_angle;
private double x_factor = .03;
private double y_factor = .03;
private double z_factor = .06;

protected MouseBehaviorCallback callback = null;
private boolean buttonPress1;
private boolean buttonPress2;
private boolean buttonPress3;
protected Vector3d rotationCenter = new Vector3d(0.0,0.0,0.0);

/**
* Creates a rotate behavior given the transform group.
* @param transformGroup The transformGroup to operate on.
*/
public DefaultMouseNavigationBehavior(TransformGroup
transformGroup) {
super(transformGroup);
}

/**
* Creates a default mouse rotate behavior.
**/
public DefaultMouseNavigationBehavior() {
super(0);
}

/**
* Creates a rotate behavior.
* Note that this behavior still needs a transform
* group to work on (use setTransformGroup(tg)) and
* the transform group must add this behavior.
* @param flags interesting flags (wakeup conditions).
*/
public DefaultMouseNavigationBehavior(int flags) {
super(flags);
}

/**
* Creates a rotate behavior that uses AWT listeners and
behavior
* posts rather than WakeupOnAWTEvent. The behavior is added
to the
* specified Component. A null component can be passed to
specify
* the behavior should use listeners. Components can then be
added
* to the behavior with the addListener(Component c) method.
* @param c The Component to add the MouseListener
* and MouseMotionListener to.
* @since Java 3D 1.2.1
*/
public DefaultMouseNavigationBehavior(Component c) {
super(c, 0);
}

/**
* Creates a rotate behavior that uses AWT listeners and
behavior
* posts rather than WakeupOnAWTEvent. The behaviors is added
to
* the specified Component and works on the given
TransformGroup.
* A null component can be passed to specify the behavior
should use
* listeners. Components can then be added to the behavior
with the
* addListener(Component c) method.
* @param c The Component to add the MouseListener and
* MouseMotionListener to.
* @param transformGroup The TransformGroup to operate on.
* @since Java 3D 1.2.1
*/
public DefaultMouseNavigationBehavior(Component c,
TransformGroup transformGroup) {
super(c, transformGroup);
}

/**
* Creates a rotate behavior that uses AWT listeners and
behavior
* posts rather than WakeupOnAWTEvent. The behavior is added
to the
* specified Component. A null component can be passed to
specify
* the behavior should use listeners. Components can then be
added to
* the behavior with the addListener(Component c) method.
* Note that this behavior still needs a transform
* group to work on (use setTransformGroup(tg)) and the
transform
* group must add this behavior.
* @param flags interesting flags (wakeup conditions).
* @since Java 3D 1.2.1
*/
public DefaultMouseNavigationBehavior(Component c, int flags) {
super(c, flags);
}

public void initialize() {
super.initialize();
x_angle = 0;
y_angle = 0;
if ((flags & INVERT_INPUT) == INVERT_INPUT) {
invert = true;
x_factor *= -1;
y_factor *= -1;
}
}

/**
* Return the x-axis movement multipler.
**/
public double getXFactor() {
return x_factor;
}

/**
* Return the y-axis movement multipler.
**/
public double getYFactor() {
return y_factor;
}

/**
* Set the x-axis amd y-axis movement multipler with factor.
**/
public void setFactor( double factor) {
x_factor = y_factor = factor;
}

/**
* Set the x-axis amd y-axis movement multipler with xFactor
and yFactor
* respectively.
**/
public void setFactor( double xFactor, double yFactor) {
x_factor = xFactor;
y_factor = yFactor;
}

@SuppressWarnings("unchecked")
public void processStimulus (Enumeration criteria) {
WakeupCriterion wakeup;
AWTEvent[] events;
MouseEvent evt;

while (criteria.hasMoreElements()) {
wakeup = (WakeupCriterion)
criteria.nextElement();
if (wakeup instanceof WakeupOnAWTEvent) {
events =
((WakeupOnAWTEvent)wakeup).getAWTEvent();
if (events.length > 0) {
evt = (MouseEvent)
events[events.length-1];
doProcess(evt);
}

} else if (wakeup instanceof
WakeupOnBehaviorPost) {
while (true) {
// access to the queue must be
synchronized
synchronized (mouseq) {
if (mouseq.isEmpty())
break;

evt =
(MouseEvent)mouseq.remove(0);

// consolidate
MOUSE_DRAG events
while ((evt.getID() ==
MouseEvent.MOUSE_DRAGGED) && !mouseq.isEmpty() &&

(((MouseEvent)mouseq.get(0)).getID() == MouseEvent.MOUSE_DRAGGED)) {
evt =
(MouseEvent)mouseq.remove(0);
}
}
doProcess(evt);
}
}

}
wakeupOn (mouseCriterion);
}

/**
* Handles mouse events
*/
public void processMouseEvent(MouseEvent evt) {
if (evt.getID()==MouseEvent.MOUSE_PRESSED) {
if (evt.getButton() == MouseEvent.BUTTON1) {
buttonPress1 = true;
} else if (evt.getButton() ==
MouseEvent.BUTTON2) {
buttonPress2 = true;
} else if (evt.getButton() ==
MouseEvent.BUTTON3) {
buttonPress3 = true;
}
return;
}
else if (evt.getID()==MouseEvent.MOUSE_RELEASED){
if (evt.getButton() == MouseEvent.BUTTON1) {
buttonPress1 = false;
} else if (evt.getButton() ==
MouseEvent.BUTTON2) {
buttonPress2 = false;
} else if (evt.getButton() ==
MouseEvent.BUTTON3) {
buttonPress3 = false;
}
wakeUp = false;
}
/*
else if (evt.getID() == MouseEvent.MOUSE_MOVED) {
// Process mouse move event
}
else if (evt.getID() == MouseEvent.MOUSE_WHEEL) {
// Process mouse wheel event
}
*/
}

protected void doProcess(MouseEvent evt) {
processMouseEvent(evt);
doNavigation(evt);
}

protected void doNavigation(MouseEvent evt) {
transformX.setIdentity();
transformY.setIdentity();
translateXY.setIdentity();
translateZ.setIdentity();

doProcessRotation(evt);
doProcessTranslation(evt);
doProcessWheel(evt);
doProcessZoom(evt);

applyTransforms();

// Update xform
transformGroup.setTransform(currXform);
}

protected void applyTransforms() {
{
// apply rotation
Matrix4d mat = new Matrix4d();
// Remember old matrix
currXform.get(mat);

// Translate to origin
System.out.println(rotationCenter+" "+invert);
currXform.setTranslation(rotationCenter);
if (invert) {
currXform.mul(currXform, transformX);
currXform.mul(currXform, transformY);
} else {
currXform.mul(transformX, currXform);
currXform.mul(transformY, currXform);
}

tmpRotationOldTranslation.set(mat.m03, mat.m13,
mat.m23);

currXform.setTranslation(tmpRotationOldTranslation);

if (callback!=null)
callback.transformChanged(
MouseBehaviorCallback.ROTATE, currXform );

}

{ // apply XY translation
if (invert) {
currXform.mul(currXform, translateXY);
} else {
currXform.mul(translateXY, currXform);
}
if (callback!=null)
callback.transformChanged(
MouseBehaviorCallback.TRANSLATE, currXform );
}

{ // apply Z translation
if (invert) {
currXform.mul(currXform, translateZ);
} else {
currXform.mul(translateZ, currXform);
}
if (callback!=null)
callback.transformChanged(
MouseBehaviorCallback.ZOOM, currXform );
}

transformChanged( currXform );
}

/**
* Users can overload this method which is called every time
* the Behavior updates the transform
*
* Default implementation does nothing
*/
public void transformChanged( Transform3D transform ) {
}

/**
* The transformChanged method in the callback class will
* be called every time the transform is updated
*/
public void setupCallback( MouseBehaviorCallback callback ) {
this.callback = callback;
}

protected void doProcessRotation(MouseEvent evt) {
int id;
int dx, dy;

if (((buttonPress1) && ((flags & MANUAL_WAKEUP) == 0))
||
((wakeUp) && ((flags & MANUAL_WAKEUP) != 0))) {

id = evt.getID();
if ((id == MouseEvent.MOUSE_DRAGGED) &&
!evt.isMetaDown() && ! evt.isAltDown()){
x = evt.getX();
y = evt.getY();

dx = x - x_last;
dy = y - y_last;

if (!reset){
x_angle = dy * y_factor;
y_angle = dx * x_factor;

transformX.rotX(x_angle);
transformY.rotY(y_angle);

} else {
reset = false;
}

x_last = x;
y_last = y;
}
else if (id == MouseEvent.MOUSE_PRESSED) {
x_last = evt.getX();
y_last = evt.getY();
}
}

System.out.println("x_angle="+x_angle+"
Y_angle="+y_angle);
}

protected Vector3d translationXY = new Vector3d();
protected Transform3D translateXY = new Transform3D();
protected void doProcessTranslation(MouseEvent evt) {
int id;
int dx, dy;

if (((buttonPress3) && ((flags & MANUAL_WAKEUP) == 0))
||
((wakeUp) && ((flags & MANUAL_WAKEUP) != 0))){

id = evt.getID();
if ((id == MouseEvent.MOUSE_DRAGGED) &&
!evt.isAltDown() && evt.isMetaDown()) {
x = evt.getX();
y = evt.getY();

dx = x - x_last;
dy = y - y_last;

if ((!reset) && ((Math.abs(dy) < 50) &&
(Math.abs(dx) < 50))) {
//System.out.println("dx " + dx
+ " dy " + dy);

transformGroup.getTransform(currXform);

translationXY.x = dx*x_factor;
translationXY.y = -dy*y_factor;

translateXY.set(translationXY);
} else {
reset = false;
}
x_last = x;
y_last = y;
}
else if (id == MouseEvent.MOUSE_PRESSED) {
x_last = evt.getX();
y_last = evt.getY();
}
}
}

protected Transform3D translateZ = new Transform3D();
protected Vector3d translationZ = new Vector3d();
protected Vector3d tmpRotationOldTranslation = new Vector3d();

protected void doProcessZoom(MouseEvent evt) {
int id;
int dy;

if (((buttonPress2) && ((flags & MANUAL_WAKEUP) == 0))
||
((wakeUp) && ((flags & MANUAL_WAKEUP) != 0))){

id = evt.getID();
if ((id == MouseEvent.MOUSE_DRAGGED) &&
evt.isAltDown() && !evt.isMetaDown()){

x = evt.getX();
y = evt.getY();

dy = y - y_last;

if (!reset){

transformGroup.getTransform(currXform);

translationZ.z = dy*z_factor;

translateZ.set(translationZ);

} else {
reset = false;
}

x_last = x;
y_last = y;
}
else if (id == MouseEvent.MOUSE_PRESSED) {
x_last = evt.getX();
y_last = evt.getY();
}
}
}

protected void doProcessWheel(MouseEvent evt) {
int units = 0;

if ((evt.getID() == MouseEvent.MOUSE_WHEEL)) {
MouseWheelEvent wheelEvent =
(MouseWheelEvent)evt;
if (wheelEvent.getScrollType() ==
MouseWheelEvent.WHEEL_UNIT_SCROLL ) {
units = -wheelEvent.getUnitsToScroll();
}

if (!reset) {
transformGroup.getTransform(currXform);
translationZ.z = units*z_factor*2;
translateZ.set(translationZ);
} else {
reset = false;
}
}
}
}

package arbitrary_point_rotation;

import java.awt.Point;
import java.awt.event.MouseEvent;

import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Point3d;

import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.picking.PickCanvas;
import com.sun.j3d.utils.picking.PickIntersection;
import com.sun.j3d.utils.picking.PickResult;
import com.sun.j3d.utils.picking.PickTool;

public class LocalOrbitNavigationBehavior extends
DefaultMouseNavigationBehavior {

protected PickCanvas pickCanvas;

protected TransformGroup currGrp;
protected static final boolean debug = false;

protected int ypos = 0;
protected int xpos = 0;

protected Point clickedPoint;

private BranchGroup root;
public LocalOrbitNavigationBehavior(Canvas3D canvas,
BranchGroup root, Bounds bounds) {
super();
currGrp = new TransformGroup();

currGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

currGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
root.addChild(currGrp);
this.root = root;
pickCanvas = new PickCanvas(canvas, root);
this.setSchedulingBounds(bounds);
pickCanvas.setMode(PickTool.GEOMETRY);
}

/**
* Sets the pick mode
* @see PickTool#setMode
**/
public void setMode(int pickMode) {
pickCanvas.setMode(pickMode);
}

/**
* Returns the pickMode
* @see PickTool#getMode
*/

public int getMode() {
return pickCanvas.getMode();
}

/**
* Sets the pick tolerance
* @see PickCanvas#setTolerance
*/
public void setTolerance(float tolerance) {
pickCanvas.setTolerance(tolerance);
}

/**
* Returns the pick tolerance
* @see PickCanvas#getTolerance
*/
public float getTolerance() {
return pickCanvas.getTolerance();
}

@Override
protected void doProcess(MouseEvent evt) {
processMouseEvent(evt);

if (clickedPoint == null) {
doProcessPick(evt);
// for some unknown reason I have to do it
twice
doProcessPick(evt);
}
else {
doNavigation(evt);
}

if (evt.getID() == MouseEvent.MOUSE_RELEASED) {
clickedPoint = null;
}
}

protected void applyTransforms() {
BranchGroup bg = new BranchGroup();
TransformGroup tg = new TransformGroup();
Transform3D tx = new Transform3D();
tx.setTranslation(rotationCenter);
tg.addChild(new Sphere(0.01f));
tg.setTransform(tx);
bg.addChild(tg);
root.addChild(bg);

super.applyTransforms();
}

protected void doProcessPick(MouseEvent event) {
if (event.getID() == MouseEvent.MOUSE_PRESSED) {
clickedPoint = event.getPoint();
pickCanvas.setShapeLocation(xpos, ypos);
PickResult pickResult =
pickCanvas.pickClosest();

if (pickResult != null) {
try {
PickIntersection intersection =
pickResult.getIntersection(0);

Point3d pickedPoint =
intersection.getPointCoordinatesVW();
if (pickedPoint != null) {

rotationCenter.set(pickedPoint.x, pickedPoint.y, pickedPoint.z);

}
} catch (ArrayIndexOutOfBoundsException
e) {

}
}
}
if (event.getID() == MouseEvent.MOUSE_RELEASED) {
clickedPoint = null;
}

if (debug)
System.out.println("got mouse event");
xpos = ((MouseEvent) event).getPoint().x;
ypos = ((MouseEvent) event).getPoint().y;

}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
jaume_aau
Offline
Joined: 2009-10-19
Points: 0

Hi list.

I have create a mouse behavior that composes MouseRotation,
MouseTranslation, MouseZoom and MouseWheelZoom into the same. It is the
class DefaultMouseNavigationBehavior. It works fine as it is.

it basically works capturing the AWT events as usual, and the events are
processed at doNavigation() method in two steps. The first one computes
the rotation (doProcessRotation()), translationXY
(doProcessTranslation()) and translationZ (doProcessWheel() and
doProcessZoom()) transforms, and the second applies the transforms
calculated (applyTransforms())

In particular, the rotation transform is applied like this (fragment
from the applyTransform() method)
{
// apply rotation
Matrix4d mat = new Matrix4d();
// Remember old matrix
currXform.get(mat);

// Translate to origin
System.out.println(rotationCenter+" "+invert);
currXform.setTranslation(rotationCenter);
if (invert) {
currXform.mul(currXform, transformX);
currXform.mul(currXform, transformY);
} else {
currXform.mul(transformX, currXform);
currXform.mul(transformY, currXform);
}

tmpRotationOldTranslation.set(mat.m03, mat.m13, mat.m23);
currXform.setTranslation(tmpRotationOldTranslation);

if (callback!=null)
callback.transformChanged(
MouseBehaviorCallback.ROTATE, currXform );

}
}

Now I wanted to go a step further and make a new behavior that allows
rotating around an arbitrary point that is calculated using an picking
canvas. I created a LocalOrbitNavigationBehavior which is a subclass of
DefaultMouseBehavior. LocalOrbitNavigationBehavior overloads the
doProcess() method to introduce another step before navigation occurs.
This step detects where the user has clicked in world coordinates. Here
is the method

protected void doProcessPick(MouseEvent event) {
if (event.getID() == MouseEvent.MOUSE_PRESSED) {
clickedPoint = event.getPoint();
pickCanvas.setShapeLocation(xpos, ypos);
PickResult pickResult = pickCanvas.pickClosest();

if (pickResult != null) {
try {
PickIntersection intersection =
pickResult.getIntersection(0);

Point3d pickedPoint = intersection.getPointCoordinatesVW();
if (pickedPoint != null) {
rotationCenter.set(pickedPoint.x, pickedPoint.y,
pickedPoint.z);

}
} catch (ArrayIndexOutOfBoundsException e) {

}
}
}
if (event.getID() == MouseEvent.MOUSE_RELEASED) {
clickedPoint = null;
}

if (debug)
System.out.println("got mouse event");
xpos = ((MouseEvent) event).getPoint().x;
ypos = ((MouseEvent) event).getPoint().y;
}

The point is calculated correctly because I insert a ball in the clicked
point and the ball is in the right place.

And here comes the question.

To my understanding, in order to rotate around a point that is not the
origin, I need to translate that point to the origin, then perform the
transforms and finally move the point back to the original position (at
least that is how I used to do it in 2D). Thus, I thought that by
capturing the point in the rotationCenter parameter and let the
DefaultMouseBehavior perform normally would solve it. But it doesn't and
I really don't know why. I think I've tried all possible combinations.
That's why I'm starting to wonder. Could you please give me some tips?

I have include a self-contained program. You just need to run Human1 as
a standalone application.
in Human1.java line 439 you can choose which behavior to use by
commenting out and enabling the corresponding line.

Thank you very much for your help.

jaume_aau
Offline
Joined: 2009-10-19
Points: 0

Nobody has any suggestion about this code (it is very easy to test), any
other kind of suggestion or an example of code to orbit locally?

On 11/17/2011 08:06 PM, jaume dominguez faus wrote:
> Hi list.
>
> I have create a mouse behavior that composes MouseRotation,
> MouseTranslation, MouseZoom and MouseWheelZoom into the same. It is
> the class DefaultMouseNavigationBehavior. It works fine as it is.
>
> it basically works capturing the AWT events as usual, and the events
> are processed at doNavigation() method in two steps. The first one
> computes the rotation (doProcessRotation()), translationXY
> (doProcessTranslation()) and translationZ (doProcessWheel() and
> doProcessZoom()) transforms, and the second applies the transforms
> calculated (applyTransforms())
>
> In particular, the rotation transform is applied like this (fragment
> from the applyTransform() method)
> {
> // apply rotation
> Matrix4d mat = new Matrix4d();
> // Remember old matrix
> currXform.get(mat);
>
> // Translate to origin
> System.out.println(rotationCenter+" "+invert);
> currXform.setTranslation(rotationCenter);
> if (invert) {
> currXform.mul(currXform, transformX);
> currXform.mul(currXform, transformY);
> } else {
> currXform.mul(transformX, currXform);
> currXform.mul(transformY, currXform);
> }
>
> tmpRotationOldTranslation.set(mat.m03, mat.m13, mat.m23);
> currXform.setTranslation(tmpRotationOldTranslation);
>
> if (callback!=null)
> callback.transformChanged(
> MouseBehaviorCallback.ROTATE, currXform );
>
> }
> }
>
> Now I wanted to go a step further and make a new behavior that allows
> rotating around an arbitrary point that is calculated using an picking
> canvas. I created a LocalOrbitNavigationBehavior which is a subclass
> of DefaultMouseBehavior. LocalOrbitNavigationBehavior overloads the
> doProcess() method to introduce another step before navigation occurs.
> This step detects where the user has clicked in world coordinates.
> Here is the method
>
> protected void doProcessPick(MouseEvent event) {
> if (event.getID() == MouseEvent.MOUSE_PRESSED) {
> clickedPoint = event.getPoint();
> pickCanvas.setShapeLocation(xpos, ypos);
> PickResult pickResult = pickCanvas.pickClosest();
>
> if (pickResult != null) {
> try {
> PickIntersection intersection =
> pickResult.getIntersection(0);
>
> Point3d pickedPoint =
> intersection.getPointCoordinatesVW();
> if (pickedPoint != null) {
> rotationCenter.set(pickedPoint.x, pickedPoint.y,
> pickedPoint.z);
>
> }
> } catch (ArrayIndexOutOfBoundsException e) {
>
> }
> }
> }
> if (event.getID() == MouseEvent.MOUSE_RELEASED) {
> clickedPoint = null;
> }
>
> if (debug)
> System.out.println("got mouse event");
> xpos = ((MouseEvent) event).getPoint().x;
> ypos = ((MouseEvent) event).getPoint().y;
> }
>
> The point is calculated correctly because I insert a ball in the
> clicked point and the ball is in the right place.
>
> And here comes the question.
>
> To my understanding, in order to rotate around a point that is not the
> origin, I need to translate that point to the origin, then perform
> the transforms and finally move the point back to the original
> position (at least that is how I used to do it in 2D). Thus, I thought
> that by capturing the point in the rotationCenter parameter and let
> the DefaultMouseBehavior perform normally would solve it. But it
> doesn't and I really don't know why. I think I've tried all possible
> combinations. That's why I'm starting to wonder. Could you please give
> me some tips?
>
> I have include a self-contained program. You just need to run Human1
> as a standalone application.
> in Human1.java line 439 you can choose which behavior to use by
> commenting out and enabling the corresponding line.
>
>
> Thank you very much for your help.
>
>
>

jamiehope
Offline
Joined: 2007-10-22
Points: 0

If I'm reading your code correctly, you're just putting the original translation back after performing the rotation. That just has you rotating in place. An actual orbit motion (as I understand it) has to put you at a new translation such that the vector to the rotation center is unchanged *in your rotated frame*. Here's how I do something like that:

Transform3D rot = new Transform3D();
Vector3d trans = new Vector3d(), diff = new Vector3d();
rot.set(currXform);
trans.set(0,0,0);
rot.setTranslation(trans); // rot now contains the current rotation but a translation of (0,0,0)
currXform.get(trans);
diff.sub(center, trans); // diff now points from my old position to the orbit center, in the frame that currXform is defined in

if(invert) {
currXform.mul(currXform, transformX);
currXform.mul(currXform, transformY);

rot.invert();
rot.transform(diff);
transformY.transform(diff);
transformX.transform(diff);
rot.invert();
rot.transform(diff);
trans.sub(center, diff); // diff has been rotated to match the new orientation, so now trans holds the appropriate translation
} else {
currXform.mul(transformX, currXform);
currXform.mul(transformY, currXform);
transformX.transform(diff);
transformY.transform(diff);
trans.sub(center, diff);
}
currXform.setTranslation(trans);

A couple of years ago I actually had the linear algebra behind all that worked out on paper, but it's been lost to the mists of time. So good luck!

[And wow, it's been a while since I posted anything here. I didn't think the comment text formatting could possibly be worse than it was before, but java.net found a way; good job, Project Kenai..]

jaume_aau
Offline
Joined: 2009-10-19
Points: 0

Thanks, I didn't quite much understand what is the difference. But your
code almost works perfectly. It does orbit around the right point, but
there is an initial jump from the current position to an another one. I
will try to polish that. Thank youu!

On 11/29/2011 06:52 PM, forums@java.net wrote:
> If I'm reading your code correctly, you're just putting the original
> translation back after performing the rotation. That just has you
> rotating in
> place. An actual orbit motion (as I understand it) has to put you at a
> new
> translation such that the vector to the rotation center is unchanged *in
> your rotated frame*. Here's how I do something like that:
>
>
>
> Transform3D rot = new Transform3D(); Vector3d trans = new Vector3d(),
> diff =
> new Vector3d();
> rot.set(currXform);
> trans.set(0,0,0);
> rot.setTranslation(trans); // rot now contains the current rotation but a
> translation of (0,0,0)
> currXform.get(trans);
> diff.sub(center, trans); // diff now points from my old position to
> the orbit
> center, in the frame that currXform is defined in
> if(invert) {
> currXform.mul(currXform, transformX);
> currXform.mul(currXform, transformY);
> rot.invert();
> rot.transform(diff);
> transformY.transform(diff);
> transformX.transform(diff);
> rot.invert();
> rot.transform(diff);
> trans.sub(center, diff); // diff has been rotated to match the new
> orientation, so now trans holds the appropriate translation
> } else {
> currXform.mul(transformX, currXform);
> currXform.mul(transformY, currXform);
> transformX.transform(diff);
> transformY.transform(diff);
> trans.sub(center, diff);
> }
> currXform.setTranslation(trans);
>
>
> A couple of years ago I actually had the linear algebra behind all that
> worked out on paper, but it's been lost to the mists of time. So good
> luck!
>
>
>
> [And wow, it's been a while since I posted anything here. I didn't
> think the
> comment text formatting could possibly be worse than it was before, but
> java.net found a way; good job, Project Kenai..]
>
>
>
>