/******************************************************************************* * 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 = "fourPointsAngle") @XmlRootElement(name = "fourPointsAngle") public class FourPointsAngleToolGraphic extends AbstractDragGraphic { private static final long serialVersionUID = -3885933187155495525L; public static final Integer POINTS_NUMBER = 8; public static final Icon ICON = new ImageIcon(FourPointsAngleToolGraphic.class.getResource("/icon/22x22/draw-4p-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$ protected static final List<Measurement> MEASUREMENT_LIST = new ArrayList<>(); static { MEASUREMENT_LIST.add(ANGLE); MEASUREMENT_LIST.add(COMPLEMENTARY_ANGLE); } // Let AB & CD two line segments which define the median line IJ Point2D ptA; Point2D ptB; Point2D ptC; Point2D ptD; // Let I,J be the middle points of AB & CD line segments Point2D ptI; Point2D ptJ; // Let EF & GH two line segments which define the median line KL Point2D ptE; Point2D ptF; Point2D ptG; Point2D ptH; // Let K,L be the middle points of EF & GH line segments Point2D ptK; Point2D ptL; // Let P be the intersection point, if exist, of the two line segments IJ & KL Point2D ptP; Point2D[] lineIJP; // (IJP) or (JIP) or (IPJ) or (JPI) <= ordered array of points along IJ segment. Point2D[] lineKLP; // (KLP) or (LKP) or (KPL) or (LPK) <= ordered array of points along KL segment. boolean lineParallel; // estimate if IJ & KL line segments are parallel not not boolean intersectIJsegment; // estimate if intersection point, if exist, is inside IJ segment or not boolean intersectKLsegment; // estimate if intersection point, if exist, is inside KL segment or not // estimate if line segments are valid or not boolean lineABvalid; boolean lineCDvalid; boolean lineEFvalid; boolean lineGHvalid; boolean lineIJvalid; boolean lineKLvalid; double angleDeg; // smallest angle in Degrees in the range of [-180 ; 180] between IJ & KL line segments public FourPointsAngleToolGraphic() { super(POINTS_NUMBER); } public FourPointsAngleToolGraphic(FourPointsAngleToolGraphic graphic) { super(graphic); } @Override public FourPointsAngleToolGraphic copy() { return new FourPointsAngleToolGraphic(this); } @Override public Icon getIcon() { return ICON; } @Override public String getUIName() { return Messages.getString("measure.four_pt_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 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); } if (lineIJvalid) { path.append(new Line2D.Double(ptI, ptJ), false); } if (lineEFvalid) { path.append(new Line2D.Double(ptE, ptF), false); } if (lineGHvalid) { path.append(new Line2D.Double(ptG, ptH), false); } if (lineKLvalid) { path.append(new Line2D.Double(ptK, ptL), 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 (lineIJvalid && lineKLvalid && !lineParallel && Math.abs(angleDeg) > 0.1) { newShape = new AdvancedShape(this, 4); AdvancedShape aShape = (AdvancedShape) newShape; aShape.addShape(path); // Let arcAngle be the partial section of the ellipse that represents the measured angle double startingAngle = GeomUtil.getAngleDeg(ptP, lineIJP[0]); double radius = 32; Rectangle2D arcAngleBounds = new Rectangle2D.Double(ptP.getX() - radius, ptP.getY() - radius, 2 * radius, 2 * radius); Shape arcAngle = new Arc2D.Double(arcAngleBounds, startingAngle, angleDeg, Arc2D.OPEN); double rMax = Math.min(ptP.distance(lineIJP[0]), ptP.distance(lineKLP[0])) * 2 / 3; double scalingMin = radius / rMax; aShape.addScaleInvShape(arcAngle, ptP, scalingMin, true); if (!intersectIJsegment) { aShape.addShape(new Line2D.Double(ptP, lineIJP[1]), getDashStroke(1.0f), true); } if (!intersectKLsegment) { aShape.addShape(new Line2D.Double(ptP, lineKLP[1]), getDashStroke(1.0f), true); } } else if (path.getCurrentPoint() != null) { newShape = path; } setShape(newShape, mouseEvent); updateLabel(mouseEvent, getDefaultView2d(mouseEvent)); } @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<>(); if (ANGLE.getComputed() || COMPLEMENTARY_ANGLE.getComputed()) { 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$ } } return measVal; } } return Collections.emptyList(); } @Override public boolean isShapeValid() { updateTool(); return (lineABvalid && lineCDvalid && lineEFvalid && lineGHvalid && lineIJvalid && lineKLvalid); } // ///////////////////////////////////////////////////////////////////////////////////////////////////// protected void init() { ptA = getHandlePoint(0); ptB = getHandlePoint(1); ptC = getHandlePoint(2); ptD = getHandlePoint(3); ptI = ptA; ptJ = ptC; ptE = getHandlePoint(4); ptF = getHandlePoint(5); ptG = getHandlePoint(6); ptH = getHandlePoint(7); ptK = ptE; ptL = ptG; lineIJP = lineKLP = null; lineParallel = intersectIJsegment = intersectKLsegment = false; lineABvalid = lineCDvalid = lineEFvalid = lineGHvalid = lineIJvalid = lineKLvalid = false; angleDeg = 0.0; } protected void updateTool() { init(); lineABvalid = ptA != null && ptB != null && !ptB.equals(ptA); if (lineABvalid) { ptI = GeomUtil.getMidPoint(ptA, ptB); } lineCDvalid = ptC != null && ptD != null && !ptC.equals(ptD); if (lineCDvalid) { ptJ = GeomUtil.getMidPoint(ptC, ptD); } lineIJvalid = ptI != null && ptJ != null && !ptI.equals(ptJ); lineEFvalid = ptE != null && ptF != null && !ptE.equals(ptF); if (lineEFvalid) { ptK = GeomUtil.getMidPoint(ptE, ptF); } lineGHvalid = ptG != null && ptH != null && !ptG.equals(ptH); if (lineGHvalid) { ptL = GeomUtil.getMidPoint(ptG, ptH); } else if (ptG == null && lineEFvalid) { ptL = GeomUtil.getPerpendicularPointFromLine(ptE, ptF, ptK, 1.0); // temporary before GHvalid } lineKLvalid = (ptK != null && ptL != null && !ptK.equals(ptL)); if (lineIJvalid && lineKLvalid) { double denominator = (ptJ.getX() - ptI.getX()) * (ptL.getY() - ptK.getY()) - (ptJ.getY() - ptI.getY()) * (ptL.getX() - ptK.getX()); lineParallel = MathUtil.isEqualToZero(denominator); // If denominator is zero, IJ & KL are parallel if (!lineParallel) { double numerator1 = (ptI.getY() - ptK.getY()) * (ptL.getX() - ptK.getX()) - (ptI.getX() - ptK.getX()) * (ptL.getY() - ptK.getY()); double numerator2 = (ptI.getY() - ptK.getY()) * (ptJ.getX() - ptI.getX()) - (ptI.getX() - ptK.getX()) * (ptJ.getY() - ptI.getY()); double r = numerator1 / denominator; // equ1 double s = numerator2 / denominator; // equ2 ptP = new Point2D.Double(ptI.getX() + r * (ptJ.getX() - ptI.getX()), ptI.getY() + r * (ptJ.getY() - ptI.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 IJ // If r<0, P is located on extension of JI // If s>1, P is located on extension of KL // If s<0, P is located on extension of LK lineIJP = new Point2D[3]; // order can be IJP (r>1) or JIP (r<0) or IPJ / JPI (0<=r<=1) lineKLP = new Point2D[3]; // order can be KLP (s>1) or LKP (s<0) or KPL / LPK (0<=s<=1) intersectIJsegment = (r >= 0 && r <= 1) ? true : false; // means IJPline[1].equals(P) intersectKLsegment = (s >= 0 && s <= 1) ? true : false; // means KLPline[1].equals(P) lineIJP[0] = r >= 0 ? ptI : ptJ; lineIJP[1] = r < 0 ? ptI : r > 1 ? ptJ : ptP; lineIJP[2] = r < 0 ? ptP : r > 1 ? ptP : ptJ; if (intersectIJsegment && ptP.distance(lineIJP[0]) < ptP.distance(lineIJP[2])) { Point2D switchPt = (Point2D) lineIJP[2].clone(); lineIJP[2] = (Point2D) lineIJP[0].clone(); lineIJP[0] = switchPt; } else if (ptP.distance(lineIJP[0]) < ptP.distance(lineIJP[1])) { Point2D switchPt = (Point2D) lineIJP[1].clone(); lineIJP[1] = (Point2D) lineIJP[0].clone(); lineIJP[0] = switchPt; } lineKLP[0] = s >= 0 ? ptK : ptL; lineKLP[1] = s < 0 ? ptK : s > 1 ? ptL : ptP; lineKLP[2] = s < 0 ? ptP : s > 1 ? ptP : ptL; if (intersectKLsegment && ptP.distance(lineKLP[0]) < ptP.distance(lineKLP[2])) { Point2D switchPt = (Point2D) lineKLP[2].clone(); lineKLP[2] = (Point2D) lineKLP[0].clone(); lineKLP[0] = switchPt; } else if (ptP.distance(lineKLP[0]) < ptP.distance(lineKLP[1])) { Point2D switchPt = (Point2D) lineKLP[1].clone(); lineKLP[1] = (Point2D) lineKLP[0].clone(); lineKLP[0] = switchPt; } angleDeg = GeomUtil.getSmallestRotationAngleDeg(GeomUtil.getAngleDeg(lineIJP[0], ptP, lineKLP[0])); } } } @Override public List<Measurement> getMeasurementList() { return MEASUREMENT_LIST; } }