/******************************************************************************* * Copyright (c) 2012, 2015 itemis AG and others. * * 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: * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef.geometry.planar; import java.util.ArrayList; import java.util.List; import org.eclipse.gef.geometry.euclidean.Angle; /** * An {@link AbstractArcBasedGeometry} describes the {@link Arc} of an * {@link Ellipse}. It provides functionality to modify and query attributes of * the {@link Arc} and to compute a {@link BezierCurve} approximation of the * {@link Arc}. * * @param <T> * the type of the inheriting class * @param <S> * the type of rotated objects (see {@link IRotatable}) * * @author mwienand * */ abstract class AbstractArcBasedGeometry<T extends AbstractArcBasedGeometry<?, ?>, S extends IGeometry> extends AbstractRectangleBasedGeometry<T, S> { private static final long serialVersionUID = 1L; /** * The counter-clockwise (CCW) {@link Angle} to the x-axis at which this * {@link AbstractArcBasedGeometry} begins. */ protected Angle startAngle; /** * The counter-clockwise (CCW) {@link Angle} that spans this * {@link AbstractArcBasedGeometry}. */ protected Angle angularExtent; /** * Constructs a new {@link AbstractArcBasedGeometry} so that it is fully * contained within the framing {@link Rectangle} defined by x, y, width, * and height, spanning the given extend (in CCW direction) from the given * start angle (relative to the x-axis). * * @param x * the x coordinate of the framing {@link Rectangle} * @param y * the y coordinate of the framing {@link Rectangle} * @param width * the width of the framing {@link Rectangle} * @param height * the height of the framing {@link Rectangle} * @param startAngle * the CCW {@link Angle} to the x-axis at which this * {@link AbstractArcBasedGeometry} begins * @param angularExtent * the CCW {@link Angle} that spans this * {@link AbstractArcBasedGeometry} */ public AbstractArcBasedGeometry(double x, double y, double width, double height, Angle startAngle, Angle angularExtent) { super(x, y, width, height); this.startAngle = startAngle; this.angularExtent = angularExtent; } /** * Computes a {@link CubicCurve} approximation for this * {@link AbstractArcBasedGeometry}. It is approximated by a maximum of four * {@link CubicCurve}s, each of which covers a maximum of 90 degrees. * * @return a {@link CubicCurve} approximation for this * {@link AbstractArcBasedGeometry} */ protected CubicCurve[] computeBezierApproximation() { double start = getStartAngle().rad(); double end = getStartAngle().rad() + getAngularExtent().rad(); // approximation is for arcs with angle < 90 degrees, so we may have to // split the arc into up to 4 cubic curves List<CubicCurve> segments = new ArrayList<>(); if (angularExtent.deg() <= 90.0) { segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start), Angle.fromRad(end))); } else { // two or more segments, the first will be an ellipse segment // approximation segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start), Angle.fromRad(start + Math.PI / 2))); if (angularExtent.deg() <= 180.0) { // two segments, calculate the second (which is below 90 // degrees) segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start + Math.PI / 2), Angle.fromRad(end))); } else { // three or more segments, so calculate the second one segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start + Math.PI / 2), Angle.fromRad(start + Math.PI))); if (angularExtent.deg() <= 270.0) { // three segments, calculate the third (which is below 90 // degrees) segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start + Math.PI), Angle.fromRad(end))); } else { // four segments (fourth below 90 degrees), so calculate the // third and fourth segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start + Math.PI), Angle.fromRad(start + 3 * Math.PI / 2))); segments.add(ShapeUtils.computeEllipticalArcApproximation(x, y, width, height, Angle.fromRad(start + 3 * Math.PI / 2), Angle.fromRad(end))); } } } return segments.toArray(new CubicCurve[] {}); } /** * Returns the extension {@link Angle} of this * {@link AbstractArcBasedGeometry}, i.e. the {@link Angle} defining the * span of this {@link AbstractArcBasedGeometry}. * * @return the extension {@link Angle} of this * {@link AbstractArcBasedGeometry} */ public Angle getAngularExtent() { return angularExtent; } /** * Returns the start {@link Point} of this {@link AbstractArcBasedGeometry}. * * @return the start {@link Point} of this {@link AbstractArcBasedGeometry} */ public Point getP1() { return getPoint(Angle.fromRad(0)); } /** * Returns the end {@link Point} of this {@link AbstractArcBasedGeometry}. * * @return the end {@link Point} of this {@link AbstractArcBasedGeometry} */ public Point getP2() { return getPoint(angularExtent); } /** * Computes a {@link Point} on this {@link AbstractArcBasedGeometry}. The * {@link Point}'s coordinates are calculated by moving the given * {@link Angle} on this {@link AbstractArcBasedGeometry} starting at this * {@link AbstractArcBasedGeometry}'s start {@link Point}. * * @param angularExtent * the {@link Angle} to move from the start {@link Point} of this * {@link AbstractArcBasedGeometry} * @return the {@link Point} at the given extension {@link Angle} */ public Point getPoint(Angle angularExtent) { double a = width / 2; double b = height / 2; return new Point( x + a + a * Math.cos(startAngle.rad() + angularExtent.rad()), y + b - b * Math.sin(startAngle.rad() + angularExtent.rad())); } /** * Returns this {@link AbstractArcBasedGeometry}'s start {@link Angle}. * * @return this {@link AbstractArcBasedGeometry}'s start {@link Angle} */ public Angle getStartAngle() { return startAngle; } /** * Returns the x coordinate of the start {@link Point} of this * {@link AbstractArcBasedGeometry}. * * @return the x coordinate of the start {@link Point} of this * {@link AbstractArcBasedGeometry} */ public double getX1() { return getP1().x; } /** * Returns the x coordinate of the end {@link Point} of this * {@link AbstractArcBasedGeometry}. * * @return the x coordinate of the end {@link Point} of this * {@link AbstractArcBasedGeometry} */ public double getX2() { return getP2().x; } /** * Returns the y coordinate of the start {@link Point} of this * {@link AbstractArcBasedGeometry}. * * @return the y coordinate of the start {@link Point} of this * {@link AbstractArcBasedGeometry} */ public double getY1() { return getP1().y; } /** * Returns the y coordinate of the end {@link Point} of this * {@link AbstractArcBasedGeometry}. * * @return the y coordinate of the end {@link Point} of this * {@link AbstractArcBasedGeometry} */ public double getY2() { return getP2().y; } /** * Sets the extension {@link Angle} of this {@link AbstractArcBasedGeometry} * . * * @param angularExtent * the new extension {@link Angle} for this * {@link AbstractArcBasedGeometry} * @return <code>this</code> for convenience */ @SuppressWarnings("unchecked") public T setAngularExtent(Angle angularExtent) { this.angularExtent = angularExtent; return (T) this; } /** * Sets the start {@link Angle} of this {@link AbstractArcBasedGeometry}. * * @param startAngle * the new start {@link Angle} for this * {@link AbstractArcBasedGeometry} * @return <code>this</code> for convenience */ @SuppressWarnings("unchecked") public T setStartAngle(Angle startAngle) { this.startAngle = startAngle; return (T) this; } /** * @see IGeometry#toPath() */ @Override public Path toPath() { return CurveUtils.toPath(computeBezierApproximation()); } }