1 /* 2 * DimensionLine.java 17 sept 2007 3 * 4 * Sweet Home 3D, Copyright (c) 2007 Emmanuel PUYBARET / eTeks <info@eteks.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 package com.eteks.sweethome3d.model; 21 22 import java.awt.Shape; 23 import java.awt.geom.GeneralPath; 24 import java.awt.geom.Line2D; 25 import java.awt.geom.Point2D; 26 import java.awt.geom.Rectangle2D; 27 28 /** 29 * A dimension line in plan. 30 * @author Emmanuel Puybaret 31 */ 32 public class DimensionLine extends HomeObject implements Selectable, Elevatable { 33 /** 34 * The properties of a dimension line that may change. <code>PropertyChangeListener</code>s added 35 * to a dimension line will be notified under a property name equal to the string value of one these properties. 36 */ 37 public enum Property {X_START, Y_START, X_END, Y_END, OFFSET, LENGTH_STYLE, LEVEL} 38 39 private static final long serialVersionUID = 1L; 40 41 private float xStart; 42 private float yStart; 43 private float xEnd; 44 private float yEnd; 45 private float offset; 46 private TextStyle lengthStyle; 47 private Level level; 48 49 private transient Shape shapeCache; 50 51 /** 52 * Creates a dimension line from (<code>xStart</code>,<code>yStart</code>) 53 * to (<code>xEnd</code>, <code>yEnd</code>), with a given offset. 54 */ DimensionLine(float xStart, float yStart, float xEnd, float yEnd, float offset)55 public DimensionLine(float xStart, float yStart, float xEnd, float yEnd, float offset) { 56 this(createId("dimensionLine"), xStart, yStart, xEnd, yEnd, offset); 57 } 58 59 /** 60 * Creates a dimension line from (<code>xStart</code>,<code>yStart</code>) 61 * to (<code>xEnd</code>, <code>yEnd</code>), with a given offset. 62 * @since 6.4 63 */ DimensionLine(String id, float xStart, float yStart, float xEnd, float yEnd, float offset)64 public DimensionLine(String id, float xStart, float yStart, float xEnd, float yEnd, float offset) { 65 super(id); 66 this.xStart = xStart; 67 this.yStart = yStart; 68 this.xEnd = xEnd; 69 this.yEnd = yEnd; 70 this.offset = offset; 71 } 72 73 /** 74 * Returns the start point abscissa of this dimension line. 75 */ getXStart()76 public float getXStart() { 77 return this.xStart; 78 } 79 80 /** 81 * Sets the start point abscissa of this dimension line. Once this dimension line 82 * is updated, listeners added to this dimension line will receive a change notification. 83 */ setXStart(float xStart)84 public void setXStart(float xStart) { 85 if (xStart != this.xStart) { 86 float oldXStart = this.xStart; 87 this.xStart = xStart; 88 this.shapeCache = null; 89 firePropertyChange(Property.X_START.name(), oldXStart, xStart); 90 } 91 } 92 93 /** 94 * Returns the start point ordinate of this dimension line. 95 */ getYStart()96 public float getYStart() { 97 return this.yStart; 98 } 99 100 /** 101 * Sets the start point ordinate of this dimension line. Once this dimension line 102 * is updated, listeners added to this dimension line will receive a change notification. 103 */ setYStart(float yStart)104 public void setYStart(float yStart) { 105 if (yStart != this.yStart) { 106 float oldYStart = this.yStart; 107 this.yStart = yStart; 108 this.shapeCache = null; 109 firePropertyChange(Property.Y_START.name(), oldYStart, yStart); 110 } 111 } 112 113 /** 114 * Returns the end point abscissa of this dimension line. 115 */ getXEnd()116 public float getXEnd() { 117 return this.xEnd; 118 } 119 120 /** 121 * Sets the end point abscissa of this dimension line. Once this dimension line 122 * is updated, listeners added to this dimension line will receive a change notification. 123 */ setXEnd(float xEnd)124 public void setXEnd(float xEnd) { 125 if (xEnd != this.xEnd) { 126 float oldXEnd = this.xEnd; 127 this.xEnd = xEnd; 128 this.shapeCache = null; 129 firePropertyChange(Property.X_END.name(), oldXEnd, xEnd); 130 } 131 } 132 133 /** 134 * Returns the end point ordinate of this dimension line. 135 */ getYEnd()136 public float getYEnd() { 137 return this.yEnd; 138 } 139 140 /** 141 * Sets the end point ordinate of this dimension line. Once this dimension line 142 * is updated, listeners added to this dimension line will receive a change notification. 143 */ setYEnd(float yEnd)144 public void setYEnd(float yEnd) { 145 if (yEnd != this.yEnd) { 146 float oldYEnd = this.yEnd; 147 this.yEnd = yEnd; 148 this.shapeCache = null; 149 firePropertyChange(Property.Y_END.name(), oldYEnd, yEnd); 150 } 151 } 152 153 /** 154 * Returns the offset of this dimension line. 155 */ getOffset()156 public float getOffset() { 157 return this.offset; 158 } 159 160 /** 161 * Sets the offset of this dimension line. Once this dimension line 162 * is updated, listeners added to this dimension line will receive a change notification. 163 */ setOffset(float offset)164 public void setOffset(float offset) { 165 if (offset != this.offset) { 166 float oldOffset = this.offset; 167 this.offset = offset; 168 this.shapeCache = null; 169 firePropertyChange(Property.OFFSET.name(), oldOffset, offset); 170 } 171 } 172 173 /** 174 * Returns the length of this dimension line. 175 */ getLength()176 public float getLength() { 177 return (float)Point2D.distance(getXStart(), getYStart(), getXEnd(), getYEnd()); 178 } 179 180 /** 181 * Returns the text style used to display dimension line length. 182 */ getLengthStyle()183 public TextStyle getLengthStyle() { 184 return this.lengthStyle; 185 } 186 187 /** 188 * Sets the text style used to display dimension line length. 189 * Once this dimension line is updated, listeners added to it will receive a change notification. 190 */ setLengthStyle(TextStyle lengthStyle)191 public void setLengthStyle(TextStyle lengthStyle) { 192 if (lengthStyle != this.lengthStyle) { 193 TextStyle oldLengthStyle = this.lengthStyle; 194 this.lengthStyle = lengthStyle; 195 firePropertyChange(Property.LENGTH_STYLE.name(), oldLengthStyle, lengthStyle); 196 } 197 } 198 199 /** 200 * Returns the level which this dimension line belongs to. 201 * @since 3.4 202 */ getLevel()203 public Level getLevel() { 204 return this.level; 205 } 206 207 /** 208 * Sets the level of this dimension line. Once this dimension line is updated, 209 * listeners added to this dimension line will receive a change notification. 210 * @since 3.4 211 */ setLevel(Level level)212 public void setLevel(Level level) { 213 if (level != this.level) { 214 Level oldLevel = this.level; 215 this.level = level; 216 firePropertyChange(Property.LEVEL.name(), oldLevel, level); 217 } 218 } 219 220 /** 221 * Returns <code>true</code> if this dimension line is at the given <code>level</code> 222 * or at a level with the same elevation and a smaller elevation index. 223 * @since 3.4 224 */ isAtLevel(Level level)225 public boolean isAtLevel(Level level) { 226 return this.level == level 227 || this.level != null && level != null 228 && this.level.getElevation() == level.getElevation() 229 && this.level.getElevationIndex() < level.getElevationIndex(); 230 231 } 232 233 /** 234 * Returns the points of the rectangle surrounding 235 * this dimension line and its extension lines. 236 * @return an array of the 4 (x,y) coordinates of the rectangle. 237 */ getPoints()238 public float [][] getPoints() { 239 double angle = Math.atan2(this.yEnd - this.yStart, this.xEnd - this.xStart); 240 float dx = (float)-Math.sin(angle) * this.offset; 241 float dy = (float)Math.cos(angle) * this.offset; 242 243 return new float [] [] {{this.xStart, this.yStart}, 244 {this.xStart + dx, this.yStart + dy}, 245 {this.xEnd + dx, this.yEnd + dy}, 246 {this.xEnd, this.yEnd}}; 247 } 248 249 /** 250 * Returns <code>true</code> if this dimension line intersects 251 * with the horizontal rectangle which opposite corners are at points 252 * (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>). 253 */ intersectsRectangle(float x0, float y0, float x1, float y1)254 public boolean intersectsRectangle(float x0, float y0, float x1, float y1) { 255 Rectangle2D rectangle = new Rectangle2D.Float(x0, y0, 0, 0); 256 rectangle.add(x1, y1); 257 return getShape().intersects(rectangle); 258 } 259 260 /** 261 * Returns <code>true</code> if this dimension line contains 262 * the point at (<code>x</code>, <code>y</code>) 263 * with a given <code>margin</code>. 264 */ containsPoint(float x, float y, float margin)265 public boolean containsPoint(float x, float y, float margin) { 266 return containsShapeAtWithMargin(getShape(), x, y, margin); 267 } 268 269 /** 270 * Returns <code>true</code> if the middle point of this dimension line 271 * is the point at (<code>x</code>, <code>y</code>) 272 * with a given <code>margin</code>. 273 */ isMiddlePointAt(float x, float y, float margin)274 public boolean isMiddlePointAt(float x, float y, float margin) { 275 double angle = Math.atan2(this.yEnd - this.yStart, this.xEnd - this.xStart); 276 float dx = (float)-Math.sin(angle) * this.offset; 277 float dy = (float)Math.cos(angle) * this.offset; 278 float xMiddle = (this.xStart + this.xEnd) / 2 + dx; 279 float yMiddle = (this.yStart + this.yEnd) / 2 + dy; 280 return Math.abs(x - xMiddle) <= margin && Math.abs(y - yMiddle) <= margin; 281 } 282 283 /** 284 * Returns <code>true</code> if the extension line at the start of this dimension line 285 * contains the point at (<code>x</code>, <code>y</code>) 286 * with a given <code>margin</code> around the extension line. 287 */ containsStartExtensionLinetAt(float x, float y, float margin)288 public boolean containsStartExtensionLinetAt(float x, float y, float margin) { 289 double angle = Math.atan2(this.yEnd - this.yStart, this.xEnd - this.xStart); 290 Line2D startExtensionLine = new Line2D.Float(this.xStart, this.yStart, 291 this.xStart + (float)-Math.sin(angle) * this.offset, 292 this.yStart + (float)Math.cos(angle) * this.offset); 293 return containsShapeAtWithMargin(startExtensionLine, x, y, margin); 294 } 295 296 /** 297 * Returns <code>true</code> if the extension line at the end of this dimension line 298 * contains the point at (<code>x</code>, <code>y</code>) 299 * with a given <code>margin</code> around the extension line. 300 */ containsEndExtensionLineAt(float x, float y, float margin)301 public boolean containsEndExtensionLineAt(float x, float y, float margin) { 302 double angle = Math.atan2(this.yEnd - this.yStart, this.xEnd - this.xStart); 303 Line2D endExtensionLine = new Line2D.Float(this.xEnd, this.yEnd, 304 this.xEnd + (float)-Math.sin(angle) * this.offset, 305 this.yEnd + (float)Math.cos(angle) * this.offset); 306 return containsShapeAtWithMargin(endExtensionLine, x, y, margin); 307 } 308 309 /** 310 * Returns <code>true</code> if <code>shape</code> contains 311 * the point at (<code>x</code>, <code>y</code>) 312 * with a given <code>margin</code>. 313 */ containsShapeAtWithMargin(Shape shape, float x, float y, float margin)314 private boolean containsShapeAtWithMargin(Shape shape, float x, float y, float margin) { 315 if (margin == 0) { 316 return shape.contains(x, y); 317 } else { 318 return shape.intersects(x - margin, y - margin, 2 * margin, 2 * margin); 319 } 320 } 321 322 /** 323 * Returns the shape matching this dimension line. 324 */ getShape()325 private Shape getShape() { 326 if (this.shapeCache == null) { 327 // Create the rectangle that matches piece bounds 328 double angle = Math.atan2(this.yEnd - this.yStart, this.xEnd - this.xStart); 329 float dx = (float)-Math.sin(angle) * this.offset; 330 float dy = (float)Math.cos(angle) * this.offset; 331 332 GeneralPath dimensionLineShape = new GeneralPath(); 333 // Append dimension line 334 dimensionLineShape.append(new Line2D.Float(this.xStart + dx, this.yStart + dy, this.xEnd + dx, this.yEnd + dy), false); 335 // Append extension lines 336 dimensionLineShape.append(new Line2D.Float(this.xStart, this.yStart, this.xStart + dx, this.yStart + dy), false); 337 dimensionLineShape.append(new Line2D.Float(this.xEnd, this.yEnd, this.xEnd + dx, this.yEnd + dy), false); 338 // Cache shape 339 this.shapeCache = dimensionLineShape; 340 } 341 return this.shapeCache; 342 } 343 344 /** 345 * Moves this dimension line of (<code>dx</code>, <code>dy</code>) units. 346 */ move(float dx, float dy)347 public void move(float dx, float dy) { 348 setXStart(getXStart() + dx); 349 setYStart(getYStart() + dy); 350 setXEnd(getXEnd() + dx); 351 setYEnd(getYEnd() + dy); 352 } 353 354 /** 355 * Returns a clone of this dimension line. 356 */ 357 @Override clone()358 public DimensionLine clone() { 359 DimensionLine clone = (DimensionLine)super.clone(); 360 clone.level = null; 361 return clone; 362 } 363 } 364