X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Futils%2FViewBoxUtils.java;h=cc5b8863871844eb076b4d296c0100003e2bb49e;hb=78d831a19c254d829e45d04c5ec6a3057680b7d7;hp=48e0392396e32c63cc6e52644d8f938fc3a7d90d;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java index 48e039239..cc5b88638 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java @@ -1,341 +1,341 @@ -/******************************************************************************* - * 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.g2d.utils; - -import java.awt.geom.AffineTransform; -import java.awt.geom.NoninvertibleTransformException; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; - -/** - * @author Tuukka Lehtonen - */ -public final class ViewBoxUtils { - - /** - * A default viewbox generated with Stetson-Harrison method for a last - * fallback value if nothing else is available. - */ - private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20); - - public static Rectangle2D getDefaultViewBox() { - Rectangle2D r = new Rectangle2D.Double(); - r.setFrame(defaultViewBox); - return r; - } - - /** - * @param x - * @param y - * @param distanceFromPlane (0, +inf) - * @param fieldOfView (0, 180) deg - * @param aspectRatio (0, +inf), view-width / view-height - * @return - */ -// public static Rectangle2D toViewBox(double x, double y, double distanceFromPlane, double fieldOfView, double aspectRatio) { -// // w/2 = d * tan(fov) -// // h = w / aspectRatio -// double w = distanceFromPlane * Math.tan(fieldOfView * 0.5); -// double h = w / aspectRatio; -// -// Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2); -// return r; -// } - - /** - * Get the view transformation of the specified canvas as an - * AffineTransform. - * - */ - public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) { - double sx = canvasSize.getX() / viewBox.getWidth(); - double sy = canvasSize.getY() / viewBox.getHeight(); - - AffineTransform tr = new AffineTransform(); - tr.scale(sx, sy); -// System.out.println("getViewTransform:"); -// System.out.println(" scale: (" + sx + ", " + sy + ")"); -// System.out.println(" after scale: " + tr); - tr.translate(-viewBox.getMinX(), -viewBox.getMinY()); -// System.out.println(" after translation: " + tr); - - return tr; - } - - /** - * Get the view transformation of the specified canvas as an - * AffineTransform. - * - */ - public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D controlSize, Point2D usableControlSize) { - double sx = usableControlSize.getX() / viewBox.getWidth(); - double sy = usableControlSize.getY() / viewBox.getHeight(); - - AffineTransform tr = new AffineTransform(); - tr.translate( - (-usableControlSize.getX()+controlSize.getX())/2, - (-usableControlSize.getX()+controlSize.getX())/2); - tr.scale(sx, sy); - tr.translate(-viewBox.getMinX(), -viewBox.getMinY()); - - return tr; - } - /** - * Interpolates a 2D position with respect to a rectangle and two normalized - * interpolation factors. - * - * @param r the rectangle inside which to interpolate a coordinate - * @param nx x interpolation value in [0,1] - * @param ny y interpolation value in [0,1] - * @return - */ - public static Point2D interpolate(Rectangle2D r, double nx, double ny) { - return new Point2D.Double( - r.getMinX() + r.getWidth() * nx, - r.getMinY() + r.getHeight() * ny); - } - - /** - * Converts the specified AffineTransform into a view box rectangle with - * respect to the size of the specified canvas instance. The client can - * specify whether the scaling should be uniform or non-uniform. In the - * uniform case the X scale factor is used for both dimensions. - * - * @param canvasSize - * @param tr - * @param uniform - * @return - */ - public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) { - double sx; - double sy; - - if (uniform) { -// if (tr.getScaleX() != tr.getScaleY()) -// System.out.println("WARNING: scale not uniform: " + tr.getScaleX() + " vs. " + tr.getScaleY()); - sx = sy = 1.0 / tr.getScaleX(); - } else { - sx = 1.0 / tr.getScaleX(); - sy = 1.0 / tr.getScaleY(); - } - - double startX = -tr.getTranslateX() * sx; - double startY = -tr.getTranslateY() * sy; - double endX = startX + sx * canvasSize.getX(); - double endY = startY + sy * canvasSize.getY(); - - return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY); - } - - /** - * Uniformly inflates the specified rectangle by a scaling factor around its - * the center of the box. Modifies the specified rectangle instance itself. - * - * @param r the rectangle to inflate - * @param scale the inflation scale factor - */ - public static void inflate(Rectangle2D r, double scale) { - double cx = r.getCenterX(); - double cy = r.getCenterY(); - double nw = r.getWidth() * scale; - double nh = r.getHeight() * scale; - double nw2 = nw * 0.5; - double nh2 = nh * 0.5; - r.setFrame(cx - nw2, cy - nh2, nw, nh); - } - - /** - * Uniformly inflates the specified rectangle by a scaling factor around its - * the center of the box. Returns the inflated rectangle as a new instance. - * The original rectangle is not modified. - * - * @param r the rectangle to inflate - * @param scale the inflation scale factor - * @return an inflated rectangle - */ - public static Rectangle2D inflated(Rectangle2D r, double scale) { - Rectangle2D r2 = new Rectangle2D.Double(); - r2.setFrame(r); - inflate(r2, scale); - return r2; - } - - /** - * Uniformly fits the specified view box to size of the specified canvas. - * - * This means that the viewbox if the viewbox coordinates are not a perfect - * multiple of the canvas size (i.e. viewBox-size * s = canvas-size), the - * view box is always scaled in either the horizontal or the vertical - * dimension. Here the scaled dimension is chosen to always inflate the view - * box, never deflate it. - * - * @param canvasSize the size of the canvas to fit the view box to - * @param viewBox the view box to fit - */ - public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) { - double cx = viewBox.getCenterX(); - double cy = viewBox.getCenterY(); - - double cw = (double) canvasSize.getX(); - double ch = (double) canvasSize.getY(); - double w = viewBox.getWidth(); - double h = viewBox.getHeight(); - double sw = cw / w; - double sh = ch / h; - - if (sw < sh) { - // The specified viewbox fits the canvas in width but in height there - // is extra space which the viewbox needs to fill! - - // height of requested viewbox in device coordinates = h' = h * sw - // height of viewbox that fits the canvas height = h * ch / h' - - double dh = h * sw; - double fh = h * ch / dh; - - viewBox.setFrameFromCenter(cx, cy, cx + w * 0.5, cy + fh * 0.5); -// System.out.println("sw < sh: " + dh + ", " + fh + ": " + viewBox); - } else if (sw > sh) { - // The specified viewbox fits the canvas in height but in width there - // is extra space which the viewbox needs to fill! - - // width of requested viewbox in device coordinates = dw = w * sh - // width of viewbox that fits the canvas height = fw = w * cw / w' - - double dw = w * sh; - double fw = w * cw / dw; - - viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5); -// System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox); - } - } - - public static void move(Rectangle2D r, double tx, double ty) { - r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight()); - } - - public static Rectangle2D moved(Rectangle2D r, double tx, double ty) { - Rectangle2D r2 = new Rectangle2D.Double(); - r2.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight()); - return r2; - } - - - /** - * @param canvasSize - * @param px - * @param py - * @param sx - * @param sy - * @return - */ - public static Rectangle2D uniformZoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy) { - return zoomedViewBox(viewBox, canvasSize, px, py, sx, sy, true); - } - - /** - * @param c - * @param px - * @param py - * @param sx - * @param sy - * @param uniform - * @return - */ - public static Rectangle2D zoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy, boolean uniform) { - if (sx <= 0 || sy <= 0) { - throw new IllegalArgumentException("invalid scaling: " + sx + ", " + sy); - } - - AffineTransform view = getViewTransform(viewBox, canvasSize); - - AffineTransform at = AffineTransform.getTranslateInstance(px, py); - at.scale(sx, sy); - at.translate(-px, -py); - at.concatenate(view); - - Rectangle2D box = transformToViewBox(canvasSize, at, uniform); - return box; - } - - - - - - - - public enum Align {Begin, Center, End, Fill}; - - /** - * Creates tranform matrix, that converts coordinates from - * uniform viewport to a 100x100 square. Aspect ratio remains the same. - * - * @param viewport - * @param horiz Horizontal alignment of 100x100 square - * @param vert Vertical alignment of 100x100 square - * @return transform (that can be concatenated) - */ - public static AffineTransform viewportToSquare( - Rectangle2D viewport, - Align horiz, - Align vert) { - boolean wide = viewport.getWidth() > viewport.getHeight(); - double shorterEdge = wide ? viewport.getHeight() : viewport.getWidth(); - double longerEdge = !wide ? viewport.getHeight() : viewport.getWidth(); - double tx = -viewport.getX(); - double ty = -viewport.getY(); - double s = 100 / shorterEdge; - if (wide) { - if (horiz == Align.Center) - tx += (longerEdge - shorterEdge) / 2; - else if (horiz == Align.End) - tx += longerEdge - shorterEdge; - else if (horiz == Align.Fill) - s = 100 / viewport.getWidth(); - } else { - if (vert == Align.Center) - ty += (longerEdge - shorterEdge) / 2; - else if (vert == Align.End) - ty += longerEdge - shorterEdge; - else if (vert == Align.Fill) - s = 100 / viewport.getHeight(); - } - - AffineTransform at = new AffineTransform(); - at.scale(s, s); - at.translate(tx, ty); - return at; - } - - /** - * Creates tranform matrix, that converts coordinates from - * 100x100 square to a uniform viewport. Aspect ratio remains the same. - * - * @param viewport - * @param horiz Horizontal alignment of 100x100 square - * @param vert Vertical alignment of 100x100 square - * @return transform (that can be concatenated) - */ - public static AffineTransform squareToViewport( - Rectangle2D viewport, - Align horiz, - Align vert) { - AffineTransform at = viewportToSquare(viewport, horiz, vert); - try { - return at.createInverse(); - } catch (NoninvertibleTransformException e) { - throw new RuntimeException(e); - } - } - - -} +/******************************************************************************* + * 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.g2d.utils; + +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +/** + * @author Tuukka Lehtonen + */ +public final class ViewBoxUtils { + + /** + * A default viewbox generated with Stetson-Harrison method for a last + * fallback value if nothing else is available. + */ + private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20); + + public static Rectangle2D getDefaultViewBox() { + Rectangle2D r = new Rectangle2D.Double(); + r.setFrame(defaultViewBox); + return r; + } + + /** + * @param x + * @param y + * @param distanceFromPlane (0, +inf) + * @param fieldOfView (0, 180) deg + * @param aspectRatio (0, +inf), view-width / view-height + * @return + */ +// public static Rectangle2D toViewBox(double x, double y, double distanceFromPlane, double fieldOfView, double aspectRatio) { +// // w/2 = d * tan(fov) +// // h = w / aspectRatio +// double w = distanceFromPlane * Math.tan(fieldOfView * 0.5); +// double h = w / aspectRatio; +// +// Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2); +// return r; +// } + + /** + * Get the view transformation of the specified canvas as an + * AffineTransform. + * + */ + public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) { + double sx = canvasSize.getX() / viewBox.getWidth(); + double sy = canvasSize.getY() / viewBox.getHeight(); + + AffineTransform tr = new AffineTransform(); + tr.scale(sx, sy); +// System.out.println("getViewTransform:"); +// System.out.println(" scale: (" + sx + ", " + sy + ")"); +// System.out.println(" after scale: " + tr); + tr.translate(-viewBox.getMinX(), -viewBox.getMinY()); +// System.out.println(" after translation: " + tr); + + return tr; + } + + /** + * Get the view transformation of the specified canvas as an + * AffineTransform. + * + */ + public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D controlSize, Point2D usableControlSize) { + double sx = usableControlSize.getX() / viewBox.getWidth(); + double sy = usableControlSize.getY() / viewBox.getHeight(); + + AffineTransform tr = new AffineTransform(); + tr.translate( + (-usableControlSize.getX()+controlSize.getX())/2, + (-usableControlSize.getX()+controlSize.getX())/2); + tr.scale(sx, sy); + tr.translate(-viewBox.getMinX(), -viewBox.getMinY()); + + return tr; + } + /** + * Interpolates a 2D position with respect to a rectangle and two normalized + * interpolation factors. + * + * @param r the rectangle inside which to interpolate a coordinate + * @param nx x interpolation value in [0,1] + * @param ny y interpolation value in [0,1] + * @return + */ + public static Point2D interpolate(Rectangle2D r, double nx, double ny) { + return new Point2D.Double( + r.getMinX() + r.getWidth() * nx, + r.getMinY() + r.getHeight() * ny); + } + + /** + * Converts the specified AffineTransform into a view box rectangle with + * respect to the size of the specified canvas instance. The client can + * specify whether the scaling should be uniform or non-uniform. In the + * uniform case the X scale factor is used for both dimensions. + * + * @param canvasSize + * @param tr + * @param uniform + * @return + */ + public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) { + double sx; + double sy; + + if (uniform) { +// if (tr.getScaleX() != tr.getScaleY()) +// System.out.println("WARNING: scale not uniform: " + tr.getScaleX() + " vs. " + tr.getScaleY()); + sx = sy = 1.0 / tr.getScaleX(); + } else { + sx = 1.0 / tr.getScaleX(); + sy = 1.0 / tr.getScaleY(); + } + + double startX = -tr.getTranslateX() * sx; + double startY = -tr.getTranslateY() * sy; + double endX = startX + sx * canvasSize.getX(); + double endY = startY + sy * canvasSize.getY(); + + return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY); + } + + /** + * Uniformly inflates the specified rectangle by a scaling factor around its + * the center of the box. Modifies the specified rectangle instance itself. + * + * @param r the rectangle to inflate + * @param scale the inflation scale factor + */ + public static void inflate(Rectangle2D r, double scale) { + double cx = r.getCenterX(); + double cy = r.getCenterY(); + double nw = r.getWidth() * scale; + double nh = r.getHeight() * scale; + double nw2 = nw * 0.5; + double nh2 = nh * 0.5; + r.setFrame(cx - nw2, cy - nh2, nw, nh); + } + + /** + * Uniformly inflates the specified rectangle by a scaling factor around its + * the center of the box. Returns the inflated rectangle as a new instance. + * The original rectangle is not modified. + * + * @param r the rectangle to inflate + * @param scale the inflation scale factor + * @return an inflated rectangle + */ + public static Rectangle2D inflated(Rectangle2D r, double scale) { + Rectangle2D r2 = new Rectangle2D.Double(); + r2.setFrame(r); + inflate(r2, scale); + return r2; + } + + /** + * Uniformly fits the specified view box to size of the specified canvas. + * + * This means that the viewbox if the viewbox coordinates are not a perfect + * multiple of the canvas size (i.e. viewBox-size * s = canvas-size), the + * view box is always scaled in either the horizontal or the vertical + * dimension. Here the scaled dimension is chosen to always inflate the view + * box, never deflate it. + * + * @param canvasSize the size of the canvas to fit the view box to + * @param viewBox the view box to fit + */ + public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) { + double cx = viewBox.getCenterX(); + double cy = viewBox.getCenterY(); + + double cw = (double) canvasSize.getX(); + double ch = (double) canvasSize.getY(); + double w = viewBox.getWidth(); + double h = viewBox.getHeight(); + double sw = cw / w; + double sh = ch / h; + + if (sw < sh) { + // The specified viewbox fits the canvas in width but in height there + // is extra space which the viewbox needs to fill! + + // height of requested viewbox in device coordinates = h' = h * sw + // height of viewbox that fits the canvas height = h * ch / h' + + double dh = h * sw; + double fh = h * ch / dh; + + viewBox.setFrameFromCenter(cx, cy, cx + w * 0.5, cy + fh * 0.5); +// System.out.println("sw < sh: " + dh + ", " + fh + ": " + viewBox); + } else if (sw > sh) { + // The specified viewbox fits the canvas in height but in width there + // is extra space which the viewbox needs to fill! + + // width of requested viewbox in device coordinates = dw = w * sh + // width of viewbox that fits the canvas height = fw = w * cw / w' + + double dw = w * sh; + double fw = w * cw / dw; + + viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5); +// System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox); + } + } + + public static void move(Rectangle2D r, double tx, double ty) { + r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight()); + } + + public static Rectangle2D moved(Rectangle2D r, double tx, double ty) { + Rectangle2D r2 = new Rectangle2D.Double(); + r2.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight()); + return r2; + } + + + /** + * @param canvasSize + * @param px + * @param py + * @param sx + * @param sy + * @return + */ + public static Rectangle2D uniformZoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy) { + return zoomedViewBox(viewBox, canvasSize, px, py, sx, sy, true); + } + + /** + * @param c + * @param px + * @param py + * @param sx + * @param sy + * @param uniform + * @return + */ + public static Rectangle2D zoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy, boolean uniform) { + if (sx <= 0 || sy <= 0) { + throw new IllegalArgumentException("invalid scaling: " + sx + ", " + sy); + } + + AffineTransform view = getViewTransform(viewBox, canvasSize); + + AffineTransform at = AffineTransform.getTranslateInstance(px, py); + at.scale(sx, sy); + at.translate(-px, -py); + at.concatenate(view); + + Rectangle2D box = transformToViewBox(canvasSize, at, uniform); + return box; + } + + + + + + + + public enum Align {Begin, Center, End, Fill}; + + /** + * Creates tranform matrix, that converts coordinates from + * uniform viewport to a 100x100 square. Aspect ratio remains the same. + * + * @param viewport + * @param horiz Horizontal alignment of 100x100 square + * @param vert Vertical alignment of 100x100 square + * @return transform (that can be concatenated) + */ + public static AffineTransform viewportToSquare( + Rectangle2D viewport, + Align horiz, + Align vert) { + boolean wide = viewport.getWidth() > viewport.getHeight(); + double shorterEdge = wide ? viewport.getHeight() : viewport.getWidth(); + double longerEdge = !wide ? viewport.getHeight() : viewport.getWidth(); + double tx = -viewport.getX(); + double ty = -viewport.getY(); + double s = 100 / shorterEdge; + if (wide) { + if (horiz == Align.Center) + tx += (longerEdge - shorterEdge) / 2; + else if (horiz == Align.End) + tx += longerEdge - shorterEdge; + else if (horiz == Align.Fill) + s = 100 / viewport.getWidth(); + } else { + if (vert == Align.Center) + ty += (longerEdge - shorterEdge) / 2; + else if (vert == Align.End) + ty += longerEdge - shorterEdge; + else if (vert == Align.Fill) + s = 100 / viewport.getHeight(); + } + + AffineTransform at = new AffineTransform(); + at.scale(s, s); + at.translate(tx, ty); + return at; + } + + /** + * Creates tranform matrix, that converts coordinates from + * 100x100 square to a uniform viewport. Aspect ratio remains the same. + * + * @param viewport + * @param horiz Horizontal alignment of 100x100 square + * @param vert Vertical alignment of 100x100 square + * @return transform (that can be concatenated) + */ + public static AffineTransform squareToViewport( + Rectangle2D viewport, + Align horiz, + Align vert) { + AffineTransform at = viewportToSquare(viewport, horiz, vert); + try { + return at.createInverse(); + } catch (NoninvertibleTransformException e) { + throw new RuntimeException(e); + } + } + + +}