/*********************************************************************** * mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.components; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.media.opengl.GL; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; import org.mt4j.components.PickResult.PickEntry; import org.mt4j.components.bounds.IBoundingShape; import org.mt4j.components.clipping.Clip; import org.mt4j.components.interfaces.IMTComponent3D; import org.mt4j.components.interfaces.IMTController; import org.mt4j.input.ComponentInputProcessorSupport; import org.mt4j.input.GestureEventSupport; import org.mt4j.input.IMTInputEventListener; import org.mt4j.input.inputData.InputCursor; import org.mt4j.input.inputData.MTInputEvent; import org.mt4j.input.inputProcessors.IGestureEventListener; import org.mt4j.input.inputProcessors.IInputProcessor; import org.mt4j.input.inputProcessors.MTGestureEvent; import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor; import org.mt4j.util.MT4jSettings; import org.mt4j.util.camera.IFrustum; import org.mt4j.util.camera.Icamera; import org.mt4j.util.math.Matrix; import org.mt4j.util.math.Ray; import org.mt4j.util.math.Tools3D; import org.mt4j.util.math.Vector3D; import processing.core.PApplet; import processing.core.PGraphics; import processing.core.PGraphics3D; import processing.opengl.PGraphicsOpenGL; /** * This is the base class for all MT4j scene graph nodes. It provides basic methods * for adding and managing child nodes/components. It also allows for changing the components position and * orientation in space. Picking those components with a picking ray is also supported, if intersection * testing is properly implemented by extending subclasses. * <p> * This base class has no visible representation an thus can be used * as a group container node for other scene graph components. * * @author Christopher Ruff */ public class MTComponent implements IMTComponent3D, IMTInputEventListener, IGestureEventListener{ /** The Constant logger. */ private static final Logger logger = Logger.getLogger(MTComponent.class.getName()); static{ // logger.setLevel(Level.ERROR); logger.setLevel(Level.WARN); // logger.setLevel(Level.DEBUG); SimpleLayout l = new SimpleLayout(); ConsoleAppender ca = new ConsoleAppender(l); logger.addAppender(ca); } /** The ID. */ private int ID; /** The current id. */ private static int currentID; /** The renderer. */ private PApplet renderer; /** The name. */ private String name; /** The visible. */ private boolean visible; /** The enabled. */ private boolean enabled; /** The pickable. */ private boolean pickable; /** The drawn on top. */ private boolean drawnOnTop; /** The parent. */ private MTComponent parent; /** The child components. */ private List<MTComponent> childComponents; // /** The custom view port. */ // private ViewportSetting customViewPort; // // /** The default view port setting. */ // private ViewportSetting defaultViewPortSetting; /** The composite. */ private boolean composite; // Matrix Stuff /** The local matrix. */ private Matrix localMatrix; /** The local inverse matrix. */ private Matrix localInverseMatrix; /** The local to Global matrix. */ private Matrix globalMatrix; /** The Global to local matrix. */ private Matrix globalToLocalMatrix; /** The local to Global matrix dirty. */ private boolean globalMatrixDirty; /** The Global to local matrix dirty. */ private boolean globalInverseMatrixDirty; /** The pgraphics3 d. */ private PGraphics3D pgraphics3D; /** The controller. */ private IMTController controller; //FIXME EXPERIMENTAL /** The light. */ private MTLight light; // private PropertyChangeSupport propertyChangeSupport; /** The state change support. */ private StateChangeSupport stateChangeSupport; /** The _translation computation. */ private Matrix[] _translationComputation; /** The _x rotation computation. */ private Matrix[] _xRotationComputation; /** The _y rotation computation. */ private Matrix[] _yRotationComputation; /** The _z rotation computation. */ private Matrix[] _zRotationComputation; /** The _scaling computation. */ private Matrix[] _scalingComputation; /** The input processors support. */ private ComponentInputProcessorSupport inputProcessorsSupport; /** The gesture evt support. */ private GestureEventSupport gestureEvtSupport; /** The allowed gestures. */ private List<Class<? extends IInputProcessor>> allowedGestures; /** The attached camera. */ private Icamera attachedCamera; /** The viewing camera. */ private Icamera viewingCamera; /** The input listeners. */ private List<IMTInputEventListener> inputListeners; /** The user data. */ private Map<Object, Object> userData; private int inversePrecisionErrors; private int orthogonalityErrors; private static final int invPrecisionThreshold = 1000; private static final int reOrthogonalizeThreshold = 1500; /** * Creates a new component. The component has no initial visual representation. * * @param pApplet the applet */ public MTComponent(PApplet pApplet){ this(pApplet , "unnamed component", null); } /** * Creates a new component. The component has no visual representation. * * @param pApplet the applet * @param name the name */ public MTComponent(PApplet pApplet, String name){ this(pApplet , name, null); } /** * Creates a new component. The component has no visual representation. * * @param pApplet the applet * @param attachedCamera the camera to view this and this components children with */ public MTComponent(PApplet pApplet, Icamera attachedCamera){ this(pApplet , "unnamed component", attachedCamera); } /** * Creates a new component. The component has no visual representation. * * @param pApplet the applet * @param name the name of the component * @param attachedCamera a camera to view this and this components children with */ public MTComponent(PApplet pApplet, String name, Icamera attachedCamera){ synchronized (this) { this.ID = currentID++; } //Defaults this.renderer = pApplet; this.visible = true; this.enabled = true; this.pickable = true; this.drawnOnTop = false; this.name = name; this.composite = false; this.childComponents = new ArrayList<MTComponent>(); // //Default viewport, can be changed in subclass //FIXME REMOVE? // this.defaultViewPortSetting = new ViewportSetting(0, 0, this.getRenderer().width ,this.getRenderer().height); // this.customViewPort = null; //(Cached) Matrices of this component this.localMatrix = new Matrix(); this.localInverseMatrix = new Matrix(); this.globalMatrix = new Matrix(); this.globalToLocalMatrix= new Matrix(); this.globalMatrixDirty = true; this.globalInverseMatrixDirty = true; //This class should only be used with a renderer derived from pgraphics3D! this.pgraphics3D = (PGraphics3D)pApplet.g; //FIXME EXPERIMENTAL light = null; // propertyChangeSupport = new PropertyChangeSupport(this); // stateChangeSupport = new StateChangeSupport(this); _translationComputation = new Matrix[]{new Matrix(), new Matrix()}; _xRotationComputation = new Matrix[]{new Matrix(), new Matrix()}; _yRotationComputation = new Matrix[]{new Matrix(), new Matrix()}; _zRotationComputation = new Matrix[]{new Matrix(), new Matrix()}; _scalingComputation = new Matrix[]{new Matrix(), new Matrix()}; allowedGestures = new ArrayList<Class<? extends IInputProcessor>>(5); //TODO lazily instantiate gesturehandler/arraylist so that graphicobjects arent expensive at creation? this.inputListeners = new ArrayList<IMTInputEventListener>(3); //Delegate input processing/gesture detection to a special handler this.inputProcessorsSupport = new ComponentInputProcessorSupport(pApplet, this); //Let the input processor support class listen to the component's input events this.addInputListener(inputProcessorsSupport); this.gestureEvtSupport = new GestureEventSupport(); this.attachedCamera = attachedCamera; this.viewingCamera = attachedCamera; //FIXME TEST this.boundsGlobalVerticesDirty = true; this.inversePrecisionErrors = 0; this.orthogonalityErrors = 0; } //TODO // BOUNDS STUFF /////////////////////////////////// /** The bounds */ private IBoundingShape bounds; /** The bounds global vertices dirty. */ private boolean boundsGlobalVerticesDirty; //TODO REMOVE THESE DEPRECATED METHODS IN THE NEXT RELEASE! /** * Sets the bounding shape. * * @param boundingShape the new bounding shape * @deprecated renamed to <code>setBounds</code> */ public void setBoundingShape(IBoundingShape boundingShape){ this.bounds = boundingShape; this.setBoundsGlobalDirty(true); } /** * Gets the bounding shape. * * @return the bounding shape * @deprecated renamed to <code>getBounds</code> */ public IBoundingShape getBoundingShape(){ return this.bounds; } /** * Checks if is bounding shape set. * @return true, if is bounding shape set * @deprecated renamed to <code>hasBounds</code> */ public boolean isBoundingShapeSet(){ return this.bounds != null; } /** * Sets the bounding shape. * @param boundingShape the new bounding shape */ public void setBounds(IBoundingShape boundingShape){ this.bounds = boundingShape; this.setBoundsGlobalDirty(true); } /** * Gets the bounding shape. * @return the bounding shape */ public IBoundingShape getBounds(){ return this.bounds; } /** * Checks if is bounding shape set. * @return true, if is bounding shape set */ public boolean hasBounds(){ return this.bounds != null; } //TODO REMOVE? /** * Sets the bounds global vertices dirty. * * @param boundsWorldVerticesDirty the new bounds world vertices dirty */ private void setBoundsGlobalDirty(boolean boundsWorldVerticesDirty) { this.boundsGlobalVerticesDirty = boundsWorldVerticesDirty; if (this.hasBounds()){ this.getBounds().setGlobalBoundsChanged(); } } // BOUNDS STUFF //////////////////////////////// // INPUT LISTENER STUF //// /** * Adds an input listener to this component. The listener will be informed if * this component recieves an input event. * * @param inputListener the input listener */ public synchronized void addInputListener(IMTInputEventListener inputListener){ if (inputListener instanceof AbstractComponentProcessor) { logger.warn("An abstract component processor (" + inputListener + ") was added to component '" + this + "' using addInputListener(). You probably need to use the registerInputProcessor() method instead!"); } this.inputListeners.add(inputListener); } /** * Removes the input listener. * @param inputListener the input listener */ public synchronized void removeInputListener(IMTInputEventListener inputListener){ this.inputListeners.remove(inputListener); } /** * Gets the input listeners. * @return the input listeners */ public IMTInputEventListener[] getInputListeners(){ return this.inputListeners.toArray(new IMTInputEventListener[this.inputListeners.size()]); } /** * Fire input event. * * @param iEvt the i evt */ protected boolean fireInputEvent(MTInputEvent iEvt){ boolean handled = false; //TODO REALLY IMPLEMENT, CHECK LISTENERS WHAT THEY RETURN, PROPAGET ETC! for (IMTInputEventListener listener : inputListeners){ boolean handledListener = listener.processInputEvent(iEvt); if (!handled && handledListener){ handled = true; } } return handled; } // INPUT LISTENER STUF //// /// CAMERA SETTINGS ///////////////////////////////////// /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent3D#getViewingCamera() */ public Icamera getViewingCamera(){ if (this.viewingCamera != null){ return this.viewingCamera; }else{ //Search up the component tree for a attached camera //automatically sets this viewcamera to the attached camera if found this.searchViewingCamera(); return this.viewingCamera; } } /** * Search viewing camera. */ private void searchViewingCamera(){ this.viewingCamera = this.searchViewingCamRecur(this); } /** * Search viewing cam recur. * * @param current the current * @return the icamera */ private Icamera searchViewingCamRecur(MTComponent current){ if (current.attachedCamera != null){ return current.attachedCamera; }else{ if (current.getParent() != null){ return searchViewingCamRecur(current.getParent()); }else{ return null; } } } /** * Propagate cam change. * * @param cam the cam */ private void propagateCamChange(Icamera cam){ this.propagateCamChangeRecur(this, cam); } /** * Propagate cam change recur. * * @param current the current * @param cam the cam */ private void propagateCamChangeRecur(MTComponent current, Icamera cam){ //Only propagate further if current has no attached cam of its own //or it is the same as the propagated one //-> dont overwrite other attached cams down the tree! if (current.getAttachedCamera() == null || (current.getAttachedCamera() != null && (current.getAttachedCamera().equals(cam))) ){ current.viewingCamera = cam; for (MTComponent child: current.getChildList()){ child.propagateCamChange(cam); } } } /** * Gets the camera attached to this component or null if it doesent * have one. * * @return the attached camera */ public Icamera getAttachedCamera() { return attachedCamera; } /** * Attaches a camera to this component. * This component and all its children will be viewed * through the specified camera. * * @param attachedCamera the attached camera */ public void attachCamera(Icamera attachedCamera) { this.attachedCamera = attachedCamera; this.viewingCamera = attachedCamera; this.propagateCamChange(attachedCamera); } /// CAMERA SETTINGS ///////////////////////////////////// // INPUT HANDLER //////////////////////////////////////// /** * Registers an component input processor with this component. Input processors are used to process * the input events a component recieves by checking them for special patterns and conditions and * firing gesture events back to the component. * To recognize a multi-touch drag gesture on a component for example, we would register a * <code>DragProcessor</code> object with this component. * * @param inputProcessor the input processor */ public void registerInputProcessor(AbstractComponentProcessor inputProcessor) { AbstractComponentProcessor[] processors = inputProcessorsSupport.getInputProcessors(); for (int i = 0; i < processors.length; i++) { AbstractComponentProcessor abstractComponentProcessor = processors[i]; if (inputProcessor.getClass() == abstractComponentProcessor.getClass()){ logger.warn("Warning: The same type of input processor (" + inputProcessor.getName() + ") is already registered at component: " + this ); } } inputProcessorsSupport.registerInputProcessor(inputProcessor); this.setGestureAllowance(inputProcessor.getClass(), true); //Enable by default } /** * Unregister a component input processor. * @param inputProcessor the input processor */ public void unregisterInputProcessor(AbstractComponentProcessor inputProcessor) { inputProcessorsSupport.unregisterInputProcessor(inputProcessor); } /** * Unregister all previously registered component input processors. */ public void unregisterAllInputProcessors() { AbstractComponentProcessor[] ps = inputProcessorsSupport.getInputProcessors(); for (int i = 0; i < ps.length; i++) { AbstractComponentProcessor p = ps[i]; inputProcessorsSupport.unregisterInputProcessor(p); } } /** * Gets the component input processors. * @return the input processors */ public AbstractComponentProcessor[] getInputProcessors() { return inputProcessorsSupport.getInputProcessors(); } // INPUT HANDLER //////////////////////////////////////// // GESTURE LISTENER EVENT SUPPORT /////////////////////////////////////// /** * Adds a gesture listener to this component. The specified gesture listener's * <code>processGestureEvent(..)</code> method will be called when a gesture event * is processed by this component. The <code>IInputProcessor</code> paramter type specifies the source of * the gesture event we are interested in. So to listen to drag events only for example, we would specify * the <code>DragProcessor.class</code> as the first parameter. * * @param gestureEvtSender the gesture evt sender * @param listener the listener */ public void addGestureListener(Class<? extends IInputProcessor> gestureEvtSender, IGestureEventListener listener){ this.gestureEvtSupport.addGestureEvtListener(gestureEvtSender, listener); } /** * Removes the gesture event listener. * @param gestureEvtSender the gesture evt sender * @param listener the listener */ public void removeGestureEventListener(Class<? extends IInputProcessor> gestureEvtSender, IGestureEventListener listener) { gestureEvtSupport.removeGestureEventListener(gestureEvtSender, listener); } /** * Removes the all gesture event listeners. */ public void removeAllGestureEventListeners() { this.gestureEvtSupport.clearListeners(); } /** * Removes the all gesture event listeners who listen to the specified input processor. * @param gestureEvtSender the gesture evt sender */ public void removeAllGestureEventListeners(Class<? extends IInputProcessor> gestureEvtSender) { IGestureEventListener[] l = this.getGestureListeners(); for (int j = 0; j < l.length; j++) { IGestureEventListener gestureEventListener = l[j]; this.removeGestureEventListener(gestureEvtSender, gestureEventListener); } } /** * Returns the gesture listeners. * @return the gesture listeners */ public final IGestureEventListener[] getGestureListeners() { return gestureEvtSupport.getListeners(); } // GESTURE LISTENER EVENT SUPPORT /////////////////////////////////////// //Property Change Support //////////////////// /* //TODO use? remove? public void addPropertyChangeListener(PropertyChangeListener listener){ this.propertyChangeSupport.addPropertyChangeListener( listener ); } public void removePropertyChangeListener(PropertyChangeListener listener){ this.propertyChangeSupport.removePropertyChangeListener( listener ); } protected void firePropertyChange(PropertyChangeEvent arg0) { propertyChangeSupport.firePropertyChange(arg0); } protected void firePropertyChange(String arg0, boolean arg1, boolean arg2) { propertyChangeSupport.firePropertyChange(arg0, arg1, arg2); } protected void firePropertyChange(String arg0, int arg1, int arg2) { propertyChangeSupport.firePropertyChange(arg0, arg1, arg2); } protected void firePropertyChange(String arg0, Object arg1, Object arg2) { propertyChangeSupport.firePropertyChange(arg0, arg1, arg2); } static final String PROPERTY_NAME_STRING = "name"; */ // Propery Change Support //////////////////// ////STATE CHANGE SUPPORT ///// /** * Checks if the map is null and then lazily initializes it. */ private void lazyInitStateChangeSupport(){ if (stateChangeSupport == null){ stateChangeSupport = new StateChangeSupport(this); } } /** * Adds the state change listener. * * @param state the state * @param listener the listener */ public void addStateChangeListener(StateChange state, StateChangeListener listener) { this.lazyInitStateChangeSupport(); stateChangeSupport.addStateChangeListener(state, listener); } /** * Removes the state change listener. * * @param state the state * @param listener the listener */ public void removeStateChangeListener(StateChange state, StateChangeListener listener) { if (stateChangeSupport != null){ stateChangeSupport.removeStateChangeListener(state, listener); } } /** * Fire state change. * * @param evt the evt */ protected void fireStateChange(StateChangeEvent evt) { this.lazyInitStateChangeSupport(); stateChangeSupport.fireStateChange(evt); } /** * Fire state change. * * @param state the state */ protected void fireStateChange(StateChange state) { this.lazyInitStateChangeSupport(); stateChangeSupport.fireStateChange(state); } /////STATE CHANGE SUPPORT ///// /** * <li>Removes this component from its parent. * <li>Calls <code>destroyComponent</code> on this component which * can be used to free resources that the component used. * <li>Recursively calls destroy on alls its child components */ public void destroy(){ // System.out.println(this + " -> DESTROY() -> (MTComponent)"); // List<MTComponent> children = this.getChildList(); //We save the children in an array because the childList might get modified //during destruction and we wont call destroy on all children then! MTComponent[] childArr = this.getChildren(); if (this.getParent() != null){ this.removeFromParent(); //really do this? this.fireStateChange(StateChange.REMOVED_FROM_PARENT); } this.destroyComponent(); this.fireStateChange(StateChange.COMPONENT_DESTROYED); // /* if (userData != null){ this.userData.clear(); } this.unregisterAllInputProcessors(); // if (this.stateChangeSupport != null){ // this.stateChangeSupport = null; // } // if (this.gestureEvtSupport != null){ // this.gestureEvtSupport = null; // } // */ for (int i = 0; i < childArr.length; i++) { childArr[i].destroy(); } } /** * <br>Override this to clean up resources when destroying a component. * This method gets called by the <code>destroy</code> method. So you shouldnt * invoke this method directly. */ protected void destroyComponent(){ } /** * Applies (multiplies) this component's local matrix to processings current matrix. */ protected void applyLocalMatrix(){ this.applyMatrixToProcessingModelView(localMatrix); } /** * Applies (multiplies) the matrix to processings current matrix. * Developer note: Processings <code>applyMatrix</code> is saver than <code>g.modelview.apply</code>(?) * because it also calculates a new inverse. (important for lightning calculations?) * * @param m the m */ private void applyMatrixToProcessingModelView(Matrix m){ //This is slower because it also calcs the inverse.. // pgraphics3D.applyMatrix(m.m00, m.m01, m.m02, m.m03, // m.m10, m.m11, m.m12, m.m13, // m.m20, m.m21, m.m22, m.m23, // m.m30, m.m31, m.m32, m.m33); pgraphics3D.modelview.apply( m.m00, m.m01, m.m02, m.m03, m.m10, m.m11, m.m12, m.m13, m.m20, m.m21, m.m22, m.m23, m.m30, m.m31, m.m32, m.m33 ); /* Matrix mInv = localInverseMatrix; pgraphics3D.modelviewInv.preApply( mInv.m00, mInv.m01, mInv.m02, mInv.m03, mInv.m10, mInv.m11, mInv.m12, mInv.m13, mInv.m20, mInv.m21, mInv.m22, mInv.m23, mInv.m30, mInv.m31, mInv.m32, mInv.m33 ); */ } //TODO REMOVE? // /** // * Checks if is matrices dirty. // * // * @return true, if is matrices dirty // */ // public boolean isMatricesDirty(){ // return (this.globalMatrixDirty || this.globalInverseMatrixDirty); // } /** * Informs the object (and its children), that its matrix - OR ONE OF ITS PARENT'S MATRIX - has been altered. * <br>Usually this shouldnt be called by the user himself. * * @param matricesDirty the matrices dirty */ public void setMatricesDirty(boolean matricesDirty) { // System.out.println("Setting matrices dirty->" + matricesDirty + " on: " + this.getName()); if (matricesDirty == true){ //FIXME BOUNDS TEST this.setBoundsGlobalDirty(true); //absolute matrix �ndert sich damit auch auch wenn dr�ber parents geadded werden! this.setGlobalMatrixDirty(true); this.setGlobalInverseMatrixDirty(true); //Also inform the children, so they know that parent changed this.propagateMatrixChange(true); }else{//baseMatrixDiry == false this.globalMatrixDirty = matricesDirty; this.globalInverseMatrixDirty = matricesDirty; } } /** * Inform the children of the matrix change. * * @param matrixDirty the matrix dirty */ private void propagateMatrixChange(boolean matrixDirty){ // System.out.println("Setting basematrix dirty on obj: " + this.getName()); for (int i = 0; i < childComponents.size(); i++) { MTComponent object = childComponents.get(i); //TEST - only propagate unitil we get to a already dirty component //this should work because the dirty component should also have dirty children already //CAUTION: object can have for example a dirty global matrix and a clean global inverse matrix //so we check if both are dirty and only then dont propagate the dirty state //FIXME NOT WORKING WITH SVG EXAMPLEaaaaaaaaa - cause of composite? // if ((!object.isGlobalInverseMatrixDirty() || !object.isGlobalMatrixDirty())){ object.setMatricesDirty(matrixDirty); // } // else{ // System.out.println("Stopping matrix changed propagation at: " + object.getName() + " because both its matrices are already dirty."); // } } } /** * Checks if Global matrix is dirty. * * @return true, if checks if is abs local to Global matrix dirty * * whether the cached Global matrix is still valid */ private boolean isGlobalMatrixDirty() { return globalMatrixDirty; } /** * Sets the Global matrix dirty. * * @param globalMatrixDirty the local to Global matrix dirty */ private void setGlobalMatrixDirty(boolean globalMatrixDirty) { /* if (localToGlobalMatrixDirty){ System.out.println(this.getName() + ": Setting global Matrix DIRTY!"); }else{ System.out.println(this.getName() + ": Setting global Matrix NOT dirty!"); } */ this.globalMatrixDirty = globalMatrixDirty; } /** * Checks if is global to local matrix dirty. * * @return true, if checks if is global to local matrix dirty * * whether the cached absolute inverse global matrix is still valid */ private boolean isGlobalInverseMatrixDirty() { return globalInverseMatrixDirty; } /** * Sets the Global Inverse matrix dirty. * * @param dirty the dirty */ private void setGlobalInverseMatrixDirty(boolean dirty) { /* if (dirty){ System.out.println(this.getName() + ": Setting global inverse Matrix DIRTY!"); }else{ System.out.println(this.getName() + ": Setting global inverse Matrix NOT dirty!"); } */ this.globalInverseMatrixDirty = dirty; } /** * Gets the local basis matrix. * This is the matrix responsible for transforming this component relative to its parent. * * @return the local basis matrix * * The matrix describing the local coordinate space of this object. */ public Matrix getLocalMatrix() { return localMatrix; } /** * Sets a matrix by which this component and its children will be transformed. * <br>Also calculates and sets the corresponding local inverse matrix. (expensive call!) * * @param localBasisMatrix the local basis matrix */ public void setLocalMatrix(Matrix localBasisMatrix) { this.setLocalMatrixInternal(localBasisMatrix); try { //THIS OPERATION IS NOT CHEAP! this.setLocalInverseMatrixInternal(this.getLocalMatrix().invert()); } catch (Exception e) { e.printStackTrace(); } } /** * Sets a matrix by which this component and its children will be transformed. * <br>This should only be called internally. The corresponding inverse matrix * has to be calculated and set also for the object to be consistent! * <br>This method doesent calculate the inverse matrix! * * @param localMatrix the local matrix */ private void setLocalMatrixInternal(Matrix localMatrix) { this.localMatrix = localMatrix; this.setMatricesDirty(true); } /** * Gets the local inverse matrix. * * @return the local inverse matrix * * the local inverse transform matrix */ public Matrix getLocalInverseMatrix() { return this.localInverseMatrix; } /** * Sets the local inverse matrix. * * @param localInverseMatrix the local inverse matrix */ private void setLocalInverseMatrixInternal(Matrix localInverseMatrix) { this.localInverseMatrix = localInverseMatrix; } /** * Multiplies all transformation matrices of the * objects parents up the this object and returns it. * <br>This is so to speak the <b>"global matrix"</b> of the object. * This matrix can be used to transform points in the components local space * to the global space, the space where they actually appear in 3D space. * * @return the local to global matrix * * the absolute transformation (global) matrix of the object */ public Matrix getGlobalMatrix(){ Matrix resMatrix = this.globalMatrix; //Calculate the absolute local to global matrix only if necessary if (this.isGlobalMatrixDirty()){ // System.out.println(this.getName() + "'s global matrix is dirty! calculate it:"); resMatrix = new Matrix(); this.getGlobalMatrixRecursive(this, resMatrix); //System.out.println("Applying Matrix of: '" + this.getName() + "' Matrix: " + this.getLocalBasisMatrix().toString()); resMatrix.multLocal(this.getLocalMatrix()); this.globalMatrix = resMatrix; this.setGlobalMatrixDirty(false); } //System.out.println(this.getName() + "'s global matrix is not dirty!"); return resMatrix; } /** * Gets the abs matrix recursive. * * @param current the current * @param currentMatrix the current matrix * * @return the abs matrix recursive */ private MTComponent getGlobalMatrixRecursive(MTComponent current, Matrix currentMatrix){ //System.out.println("Processing: " + current.getName()); if (current.getParent() != null){ if (current.getParent().isGlobalMatrixDirty()){ //System.out.println(">Parent not null: " + current.getParent().getName()); // System.out.println(" Recursive loop - " + current.getParent().getName() + "'s global matrix IS dirty"); MTComponent res = this.getGlobalMatrixRecursive(current.getParent(), currentMatrix); if (!res.getLocalMatrix().isIdentity()){ currentMatrix.multLocal(res.getLocalMatrix()); //We can set // System.out.println(" Recursive loop - setting " + res.getName() + " global matrix"); res.globalMatrix = new Matrix(currentMatrix); res.setGlobalMatrixDirty(false); } //System.out.println("Applying Matrix of: '" + res.getName() + "' Matrix: " + res.getLocalBasisMatrix().toString()); }else{ //Currents global matrix isnt dirty -> apply the global matrix and stop recursion upwards // System.out.println(" Recursive loop - " + current.getParent().getName() + "'s global matrix is NOT dirty - stop recursion"); Matrix parentLocalToGlobal = current.getParent().getGlobalMatrix(); currentMatrix.multLocal(parentLocalToGlobal); } }else{ // System.out.println(" Recursive loop - " + current.getName() + " has no parent - stop recursion and use its local as its global matrix"); if (current.isGlobalMatrixDirty()){ current.globalMatrix = new Matrix(current.getLocalMatrix()); current.setGlobalMatrixDirty(false); } } return current; } /** * Returns the absolute inverse matrix (inverse of the global) which inverts all transforms made * from the parents down to this child. * This matrix can be used to transform a point in global space to the component's local object space (untransformed space). * * @return the absolute global to local matrix * , the absolute inverse transformation matrix of the object */ public Matrix getGlobalInverseMatrix() { Matrix resMatrix = this.globalToLocalMatrix; //Calculate the absolute local to global matrix only if necessary if (this.isGlobalInverseMatrixDirty()){ // System.out.println("Getting global inverse of: " + this.getName() + " -its dirty!"); if (this.getParent()!= null){ resMatrix = new Matrix(this.getLocalInverseMatrix()); this.getGlobalInvMatrixRecursive(this.getParent(), resMatrix); this.globalToLocalMatrix = resMatrix; }else{ //no parent -> Global inverse is local inverse this.globalToLocalMatrix = this.getLocalInverseMatrix(); resMatrix = this.globalToLocalMatrix; } this.setGlobalInverseMatrixDirty(false); } return resMatrix; } //TODO maybe cheaper to call globalMatrix.invert() than get the matrix recursively? /** * Gets the global inverse matrix recursive. * * @param current the current * @param currentMatrix the current matrix * * @return the abs inv matrix recursive */ private void getGlobalInvMatrixRecursive(MTComponent current, Matrix currentMatrix){ // System.out.println("processing: " + current.getName() + " Inverse Matrix: " + current.getLocalInverseMatrix()) ; if (current.isGlobalInverseMatrixDirty()){ // System.out.println(" Recursive Loop -> " + current.getName() + "'s global inverse is dirty - applying it"); if (!current.getLocalInverseMatrix().isIdentity()){ currentMatrix.multLocal(current.getLocalInverseMatrix()); } if (current.getParent() != null) this.getGlobalInvMatrixRecursive(current.getParent(), currentMatrix); }else{ //current isnt dirty, -> get the current absolute global inverse and apply it and stop recursion // System.out.println(" Recursive Loop -> " + current.getName() + "'s global inverse is not dirty - get " + current.getName() + "'s currents global inverse matrix and stop recursion"); if (!current.getGlobalInverseMatrix().isIdentity()){ currentMatrix.multLocal(current.getGlobalInverseMatrix()); } } } /** * Transforms the point - defined in the objects coordinate space - into parent relative space. * This is done by multiplying the point with the objects local basis matrix. * * @param referenceComp the reference comp * @param point the point * * @return the obj space vec to parent relative space */ public static Vector3D getLocalVecToParentRelativeSpace(MTComponent referenceComp, Vector3D point){ Vector3D ret = point.getCopy(); ret.transform(referenceComp.getLocalMatrix()); return ret; } /** * Transforms the given vector in global space coordinates * to be relative to the given reference objects parent space. * <br>Applies the inverse transforms associated with this parents actor * and its ancestors to the vector. * <br>NOTE: This transforms the global vector into the reference objects partent * space! Not the reference obj's local space! * * <br>Example:<br> * If we would want to rotate an object that is in an arbitrary * transformation hierarchy in the scene graph, around a point that is defined in Global coordinates, * we have to transform the point to be relative to the object's parent transformation space. * * <br>This is done by transforming the point by the inverse Global matrix of the objects parent. * * @param referenceComp the vector will be relative to this components parent space * @param point the point * * @return the global vec to parent relative space * * the transformed Vector */ public static Vector3D getGlobalVecToParentRelativeSpace(MTComponent referenceComp, Vector3D point){ //Returns point relative to the references parent! if (referenceComp.getParent() == null){ return point.getCopy(); }else{ Vector3D ret = point.getCopy(); // System.out.println("parent abs world to local matrix: " + referenceComp.getParent().getAbsoluteWorldToLocalMatrix()); ret.transform(referenceComp.getParent().getGlobalInverseMatrix()); return ret; } // Vector3D ret = point.getCopy(); //OLD WAY // ret.transform(referenceComp.getAbsoluteWorldToLocalMatrix()); // return ret; } /** * Calculates the transformation necessary to transform a component to be relative * to the destination component. * <p> * So for example, if you want to add a component from a random position in the scence graph * to a different component somewhere else in the scene graph, with the component remaining at the same global position, * you would transform the first component with the Matrix from the call of * <code>getTransformToDestinationParentSpace(originComponent,destinationComponent)</code>. * Then you would add the component to the destination component as its child. * The component will have the same global coordinates as before, but will now * be under the influence of the new parents transforms etc. * * @param originComponent the origin component * @param destinationComponent the destination component * * @return the transform to destination parent space * * the matrix */ public static Matrix getTransformToDestinationParentSpace(MTComponent originComponent, MTComponent destinationComponent){ // /* if (originComponent.getParent() != null){ //Transform to world space, keeping only the objects internal transform Matrix compParentWorld = new Matrix(originComponent.getParent().getGlobalMatrix()); //Transform to destination space, so that the destination //will not change the actual objects transform, shape and position //by negating the destination obj's absolute transform destinationComponent.getGlobalInverseMatrix().mult(compParentWorld, compParentWorld); return compParentWorld; }else{ // componentToTransform.transform(destinationComponent.getAbsoluteWorldToLocalMatrix()); return destinationComponent.getGlobalInverseMatrix(); } // */ /* //Transform to world space, keeping only the objects internal transform // componentToTransfrom.transform(componentToTransform.getParent().getAbsoluteLocalToWorldMatrix()); Matrix compParentWorld = new Matrix(originComponent.getAbsoluteLocalToWorldMatrix()); //Transform to destination space, so that the destination //will not change the actual objects transform, shape and position //by negating the destination obj's absolute transform // componentToTransform.transform(destinationComponent.getAbsoluteWorldToLocalMatrix()); if (destinationComponent.getParent() != null){ destinationComponent.getParent().getAbsoluteWorldToLocalMatrix().mult(compParentWorld, compParentWorld); return compParentWorld; }else{ return compParentWorld; } */ } /** * Gets the transform to destination local space. * * @param originComponent the origin component * @param destinationComponent the destination component * * @return the transform to destination local space */ public static Matrix getTransformToDestinationLocalSpace(MTComponent originComponent, MTComponent destinationComponent){ // /* //Worked with centerpoint //Transform to global space, keeping only the objects internal transform // componentToTransfrom.transform(componentToTransform.getParent().getAbsoluteLocalToWorldMatrix()); Matrix compParentWorld = new Matrix(originComponent.getGlobalMatrix()); //Transform to destination space, so that the destination //will not change the actual objects transform, shape and position //by negating the destination obj's absolute transform // componentToTransform.transform(destinationComponent.getAbsoluteWorldToLocalMatrix()); if (destinationComponent.getParent() != null){ destinationComponent.getParent().getGlobalInverseMatrix().mult(compParentWorld, compParentWorld); return compParentWorld; }else{ return compParentWorld; } // */ /* //Transform to world space, keeping only the objects internal transform //Works with textarea dragaway Matrix compParentWorld; if (originComponent.getParent() != null){ // compParentWorld = new Matrix(originComponent.getAbsoluteLocalToWorldMatrix()); compParentWorld = new Matrix(originComponent.getParent().getAbsoluteLocalToWorldMatrix()); }else{ compParentWorld = new Matrix(); } //Transform to destination space, so that the destination //will not change the actual objects transform, shape and position //by negating the destination obj's absolute transform destinationComponent.getAbsoluteWorldToLocalMatrix().mult(compParentWorld, compParentWorld); return compParentWorld; */ } /** * Converts the Vector3D object from the component's (local) coordinates to the world/canvas (global) coordinates. * <br><br> * This method allows you to convert any given x, y and z coordinates from values that are relative to * the origin (0,0) of a specific component (local coordinates) to values that are relative to * the origin of the canvas (global coordinates). * <br><br> * To use this method, first create an instance of the Vector3D class. * The x, y and z values that you assign represent local coordinates because they relate to the origin of the component. * <br><br> * You then pass the Vector3D instance that you created as the parameter to the localToGlobal() method. *<br> * The method returns a new Vector3D object with x, y and z values that relate to the origin of the global/canvas instead of * the origin of the component. * * @param point the point * * @return A new vector3D object with coordinates relative to the global/canvas. */ public Vector3D localToGlobal(Vector3D point){ Vector3D ret = point.getCopy(); ret.transform(this.getGlobalMatrix()); return ret; } /** * Converts the Vector3D object from the component's (local) coordinates to the parent component's coordinates. * <br><br> * This method allows you to convert any given x, y and z coordinates from values that are relative to * the origin (0,0) of a specific component (local coordinates) to values that are relative to * the origin of the parent component. * <br><br> * To use this method, first create an instance of the Vector3D class. * The x, y and z values that you assign represent local coordinates because they relate to the origin of the component. * <br><br> * You then pass the Vector3D instance that you created as the parameter to the localToParent() method. *<br> * The method returns a new Vector3D object with x, y and z values that relate to the origin of the parent instead of * the origin of the component. * * @param point the point * * @return A new vector3D object with coordinates relative to the parent component. */ public Vector3D localToParent(Vector3D point){ Vector3D ret = point.getCopy(); ret.transform(this.getLocalMatrix()); return ret; } /** * Converts the Vector3D object from the parent component's coordinates to this component's (local) coordinates. * <br><br> * This method allows you to convert any given x, y and z coordinates from values that are relative to * the origin (0,0) of a parent to values that are relative to * the origin of this component. * <br><br> * To use this method, first create an instance of the Vector3D class. * The x, y and z values that you assign represent parent relative coordinates because they relate to the origin of * the component's parent. * <br><br> * You then pass the Vector3D instance that you created as the parameter to the parentToLocal() method. * <br> * The method returns a new Vector3D object with x, y and z values that relates to the origin of this component * instead of the origin of the parent component. * * @param point the point * * @return A new vector3D object with coordinates relative to the components local space. */ public Vector3D parentToLocal(Vector3D point){ Vector3D ret = point.getCopy(); ret.transform(this.getLocalInverseMatrix()); return ret; } /** * Converts the Vector3D object from the world (global) coordinates to the component's (local) coordinates. *<br><br> * To use this method, first create an instance of the Vector3D class. * <br> * The x, y and z values that you assign represent global coordinates because they relate to the origin (0,0) of the main display area. * Then pass the Vector3D instance as the parameter to the globalToLocal() method. * <br> * The method returns a new Vector3D object with x and y values that relate to the origin of the component * instead of the origin of the world. * * @param point the point * * @return a new vector3D object with coordinates relative to the component. */ public Vector3D globalToLocal(Vector3D point){ Vector3D ret = point.getCopy(); ret.transform(this.getGlobalInverseMatrix()); return ret; } /** * Transforms the global ray into local coordinate space and returns the new ray. * * @param globalRay the global ray * * @return the ray */ public Ray globalToLocal(Ray globalRay){ // return Ray.getTransformedRay(globalRay, this.getGlobalMatrix().invert()); return Ray.getTransformedRay(globalRay, this.getGlobalInverseMatrix()); } ////////////////////////////////////////////////////////////// // COMPONENT TRANSFORMATIONS // ////////////////////////////////////////////////////////////// /* * NOTE: the matrix and the inverse may suffer from rounding erros and not be exact inverse to each other! * we calc the inverse incrementally -> rounding off errors accumulate * -> after a certain number of rotations we use the invert() method on the localMatrices instead * Also from incremental rotations the matrix may loose its orthogonality * -> we re-orthogonalize after a certain number of rotations */ /** * Transforms the shapes local coordinate space by the specified matrix. This operation * can be quite costly since it involves a matrix multiplication and a calculation of its inverse. * * @param transformMatrix the transform matrix */ public void transform(Matrix transformMatrix) { this.setLocalMatrixInternal(transformMatrix.mult(this.getLocalMatrix(), this.getLocalMatrix())); try { //THIS OPERATION IS NOT CHEAP! //TODO maybe also only calculate this on demand? (at getLocalInverse() or getGlobalInverse()) this.setLocalInverseMatrixInternal(this.getLocalMatrix().invert()); this.inversePrecisionErrors = 0; } catch (Exception e) { e.printStackTrace(); } } /** * Translate. Move the component in the direction of the specified vector. * The transformspace specifies the space, which the translation should be relative * to. * * @param dirVect the dir vect * @param transformSpace the transform space */ public void translate(Vector3D dirVect, TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: dirVect.transformDirectionVector(this.getLocalMatrix()); break; case RELATIVE_TO_PARENT: //default break; case GLOBAL: if (this.getParent()!= null){ //Transform direction vector from world space to this objs parent space dirVect.transformDirectionVector(this.getParent().getGlobalInverseMatrix()); } break; default: break; } this.translate(dirVect); } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#translateGlobal(org.mt4j.util.math.Vector3D) */ public void translateGlobal(Vector3D dirVect) { this.translate(dirVect, TransformSpace.GLOBAL); } /** * Translates this component in the give direction, relative to its parent component. * * @param dirVect the dir vect */ public void translate(Vector3D dirVect) { // Matrix[] ms = Matrix.getTranslationMatrixAndInverse(dirVect.getX(), dirVect.getY(), dirVect.getZ()); Matrix[] ms = _translationComputation; //use existing object to avoid object creation Matrix.toTranslationMatrixAndInverse(ms[0], ms[1], dirVect.x, dirVect.y, dirVect.z); // this.setLocalBasisMatrixInternal(ms[0].mult(this.getLocalBasisMatrix(), this.getLocalBasisMatrix())); //Using special multiplication with fewer operations - seems to work ;) this.setLocalMatrixInternal(ms[0].translateMult(this.getLocalMatrix(), this.getLocalMatrix())); try { // this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().multLocal(ms[1])); this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().translateMultLocal(ms[1])); } catch (Exception e) { e.printStackTrace(); } } /** * X rotate. * The transformspace parameter indicates in which * coordinate space the point is specified in. * @param rotationPoint the rotation point * @param degree the degree * @param transformSpace the transform space */ public void rotateX(Vector3D rotationPoint, float degree, TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: rotationPoint = MTComponent.getLocalVecToParentRelativeSpace(this, rotationPoint); break; case RELATIVE_TO_PARENT: //default break; case GLOBAL: rotationPoint = MTComponent.getGlobalVecToParentRelativeSpace(this, rotationPoint); break; default: break; } this.rotateX(rotationPoint, degree); } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent3D#rotateXGlobal(org.mt4j.util.math.Vector3D, float) */ public void rotateXGlobal(Vector3D rotationPoint, float degree) { this.rotateX(rotationPoint, degree, TransformSpace.GLOBAL); } /** * X rotate. * Rotates relative to the parent coordinate space. * * @param rotationPoint the rotation point * @param degree the degree */ public void rotateX(Vector3D rotationPoint, float degree) { // Matrix[] ms = Matrix.getXRotationMatrixAndInverse(rotationPoint, degree); Matrix[] ms = _xRotationComputation; Matrix.toXRotationMatrixAndInverse(ms[0], ms[1], rotationPoint, degree); this.setLocalMatrixInternal(ms[0].mult(this.getLocalMatrix(), this.getLocalMatrix())); this.inversePrecisionErrors ++; this.orthogonalityErrors ++; if (this.orthogonalityErrors >= reOrthogonalizeThreshold){ // System.out.println("Matrix re-orthogonalized and inverted at: " + this); this.reOrthogonalize(); //This also calculates the inverse in call of setLocalMatrix(..) this.orthogonalityErrors = 0; this.inversePrecisionErrors = 0; }else{ try { if (this.inversePrecisionErrors >= invPrecisionThreshold){ this.inversePrecisionErrors = 0; // System.out.println("Matrix inverted at: " + this); this.setLocalInverseMatrixInternal(new Matrix(this.getLocalMatrix()).invertLocal()); }else{ this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().multLocal(ms[1])); } } catch (Exception e) { e.printStackTrace(); } } } /** * Y rotate. * The transformspace parameter indicates in which * coordinate space the point is specified in. * * @param rotationPoint the rotation point * @param degree the degree * @param transformSpace the transform space */ public void rotateY(Vector3D rotationPoint, float degree, TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: rotationPoint = MTComponent.getLocalVecToParentRelativeSpace(this, rotationPoint); break; case RELATIVE_TO_PARENT: //default break; case GLOBAL: rotationPoint = MTComponent.getGlobalVecToParentRelativeSpace(this, rotationPoint); break; default: break; } this.rotateY(rotationPoint, degree); } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent3D#rotateYGlobal(org.mt4j.util.math.Vector3D, float) */ public void rotateYGlobal(Vector3D rotationPoint, float degree) { this.rotateY(rotationPoint, degree, TransformSpace.GLOBAL); } /** * Y rotate. * Rotates relative to the parent coordinate space. * * @param rotationPoint the rotation point * @param degree the degree */ public void rotateY(Vector3D rotationPoint, float degree) { // Matrix[] ms = Matrix.getYRotationMatrixAndInverse(rotationPoint, degree); Matrix[] ms = _yRotationComputation; Matrix.toYRotationMatrixAndInverse(ms[0], ms[1], rotationPoint, degree); this.setLocalMatrixInternal(ms[0].mult(this.getLocalMatrix(), this.getLocalMatrix())); this.inversePrecisionErrors ++; this.orthogonalityErrors ++; if (this.orthogonalityErrors >= reOrthogonalizeThreshold){ // System.out.println("Matrix re-orthogonalized and inverted at: " + this); this.reOrthogonalize(); //This also calculates the inverse in call of setLocalMatrix(..) this.orthogonalityErrors = 0; this.inversePrecisionErrors = 0; }else{ try { if (this.inversePrecisionErrors >= invPrecisionThreshold){ this.inversePrecisionErrors = 0; // System.out.println("Matrix inverted at: " + this); this.setLocalInverseMatrixInternal(new Matrix(this.getLocalMatrix()).invertLocal()); }else{ this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().multLocal(ms[1])); } } catch (Exception e) { e.printStackTrace(); } } } /** * Rotates the component around the specified point on the Z axis. * The transformspace parameter indicates in which * coordinate space the point is specified in. * * @param rotationPoint the rotation point * @param degree the degree * @param transformSpace the transform space */ public void rotateZ(Vector3D rotationPoint, float degree, TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: rotationPoint = MTComponent.getLocalVecToParentRelativeSpace(this, rotationPoint); break; case RELATIVE_TO_PARENT: //default break; case GLOBAL: rotationPoint = MTComponent.getGlobalVecToParentRelativeSpace(this, rotationPoint); break; default: break; } this.rotateZ(rotationPoint, degree); } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#rotateZGlobal(org.mt4j.util.math.Vector3D, float) */ public void rotateZGlobal(Vector3D rotationPoint, float degree) { this.rotateZ(rotationPoint, degree, TransformSpace.GLOBAL); } /** * Rotates the obj around the z-axis around the rotationpoint. * The rotation point is parent-transformation space-relative. * * @param rotationPoint the rotation point * @param degree the degree */ public void rotateZ(Vector3D rotationPoint, float degree) { // Matrix[] ms = Matrix.getZRotationMatrixAndInverse(rotationPoint, degree); Matrix[] ms = _zRotationComputation; Matrix.toZRotationMatrixAndInverse(ms[0], ms[1], rotationPoint, degree); //Using special multiplication with fewer operations - seems to work ;) this.setLocalMatrixInternal(ms[0].zRotateMult(this.getLocalMatrix(), this.getLocalMatrix())); this.inversePrecisionErrors ++; this.orthogonalityErrors ++; if (this.orthogonalityErrors >= reOrthogonalizeThreshold){ // System.out.println("Matrix re-orthogonalized and inverted at: " + this); this.reOrthogonalize(); //This also calculates the inverse in call of setLocalMatrix(..) this.orthogonalityErrors = 0; this.inversePrecisionErrors = 0; }else{ try { if (this.inversePrecisionErrors >= invPrecisionThreshold){ this.inversePrecisionErrors = 0; // System.out.println("Matrix inverted at: " + this); this.setLocalInverseMatrixInternal(new Matrix(this.getLocalMatrix()).invertLocal()); }else{ this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().fastMult43(ms[1],this.getLocalInverseMatrix())); } } catch (Exception e) { e.printStackTrace(); } } } /** * Scales the obj around the scalingPoint. The transformspace parameter indicates in which * coordinate space the point is specified in. * <br><strong>Note:</strong> Non-uniform scaling may lead to bad results! * * @param X the x * @param Y the y * @param Z the z * @param scalingPoint the scaling point * @param transformSpace the transform space */ public void scale(float X, float Y, float Z, Vector3D scalingPoint, TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: scalingPoint = MTComponent.getLocalVecToParentRelativeSpace(this, scalingPoint); break; case RELATIVE_TO_PARENT: //default break; case GLOBAL: scalingPoint = MTComponent.getGlobalVecToParentRelativeSpace(this, scalingPoint); break; default: break; } this.scale(X, Y, Z, scalingPoint); } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#scaleGlobal(float, float, float, org.mt4j.util.math.Vector3D) */ public void scaleGlobal(float X, float Y, float Z, Vector3D scalingPoint) { this.scale(X, Y, Z, scalingPoint, TransformSpace.GLOBAL); } //FIXME scale non uniform um parent space punkt realisieren, //so gibts bug z.b. beim picking, und bei tastatur /** * <b>CURRENTLY DOES NOT REALLY SUPPORT NON-UNIFORM SCALING!</b> * <p>Scales the polygon around the scalingPoint. * * @param X the x * @param Y the y * @param Z the z * @param scalingPoint the scaling point */ public void scale(float X, float Y, float Z, Vector3D scalingPoint) { /*//TODO !!//TODO mit scaling point umgehen, im moment ignoriert! also immer um 0,0,0/z in der diagonalen if (!isScaleUniformXY(X,Y,Z)){ //TODO mit scaling point umgehen, im moment ignoriert! also immer um 0,0,0/z in der diagonalen //SetSize scaled jetzt um object frame origin! :( Matrix m = Matrix.getNonUniformScalingTrialMatrix(this.getLocalBasisMatrix(), X, Y, Z); // this.setLocalBasisMatrix(m); this.setLocalBasisMatrixInternal(m); //TODO Funktionier auch? unterschied zu unten?? dann k�nnte man auch einfach setLocalBasisMatrix(m) aufrufen.. this.setLocalInverseMatrixInternal(this.getLocalBasisMatrix().invert()); //Inverse wird von hand unten berechnet und ist nicht die directe inverse // Matrix mInv = Matrix.getInvScalingMatrix(scalingPoint, X, Y, Z); // this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().mult(mInv)); }else */ { // /*//For uniform scalings or non uniform scalings before any other transform has happened // Matrix[] ms = Matrix.getScalingMatrixAndInverse(scalingPoint, X, Y, Z); Matrix[] ms = _scalingComputation; Matrix.toScalingMatrixAndInverse(ms[0], ms[1], scalingPoint, X, Y, Z); // this.setLocalBasisMatrixInternal(ms[0].mult(this.getLocalBasisMatrix(), this.getLocalBasisMatrix())); //working original // this.setLocalBasisMatrixInternal(this.getLocalBasisMatrix().mult(ms[0])); //FIXME TRIAL! PROBLEM WITH NON-UNIFORM SCALING!! this.setLocalMatrixInternal(ms[0].scaleMult(this.getLocalMatrix(), this.getLocalMatrix())); try { // this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().multLocal(ms[1])); //working original! // this.setLocalInverseMatrixInternal(ms[1].mult(this.getLocalInverseMatrix())); this.setLocalInverseMatrixInternal(this.getLocalInverseMatrix().scaleMultLocal(ms[1])); } catch (Exception e) { e.printStackTrace(); } // */ } //TODO this is a hack to allow non-uniform scaling for //Abstract shapes. This scales the shapes geometry, not their transformation. //Thus, children arent scaled. //=>Problem with complexpolys for ex. since they have more geometry than in the geometryInfo.. //=>setSize() wont work then //non uniform scaling makes problems when comp has been rotated and isnt axis aligned /* if (!isScaleUniformXY(X,Y,Z) && this instanceof AbstractShape) { AbstractShape shape = (AbstractShape) this; Vertex[] localVecs = shape.getVerticesLocal(); Vector3D scalingInv = scalingPoint.getCopy(); //Transform local scalingpoint into object space // scalingInv.transform(this.getLocalInverseMatrix()); // scalingInv.transform(this.getAbsoluteWorldToLocalMatrix()); scalingInv.transformDirectionVector(this.getLocalBasisMatrix()); //Transform object vertices Vector3D.transFormArray(Matrix.getScalingMatrix(scalingInv, X, Y, Z), localVecs); shape.setVerticesLocal(localVecs); //TODO bei kindern das gleich probieren? //TODO auch bei global machen }else */ } /* private boolean isScaleUniformXY(float x, float y, float z){ return x==y; } */ /** * This method is called just before the components drawComponent method is invoked. * It sets up the components matrix, clipping and other stuff. * @param g the graphics context */ public void preDraw(PGraphics g) { if (this.isDepthBufferDisabled()){ Tools3D.disableDepthBuffer(g); } g.pushMatrix(); MTLight aLight = this.getLight(); if (aLight != null){ GL gl = ((PGraphicsOpenGL)g).gl; gl.glEnable(GL.GL_LIGHTING); //this is expensive aLight.enable(); } if (!this.getLocalMatrix().isIdentity()) this.applyLocalMatrix(); if (this.getClip() != null){ this.getClip().enableClip(g); } } /** * Executes this component's drawing commands (Not its children!). * The component's matrix has to be made current before drawing. * <br>This method can be overridden in subclasses * and filled with drawing commands. * <br>NOTE: This method is called by the application. Usually you should * not invoke this method directly! * @param g the graphics context */ public void drawComponent(PGraphics g){ } /** * Post draw. * Called immediatly after drawing this component. * * @param g the g */ public void postDraw(PGraphics g) { if (this.getClip() != null){ this.getClip().disableClip(g); } if (this.getChildClip() != null){ this.getChildClip().enableClip(g); } } /** * Post draw Children. * Called after drawing this component and its children. * @param g the graphics context */ public void postDrawChildren(PGraphics g) { if (this.isDepthBufferDisabled()){ Tools3D.restoreDepthBuffer(g); } if (this.getChildClip() != null){ this.getChildClip().disableClip(g); } renderer.popMatrix(); //FIXME TRIAL MTLight aLight = this.getLight(); if (aLight != null){ aLight.disable(); GL gl = ((PGraphicsOpenGL)g).gl; gl.glDisable(GL.GL_LIGHTING); } } // CLIP //////////////// /** The clip. */ protected Clip clip; /** * Gets the clip. * @return the clip */ public Clip getClip() { return clip; } /** * Sets the clip mask for this component. This restricts the drawing * of this component to the specified clip area. * <br>NOTE: Only supported when using OpenGL as the renderer! * * @param clip the new clip */ public void setClip(Clip clip) { if (MT4jSettings.getInstance().isOpenGlMode()){ this.clip = clip; } } // CLIP //////////////// // CHILDREN CLIP ///////////////////// /** The child clip. */ private Clip childClip; /** * Gets the child clip. * @return the child clip */ public Clip getChildClip() { return childClip; } /** * Sets the clip mask for this components children. * Only children contained in the specified clipping shape will be visible. * <br>NOTE: Only supported when using OpenGL as the renderer! * * @param childClip the child clip mask */ public void setChildClip(Clip childClip) { if (MT4jSettings.getInstance().isOpenGlMode()){ this.childClip = childClip; } } // CHILD CLIP MASK ///////////////////// //FIXME TRIAL OPENGL LIGHTS //////////// /** * Sets the light. * <br>NOTE: Only supported when using OpenGL as the renderer! * * @param light the new light */ public void setLight(MTLight light){ this.light = light; } /** * Gets the light. * * @return the light */ public MTLight getLight() { return light; } //TRIAL OPENGL LIGHTS //////////// //FIXME REMOVE THIS? THIS IS OBSOLETE BECAUSE OF UPDATECOMPONENT AND drawAndUpdateRectursive! // /** // * Calls the updateComponent() method on this component // * and the update() method on its children. // * This is handled automatically by the MTCanvas! Dont invoke this! // * // * @param timeDelta the time delta since the last frame // */ // public void update(long timeDelta){ // this.updateComponent(timeDelta); // // for (MTComponent child : childComponents) // child.update(timeDelta); // } /** * Tells the component to update its state if neccessary. This is called * shortly before the component's <code>drawComponent()</code> method is invoked at every frame. * The <code>timeDelta</code> * parameter indicates the time passed since the last frame was drawn and can be used * for animations for example.<br> * Also, this updates the associated <code>IMTController</code> object if existing. * If overriden, the superclass implementation should always be called! * <br>NOTE: Be aware that this method is called every frame, so doing expensive calculations in it may slow down the * application. * * @param timeDelta the time delta */ public void updateComponent(long timeDelta) { if (controller != null){ controller.update(timeDelta); } } /** * Adds a component as a child to this component. * By doing this, it will be under the influence of the parents * transformations. * <br>NOTE: adding children during traversal of the component hierarchy * will result in a concurrent modification error (e.g. in methods like * drawComponent or updateComponent). * To resolve this, we can use the AddNodeActionThreadSafe class to add * as an IPreDrawAction to our current scene * (looking something like this: yourScene.addPreDrawAction(new AddNodeActionThreadSafe(..));) * The component will then be added before the next rendering loop. * * @param tangibleComp the tangible comp */ public void addChild(MTComponent tangibleComp){ this.addChild(this.childComponents.size(), tangibleComp); } /** * Adds the child at the specified position in the list of children. * * @param i the i * @param tangibleComp the tangible comp * * @see MTComponent#addChild */ public void addChild(int i, MTComponent tangibleComp){ MTComponent oldParent = tangibleComp.getParent(); boolean sameParent = false; if (oldParent != null){ oldParent.removeChild(tangibleComp); if (oldParent.equals(this)){ i--;//If we removed the comp from this (same parent) we have to decrease the index sameParent = true; } i = (i<0)? 0 : i; //ensure i > 0 } tangibleComp.setParent(this); childComponents.add(i, tangibleComp); if (!sameParent){ //TEST - only mark dirty if comp was added to different parent //To inform its children, that they have to update their //global matrices, because this new parent could //change it with its own tangibleComp.setMatricesDirty(true); //search up the tree and update the camera responsible for drawing the component tangibleComp.searchViewingCamera(); } //Fire state change event this.fireStateChange(StateChange.CHILD_ADDED); tangibleComp.fireStateChange(StateChange.ADDED_TO_PARENT); } /** * Adds an array of components to this component as children. * * @param tangibleComps the tangible comps */ public void addChildren(MTComponent[] tangibleComps){ for (int i = 0; i < tangibleComps.length; i++) { MTComponent object = tangibleComps[i]; this.addChild(object); } } /** * Gets the child list which is also used internally in <code>MTComponent</code>. * Therefor, this should be used for read operations only! * <p>This method is provided for performance reasons, because <code>getChildren()</code> * contains overhead because it creates a new array for each call. * * @return the child list */ protected List<MTComponent> getChildList(){ return childComponents; } /** * Gets the children. * @return the children */ public MTComponent[] getChildren(){ return childComponents.toArray(new MTComponent[childComponents.size()]); } /** * Gets the child by its unique ID. * <br>NOTE: the specified number is the unique component ID, not the index in the children array! * * @param ID the iD * * @return the child */ public MTComponent getChildbyID(int ID){ MTComponent returnObject = null; for (int i = 0; i < childComponents.size(); i++) { MTComponent object = (MTComponent)childComponents.get(i); if (object.getID() == ID) returnObject = object; } return returnObject; } /** * Gets the child by index. * * @param index the index * * @return the child by index */ public MTComponent getChildByIndex(int index){ return childComponents.get(index); } /** * Gets the child by name. * * @param name the name * * @return the child by name */ public MTComponent getChildByName(String name){ MTComponent returnObject = null; for (int i = 0; i < childComponents.size(); i++) { MTComponent object = (MTComponent)childComponents.get(i); if (object.getName().equals(name)) returnObject = object; } return returnObject; } /** * Goes through all children and their children * to check if this component tree contains the given component. * * @param tangibleComp the tangible comp * * @return true, if contains child */ public boolean containsChild(MTComponent tangibleComp){ if (tangibleComp==null) return false; for (int i = 0; i < childComponents.size(); i++) { MTComponent currentChildComponent = childComponents.get(i); if (currentChildComponent.equals(tangibleComp)) return true; else if (currentChildComponent.containsChild(tangibleComp)) return true; } return false; } /** * Checks if the given component is a direct child of this component. * * @param tangibleComp the tangible comp * * @return true, if contains direct child */ public boolean containsDirectChild(MTComponent tangibleComp){ return (childComponents.contains(tangibleComp)); } /* // ///////Mal ausproibern und evtl removen public boolean containsRecursive(TComponent tangibleComp){ return (containsRecursiveRec(this.getChildren(), tangibleComp)); } private boolean containsRecursiveRec(TComponent[] tangibleComps, TComponent tangibleComp){ boolean returnBool = false; for (int i = 0; i < tangibleComps.length; i++) { TComponent currentComponent = tangibleComps[i]; System.out.println(currentComponent.getName()); if (currentComponent.equals(tangibleComp)){ //return true; return true; }else{ TComponent[] childComps = currentComponent.getChildren(); //return containsRecursiveRec(childComps, tangibleComp); returnBool = containsRecursiveRec(childComps, tangibleComp); } } return returnBool; } // ///////Mal ausproibern und evtl removen * */ /** * Gets the ancestor. * * @return the ancestor * * the ancestor - the upper most parent in the hierarchy of this component */ public MTComponent getRoot(){ return getRootRecursive(this); } /* careful not to have cycles in the hierarchy! */ /** * Gets the ancestor recursive. * * @param current the current * * @return the ancestor recursive */ private MTComponent getRootRecursive(MTComponent current){ if (current.getParent() == null){ return current; }else{ return getRootRecursive(current.getParent()); } } /** * Gets the child count. * * @return the child count * * the number of childs this component has */ public int getChildCount(){ return childComponents.size(); } /** * Gets the parent. * * @return the parent * * the parent of this child or null if it has none. */ public MTComponent getParent(){ return this.parent; } /** * Used internally when adding a component to another. * * @param parent the parent */ private void setParent(MTComponent parent){ //remove this from old parent //THIS IS DONE AT ADDCHILD()! // if (this.getParent() != null){ // this.getParent().removeChild(this); // } this.parent = parent; } /** * Tries to remove the specified child from this component. * * @param i the i */ public void removeChild(int i){ try { MTComponent comp = childComponents.get(i); this.removeChild(comp); } catch (Exception e) { e.printStackTrace(); } } /** * Removes this component from its parent. */ public void removeFromParent(){ if (this.getParent() != null){ this.getParent().removeChild(this); } } /** * Tries to remove the specified child from this component. * * @param comp the comp */ public void removeChild(MTComponent comp){ try { comp.setParent(null); childComponents.remove(comp); //search up the tree and update the camera responsible for drawing the component //will probably be null here comp.searchViewingCamera(); comp.fireStateChange(StateChange.REMOVED_FROM_PARENT); } catch (Exception e) { e.printStackTrace(); } } /** * Removes all direct children of this component. */ public void removeAllChildren(){ for (int i = childComponents.size()-1; i >= 0; i--) { MTComponent child = childComponents.get(i); child.removeFromParent(); } childComponents.clear(); } /** * Gets the child index of a child. * @param comp the comp * * @return the child index of */ public int getChildIndexOf(MTComponent comp){ return childComponents.indexOf(comp); } /** * <br>If the depth buffer is disabled, the order in which the components are drawn alone decides which objects will appear * ontop of others, instead of their distance to the camera. This is useful to avoid "z-fighting" when drawing * co-planar objects (ie. 2D windows with ui objects on them). * If set to true, this component <b>and all its children</b> will always be drawn above all previously drawn objects, * even if the other objects are "in front" of this component. * * @param drawOnTop the draw on top option */ public void setDepthBufferDisabled(boolean drawOnTop){ this.sendToFront(); this.drawnOnTop = drawOnTop; } /** * Checks if is always drawn on top. * * @return true, if is always drawn on top */ public boolean isDepthBufferDisabled() { return drawnOnTop; } /** * Puts this components to the end of the children list of * its parent. * This will result in this component being drawn last and on top * of others at the same z-position. */ public void sendToFront(){ if (this.getParent()!= null){ this.getParent().sendChildToFront(this); } } /** * Puts this child to the end of the children list of this component. * This will result in this child being drawn last and on top * of others at the same Z-position. * @param child the child */ protected void sendChildToFront(MTComponent child){ if (this.containsDirectChild(child) && !getChildByIndex(getChildCount()-1).equals(child) ){ //System.out.println("Drawlast: " + tangibleComp.getName()); // this.removeChild((child)); // this.addChild((child)); //FIXME THIS DOES A LOT OF UNNECESSARY STUFF - statechange, cam search, matrix dirty! childComponents.add(getChildCount(),child); childComponents.remove(child); // childComponents.remove(child); // childComponents.add(getChildCount(),child); } } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#setVisible(boolean) */ public void setVisible(boolean visible){ this.visible = visible; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#isEnabled() */ public boolean isEnabled() { return enabled; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#setEnabled(boolean) */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#getID() */ public int getID(){ return this.ID; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#setName(java.lang.String) */ public void setName(String name){ this.name = name; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#getName() */ public String getName(){ return this.name; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#getRenderer() */ public PApplet getRenderer(){ return this.renderer; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#isVisible() */ public boolean isVisible() { return visible; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent3D#isPickable() */ public boolean isPickable() { return pickable; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent3D#setPickable(boolean) */ public void setPickable(boolean pickable) { this.pickable = pickable; } /* public Point getCenterPoint() { //TODO get centerPoint ffrom all childs and integrate? float x=0,y=0,z=0; x+=this.getCenterPoint().getX(); y+=this.getCenterPoint().getY(); z+=this.getCenterPoint().getZ(); TComponent[] childs = this.getChildren(); for (int i = 0; i < childs.length; i++) { TComponent child = childs[i]; x+=child.getCenterPoint().getX(); y+=child.getCenterPoint().getY(); z+=child.getCenterPoint().getZ(); } //GO TRHOUGH ALL CHILDS return null; } protected Point getComponentCenterPoint() { //get centerPoint ffrom all childs and integrate? // Auto-generated method stub return null; } */ /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#containsPointGlobal(org.mt4j.util.math.Vector3D) */ public boolean containsPointGlobal(Vector3D testPoint) { // if (this.componentContainsPointLocal(testPoint)) // return true; // for (int i = childComponents.size()-1; i >= 0; i--) { // MTComponent component = childComponents.get(i); // if (component.containsPointLocal(testPoint)) // return true; // } // return false; if (this.componentContainsPointLocal(this.globalToLocal(testPoint))) return true; for (int i = childComponents.size()-1; i >= 0; i--) { if (childComponents.get(i).containsPointGlobal(testPoint)) return true; } return false; } /** * Checks whether the specified point is contained in this component. * This method gets called from the componentContainsPointGlobal method. * So in a extending class we would override the componentContainsPointLocal * with our intersection code only! * * @param testPoint the test point * * @return true, if successful */ protected boolean componentContainsPointLocal(Vector3D testPoint) { //TODO rename containPointLocal // return this.containsPointBoundsLocal(testPoint); if (this.hasBounds()){ // System.out.println("\"" + this.getName() + "\": -> BOUNDS only check"); return this.getBounds().containsPointLocal(testPoint); }else{ return false; } } // /** // * Contains point bounds local. // * // * @param testPoint the test point in local coordiantes // * @return true, if successful // */ // protected boolean containsPointBoundsLocal(Vector3D testPoint){ // if (this.isBoundingShapeSet()){ //// System.out.println("\"" + this.getName() + "\": -> BOUNDS only check"); // return this.getBoundingShape().containsPointLocal(testPoint); // }else{ // return false; // } // } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent3D#getIntersectionGlobal(org.mt4j.util.math.Ray) */ public Vector3D getIntersectionGlobal(Ray ray) { float currentDistance = Float.MAX_VALUE; //high value so that the first time a object is found this distance is exchanged with his float objDistance = 0; Vector3D returnPoint = null; Vector3D interSP = null; if (this.isVisible() && this.isPickable()) { //Get the real ray for this obj, takes the custom camera and viewport of this obj into account //-> changes rayStartPoint and point in ray direction if (this.getAttachedCamera() != null){ ray = getChangedCameraPickRay(this.getRenderer(), this, ray); } //Transforms the ray into local object space Ray invertedRay = this.globalToLocal(ray); //Check if component is clipped and only proceed if the ray intersects the clip shape Clip clip = this.getClip(); if (clip == null || (clip != null && clip.getClipShapeIntersectionLocal(invertedRay) != null)){ interSP = this.getIntersectionLocal(invertedRay); if (interSP != null){ //FIXME TRIAL - muss f�r die distance messung der world ray genommen //werden oder geht der invertierte ray? interSP.transform(this.getGlobalMatrix()); //Get distance from raystart to the intersecting point objDistance = interSP.getSubtracted(ray.getRayStartPoint()).length(); //If the distance is the smalles yet = closest to the raystart replace the returnObject and current distanceFrom if ((objDistance - PickResult.HIT_TOLERANCE) < currentDistance ){ returnPoint = interSP; currentDistance = objDistance; } } } //Check for child clip intersection, if not intersecting, dont try to pick children Clip childClip = this.getChildClip(); if (childClip != null && childClip.getClipShapeIntersectionLocal(invertedRay) == null){ return returnPoint; } } /* Go through all Children */ // for (int i = childComponents.size()-1; i >= 0; i--) { for (int i = 0; i < childComponents.size(); i++) { MTComponent child = childComponents.get(i); //Get the intersectionpoint ray/object if there is one interSP = child.getIntersectionGlobal(ray); if (interSP != null ){ //if ray intersects object at a point //System.out.println("Intersection at: " + interSP); //Get distance from raystart to the intersecting point objDistance = interSP.getSubtracted(ray.getRayStartPoint()).length(); //If the distance is the smalles yet = closest to the raystart replace the returnObject and current distanceFrom if (objDistance < currentDistance ){ returnPoint = interSP; currentDistance = objDistance; } }//if intersection!=null }// for return returnPoint; } /** * Returns the intersection point of the ray and this component (children are not checked for * intersections). Usually, if the component has a bounding shape assigned, the bounds are checked * for an intersection. Shapes may also check the shape itself for intersection. * <br>The ray is assumed to already be in local component space (not in global space). * <br>If the component is not intersected, null is returned. * * @param localRay the rays, in local space * @return the component local intersection point * @see #globalToLocal */ public Vector3D getIntersectionLocal(Ray localRay) { // return this.getBoundsIntersectionLocal(localRay);//FIXME TEST if (this.hasBounds()){ return this.getBounds().getIntersectionLocal(localRay); }else{ return null; } } // /** // * Gets the bounds intersection local. Test if the ray in local coordinates // * intersection this component's bounding shape. // * Return the local intersection point or null if there is no intersection or no bounding shape // * is set. // * // * @param localRay the local ray // * @return the local intersection point // */ // protected Vector3D getBoundsIntersectionLocal(Ray localRay){//FIXME TEST // if (this.isBoundingShapeSet()){ // return this.getBoundingShape().getIntersectionLocal(localRay); // }else{ // return null; // } // } // /** // * Sometimes the wrong obj gets picked if they are on the same plane but with different inverted rays.. // * probably math rounding off errors with floats etc. (at inverting the ray?) // * <br>This makes sure, objs which are checked later for a hit, // * (and are probably drawn ontop of the previous ones because drawn later), // * are picked more likely. // * <br>Still this is kind of a hack // */ // private static final float HIT_TOLERANCE = 0.3f; //0.03f; //FIXME reset to old value!? // /** // * This method allows to pick (Select) an object in the scene. // * // * // * @param pickInfo the pick info // * // * @return the pick result // */ // public PickResult pick(PickInfo pickInfo){ // PickResult pickResult = new PickResult(); // this.pickRecursive(pickInfo, pickResult, Float.MAX_VALUE, pickInfo.getPickRay()); // return pickResult; // } /** * Checks which object lies under the specified screen coordinates. * The the results are stored in the returned PickResult object. This component and * its children will be checked. * * @param x the x * @param y the y * * @return the pick result */ public PickResult pick(float x, float y){ PickResult pickResult = new PickResult(); PickInfo pickInfo = new PickInfo(x,y, Tools3D.getCameraPickRay(this.getRenderer(), this, x, y)); this.pickRecursive(pickInfo, pickResult, Float.MAX_VALUE, pickInfo.getPickRay(), true); // pickResult.printList(); return pickResult; } /** * Gets the intersection of this component with the input cursor. * * @param cursor the cursor * @return the intersection global or null if no intersection */ public Vector3D getIntersectionGlobal(InputCursor cursor){ return this.getIntersectionGlobal(Tools3D.getCameraPickRay(getRenderer(), this, cursor)); } /** * Checks which object lies under the specified screen coordinates. * The the results are stored in the returned PickResult object. This component and * its children will be checked. * * @param x the x * @param y the y * @param onlyPickables check the only pickable components * * @return the pick result */ public PickResult pick(float x, float y, boolean onlyPickables){ PickResult pickResult = new PickResult(); PickInfo pickInfo = new PickInfo(x,y, Tools3D.getCameraPickRay(this.getRenderer(), this, x, y)); this.pickRecursive(pickInfo, pickResult, Float.MAX_VALUE, pickInfo.getPickRay(), onlyPickables); // pickResult.printList(); return pickResult; } //FIXME currObjDist now in pickresult vistor, so parameter is obsolete //could make currObjDistance return parameter .. /** * Pick closest comp with ray recursive. * * @param pickInfo the pick info * @param pickResult the pick result * @param currObjDist the curr obj dist * @param currentRay the current ray * @param onlyPickables the only pickables * @return the float */ private float pickRecursive(PickInfo pickInfo, PickResult pickResult, float currObjDist, Ray currentRay, boolean onlyPickables){ Vector3D interSP = null; float objDistance = 0; //TEST, Wenns probleme gibt das wieder aktivieren // currObjDist = pickResult.getDistanceNearestPickObj(); // System.out.println("At: " + this.getName() + " Current Distance: " + currObjDist); if (this.isVisible() && ((onlyPickables && this.isPickable()) || !onlyPickables) ){ //Get the real ray for this obj, takes the viewing camera and viewport of this obj into account //-> changes rayStartPoint and point in ray direction if (this.getAttachedCamera() != null){ currentRay = getChangedCameraPickRay(this.getRenderer(), this, pickInfo); } Ray invertedRay; if (this.getGlobalInverseMatrix().isIdentity()){ invertedRay = currentRay; }else{ invertedRay = this.globalToLocal(currentRay); } /* //FIXME REMOVE!!!!! //This adds lines indicating the world ray and the local object ray used for ray-test MTLine l1 = new MTLine(this.getRenderer(), new Vertex(currentRay.getRayStartPoint()), new Vertex(currentRay.getPointInRayDirection())); this.getAncestor().addChild(l1); MTLine l2 = new MTLine(this.getRenderer(), new Vertex(invertedRay.getRayStartPoint()), new Vertex(invertedRay.getPointInRayDirection())); l2.setStrokeColor(255, 10, 10, 255); this.getAncestor().addChild(l2); */ //Check if component is clipped and only proceed if the ray intersects the clip shape Clip clip = this.getClip(); if (clip == null || (clip != null && clip.getClipShapeIntersectionLocal(invertedRay) != null)){ interSP = this.getIntersectionLocal(invertedRay); if (interSP != null){ //FIXME TRIAL - muss f�r die distance messung der world ray genommen //werden oder geht der invertierte ray? -> musss wohl der world ray sein interSP.transform(this.getGlobalMatrix()); // Get distance from raystart to the intersecting point objDistance = interSP.getSubtracted(currentRay.getRayStartPoint()).length(); //System.out.println("Pick found: " + this.getName() + " InterSP: " + interSP + " ObjDist: " + objDistance + " Mouse Pos: " + pickInfo.getScreenXCoordinate() + "," + pickInfo.getScreenYCoordinate() + " InvRay RS:" + invertedRay.getRayStartPoint() + ",RE: " + invertedRay.getPointInRayDirection()); // //If the distance is the smallest yet = closest to the raystart: replace the returnObject and current distanceFrom // if ( (objDistance - HIT_TOLERANCE) <= currObjDist /*|| this.isAlwaysDrawnOnTop()*/){//take isDrawnOnTop into account here?? -> OBJDistance auf 0 setzen? // currObjDist = objDistance; // pickResult.addPickedObject(this, interSP, objDistance); //// System.out.println("-> Now nearest: " + this.getName()); // } //FIXME TEST - ADD ALL PICKED OBJECTS - SORT LATER pickResult.addPickedObject(this, interSP, objDistance); } } //Check for child clipping shape intersection, if not intersecting -> dont try to pick children Clip childClip = this.getChildClip(); if (childClip != null && childClip.getClipShapeIntersectionLocal(invertedRay) == null){ return currObjDist; } } /* recursively check all children now */ for (int i = 0; i < childComponents.size(); i++) { MTComponent child = childComponents.get(i); if (child.isVisible()) { if (composite){ //Start a new picking with a new Pickresult obj from here PickResult compositePickRes = new PickResult(); float compDistance = child.pickRecursive(pickInfo, compositePickRes, Float.MAX_VALUE, currentRay, onlyPickables); //Add the composites picks to the overall picks if (compositePickRes.getNearestPickResult() != null){ // System.out.println("In: " + this.getName() + " Composites child picked, pick resultDistance: " + compDistance); /*//TODO m�sste diese hier nach distanz geordnet in insgesamt pickresult einf�gen.. ArrayList<MTBaseComponent> pickList = compositePickRes.getPickList(); for(MTBaseComponent comp : pickList){ pickResult.addPickedObject(comp, compositePickRes.getInterSectionPointOfPickedObj(comp), compositePickRes.getDistanceOfPickedObj(comp)); } */ //Add this composite as the last one picked with the distance of the last one picked in the composite pick // pickResult.addPickedObjects(compositePickRes.getPickList()); // pickResult.addPickedObject(this, compositePickRes.getInterSectionPointNearestPickedObj(), compositePickRes.getDistanceNearestPickObj()); // if (//compDistance <= currObjDist // (compDistance - HIT_TOLERANCE) <= currObjDist // ){ //// System.out.println("Composites child picked and now nearest: " + this.getName()+ " dist: " + compDistance); // pickResult.addPickedObject(this, compositePickRes.getInterSectionPointNearestPickedObj(), compositePickRes.getDistanceNearestPickObj()); // currObjDist = compDistance; // } //FIXME TEST - ADD ALL PICKED OBJECTS - SORT LATER PickEntry nearestPickEntry = compositePickRes.getNearestPickEntry(); pickResult.addPickedObject(this, nearestPickEntry.intersectionPoint, nearestPickEntry.cameraDistance); } }else{ currObjDist = child.pickRecursive(pickInfo, pickResult, currObjDist, currentRay, onlyPickables); } } } return currObjDist; } /** * Calculates the "real" picking ray for the object. * <br>If the obj has a custom camera attached to it, this camera's position is the new ray origin and * the point in the ray direction is the unprojected x,y, coordinates while this camera is active. * * @param pa the papplet * @param obj the obj * @param ray the ray * * @return the real pick ray * * the new calculated ray, or the original ray, if the obj has no custom camera attached to it. */ private static Ray getChangedCameraPickRay(PApplet pa, IMTComponent3D obj, Ray ray){ Vector3D pointInRayDirection = ray.getPointInRayDirection(); Vector3D projected = Tools3D.project(pa, obj.getViewingCamera(), pointInRayDirection); // Vector3D projected = Tools3D.project(pa, pointInRayDirection); return getChangedCameraPickRay(pa, obj, new PickInfo( projected.x, projected.y, ray)); } /** * Calculates the "real" pickray for the object. * <br>If the obj has a custom camera attached to it, this cameras position is the new ray origin and * the point in the ray direction is the unprojected x,y, coordinates while this camera is active. * * @param pa the pa * @param obj the obj * @param pickInfo the pick info * @return the real pick ray * * the new calculated ray, or the original ray, if the obj has no custom camera attached to it. */ private static Ray getChangedCameraPickRay(PApplet pa, IMTComponent3D obj, PickInfo pickInfo){ if (obj.getViewingCamera() != null){ //FIXME TEST //Re-Project unprojected world coords to projected viewport screen coords (Tuio INput) float x = pickInfo.getScreenXCoordinate(); float y = pickInfo.getScreenYCoordinate(); return Tools3D.getCameraPickRay(pa, obj, x, y); }else{ return pickInfo.getPickRay(); } /*//FIXME disabled for performance for now! if (obj.hasCustomViewPort()){ //Take VIEWPORT changes into account, too ViewportSetting customViewPort = obj.getCustomViewportSetting(); ViewportSetting defaultViewPortSetting = obj.getDefaultViewportSetting(); rayStartPoint.setX(customViewPort.getStartX() + (rayStartPoint.getX() * (customViewPort.getWidth()/defaultViewPortSetting.getWidth()))); rayStartPoint.setY(customViewPort.getStartY() + (rayStartPoint.getY() * (customViewPort.getHeight()/defaultViewPortSetting.getHeight()))); pointInRayDirection.setX(customViewPort.getStartX() + (pointInRayDirection.getX() * (customViewPort.getWidth()/defaultViewPortSetting.getWidth()))); pointInRayDirection.setY(customViewPort.getStartY() + (pointInRayDirection.getY() * (customViewPort.getHeight()/defaultViewPortSetting.getHeight()))); ///// } */ // return new Ray(rayStartPoint, pointInRayDirection); } // /* (non-Javadoc) // * @see org.mt4j.components.interfaces.IMTComponent3D#getDefaultViewportSetting() // */ // public ViewportSetting getDefaultViewportSetting(){ // return this.defaultViewPortSetting; // } // // //TODO make function 2Dshift3DObj? das dann viewport �ndert? // /* (non-Javadoc) // * @see org.mt4j.components.interfaces.IMTComponent3D#getCustomViewportSetting() // */ // public ViewportSetting getCustomViewportSetting() { // return customViewPort; // } // // //FIXME funktioniert das so, dass man in subclass das erzeugen und setzen kann? // /** // * Sets the view port settings. // * // * @param viewPortSettings the new view port settings // */ // public void setViewPortSettings(ViewportSetting viewPortSettings) { // this.customViewPort = viewPortSettings; // } // // /* (non-Javadoc) // * @see org.mt4j.components.interfaces.IMTComponent3D#hasCustomViewPort() // */ // public boolean hasCustomViewPort(){ // return this.customViewPort != null; // } //TODO bubble events up and down hierarchy? //@Override /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#processInputEvent(org.mt4j.input.inputData.MTInputEvent) */ public boolean processInputEvent(MTInputEvent inEvt) { if (this.isEnabled()){ // System.out.println("Comp: " + this.getName() + " Evt: " + inEvt); //TODO do only if not handled maybe? //THIS IS A HACK TO ALLOW Global GESTURE PROCESSORS to send MTGEstureevents TO WORK if (inEvt instanceof MTGestureEvent){ this.processGestureEvent((MTGestureEvent)inEvt); }else{ //Fire the same input event to all of this components' input listeners this.fireInputEvent(inEvt); } return false; }else{ return false; } } /** * Processes gesture events.<br> * Fires the specified gesture event to the attached IGestureEventListers of this component. * * @param gestureEvent the gesture event * @return true, if successful */ public boolean processGestureEvent(MTGestureEvent gestureEvent){ this.gestureEvtSupport.fireGestureEvt(gestureEvent); // System.out.println("processGestureEvent on obj: " + this.getName() + " gestureEvent source: " + gestureEvent.getSource() +" ID: " + gestureEvent.getId()); return false; } /** * Checks if is composite. * @return true, if is composite */ public boolean isComposite() { return composite; } /** * Setting a components <code>setComposite</code> to 'true' will result in * THIS component getting picked and returned * when a child of this component is picked. So this component sort of consumes all picking * of its children. * This behaviour is desireable if we have a component with children that should be treated * as one component as a whole by gestures etc. * * @param composite the composite */ public void setComposite(boolean composite) { this.composite = composite; } /** * Gets the controller. * @return the controller */ public IMTController getController() { return controller; } /** * This attaches a controller object to the component. * The controller's update method is called everytime the component's * updateComponent method is called. This allows to update the component from the outside, * without extending the component and overriding its update method. * * @param controller the controller * * @return the old IMT controller (may be null) */ public IMTController setController(IMTController controller) { IMTController oldController = this.controller; this.controller = controller; return oldController; } /* (non-Javadoc) * @see org.mt4j.components.interfaces.IMTComponent#isGestureAllowed(java.lang.Class) */ public boolean isGestureAllowed(Class<? extends IInputProcessor> c){ return this.allowedGestures.contains(c); } /** * Sets the gesture allowance. * @param c the gesture processors class * @param allowed allowance */ public void setGestureAllowance(Class<? extends IInputProcessor> c, boolean allowed){ if (allowed){ if (!this.allowedGestures.contains(c)){ this.allowedGestures.add(c); } }else{ if (this.allowedGestures.contains(c)){ this.allowedGestures.remove(c); } } } //TODO put in Interface? /** * Checks if this component is contained in the specified viewing frustum (is currently visible). * @param frustum the frustum * * @return true, if is contained in */ public boolean isContainedIn(IFrustum frustum){ //Check if bounds are contained in the frustum //if shape has no boundingshape return true by default if (this.hasBounds()){ return this.getBounds().isContainedInFrustum(frustum); }else{ return true; } } /** * Re orthogonalizes the components local matrix. As we use incremental matrix operations, floating * point errors can add up and lead to loss of precision and matrix orthogonality. This method * re-orthogonalizes the matrix. * <br>- EXPERIMENTAL! - I think that this will destroy any shearing of the matrix * <br>- EXPENSIVE OPERATION! */ public void reOrthogonalize(){ // Matrix local = this.getLocalMatrix(); // Vector3D trans = new Vector3D(); // Vector3D rot = new Vector3D(); // Vector3D scale = new Vector3D(); // local.decompose(trans, rot, scale); Vector3D scale = this.getLocalMatrix().getScale(); // System.out.println("Det b4: " + getLocalMatrix().determinant()); // Vector3D v1 = new Vector3D(2,3,4); // Vector3D v2 = new Vector3D(v1); // v1.transform(this.getLocalMatrix()); Matrix m = new Matrix(this.getLocalMatrix()); // m.orthonormalizeLocal(); //can we use 3x3 otrhogonalization on a 4x4 matrix just using the middle part? //-seems yes m.orthonormalizeUpperLeft(); //Re-Apply scale because its removed at orthonormalization // m.mult(Matrix.getScalingMatrix(Vector3D.ZERO_VECTOR, scale.x, scale.y, scale.z), m); m.scale(scale); //Automatically inverts() the localMatrix, so exact inverse again! :) this.setLocalMatrix(m); // v2.transform(this.getLocalMatrix()); // System.out.println("Diff: " + v2.getSubtracted(v1)); // logger.debug("Determinant after orthogonalize: " + getLocalMatrix().determinant()); } /** * Sets user data for this component. This mechanism can be * used to attach arbitrary information to this component by storing * a key and a corresponding value object. The value object can then later * be retrieved if the key is provided. * * @param key the key * @param value the value * @see #getUserData */ public void setUserData(Object key, Object value){ if (userData == null){ //lazily initialize map // userData = new WeakHashMap<Object, Object>(); //use weak map? userData = new HashMap<Object, Object>(); } userData.put(key, value); } /** * Gets the user data associated with the specified key. * @param key the key * * @return the user data */ public Object getUserData(Object key){ if (userData == null){ return null; } return userData.get(key); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "\"" + this.getName() + "\"" + " [" + super.toString() + "]"; } }