/******************************************************************************* * Copyright (c) 2016 Weasis Team 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: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.ui.model.graphic.imp.angle; import java.awt.Shape; import java.awt.geom.Arc2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import org.weasis.core.api.gui.util.GeomUtil; import org.weasis.core.api.gui.util.MathUtil; import org.weasis.core.api.image.measure.MeasurementsAdapter; import org.weasis.core.api.image.util.MeasurableLayer; import org.weasis.core.api.image.util.Unit; import org.weasis.core.ui.Messages; import org.weasis.core.ui.model.graphic.AbstractDragGraphic; import org.weasis.core.ui.model.utils.bean.AdvancedShape; import org.weasis.core.ui.model.utils.bean.MeasureItem; import org.weasis.core.ui.model.utils.bean.Measurement; import org.weasis.core.ui.model.utils.exceptions.InvalidShapeException; import org.weasis.core.ui.util.MouseEventDouble; @XmlType(name = "openAngle") @XmlRootElement(name = "openAngle") public class OpenAngleToolGraphic extends AbstractDragGraphic { private static final long serialVersionUID = -189635138276915405L; public static final Integer POINTS_NUMBER = 4; public static final Icon ICON = new ImageIcon(OpenAngleToolGraphic.class.getResource("/icon/22x22/draw-open-angle.png")); //$NON-NLS-1$ public static final Measurement ANGLE = new Measurement(Messages.getString("measure.angle"), 1, true); //$NON-NLS-1$ public static final Measurement COMPLEMENTARY_ANGLE = new Measurement(Messages.getString("measure.complement_angle"), 2, true, true, false); //$NON-NLS-1$ public static final Measurement REFLEX_ANGLE = new Measurement(Messages.getString("AngleToolGraphic.reflex_angle"), 3, true, true, false); //$NON-NLS-1$ protected static final List<Measurement> MEASUREMENT_LIST = new ArrayList<>(); static { MEASUREMENT_LIST.add(ANGLE); MEASUREMENT_LIST.add(COMPLEMENTARY_ANGLE); MEASUREMENT_LIST.add(REFLEX_ANGLE); } // Let AB & CD two line segments protected Point2D ptA; protected Point2D ptB; protected Point2D ptC; protected Point2D ptD; // Let P be the intersection point, if exist, of the two line segments AB & CD protected Point2D ptP; protected Point2D[] lineABP; // (ABP) or (BAP) or (APB) or (BPA) <= ordered array of points along AB segment. protected Point2D[] lineCDP; // (CDP) or (DCP) or (CPD) or (DPC) <= ordered array of points along CD segment. protected boolean linesParallel; // estimate if AB & CD line segments are parallel not not protected boolean intersectABsegment; // estimate if intersection point, if exist, is inside AB segment or not protected boolean intersectCDsegment; // estimate if intersection point, if exist, is inside CD segment or not // estimate if line segments are valid or not protected boolean lineABvalid; protected boolean lineCDvalid; protected double angleDeg; // smallest angle in Degrees in the range of [-180 ; 180] between AB & CD line segments public OpenAngleToolGraphic() { super(POINTS_NUMBER); } public OpenAngleToolGraphic(Integer pointsNumber) { super(pointsNumber); } public OpenAngleToolGraphic(OpenAngleToolGraphic graphic) { super(graphic); } @Override public OpenAngleToolGraphic copy() { return new OpenAngleToolGraphic(this); } @Override public List<Measurement> getMeasurementList() { return MEASUREMENT_LIST; } @Override public Icon getIcon() { return ICON; } @Override public String getUIName() { return Messages.getString("measure.open_angle"); //$NON-NLS-1$ } @Override protected void prepareShape() throws InvalidShapeException { if (!isShapeValid()) { throw new InvalidShapeException("This shape cannot be drawn"); //$NON-NLS-1$ } buildShape(null); } @Override public boolean isShapeValid() { updateTool(); return lineABvalid && lineCDvalid; } @Override public void buildShape(MouseEventDouble mouseEvent) { updateTool(); Shape newShape = null; Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO, 6); if (lineABvalid) { path.append(new Line2D.Double(ptA, ptB), false); } if (lineCDvalid) { path.append(new Line2D.Double(ptC, ptD), false); } // Do not show decoration when lines are nearly parallel // Can cause stack overflow BUG on paint method when drawing infinite line with DashStroke if (lineABvalid && lineCDvalid && !linesParallel && Math.abs(angleDeg) > 0.1) { newShape = new AdvancedShape(this, 5); AdvancedShape aShape = (AdvancedShape) newShape; aShape.addShape(path); // Let arcAngle be the partial section of the ellipse that represents the measured angle // Let Ix,Jx points of line segments AB & CD used to compute arcAngle radius Point2D ptI1 = GeomUtil.getColinearPointWithRatio(lineABP[1], lineABP[0], 0.25); Point2D ptJ1 = GeomUtil.getColinearPointWithRatio(lineCDP[1], lineCDP[0], 0.25); Point2D ptI2 = GeomUtil.getColinearPointWithRatio(lineABP[0], lineABP[1], 0.25); Point2D ptJ2 = GeomUtil.getColinearPointWithRatio(lineCDP[0], lineCDP[1], 0.25); double maxRadius = Math.min(ptP.distance(ptI2), ptP.distance(ptJ2)); double radius = Math.min(maxRadius, (ptP.distance(ptI1) + ptP.distance(ptJ1)) / 2); double startingAngle = GeomUtil.getAngleDeg(ptP, lineABP[0]); Rectangle2D arcAngleBounds = new Rectangle2D.Double(ptP.getX() - radius, ptP.getY() - radius, 2 * radius, 2 * radius); Arc2D arcAngle = new Arc2D.Double(arcAngleBounds, startingAngle, angleDeg, Arc2D.OPEN); aShape.addShape(arcAngle, getStroke(1.0f), true); if (!intersectABsegment) { aShape.addShape(new Line2D.Double(ptP, lineABP[1]), getDashStroke(1.0f), true); } if (!intersectCDsegment) { aShape.addShape(new Line2D.Double(ptP, lineCDP[1]), getDashStroke(1.0f), true); } // Let intersectPtShape be a cross line point inside a circle int iPtSize = 8; Path2D intersectPtShape = new Path2D.Double(Path2D.WIND_NON_ZERO, 5); Rectangle2D intersecPtBounds = new Rectangle2D.Double(ptP.getX() - (iPtSize / 2.0), ptP.getY() - (iPtSize / 2.0), iPtSize, iPtSize); intersectPtShape.append(new Line2D.Double(ptP.getX() - iPtSize, ptP.getY(), ptP.getX() - 2, ptP.getY()), false); intersectPtShape.append(new Line2D.Double(ptP.getX() + 2, ptP.getY(), ptP.getX() + iPtSize, ptP.getY()), false); intersectPtShape.append(new Line2D.Double(ptP.getX(), ptP.getY() - iPtSize, ptP.getX(), ptP.getY() - 2), false); intersectPtShape.append(new Line2D.Double(ptP.getX(), ptP.getY() + 2, ptP.getX(), ptP.getY() + iPtSize), false); intersectPtShape.append(new Arc2D.Double(intersecPtBounds, 0, 360, Arc2D.OPEN), false); aShape.addScaleInvShape(intersectPtShape, ptP, getStroke(0.5f), true); } else if (path.getCurrentPoint() != null) { newShape = path; } setShape(newShape, mouseEvent); updateLabel(mouseEvent, getDefaultView2d(mouseEvent)); } protected void updateTool() { ptA = getHandlePoint(0); ptB = getHandlePoint(1); ptC = getHandlePoint(2); ptD = getHandlePoint(3); lineABP = lineCDP = null; linesParallel = intersectABsegment = intersectCDsegment = false; angleDeg = 0.0; lineABvalid = ptA != null && ptB != null && !ptB.equals(ptA); lineCDvalid = ptC != null && ptD != null && !ptC.equals(ptD); if (lineABvalid && lineCDvalid) { double denominator = (ptB.getX() - ptA.getX()) * (ptD.getY() - ptC.getY()) - (ptB.getY() - ptA.getY()) * (ptD.getX() - ptC.getX()); linesParallel = MathUtil.isEqualToZero(denominator); // If denominator is zero, AB & CD are parallel if (!linesParallel) { double numerator1 = (ptA.getY() - ptC.getY()) * (ptD.getX() - ptC.getX()) - (ptA.getX() - ptC.getX()) * (ptD.getY() - ptC.getY()); double numerator2 = (ptA.getY() - ptC.getY()) * (ptB.getX() - ptA.getX()) - (ptA.getX() - ptC.getX()) * (ptB.getY() - ptA.getY()); double r = numerator1 / denominator; // equ1 double s = numerator2 / denominator; // equ2 ptP = new Point2D.Double(ptA.getX() + r * (ptB.getX() - ptA.getX()), ptA.getY() + r * (ptB.getY() - ptA.getY())); // If 0<=r<=1 & 0<=s<=1, segment intersection exists // If r<0 or r>1 or s<0 or s>1, line segments intersect but not segments // If r>1, P is located on extension of AB // If r<0, P is located on extension of BA // If s>1, P is located on extension of CD // If s<0, P is located on extension of DC lineABP = new Point2D[3]; // order can be ABP (r>1) or BAP (r<0) or APB / BPA (0<=r<=1) lineCDP = new Point2D[3]; // order can be CDP (s>1) or DCP (s<0) or CPD / DPC (0<=s<=1) intersectABsegment = (r >= 0 && r <= 1) ? true : false; // means lineABP[1].equals(P) intersectCDsegment = (s >= 0 && s <= 1) ? true : false; // means lineCDP[1].equals(P) lineABP[0] = r >= 0 ? ptA : ptB; lineABP[1] = r < 0 ? ptA : r > 1 ? ptB : ptP; lineABP[2] = r < 0 ? ptP : r > 1 ? ptP : ptB; if (intersectABsegment) { if (ptP.distance(lineABP[0]) < ptP.distance(lineABP[2])) { Point2D switchPt = (Point2D) lineABP[2].clone(); lineABP[2] = (Point2D) lineABP[0].clone(); lineABP[0] = switchPt; } } else if (ptP.distance(lineABP[0]) < ptP.distance(lineABP[1])) { Point2D switchPt = (Point2D) lineABP[1].clone(); lineABP[1] = (Point2D) lineABP[0].clone(); lineABP[0] = switchPt; } lineCDP[0] = s >= 0 ? ptC : ptD; lineCDP[1] = s < 0 ? ptC : s > 1 ? ptD : ptP; lineCDP[2] = s < 0 ? ptP : s > 1 ? ptP : ptD; if (intersectCDsegment) { if (ptP.distance(lineCDP[0]) < ptP.distance(lineCDP[2])) { Point2D switchPt = (Point2D) lineCDP[2].clone(); lineCDP[2] = (Point2D) lineCDP[0].clone(); lineCDP[0] = switchPt; } } else if (ptP.distance(lineCDP[0]) < ptP.distance(lineCDP[1])) { Point2D switchPt = (Point2D) lineCDP[1].clone(); lineCDP[1] = (Point2D) lineCDP[0].clone(); lineCDP[0] = switchPt; } angleDeg = GeomUtil.getSmallestRotationAngleDeg(GeomUtil.getAngleDeg(lineABP[0], ptP, lineCDP[0])); } } } @Override public List<MeasureItem> computeMeasurements(MeasurableLayer layer, boolean releaseEvent, Unit displayUnit) { if (layer != null && layer.hasContent() && isShapeValid()) { MeasurementsAdapter adapter = layer.getMeasurementAdapter(displayUnit); if (adapter != null) { ArrayList<MeasureItem> measVal = new ArrayList<>(); double positiveAngle = Math.abs(angleDeg); if (ANGLE.getComputed()) { measVal.add(new MeasureItem(ANGLE, positiveAngle, Messages.getString("measure.deg"))); //$NON-NLS-1$ } if (COMPLEMENTARY_ANGLE.getComputed()) { measVal.add( new MeasureItem(COMPLEMENTARY_ANGLE, 180.0 - positiveAngle, Messages.getString("measure.deg"))); //$NON-NLS-1$ } if (REFLEX_ANGLE.getComputed()) { measVal .add(new MeasureItem(REFLEX_ANGLE, 360.0 - positiveAngle, Messages.getString("measure.deg"))); //$NON-NLS-1$ } return measVal; } } return Collections.emptyList(); } }