/*********************************************************************** * mt4j Copyright (c) 2008 - 2009 Christopher 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.visibleComponents.widgets; import org.mt4j.components.MTComponent; import org.mt4j.components.TransformSpace; import org.mt4j.components.visibleComponents.shapes.AbstractShape; import org.mt4j.input.gestureAction.DefaultDragAction; import org.mt4j.input.gestureAction.DefaultRotateAction; import org.mt4j.input.gestureAction.DefaultScaleAction; import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor; import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor; import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor; import org.mt4j.util.math.Matrix; import org.mt4j.util.math.Vector3D; import org.mt4j.util.math.Vertex; import org.mt4j.util.xml.svg.SVGLoader; import processing.core.PApplet; /** * The Class MTSvg. Loads and displays scalable vector graphics (SVG) files. * * @author Christopher Ruff */ public class MTSvg extends MTComponent { /** The width vect. */ private Vector3D widthVect; /** The height vect. */ private Vector3D heightVect; /** The width. */ private float width; /** The height. */ private float height; /** The center point local. */ private Vector3D centerPointLocal; //TODO we could extens mtpolygon and set the polygon points to be the svg's bounding rectangle /** * Instantiates a new mT svg. * * @param applet the applet * @param fileName the file name */ public MTSvg(PApplet applet, String fileName) { super(applet); this.setName("SVG: " + fileName); this.setComposite(true); this.registerInputProcessor(new DragProcessor(applet)); this.addGestureListener(DragProcessor.class, new DefaultDragAction()); this.registerInputProcessor(new RotateProcessor(applet)); this.addGestureListener(RotateProcessor.class, new DefaultRotateAction()); this.registerInputProcessor(new ScaleProcessor(applet)); this.addGestureListener(ScaleProcessor.class, new DefaultScaleAction()); SVGLoader loader = new SVGLoader(applet); MTComponent svg = loader.loadSvg(fileName); this.addChild(svg); //so lange MTSvg noch nicht an einem vater h�ngt und transform=identity, ist die world width die //parent relative width (und local width?) //wenn added to parent -> parentrelative width vect * globalMatrix // Tools3D.getMinXYMaxXY(Vector3DLists) //Float.MAX_VALUE[0]minX, Float.MAX_VALUE[1]minY, Float.MIN_VALUE[2]maxX, Float.MIN_VALUE[3]maxY float[] bounds = calcBounds(this, new float[]{Float.MAX_VALUE, Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_VALUE}); // System.out.println("SVG Bounds -> minX: " + bounds[0] + " minY: " + bounds[2] + " maxX: " + bounds[1] + " maxY: " + bounds[3]); float minX = bounds[0]; float minY = bounds[2]; float maxX = bounds[1]; float maxY = bounds[3]; Vector3D upperLeft = new Vector3D(minX, minY, 0); Vector3D upperRight = new Vector3D(maxX, minY, 0); Vector3D lowerRight = new Vector3D(maxX, maxY, 0); this.widthVect = upperRight.getSubtracted(upperLeft); this.heightVect = lowerRight.getSubtracted(upperRight); this.width = maxX-minX; this.height = maxY-minY; this.centerPointLocal = new Vector3D( upperLeft.x + ((upperRight.x - upperLeft.x)/2f), upperRight.y + ((lowerRight.y - upperRight.y)/2f), 0); //System.out.println("Center local " + centerPointLocal); // this.addStateChangeListener(StateChange.ADDED_TO_PARENT, new StateChangeListener(){ // @Override // public void stateChanged(StateChangeEvent evt) { // Vector3D widthCopy = widthVect.getCopy(); // // } // }); //Draw this component and its children above //everything previously drawn and avoid z-fighting this.setDepthBufferDisabled(true); } /** * Calc bounds. * * @param currentComp the current comp * @param bounds the bounds * * @return the float[] */ private float[] calcBounds(MTComponent currentComp, float[] bounds){ if (currentComp instanceof AbstractShape){ AbstractShape shape = (AbstractShape)currentComp; Vertex[] globalVecs = shape.getVerticesGlobal(); for (int j = 0; j < globalVecs.length; j++) { Vertex v = globalVecs[j]; if (v.x < bounds[0]) bounds[0] = v.x; if (v.x > bounds[1]) bounds[1] = v.x; if (v.y < bounds[2]) bounds[2] = v.y; if (v.y > bounds[3]) bounds[3] = v.y; } } for(MTComponent child : currentComp.getChildren()){ bounds = calcBounds(child, bounds); } return bounds; } // public float getWidthXY(TransformSpace transformSpace) { // switch (transformSpace) { // case LOCAL: // return this.getWidthXYLocal(); // case RELATIVE_TO_PARENT: // return this.getWidthXYRelativeToParent(); // case GLOBAL: // return this.getWidthXYGlobal(); // default: // return -1; // } // } /** * Calculates the width of this shape, by using its * bounding rect. * Uses the objects local transform. So the width will be * relative to the parent only - not the whole world * * @return the width xy relative to parent * * the width */ public float getWidthXYRelativeToParent() { Vector3D p = this.widthVect.getCopy(); Matrix m = new Matrix(this.getLocalMatrix()); m.removeTranslationFromMatrix(); p.transform(m); return p.length(); } /** * Gets the "Width vector" and transforms it to world space, then calculates * its length. * * @return the width xy global * * the Width relative to the world space */ public float getWidthXYGlobal() { Vector3D p = this.widthVect.getCopy(); Matrix m = new Matrix(this.getGlobalMatrix()); m.removeTranslationFromMatrix(); p.transform(m); return p.length(); } // public float getHeightXY(TransformSpace transformSpace) { // switch (transformSpace) { // case LOCAL: // return this.getHeightXYLocal(); // case RELATIVE_TO_PARENT: // return this.getHeightXYRelativeToParent(); // case GLOBAL: // return this.getHeightXYGlobal(); // default: // return -1; // } // } /** * Gets the "height vector" and transforms it to parent relative space, then calculates * its length. * * @return the height xy relative to parent * * the height relative to its parent space frame */ public float getHeightXYRelativeToParent() { Vector3D p = this.heightVect.getCopy(); Matrix m = new Matrix(this.getLocalMatrix()); m.removeTranslationFromMatrix(); p.transform(m); return p.length(); } /** * Gets the "height vector" and transforms it to world space, then calculates * its length. * * @return the height xy global * * the height relative to the world space */ public float getHeightXYGlobal() { Vector3D p = this.heightVect.getCopy(); Matrix m = new Matrix(this.getGlobalMatrix()); m.removeTranslationFromMatrix(); p.transform(m); return p.length(); } /** * Scales the shape to the given height relative to parent space. * Aspect ratio is preserved! The scaling is done Axis aligned, so * shearing might occour if rotated! * <br>Uses the shapes bounding shape for calculation. * * @param height the height * * @return true, if the height isnt negative */ public boolean setHeightXYRelativeToParent(float height){ if (height > 0){ Vector3D centerPoint = this.getCenterPointGlobal(); this.scale(1/this.getHeightXYRelativeToParent(), 1/this.getHeightXYRelativeToParent(), 1, centerPoint); this.scale(height, height, 1, centerPoint); return true; }else return false; } /** * Scales the shape to the given height relative to world space. * Aspect ratio is preserved! The scaling is done Axis aligned, so * shearing might occour if rotated! * <br>Uses the shapes bounding shape for calculation. * * @param height the height * * @return true, if sets the height xy global */ public boolean setHeightXYGlobal(float height){ if (height > 0){ Vector3D centerPoint = this.getCenterPointGlobal(); this.scaleGlobal(1/this.getHeightXYGlobal(), 1/this.getHeightXYGlobal(), 1, centerPoint); this.scaleGlobal(height, height, 1, centerPoint); return true; }else return false; } /** * Scales the shape to the given width relative to parent space. * Aspect ratio is preserved! * <br>NOTE: The scaling is done Axis aligned, so * shearing might occour if rotated before! * <br>Uses the shapes bounding shape for calculation. * * @param width the width * * @return true, if the width isnt negative */ public boolean setWidthXYRelativeToParent(float width){ if (width > 0){ Vector3D centerPoint = this.getCenterPointGlobal(); this.scale(1/this.getWidthXYRelativeToParent(), 1/this.getWidthXYRelativeToParent(), 1, centerPoint); this.scale(width, width, 1, centerPoint); return true; }else return false; } /** * Scales the shape to the given width relative to world space. * Aspect ratio is preserved! The scaling is done Axis aligned, so * shearing might occour if rotated! * <br>Uses the shapes bounding shape for calculation. * * @param width the width * * @return true, if sets the width xy global */ public boolean setWidthXYGlobal(float width){ if (width > 0){ Vector3D centerPoint = this.getCenterPointGlobal(); this.scaleGlobal(1/this.getWidthXYGlobal(), 1/this.getWidthXYGlobal(), 1, centerPoint); this.scaleGlobal(width, width, 1, centerPoint); return true; }else return false; } /** * Sets the size xy relative to parent. * * @param width the width * @param height the height * * @return true, if successful */ public boolean setSizeXYRelativeToParent(float width, float height){ if (width > 0 && height > 0){ Vector3D centerPoint = this.getCenterPointGlobal(); //FIXME use centerRelToParent? this.scale(1/this.getWidthXYRelativeToParent(), 1/this.getHeightXYRelativeToParent(), 1, centerPoint); this.scale(width, height, 1, centerPoint); return true; }else return false; } /** * Sets the size xy global. * * @param width the width * @param height the height * * @return true, if successful */ public boolean setSizeXYGlobal(float width, float height){ if (width > 0 && height > 0){ Vector3D centerPoint = this.getCenterPointGlobal(); this.scaleGlobal(1/this.getWidthXYGlobal(), 1/this.getHeightXYGlobal(), 1, centerPoint); this.scaleGlobal(width, height, 1, centerPoint); return true; }else return false; } /** * Gets the center point local. * * @return the center point local */ public Vector3D getCenterPointLocal(){ return this.centerPointLocal.getCopy(); } /** * Gets the center point relative to parent. * * @return the center point relative to parent */ public Vector3D getCenterPointRelativeToParent(){ Vector3D c = this.centerPointLocal.getCopy(); c.transform(this.getLocalMatrix()); return c; } /** * Gets the center point global. * * @return the center point global */ public Vector3D getCenterPointGlobal(){ Vector3D c = this.centerPointLocal.getCopy(); c.transform(this.getGlobalMatrix()); return c; } /** * Sets the global position of the component. (In global coordinates) * <br>Note: The center of this component is the reference point, not the upper left corner. * * @param pos the pos */ public void setPositionGlobal(Vector3D pos){ this.translateGlobal(pos.getSubtracted(this.getCenterPointGlobal())); } /** * Sets the position of the component, relative to its parent component. * <br>Note: The center of this component is the reference point, not the upper left corner. * * @param pos the pos */ public void setPositionRelativeToParent(Vector3D pos){ this.translate(pos.getSubtracted(this.getCenterPointRelativeToParent()), TransformSpace.RELATIVE_TO_PARENT); } /** * Returns the component with the largest x,y dimension to use for picking by default. * * @param comp the comp * @param compWithBiggestBoundingRect the comp with biggest bounding rect * @param biggestWidth the biggest width * @param biggestHeight the biggest height * * @return the largest svg comp */ private AbstractShape getLargestSvgComp(MTComponent comp, AbstractShape compWithBiggestBoundingRect, float biggestWidth, float biggestHeight){ //// System.out.println("Checking: " + comp.getName()); if (comp instanceof AbstractShape) { AbstractShape shape = (AbstractShape) comp; float rectWidthGlobal = shape.getWidthXY(TransformSpace.GLOBAL); float rectHeightGlobal = shape.getHeightXY(TransformSpace.GLOBAL); if ( // compWithBiggestBoundingRect != null biggestWidth > 0 && biggestHeight > 0 ){ // System.out.println("Fromer biggest != null, checking if " + comp.getName() + " is bigger."); if ( rectWidthGlobal >= compWithBiggestBoundingRect.getWidthXY(TransformSpace.GLOBAL) || rectHeightGlobal >= compWithBiggestBoundingRect.getHeightXY(TransformSpace.GLOBAL) ){ // System.out.println(comp.getName() + " is bigger!"); compWithBiggestBoundingRect = shape; }else{ // System.out.println(compWithBiggestBoundingRect.getName() + " is still bigger"); } }else{ // System.out.println("Reference is null, take " + comp.getName() + " as the new biggest."); compWithBiggestBoundingRect = shape; } } for(MTComponent child : comp.getChildren()){ compWithBiggestBoundingRect = getLargestSvgComp(child, compWithBiggestBoundingRect, biggestWidth, biggestHeight); } return compWithBiggestBoundingRect; } /* (non-Javadoc) * @see org.mt4j.components.MTComponent#setPickable(boolean) */ @Override public void setPickable(boolean pickable) { super.setPickable(pickable); setPickableRecursive(this, pickable); } /** * Sets the pickable recursive. * * @param current the current * @param pickable the pickable */ private void setPickableRecursive(MTComponent current, boolean pickable){ if (!current.equals(this)) current.setPickable(pickable); MTComponent[] children = current.getChildren(); for (int i = 0; i < children.length; i++) { MTComponent child = children[i]; setPickableRecursive(child, pickable); } } }