/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.display3d.simple3d; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.numerics.Quaternion; import org.opensourcephysics.numerics.Transformation; import org.opensourcephysics.numerics.VectorMath; public class Camera implements org.opensourcephysics.display3d.core.Camera { static private final double ratioToScreen = 2.5, ratioToFocus = 2.0; static private final double[] vertical = {0, 0, 1}; static final int CHANGE_ANY = 0; static final int CHANGE_MODE = 1; static final int CHANGE_POSITION = 2; static final int CHANGE_FOCUS = 3; static final int CHANGE_ROTATION = 4; static final int CHANGE_SCREEN = 5; static final int CHANGE_ANGLES = 6; // Configuration variables private int projectionMode = org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE; private double posX, posY, posZ; private double focusX, focusY, focusZ; private double distanceToScreen, rotationAngle = 0; private double alpha = 0.0, beta = 0.0; // Implementation variables private double distanceToFocus, panelMaxSizeConstant; double cosAlpha = 1, sinAlpha = 0, cosBeta = 1, sinBeta = 0; private double cosRot = 1, sinRot = 0; private double[] e1, e2, e3; private Projection projection = new Projection(); private Quaternion rotation = new Quaternion(1, 0, 0, 0); /** * The DrawingPanel3D to which it belongs. * This is needed to report to it any change that implies a call to update() */ private DrawingPanel3D panel; Camera(DrawingPanel3D aPanel) { this.panel = aPanel; } /** * Only for the use of the XMLLoader for DrawingPanel3D! * Sets the panel of this camera * @param aPanel DrawingPanel3D */ void setPanel(DrawingPanel3D aPanel) { this.panel = aPanel; } // ----------------------------- // Implementation of Camera // ---------------------------- public void setProjectionMode(int mode) { projectionMode = mode; if(panel!=null) { panelMaxSizeConstant = panel.getMaximum3DSize()*0.01; panel.cameraChanged(CHANGE_MODE); } } final public int getProjectionMode() { return projectionMode; } public void reset() { double[] center = panel.getCenter(); focusX = center[0]; focusY = center[1]; focusZ = center[2]; panelMaxSizeConstant = panel.getMaximum3DSize(); rotationAngle = 0; cosRot = 1; sinRot = 0; distanceToScreen = ratioToScreen*panelMaxSizeConstant; distanceToFocus = ratioToFocus*panelMaxSizeConstant; posX = center[0]+distanceToFocus; posY = center[1]; posZ = center[2]; alpha = 0; cosAlpha = 1; sinAlpha = 0; beta = 0; cosBeta = 1; sinBeta = 0; e1 = new double[] {-1, 0, 0}; e2 = new double[] {0, 1, 0}; e3 = new double[] {0, 0, 1}; panelMaxSizeConstant *= 0.01; panel.cameraChanged(CHANGE_ANY); } public void setXYZ(double x, double y, double z) { posX = x; posY = y; posZ = z; updateCamera(CHANGE_POSITION); } public void setXYZ(double[] point) { setXYZ(point[0], point[1], point[2]); } final public double getX() { return posX; } final public double getY() { return posY; } final public double getZ() { return posZ; } public void setFocusXYZ(double x, double y, double z) { focusX = x; focusY = y; focusZ = z; updateCamera(CHANGE_FOCUS); } public void setFocusXYZ(double[] point) { setFocusXYZ(point[0], point[1], point[2]); } final public double getFocusX() { return focusX; } final public double getFocusY() { return focusY; } final public double getFocusZ() { return focusZ; } public void setRotation(double angle) { rotationAngle = angle; cosRot = Math.cos(rotationAngle/2); sinRot = Math.sin(rotationAngle/2); updateCamera(CHANGE_ROTATION); } final public double getRotation() { return rotationAngle; } public void setDistanceToScreen(double distance) { distanceToScreen = distance; if(panel!=null) { panel.cameraChanged(CHANGE_SCREEN); } } final public double getDistanceToScreen() { return distanceToScreen; } public void setAzimuth(double angle) { alpha = angle; cosAlpha = Math.cos(alpha); sinAlpha = Math.sin(alpha); updateCamera(CHANGE_ANGLES); } final public double getAzimuth() { return alpha; } public void setAltitude(double angle) { beta = angle; if(beta<-Math.PI/2) { beta = -Math.PI/2; } else if(beta>Math.PI/2) { beta = Math.PI/2; } cosBeta = Math.cos(beta); sinBeta = Math.sin(beta); updateCamera(CHANGE_ANGLES); } final public double getAltitude() { return beta; } public void setAzimuthAndAltitude(double azimuth, double altitude) { alpha = azimuth; beta = altitude; if(beta<-Math.PI/2) { beta = -Math.PI/2; } else if(beta>Math.PI/2) { beta = Math.PI/2; } cosAlpha = Math.cos(alpha); sinAlpha = Math.sin(alpha); cosBeta = Math.cos(beta); sinBeta = Math.sin(beta); updateCamera(CHANGE_ANGLES); } final public Transformation getTransformation() { return projection; } public void copyFrom(org.opensourcephysics.display3d.core.Camera camera) { projectionMode = camera.getProjectionMode(); if(panel!=null) { panelMaxSizeConstant = panel.getMaximum3DSize()*0.01; } posX = camera.getX(); posY = camera.getY(); posZ = camera.getZ(); focusX = camera.getFocusX(); focusY = camera.getFocusY(); focusZ = camera.getFocusZ(); rotationAngle = camera.getRotation(); cosRot = Math.cos(rotationAngle/2); sinRot = Math.sin(rotationAngle/2); distanceToScreen = camera.getDistanceToScreen(); updateCamera(CHANGE_ANY); } // ------------------------------------- // Private methods // ------------------------------------- private void updateCamera(int change) { switch(change) { case CHANGE_POSITION : case CHANGE_FOCUS : distanceToFocus = computeCameraVectors(); alpha = Math.atan2(-e1[1], -e1[0]); beta = Math.atan2(-e1[2], Math.abs(e1[0])); cosAlpha = Math.cos(alpha); sinAlpha = Math.sin(alpha); cosBeta = Math.cos(beta); sinBeta = Math.sin(beta); break; case CHANGE_ROTATION : computeCameraVectors(); // e2 and e3 are different (rotated) break; case CHANGE_ANGLES : posX = focusX+distanceToFocus*cosBeta*cosAlpha; posY = focusY+distanceToFocus*cosBeta*sinAlpha; posZ = focusZ+distanceToFocus*sinBeta; computeCameraVectors(); break; case CHANGE_ANY : distanceToFocus = computeCameraVectors(); alpha = Math.atan2(-e1[1], -e1[0]); beta = Math.atan2(-e1[2], Math.abs(e1[0])); cosAlpha = Math.cos(alpha); sinAlpha = Math.sin(alpha); cosBeta = Math.cos(beta); sinBeta = Math.sin(beta); computeCameraVectors(); // e2 and e3 are different (rotated) break; } if(panel!=null) { panel.cameraChanged(change); } } private double computeCameraVectors() { e1 = new double[] {focusX-posX, focusY-posY, focusZ-posZ}; double magnitudeE1 = VectorMath.magnitude(e1); for(int i = 0; i<e1.length; i++) { e1[i] /= magnitudeE1; } e2 = VectorMath.cross3D(e1, vertical); double magnitude = VectorMath.magnitude(e2); for(int i = 0; i<e2.length; i++) { e2[i] /= magnitude; } e3 = VectorMath.cross3D(e2, e1); magnitude = VectorMath.magnitude(e3); for(int i = 0; i<e3.length; i++) { e3[i] /= magnitude; } // Finally apply the rotation rotation.setCoordinates(cosRot, e1[0]*sinRot, e1[1]*sinRot, e1[2]*sinRot); rotation.direct(e2); rotation.direct(e3); return magnitudeE1; } // ------------------------------------- // Projection methods // ------------------------------------- /** * Whether the projection mode is three-dimensional * @return boolean */ boolean is3dMode() { switch(projectionMode) { case MODE_PLANAR_XY : case MODE_PLANAR_XZ : case MODE_PLANAR_YZ : return false; default : return true; } } /** * Computes the projection of a size at a given point. * For internal use of DrawingPanel3D only */ double[] projectSize(double[] p, double[] size, double[] pixelSize) { switch(projectionMode) { case MODE_PLANAR_XY : pixelSize[0] = size[0]; pixelSize[1] = size[1]; return pixelSize; case MODE_PLANAR_XZ : pixelSize[0] = size[0]; pixelSize[1] = size[2]; return pixelSize; case MODE_PLANAR_YZ : pixelSize[0] = size[1]; pixelSize[1] = size[2]; return pixelSize; case MODE_NO_PERSPECTIVE : case MODE_PERSPECTIVE_OFF : pixelSize[0] = Math.max(size[0], size[1]); pixelSize[1] = size[2]; return pixelSize; default : case MODE_PERSPECTIVE : case MODE_PERSPECTIVE_ON : double factor = (p[0]-posX)*e1[0]+(p[1]-posY)*e1[1]+(p[2]-posZ)*e1[2]; if(Math.abs(factor)<panelMaxSizeConstant) { factor = panelMaxSizeConstant; // Avoid division by zero } factor = distanceToScreen/factor; pixelSize[0] = Math.max(size[0], size[1])*factor; pixelSize[1] = size[2]*factor; return pixelSize; } } private class Projection implements org.opensourcephysics.numerics.Transformation { public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException exc) { exc.printStackTrace(); return null; } } public double[] direct(double[] p) { switch(projectionMode) { case MODE_PLANAR_XY : p[0] = p[0]-focusX; p[1] = p[1]-focusY; p[2] = 1.0-(p[2]-focusZ)/distanceToFocus; return p; case MODE_PLANAR_XZ : { double aux = p[1]; p[0] = p[0]-focusX; p[1] = p[2]-focusZ; p[2] = 1.0-(aux-focusY)/distanceToFocus; return p; } case MODE_PLANAR_YZ : { double aux = p[0]; p[0] = p[1]-focusY; p[1] = p[2]-focusZ; p[2] = 1.0-(aux-focusX)/distanceToFocus; return p; } case MODE_NO_PERSPECTIVE : case MODE_PERSPECTIVE_OFF : { p[0] -= posX; p[1] -= posY; p[2] -= posZ; double aux1 = VectorMath.dot(p, e1); double aux2 = VectorMath.dot(p, e2); p[1] = VectorMath.dot(p, e3); p[0] = aux2; p[2] = aux1/distanceToFocus; return p; } default : case MODE_PERSPECTIVE : case MODE_PERSPECTIVE_ON : { p[0] -= posX; p[1] -= posY; p[2] -= posZ; double factor = VectorMath.dot(p, e1), aux1 = factor; if(Math.abs(factor)<panelMaxSizeConstant) { factor = panelMaxSizeConstant; // Avoid division by zero } // if (aux1<0) aux1 = Double.NaN; factor = distanceToScreen/factor; double aux2 = VectorMath.dot(p, e2)*factor; p[1] = VectorMath.dot(p, e3)*factor; p[0] = aux2; p[2] = aux1/distanceToFocus; return p; } } } public double[] inverse(double[] point) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } } // ---------------------------------------------------- // XML loader // ---------------------------------------------------- public static XML.ObjectLoader getLoader() { return new CameraLoader(); } protected static class CameraLoader extends org.opensourcephysics.display3d.core.Camera.Loader { public Object createObject(XMLControl control) { return new Camera((DrawingPanel3D) null); } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */