X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Futils%2FGeometryUtils.java;h=a3f4ec8bee57b770491b4a7da99a606928cf5fc7;hb=2bd26538bb54ec285c01ba1a170f2486086c96a4;hp=e329c47c0acea3155243505e8029dbfddf71395a;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java index e329c47c0..a3f4ec8be 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/GeometryUtils.java @@ -1,511 +1,511 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management - * in Industry THTH ry. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.scenegraph.utils; - -import java.awt.BasicStroke; -import java.awt.Stroke; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; - -import org.simantics.utils.page.MarginUtils.Margins; - -/** - * Basic utilities for geometric calculations and testing. - * - * @author J-P Laine - * @author Tuukka Lehtonen - */ -public final class GeometryUtils { - - /** - * @param p the point of distance measure - * @param p1 other end of line - * @param p2 other end of line - * @return distance of p from the line defined by p1 and p2 - */ - public static double distanceFromLine(Point2D p, Point2D p1, Point2D p2) { - return distanceFromLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p.getX(), p.getY()); - } - - /** - * @param x1 line segment end 1 x - * @param y1 line segment end 1 y - * @param x2 line segment end 2 x - * @param y2 line segment end 2 y - * @param px point x - * @param py point y - * @return - */ - public static double distanceFromLine(double x1, double y1, double x2, double y2, double px, double py) { - // Adjust vectors relative to x1,y1 - // x2,y2 becomes relative vector from x1,y1 to end of segment - x2 -= x1; - y2 -= y1; - // px,py becomes relative vector from x1,y1 to test point - px -= x1; - py -= y1; - double dotprod = px * x2 + py * y2; - // dotprod is the length of the px,py vector - // projected on the x1,y1=>x2,y2 vector times the - // length of the x1,y1=>x2,y2 vector - double lineLenSq = x2 * x2 + y2 * y2; - double lineLen = Math.sqrt(lineLenSq); - double projLen = dotprod / lineLen; - - // Check whether the projection of (px,py) is outside the specified line. - if (projLen < 0) { - return Math.sqrt(px * px + py * py); - } else if (projLen > lineLen) { - double dx = px - x2; - double dy = py - y2; - return Math.sqrt(dx * dx + dy * dy); - } - return Math.sqrt(px * px + py * py - projLen * projLen); - } - - /** - * @param x1 line segment end 1 x - * @param y1 line segment end 1 y - * @param x2 line segment end 2 x - * @param y2 line segment end 2 y - * @param px point x - * @param py point y - * @return - */ - public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) { - double xx2 = x2; - double yy2 = y2; - // Adjust vectors relative to x1,y1 - // x2,y2 becomes relative vector from x1,y1 to end of segment - x2 -= x1; - y2 -= y1; - // px,py becomes relative vector from x1,y1 to test point - px -= x1; - py -= y1; - double dotprod = px * x2 + py * y2; - // dotprod is the length of the px,py vector - // projected on the x1,y1=>x2,y2 vector times the - // length of the x1,y1=>x2,y2 vector - double lineLenSq = x2 * x2 + y2 * y2; - double lineLen = Math.sqrt(lineLenSq); - if (lineLen <= Double.MIN_VALUE) - return new Point2D.Double(x1, y1); - - double projLen = dotprod / lineLen; - - // Check whether the projection of (px,py) is outside the specified line. - if (projLen < 0) { - return new Point2D.Double(x1, y1); - } else if (projLen > lineLen) { - return new Point2D.Double(xx2, yy2); - } - return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen); - } - - /** - * Expands margins to a rectangle - * @param rect - * @param top - * @param bottom - * @param left - * @param right - */ - public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right) - { - if (rect==null) throw new IllegalArgumentException("null arg"); - rect.setRect( - rect.getX() - left, - rect.getY() - top, - rect.getWidth() + left + right, - rect.getHeight() + top + bottom); - return rect; - } - - public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) { - return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand); - } - - public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) { - return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand); - } - - public static BasicStroke scaleStroke(Stroke stroke, float factor) - { - return scaleAndOffsetStroke(stroke, factor, 0.0f); - } - - public static BasicStroke offsetStroke(Stroke stroke, float offset) - { - BasicStroke s = (BasicStroke) stroke; - float[] dash = s.getDashArray(); - if (dash == null) - return s; - - return new BasicStroke( - s.getLineWidth(), - s.getEndCap(), - s.getLineJoin(), - s.getMiterLimit(), - dash, - s.getDashPhase() + offset - ); - } - - public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset) - { - BasicStroke s = (BasicStroke) stroke; - float[] dash = s.getDashArray(); - if (dash!=null) { - assert(factor!=0); - dash = scaleArray(factor, dash, new float[dash.length]); - } - if (dash==null) - return new BasicStroke( - s.getLineWidth() * factor, - s.getEndCap(), - s.getLineJoin(), - s.getMiterLimit() - ); - return new BasicStroke( - s.getLineWidth() * factor, - s.getEndCap(), - s.getLineJoin(), - s.getMiterLimit(), - dash, - s.getDashPhase() * factor + offset - ); - } - - public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor) - { - BasicStroke s = (BasicStroke) stroke; - return new BasicStroke( - s.getLineWidth() * factor, - s.getEndCap(), - s.getLineJoin(), - s.getMiterLimit(), - s.getDashArray(), - s.getDashPhase() - ); - } - - public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset) - { - BasicStroke s = (BasicStroke) stroke; - return new BasicStroke( - s.getLineWidth() * factor + widthOffset, - s.getEndCap(), - s.getLineJoin(), - s.getMiterLimit(), - s.getDashArray(), - s.getDashPhase() - ); - } - - /** - * Scales every element in array - * @param array - * @param factor - * @return new scaled array - */ - public static float[] scaleArray(float factor, float [] array, float [] targetArray) - { - assert(array!=null); - if (targetArray==null) - targetArray = new float[array.length]; - for (int i=0; i= 0.0) { - /* - * trace/2 +- sqrt(trace^2 / 4 - determinant) - * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b) - * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b) - * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4) - * = (a+b)/2 +- (a-b)/2 - * = a or b - * - * Thus the formula below calculates the greatest - * absolute value of the eigenvalues max(abs(a), abs(b)) - */ - return Math.abs(trace*0.5) + Math.sqrt(dd); - } - else { - /* - * If dd < 0, then the eigenvalues a and b are not real. - * Because both trace and determinant are real, a and b - * have form: - * a = x + i y - * b = x - i y - * - * Then - * sqrt(determinant) - * = sqrt(a b) - * = sqrt(x^2 + y^2), - * which is the absolute value of the eigenvalues. - */ - return Math.sqrt(determinant); - } - } - - /** - * Get a transform that makes canvas area fully visible. - * @param controlArea - * @param diagramArea - * @param margins margins - * @return transform - */ - public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea) - { - double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight(); - double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight(); - // Control is really wide => center canvas horizontally, match vertically - double scale = 1.0; - double tx = 0.0; - double ty = 0.0; - if (controlAspectRatio>canvasAspectRatio) - { - scale = controlArea.getHeight() / diagramArea.getHeight(); - tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2; - } else - // Control is really tall => center canvas vertically, match horizontally - { - scale = controlArea.getWidth() / diagramArea.getWidth(); - ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2; - } - AffineTransform at = new AffineTransform(); - at.translate(tx, ty); - at.translate(controlArea.getMinX(), controlArea.getMinY()); - at.scale(scale, scale); - at.translate(-diagramArea.getMinX(), -diagramArea.getMinY()); - //System.out.println("FIT TRANSFORM: " + at); - return at; - } - - /** - * Rotates rectangle. Note, general rotation is not supported. - * @param transform - * @param rect - * @return transformed rectangle - */ - public static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) { - int type = transform.getType(); - if (type == AffineTransform.TYPE_IDENTITY) - return new Rectangle2D.Double(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight()); - if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) { - double x0 = rect.getMinX(); - double y0 = rect.getMinY(); - double x1 = rect.getMaxX(); - double y1 = rect.getMaxY(); - double m00 = transform.getScaleX(); - double m10 = transform.getShearY(); - double m01 = transform.getShearX(); - double m11 = transform.getScaleY(); - double X0, Y0, X1, Y1; - if(m00 > 0.0) { - X0 = m00 * x0; - X1 = m00 * x1; - } - else { - X1 = m00 * x0; - X0 = m00 * x1; - } - if(m01 > 0.0) { - X0 += m01 * y0; - X1 += m01 * y1; - } - else { - X1 += m01 * y0; - X0 += m01 * y1; - } - if(m10 > 0.0) { - Y0 = m10 * x0; - Y1 = m10 * x1; - } - else { - Y1 = m10 * x0; - Y0 = m10 * x1; - } - if(m11 > 0.0) { - Y0 += m11 * y0; - Y1 += m11 * y1; - } - else { - Y1 += m11 * y0; - Y0 += m11 * y1; - } - return new Rectangle2D.Double(X0+transform.getTranslateX(), - Y0+transform.getTranslateY(), X1-X0, Y1-Y0); - } - - // Generic but much slower. - return transform.createTransformedShape(rect).getBounds2D(); - } - - public static Rectangle2D transformRectangleInv(AffineTransform transform, Rectangle2D rect) { - assert( (transform.getType() & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0); - double[] mx = new double[6]; - transform.getMatrix(mx); - double x0 = rect.getMinX() - mx[4]; - double y0 = rect.getMinY() - mx[5]; - double x1 = rect.getMaxX() - mx[4]; - double y1 = rect.getMaxY() - mx[5]; - double det = mx[0]*mx[3] - mx[1]*mx[2]; - double m00 = mx[3] / det; - double m10 = -mx[1] / det; - double m01 = -mx[2] / det; - double m11 = mx[0] / det; - double X0, Y0, X1, Y1; - if(m00 > 0.0) { - X0 = m00 * x0; - X1 = m00 * x1; - } - else { - X1 = m00 * x0; - X0 = m00 * x1; - } - if(m01 > 0.0) { - X0 += m01 * y0; - X1 += m01 * y1; - } - else { - X1 += m01 * y0; - X0 += m01 * y1; - } - if(m10 > 0.0) { - Y0 = m10 * x0; - Y1 = m10 * x1; - } - else { - Y1 = m10 * x0; - Y0 = m10 * x1; - } - if(m11 > 0.0) { - Y0 += m11 * y0; - Y1 += m11 * y1; - } - else { - Y1 += m11 * y0; - Y0 += m11 * y1; - } - return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0); - } - - /** - * @param points - * @return millimeters - */ - public static float pointToMillimeter(float points) { - return points * 25.4f / 72.0f; - } - - /** - * @param points - * @return millimeters - */ - public static double pointToMillimeter(double points) { - return points * 25.4 / 72.0; - } - - /** - * Get a transform that makes canvas area fully visible. - * @param controlArea - * @param diagramArea - * @param margins margins - * @return transform - */ - public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins) - { - diagramArea = expandRectangle(diagramArea, - margins.top.diagramAbsolute, - margins.bottom.diagramAbsolute, - margins.left.diagramAbsolute, - margins.right.diagramAbsolute); - controlArea = expandRectangle(controlArea, - -margins.top.controlAbsolute - margins.top.controlRelative * controlArea.getHeight(), - -margins.bottom.controlAbsolute - margins.bottom.controlRelative * controlArea.getHeight(), - -margins.left.controlAbsolute - margins.left.controlRelative * controlArea.getWidth(), - -margins.right.controlAbsolute - margins.right.controlRelative * controlArea.getWidth()); - - double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight(); - double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight(); - // Control is really wide => center canvas horizontally, match vertically - double scale = 1.0; - double tx = 0.0; - double ty = 0.0; - if (controlAspectRatio>canvasAspectRatio) - { - scale = controlArea.getHeight() / diagramArea.getHeight(); - tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2; - } else - // Control is really tall => center canvas vertically, match horizontally - { - scale = controlArea.getWidth() / diagramArea.getWidth(); - ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2; - } - AffineTransform at = new AffineTransform(); - at.translate(tx, ty); - at.translate(controlArea.getMinX(), controlArea.getMinY()); - at.scale(scale, scale); - at.translate(-diagramArea.getMinX(), -diagramArea.getMinY()); - //System.out.println("FIT TRANSFORM: " + at); - return at; - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.scenegraph.utils; + +import java.awt.BasicStroke; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import org.simantics.utils.page.MarginUtils.Margins; + +/** + * Basic utilities for geometric calculations and testing. + * + * @author J-P Laine + * @author Tuukka Lehtonen + */ +public final class GeometryUtils { + + /** + * @param p the point of distance measure + * @param p1 other end of line + * @param p2 other end of line + * @return distance of p from the line defined by p1 and p2 + */ + public static double distanceFromLine(Point2D p, Point2D p1, Point2D p2) { + return distanceFromLine(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p.getX(), p.getY()); + } + + /** + * @param x1 line segment end 1 x + * @param y1 line segment end 1 y + * @param x2 line segment end 2 x + * @param y2 line segment end 2 y + * @param px point x + * @param py point y + * @return + */ + public static double distanceFromLine(double x1, double y1, double x2, double y2, double px, double py) { + // Adjust vectors relative to x1,y1 + // x2,y2 becomes relative vector from x1,y1 to end of segment + x2 -= x1; + y2 -= y1; + // px,py becomes relative vector from x1,y1 to test point + px -= x1; + py -= y1; + double dotprod = px * x2 + py * y2; + // dotprod is the length of the px,py vector + // projected on the x1,y1=>x2,y2 vector times the + // length of the x1,y1=>x2,y2 vector + double lineLenSq = x2 * x2 + y2 * y2; + double lineLen = Math.sqrt(lineLenSq); + double projLen = dotprod / lineLen; + + // Check whether the projection of (px,py) is outside the specified line. + if (projLen < 0) { + return Math.sqrt(px * px + py * py); + } else if (projLen > lineLen) { + double dx = px - x2; + double dy = py - y2; + return Math.sqrt(dx * dx + dy * dy); + } + return Math.sqrt(px * px + py * py - projLen * projLen); + } + + /** + * @param x1 line segment end 1 x + * @param y1 line segment end 1 y + * @param x2 line segment end 2 x + * @param y2 line segment end 2 y + * @param px point x + * @param py point y + * @return + */ + public static Point2D intersectionToLine(double x1, double y1, double x2, double y2, double px, double py) { + double xx2 = x2; + double yy2 = y2; + // Adjust vectors relative to x1,y1 + // x2,y2 becomes relative vector from x1,y1 to end of segment + x2 -= x1; + y2 -= y1; + // px,py becomes relative vector from x1,y1 to test point + px -= x1; + py -= y1; + double dotprod = px * x2 + py * y2; + // dotprod is the length of the px,py vector + // projected on the x1,y1=>x2,y2 vector times the + // length of the x1,y1=>x2,y2 vector + double lineLenSq = x2 * x2 + y2 * y2; + double lineLen = Math.sqrt(lineLenSq); + if (lineLen <= Double.MIN_VALUE) + return new Point2D.Double(x1, y1); + + double projLen = dotprod / lineLen; + + // Check whether the projection of (px,py) is outside the specified line. + if (projLen < 0) { + return new Point2D.Double(x1, y1); + } else if (projLen > lineLen) { + return new Point2D.Double(xx2, yy2); + } + return new Point2D.Double(x1 + x2/lineLen*projLen, y1 + y2/lineLen*projLen); + } + + /** + * Expands margins to a rectangle + * @param rect + * @param top + * @param bottom + * @param left + * @param right + */ + public static Rectangle2D expandRectangle(Rectangle2D rect, double top, double bottom, double left, double right) + { + if (rect==null) throw new IllegalArgumentException("null arg"); + rect.setRect( + rect.getX() - left, + rect.getY() - top, + rect.getWidth() + left + right, + rect.getHeight() + top + bottom); + return rect; + } + + public static Rectangle2D expandRectangle(Rectangle2D rect, double horizontalExpand, double verticalExpand) { + return expandRectangle(rect, verticalExpand, verticalExpand, horizontalExpand, horizontalExpand); + } + + public static Rectangle2D expandRectangle(Rectangle2D rect, double evenExpand) { + return expandRectangle(rect, evenExpand, evenExpand, evenExpand, evenExpand); + } + + public static BasicStroke scaleStroke(Stroke stroke, float factor) + { + return scaleAndOffsetStroke(stroke, factor, 0.0f); + } + + public static BasicStroke offsetStroke(Stroke stroke, float offset) + { + BasicStroke s = (BasicStroke) stroke; + float[] dash = s.getDashArray(); + if (dash == null) + return s; + + return new BasicStroke( + s.getLineWidth(), + s.getEndCap(), + s.getLineJoin(), + s.getMiterLimit(), + dash, + s.getDashPhase() + offset + ); + } + + public static BasicStroke scaleAndOffsetStroke(Stroke stroke, float factor, float offset) + { + BasicStroke s = (BasicStroke) stroke; + float[] dash = s.getDashArray(); + if (dash!=null) { + assert(factor!=0); + dash = scaleArray(factor, dash, new float[dash.length]); + } + if (dash==null) + return new BasicStroke( + s.getLineWidth() * factor, + s.getEndCap(), + s.getLineJoin(), + s.getMiterLimit() + ); + return new BasicStroke( + s.getLineWidth() * factor, + s.getEndCap(), + s.getLineJoin(), + s.getMiterLimit(), + dash, + s.getDashPhase() * factor + offset + ); + } + + public static BasicStroke scaleStrokeWidth(Stroke stroke, float factor) + { + BasicStroke s = (BasicStroke) stroke; + return new BasicStroke( + s.getLineWidth() * factor, + s.getEndCap(), + s.getLineJoin(), + s.getMiterLimit(), + s.getDashArray(), + s.getDashPhase() + ); + } + + public static BasicStroke scaleAndOffsetStrokeWidth(Stroke stroke, float factor, float widthOffset) + { + BasicStroke s = (BasicStroke) stroke; + return new BasicStroke( + s.getLineWidth() * factor + widthOffset, + s.getEndCap(), + s.getLineJoin(), + s.getMiterLimit(), + s.getDashArray(), + s.getDashPhase() + ); + } + + /** + * Scales every element in array + * @param array + * @param factor + * @return new scaled array + */ + public static float[] scaleArray(float factor, float [] array, float [] targetArray) + { + assert(array!=null); + if (targetArray==null) + targetArray = new float[array.length]; + for (int i=0; i= 0.0) { + /* + * trace/2 +- sqrt(trace^2 / 4 - determinant) + * = (a+b)/2 +- sqrt((a+b)^2 / 4 - a b) + * = (a+b)/2 +- sqrt(a^2 / 4 + a b / 2 + b^2 / 4 - a b) + * = (a+b)/2 +- sqrt(a^2 / 4 - a b / 2 + b^2 / 4) + * = (a+b)/2 +- (a-b)/2 + * = a or b + * + * Thus the formula below calculates the greatest + * absolute value of the eigenvalues max(abs(a), abs(b)) + */ + return Math.abs(trace*0.5) + Math.sqrt(dd); + } + else { + /* + * If dd < 0, then the eigenvalues a and b are not real. + * Because both trace and determinant are real, a and b + * have form: + * a = x + i y + * b = x - i y + * + * Then + * sqrt(determinant) + * = sqrt(a b) + * = sqrt(x^2 + y^2), + * which is the absolute value of the eigenvalues. + */ + return Math.sqrt(determinant); + } + } + + /** + * Get a transform that makes canvas area fully visible. + * @param controlArea + * @param diagramArea + * @param margins margins + * @return transform + */ + public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea) + { + double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight(); + double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight(); + // Control is really wide => center canvas horizontally, match vertically + double scale = 1.0; + double tx = 0.0; + double ty = 0.0; + if (controlAspectRatio>canvasAspectRatio) + { + scale = controlArea.getHeight() / diagramArea.getHeight(); + tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2; + } else + // Control is really tall => center canvas vertically, match horizontally + { + scale = controlArea.getWidth() / diagramArea.getWidth(); + ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2; + } + AffineTransform at = new AffineTransform(); + at.translate(tx, ty); + at.translate(controlArea.getMinX(), controlArea.getMinY()); + at.scale(scale, scale); + at.translate(-diagramArea.getMinX(), -diagramArea.getMinY()); + //System.out.println("FIT TRANSFORM: " + at); + return at; + } + + /** + * Rotates rectangle. Note, general rotation is not supported. + * @param transform + * @param rect + * @return transformed rectangle + */ + public static Rectangle2D transformRectangle(AffineTransform transform, Rectangle2D rect) { + int type = transform.getType(); + if (type == AffineTransform.TYPE_IDENTITY) + return new Rectangle2D.Double(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight()); + if ((type & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) { + double x0 = rect.getMinX(); + double y0 = rect.getMinY(); + double x1 = rect.getMaxX(); + double y1 = rect.getMaxY(); + double m00 = transform.getScaleX(); + double m10 = transform.getShearY(); + double m01 = transform.getShearX(); + double m11 = transform.getScaleY(); + double X0, Y0, X1, Y1; + if(m00 > 0.0) { + X0 = m00 * x0; + X1 = m00 * x1; + } + else { + X1 = m00 * x0; + X0 = m00 * x1; + } + if(m01 > 0.0) { + X0 += m01 * y0; + X1 += m01 * y1; + } + else { + X1 += m01 * y0; + X0 += m01 * y1; + } + if(m10 > 0.0) { + Y0 = m10 * x0; + Y1 = m10 * x1; + } + else { + Y1 = m10 * x0; + Y0 = m10 * x1; + } + if(m11 > 0.0) { + Y0 += m11 * y0; + Y1 += m11 * y1; + } + else { + Y1 += m11 * y0; + Y0 += m11 * y1; + } + return new Rectangle2D.Double(X0+transform.getTranslateX(), + Y0+transform.getTranslateY(), X1-X0, Y1-Y0); + } + + // Generic but much slower. + return transform.createTransformedShape(rect).getBounds2D(); + } + + public static Rectangle2D transformRectangleInv(AffineTransform transform, Rectangle2D rect) { + assert( (transform.getType() & (AffineTransform.TYPE_GENERAL_ROTATION|AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0); + double[] mx = new double[6]; + transform.getMatrix(mx); + double x0 = rect.getMinX() - mx[4]; + double y0 = rect.getMinY() - mx[5]; + double x1 = rect.getMaxX() - mx[4]; + double y1 = rect.getMaxY() - mx[5]; + double det = mx[0]*mx[3] - mx[1]*mx[2]; + double m00 = mx[3] / det; + double m10 = -mx[1] / det; + double m01 = -mx[2] / det; + double m11 = mx[0] / det; + double X0, Y0, X1, Y1; + if(m00 > 0.0) { + X0 = m00 * x0; + X1 = m00 * x1; + } + else { + X1 = m00 * x0; + X0 = m00 * x1; + } + if(m01 > 0.0) { + X0 += m01 * y0; + X1 += m01 * y1; + } + else { + X1 += m01 * y0; + X0 += m01 * y1; + } + if(m10 > 0.0) { + Y0 = m10 * x0; + Y1 = m10 * x1; + } + else { + Y1 = m10 * x0; + Y0 = m10 * x1; + } + if(m11 > 0.0) { + Y0 += m11 * y0; + Y1 += m11 * y1; + } + else { + Y1 += m11 * y0; + Y0 += m11 * y1; + } + return new Rectangle2D.Double(X0, Y0, X1-X0, Y1-Y0); + } + + /** + * @param points + * @return millimeters + */ + public static float pointToMillimeter(float points) { + return points * 25.4f / 72.0f; + } + + /** + * @param points + * @return millimeters + */ + public static double pointToMillimeter(double points) { + return points * 25.4 / 72.0; + } + + /** + * Get a transform that makes canvas area fully visible. + * @param controlArea + * @param diagramArea + * @param margins margins + * @return transform + */ + public static AffineTransform fitArea(Rectangle2D controlArea, Rectangle2D diagramArea, Margins margins) + { + diagramArea = expandRectangle(diagramArea, + margins.top.diagramAbsolute, + margins.bottom.diagramAbsolute, + margins.left.diagramAbsolute, + margins.right.diagramAbsolute); + controlArea = expandRectangle(controlArea, + -margins.top.controlAbsolute - margins.top.controlRelative * controlArea.getHeight(), + -margins.bottom.controlAbsolute - margins.bottom.controlRelative * controlArea.getHeight(), + -margins.left.controlAbsolute - margins.left.controlRelative * controlArea.getWidth(), + -margins.right.controlAbsolute - margins.right.controlRelative * controlArea.getWidth()); + + double controlAspectRatio = controlArea.getWidth() / controlArea.getHeight(); + double canvasAspectRatio = diagramArea.getWidth() / diagramArea.getHeight(); + // Control is really wide => center canvas horizontally, match vertically + double scale = 1.0; + double tx = 0.0; + double ty = 0.0; + if (controlAspectRatio>canvasAspectRatio) + { + scale = controlArea.getHeight() / diagramArea.getHeight(); + tx = ( controlArea.getWidth() - diagramArea.getWidth() * scale ) / 2; + } else + // Control is really tall => center canvas vertically, match horizontally + { + scale = controlArea.getWidth() / diagramArea.getWidth(); + ty = ( controlArea.getHeight() - diagramArea.getHeight() * scale ) / 2; + } + AffineTransform at = new AffineTransform(); + at.translate(tx, ty); + at.translate(controlArea.getMinX(), controlArea.getMinY()); + at.scale(scale, scale); + at.translate(-diagramArea.getMinX(), -diagramArea.getMinY()); + //System.out.println("FIT TRANSFORM: " + at); + return at; + } + +}