/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2007, by :
* Corporate:
* EADS Astrium
* Individual:
* Claude Cazenave
*
* $Id: ViewTransform.java,v 1.3 2008/02/11 14:09:28 cazenave Exp $
*
* Changes
* -------
* 4 janv. 08 : Initial public release
*
*/
package jsynoptic.plugins.java3d;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Timer;
import java.util.TimerTask;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.View;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import com.sun.j3d.utils.universe.ViewingPlatform;
public class ViewTransform implements Serializable, Cloneable {
static final long serialVersionUID = 723655790680357786L;
/**
* In rotation mode, the user rotates the whole scene and can zoom in or
* out. Rotation mode is adapted to study one object. This is the default
*/
public static final int ROTATION_MODE = 0;
/**
* In fly-by mode, the user can freely move in the scene. This is more
* adapted for scenes with multiple objects or for objects with a complex
* internal geometry to study.
*/
public static final int FLY_BY_MODE = 1;
/**
* The current viewing mode FLY BY or ROTATION
*/
private int _viewingMode;
private Matrix4f _zoom;
private Matrix4f _rot;
private Matrix4f _drotX;
private Matrix4f _drotY;
private Matrix4f _trans;
private Vector3f _vtrans;
private int _pos2DX, _pos2DY;
private boolean _perspective;
private float _wsize;
private float _zoomFactor;
private float _scaleFactor;
private float _eyeDistance;
private float _transFactorX;
private float _transFactorY;
private int _accumulator;
private transient Timer _zoomAccumulator;
private transient Transform3D _transform;
private transient Transform3D _tempTransform;
private transient ViewingPlatform _viewingPlatform;
// TODO get platform and view according to persitent info and plugin support
public ViewTransform(ViewingPlatform viewingPlatform) {
_viewingMode = ROTATION_MODE;
_viewingPlatform = viewingPlatform;
_transform = new Transform3D();
// avoids cumulative rounding errors
// This should avoid "non-congruent transform above view transform"
// exception
_transform.setAutoNormalize(true);
_tempTransform = new Transform3D();
_zoom = new Matrix4f();
_zoom.setIdentity();
_rot = new Matrix4f();
_rot.setIdentity();
_trans = new Matrix4f();
_trans.setIdentity();
_drotX = new Matrix4f();
_drotY = new Matrix4f();
_vtrans = new Vector3f();
_zoomFactor = 0.f;
_scaleFactor = 1.f;
_eyeDistance = 2.f;
_transFactorX = 0.f;
_transFactorY = 0.f;
_perspective = true;
_wsize = 100;
_accumulator = 0;
updateZoomMatrix();
updateTranslateMatrix();
// Use J3D recommendation to set this between 100 and 1000
// default is a factor of 100 (front=0.1, back=10)
// Make this 1000 to have more space
getView().setBackClipDistance(getView().getFrontClipDistance() * 1000);
}
private View getView() {
return _viewingPlatform.getViewers()[0].getView();
}
public float getEyeDistance() {
return _eyeDistance;
}
public void setEyeDistance(float eyeDistance) {
this._eyeDistance = eyeDistance;
}
public float getScaleFactor() {
return _scaleFactor;
}
public void setScaleFactor(float scaleFactor) {
this._scaleFactor = scaleFactor;
}
public float getTransFactorX() {
return _transFactorX;
}
public void setTransFactorX(float transFactorX) {
this._transFactorX = transFactorX;
}
public float getTranslationFactorY() {
return _transFactorY;
}
public void setTranslationFactorY(float transFactorY) {
this._transFactorY = transFactorY;
}
public float getWSize() {
return _wsize;
}
public void setWSize(float wsize) {
this._wsize = wsize;
}
public float getZoomFactor() {
return _zoomFactor;
}
public void setZoomFactor(float zoomFactor) {
this._zoomFactor = zoomFactor;
}
public void setPerspective(boolean p) {
_perspective = p;
}
public boolean isPerspective() {
return _perspective;
}
/**
* Updates the translation matrix with the translation factors previously
* specified This is intentionally a separate function, so that it is
* possible to do multiple factor modifications, in both directions, then
* finally update the matrix at the end.
*/
public void updateTranslateMatrix() {
if (_perspective) {
_vtrans.x = 2.f * _transFactorX * _scaleFactor;
_vtrans.y = 2.f * _transFactorY * _scaleFactor;
} else {
_vtrans.x = 2.f * _transFactorX * _scaleFactor;
_vtrans.y = 2.f * _transFactorY * _scaleFactor;
}
_vtrans.z = _eyeDistance;
_trans.set(_vtrans);
}
/**
* Updates the zoom matrix with the translation factors previously specified
* This is intentionally a separate function, so that it is possible to do
* multiple factor modifications, then finally update the matrix at the end.
*/
public void updateZoomMatrix() {
if (_perspective) {
_vtrans.x = 0.f;
_vtrans.y = 0.f;
_vtrans.z = _zoomFactor / _wsize * 10.f; // TODO scale
_scaleFactor = _eyeDistance / (_eyeDistance - _vtrans.z);
_zoom.set(_vtrans);
} else {
_scaleFactor = (float) Math.pow(1.01, _zoomFactor);
_zoom.setScale(_scaleFactor);
}
}
/**
* Initialize 2D rotation algorithm with the current position as origin.
* Typically, this is related to mouse positions in X and Y. Note that WSize
* should be set to the same value referential as the positions. This is
* typically a window dimension.
*/
public void init2DPosition(int posX, int posY) {
_pos2DX = posX;
_pos2DY = posY;
}
/**
* Does a rotation of the scene according to moves in a 2D coordinate
* system. Use init2DPosition to position an origin, then do as many
* rotate2D as required. Note that WSize should be set to the same value
* referential as the positions. This is typically a window dimension.
*
* @param newX
* The new X position in 2D, typically a mouse position
* @param newY
* The new Y position in 2D, typically a mouse position
*/
public void rotate2D(int newX, int newY) {
rotate2D(newX, newY, true);
}
/**
* Does a rotation of the scene according to moves in a 2D coordinate
* system. Use init2DPosition to position an origin, then do as many
* rotate2D as required. Note that WSize should be set to the same value
* referential as the positions. This is typically a window dimension.
*
* @param newX
* The new X position in 2D, typically a mouse position
* @param newY
* The new Y position in 2D, typically a mouse position
* @param inverse
* Inverse the direction of the rotation if true. This is used
* ROTATION_MODE viewing mode for the scene, in which case the
* scene rotates according to the mouse => in fact, the camera
* rotates in the opposite direction. => default is true
*/
public void rotate2D(int newX, int newY, boolean inverse) {
float dx = (float) (newX - _pos2DX) / _wsize;
float dy = (float) (newY - _pos2DY) / _wsize;
_pos2DX = newX;
_pos2DY = newY;
// Default is ROTATION_MODE => rotate the object according to the mouse
// => default is to move the camera in inverse direction as the mouse
if (inverse) {
dx = -dx;
dy = -dy;
}
_drotX.rotX(dy * 2 * (float) Math.PI);
_drotY.rotY(dx * 2 * (float) Math.PI);
_rot.mul(_drotX);
_rot.mul(_drotY);
applyTransform();
}
/**
* Turns perspective on an off
*/
public void changeProjection() {
setPerspective(!_perspective);
updateZoomMatrix();
updateTranslateMatrix();
applyTransform();
}
/** Reset all values to default */
public void reset() {
_rot.setIdentity();
_zoomFactor = 0.f;
updateZoomMatrix();
_transFactorX = 0.f;
_transFactorY = 0.f;
updateTranslateMatrix();
applyTransform();
}
/**
* @return Returns the pos2DX.
*/
public int getPos2DX() {
return _pos2DX;
}
/**
* @param pos2DX
* The pos2DX to set.
*/
public void setPos2DX(int pos2DX) {
this._pos2DX = pos2DX;
}
/**
* @return Returns the pos2DY.
*/
public int getPos2DY() {
return _pos2DY;
}
/**
* @param pos2DY
* The pos2DY to set.
*/
public void setPos2DY(int pos2DY) {
this._pos2DY = pos2DY;
}
/**
* @return Returns the viewingMode.
*/
public int getViewingMode() {
return _viewingMode;
}
/**
* @param viewingMode
* The viewingMode to set.
*/
public void setViewingMode(int viewingMode) {
_viewingMode = viewingMode;
}
/** Auto zooms the scene out of all objects */
public void autoZoom() {
float size = getSceneSize();
if (size <= 0)
return;
_eyeDistance = size * 2f;
_zoomFactor = 0.f;
updateZoomMatrix();
updateTranslateMatrix();
applyTransform();
}
/**
* Used for auto-zooming to englobe the scene
*
* @return <=0 if not supported, or the max distance from the origin for
* any point in the scene otherwise
*/
protected float getSceneSize() {
Bounds res=null;
Enumeration<?> e=_viewingPlatform.getLocale().getAllBranchGraphs();
while(e.hasMoreElements()){
BranchGroup bg=(BranchGroup)e.nextElement();
Bounds b=bg.getBounds();
if(res==null){
res=b;
}
else{
res.combine(b);
}
}
if (res instanceof BoundingSphere) {
return (float)((BoundingSphere)res).getRadius();
}
// else, include this bounds object in a bounding sphere and get radius
BoundingSphere bs=new BoundingSphere(res);
return (float)bs.getRadius();
}
/**
* Zooms in or out of the scene according to the increment. This is an
* arbitrary algorithm to help create correct zoom factors. You could use
* setZoomFactor and then update the zoom matrix directly, with the same
* effect.<br>
* The added value of this function is to provide a relative and easy to
* manipulate way to zoom in or out, with increments like +1 or -1 for small
* zooms, and +10 and -10 for greater zooms, etc...<br>
* Note that WSize should be set correctly.
*
* @param zoomIncrement
* A value typically 1 or -1, but which can be greater for fast
* zooms. Positive values zoom in, negative values zoom out.
*/
public void zoom(int zoomIncrement) {
if (getViewingMode() == ROTATION_MODE) {
// If user does a real fast wheel rotation, then do exponentially
// fast zoom
_zoomFactor += Math.pow(2.0, Math.abs(zoomIncrement))
* (zoomIncrement < 0 ? -1 : 1);
updateZoomMatrix();
updateTranslateMatrix();
applyTransform();
return;
}
synchronized (this) {
_accumulator += zoomIncrement;
if (_zoomAccumulator != null)
return;
_zoomAccumulator = new Timer();
_zoomAccumulator.schedule(new TimerTask() {
public void run() {
int accuCopy;
synchronized (ViewTransform.this) {
accuCopy = _accumulator;
_accumulator = 0;
_zoomAccumulator = null;
}
Vector3f view = new Vector3f(0, 0, -1);
_rot.transform(view);
float scale = (float) Math.pow(2.0, Math.abs(accuCopy))
* getSceneSize() / 200.0f;
if (accuCopy < 0)
scale = -scale;
view.scale(scale);
_trans.m03 += view.x;
_trans.m13 += view.y;
_trans.m23 += view.z;
applyTransform();
}
}, 200);
}
}
/**
* Uses the 2D increments in position to compute a zoom factor, then zooms
* the scene accordingly.
*
* @param newX
* The new X position in 2D, typically a mouse position
* @param newY
* The new Y position in 2D, typically a mouse position
*/
public void zoom2D(int newX, int newY) {
if (getViewingMode() == ROTATION_MODE) {
float dx = (float) (newX - _pos2DX) / _wsize;
float dy = (float) (newY - _pos2DY) / _wsize;
_pos2DX = newX;
_pos2DY = newY;
double arbitraryDelta = (dx + dy) * 10.0;
_zoomFactor += Math.pow(2.0, Math.abs(arbitraryDelta))
* (arbitraryDelta < 0 ? -1 : 1);
updateZoomMatrix();
updateTranslateMatrix();
applyTransform();
return;
}
float dx = (float) (newX - _pos2DX) / _wsize;
float dy = -(float) (newY - _pos2DY) / _wsize;
_pos2DX = newX;
_pos2DY = newY;
Vector3f viewX = new Vector3f(1, 0, 0);
_rot.transform(viewX);
viewX.scale(dx / 5.0f);
Vector3f viewY = new Vector3f(0, 1, 0);
_rot.transform(viewY);
viewY.scale(dy / 5.0f);
_trans.m03 += viewX.x + viewY.x;
_trans.m13 += viewX.y + viewY.y;
_trans.m23 += viewX.z + viewY.z;
applyTransform();
}
/**
* Does a translation of the scene according to moves in a 2D coordinate
* system. Use init2DPosition to position an origin, then do as many
* translate2D as required. Note that WSize should be set to the same value
* referential as the positions. This is typically a window dimension.
*
* @param newX
* The new X position in 2D, typically a mouse position
* @param newY
* The new Y position in 2D, typically a mouse position
*/
public void translate2D(int newX, int newY) {
if (getViewingMode() == ROTATION_MODE) {
float dx = (float) (newX - _pos2DX) / _wsize;
float dy = (float) (newY - _pos2DY) / _wsize;
_pos2DX = newX;
_pos2DY = newY;
_transFactorX -= dx;
_transFactorY += dy;
updateTranslateMatrix();
applyTransform();
return;
}
// rotation around view vector
Vector3f view = new Vector3f(0, 0, -1);
_rot.transform(view);
float dx = (float) (newX - _pos2DX) / _wsize;
float dy = (float) (newY - _pos2DY) / _wsize;
_pos2DX = newX;
_pos2DY = newY;
AxisAngle4f aa = new AxisAngle4f(view, (dx - dy) * (float) Math.PI);
Matrix4f mat = new Matrix4f();
mat.setIdentity();
mat.setRotation(aa);
mat.mul(_rot);
_rot = mat;
applyTransform();
}
public void applyTransform() {
if (getViewingMode() == ROTATION_MODE) {
_transform.set(_rot);
_tempTransform.set(_trans);
} else {
_transform.set(_trans);
_tempTransform.set(_rot);
}
_transform.mul(_tempTransform);
_tempTransform.set(_zoom);
_transform.mul(_tempTransform);
_viewingPlatform.getViewPlatformTransform().setTransform(_transform);
}
public Object clone() throws CloneNotSupportedException{
ViewTransform res=(ViewTransform)super.clone();
res._zoom=new Matrix4f(_zoom);
res._rot=new Matrix4f(_rot);
res._drotX=new Matrix4f(_drotX);
res._drotY=new Matrix4f(_drotY);
res._trans=new Matrix4f(_trans);
res._vtrans=new Vector3f(_vtrans);
res._transform = new Transform3D();
res._transform.setAutoNormalize(true);
res._tempTransform = new Transform3D();
res._viewingPlatform=null;
res._zoomAccumulator=null;
return res;
}
void setViewingPlatform(ViewingPlatform viewingPlatform) {
_viewingPlatform=viewingPlatform;
}
private void writeObject(java.io.ObjectOutputStream out)
throws IOException{
out.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException{
in.defaultReadObject();
_transform = new Transform3D();
_transform.setAutoNormalize(true);
_tempTransform = new Transform3D();
_viewingPlatform=null;
_zoomAccumulator=null;
}
}