001/* 002 * Triangle -- A concrete triangle in nD space. 003 * 004 * Copyright (C) 2015, Joseph A. Huwaldt 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public License 018 * along with this program; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 020 * Or visit: http://www.gnu.org/licenses/lgpl.html 021 */ 022package geomss.geom; 023 024import static geomss.geom.AbstractGeomElement.RESOURCES; 025import jahuwaldt.js.param.Parameter; 026import java.text.MessageFormat; 027import java.util.List; 028import java.util.Objects; 029import static java.util.Objects.requireNonNull; 030import javax.measure.converter.ConversionException; 031import javax.measure.quantity.Area; 032import javax.measure.quantity.Dimensionless; 033import javax.measure.quantity.Length; 034import javax.measure.unit.Unit; 035import javolution.context.ObjectFactory; 036import javolution.context.StackContext; 037import javolution.lang.ValueType; 038import javolution.xml.XMLFormat; 039import javolution.xml.stream.XMLStreamException; 040 041/** 042 * A concrete triangle in n-dimensional space. A triangle is represented by 043 * exactly three vertex points arranged in a counter-clockwise direction (or 044 * winding) when viewed from the "outside" (the direction the normal vector 045 * points). 046 * 047 * <p> Modified by: Joseph A. Huwaldt </p> 048 * 049 * @author Joseph A. Huwaldt, Date: August 26, 2015 050 * @version November 28, 2015 051 */ 052@SuppressWarnings({"serial", "CloneableImplementsClone"}) 053public final class Triangle extends GeomTriangle implements ValueType { 054 055 /** 056 * The corners of the triangle. 057 */ 058 private Point _p1, _p2, _p3; 059 060 /** 061 * The surface area of this triangle. 062 */ 063 private Parameter<Area> _area; 064 065 /** 066 * The surface normal vector for this triangle. 067 */ 068 private Vector<Dimensionless> _n; 069 070 071 /** 072 * Returns a <code>Triangle</code> instance with the specified corner 073 * vertices in counter-clockwise order/winding when looking down the surface normal 074 * vector. The units of the triangle will be the units of the start point. 075 * 076 * @param p1 The start (beginning) of the triangle. May not be null. 077 * @param p2 The second point in the triangle. May not be null. 078 * @param p3 The third and last point in the triangle. May not be null. 079 * @return A <code>Triangle</code> instance defined by the input points. 080 */ 081 public static Triangle valueOf(GeomPoint p1, GeomPoint p2, GeomPoint p3) { 082 083 // Check the dimensionality. 084 int numDims = GeomUtil.maxPhyDimension(p1,p2,p3); 085 if (numDims < 2) 086 throw new DimensionException(MessageFormat.format( 087 RESOURCES.getString("dimensionNotAtLeast2"), "triangle",numDims) ); 088 Unit<Length> unit = p1.getUnit(); 089 090 Triangle T = newInstance(); 091 T._p1 = p1.immutable().toDimension(numDims); 092 T._p2 = p2.immutable().to(unit).toDimension(numDims); 093 T._p3 = p3.immutable().to(unit).toDimension(numDims); 094 095 computeTriData(T); 096 097 return T; 098 } 099 100 /** 101 * Returns a <code>Triangle</code> instance with the specified corner 102 * vertices in counter-clockwise order/winding when looking down the surface normal 103 * vector. The units of the triangle will be the units of the start point. 104 * 105 * @param points A list of 3 points that form the triangle. May not be null. 106 * @return A <code>Triangle</code> instance defined by the input points. 107 */ 108 public static Triangle valueOf(List<? extends GeomPoint> points) { 109 if (points.size() < 3) 110 throw new IllegalArgumentException(RESOURCES.getString("triThreePointsOnly")); 111 112 return valueOf(points.get(0), points.get(1), points.get(2)); 113 } 114 115 /** 116 * Returns a <code>Triangle</code> instance with the specified corner 117 * vertices in counter-clockwise order/winding when looking down the surface normal 118 * vector. The units of the triangle will be the units of the start point. 119 * 120 * @param points An array of 3 points that form the triangle. May not be null. 121 * @return A <code>Triangle</code> instance defined by the input points. 122 */ 123 public static Triangle valueOf(GeomPoint[] points) { 124 if (points.length < 3) 125 throw new IllegalArgumentException(RESOURCES.getString("triThreePointsOnly")); 126 127 return valueOf(points[0], points[1], points[2]); 128 } 129 130 /** 131 * Compute some values for a triangle defined by 3 points. It is assumed that the 132 * points have been set for the Triangle "T" before calling this method. 133 */ 134 private static void computeTriData(Triangle T) { 135 Point p1 = T._p1; 136 Point p2 = T._p2; 137 Point p3 = T._p3; 138 139 StackContext.enter(); 140 try { 141 // Compute the triangle surface area and normal vector. 142 Vector<Length> v13 = p3.minus(p1).toGeomVector(); 143 Vector<Length> v12 = p2.minus(p1).toGeomVector(); 144 Vector n; 145 Parameter<Area> area; 146 int numDims = T.getPhyDimension(); 147 if (numDims > 2) { 148 // area = |v12 X v13|/2 = |n|/2 149 n = v12.cross(v13); 150 area = n.norm().times(0.5); 151 } else { 152 // Triangle area = (v1.X*v2.Y - v1.Y*v2.X)/2 153 area = GeomUtil.crossArea(v12, v13).times(0.5); 154 Parameter<Area> ZERO = Parameter.ZERO_AREA.to(area.getUnit()); 155 n = Vector.valueOf(ZERO, ZERO, area); 156 area = area.abs(); 157 } 158 159 if (area.isApproxZero()) 160 n = Vector.newInstance(p1.getPhyDimension()); 161 n = n.toUnitVector(); 162 163 // Calculate the mean vertex location and set normal vector to that location. 164 Point mp = p1.plus(p2).plus(p3).divide(3); 165 n.setOrigin(mp); 166 167 // Copy out the results. 168 T._area = StackContext.outerCopy(area); 169 T._n = StackContext.outerCopy(n); 170 171 } finally { 172 StackContext.exit(); 173 } 174 } 175 176 /** 177 * Recycles a <code>Triangle</code> instance immediately (on the stack when 178 * executing in a <code>StackContext</code>). 179 * 180 * @param instance The instance to recycle immediately. 181 */ 182 public static void recycle(Triangle instance) { 183 FACTORY.recycle(instance); 184 } 185 186 /** 187 * Return the first vertex in this triangle. 188 * 189 * @return The first vertex in this triangle. 190 */ 191 @Override 192 public Point getP1() { 193 return _p1; 194 } 195 196 /** 197 * Return the second vertex in this triangle. 198 * 199 * @return The second vertex in this triangle. 200 */ 201 @Override 202 public Point getP2() { 203 return _p2; 204 } 205 206 /** 207 * Return the third and last vertex in this triangle. 208 * 209 * @return The third and last vertex in this triangle. 210 */ 211 @Override 212 public Point getP3() { 213 return _p3; 214 } 215 216 /** 217 * Return the surface unit normal vector for this triangle. 218 * If the triangle is degenerate (zero area), then the 219 * normal vector will have zero length. 220 * 221 * @return The surface normal vector for this triangle. 222 */ 223 @Override 224 public Vector<Dimensionless> getNormal() { 225 return _n.copy(); 226 } 227 228 /** 229 * Return the surface area of one side of this triangle. 230 * The returned area is always positive, but can be zero. 231 * 232 * @return The surface area of one side of this triangle. 233 */ 234 @Override 235 public Parameter<Area> getArea() { 236 return _area; 237 } 238 239 /** 240 * Return a new triangle that is identical to this one, but with the order 241 * of the points (and the surface normal direction) reversed. 242 * 243 * @return A new Triangle that is identical to this one, but with the order 244 * of the points reversed. 245 */ 246 @Override 247 public Triangle reverse() { 248 249 // Create the reversed curve. 250 Triangle T = Triangle.valueOf(_p3, _p2, _p1); 251 252 return copyState(T); // Copy over the super-class state for this object to the new one. 253 } 254 255 /** 256 * Return the coordinate point representing the minimum bounding box corner 257 * of this geometry element (e.g.: min X, min Y, min Z). 258 * 259 * @return The minimum bounding box coordinate for this geometry element. 260 */ 261 @Override 262 public Point getBoundsMin() { 263 return _p1.min(_p2.min(_p3)); 264 } 265 266 /** 267 * Return the coordinate point representing the maximum bounding box corner 268 * (e.g.: max X, max Y, max Z). 269 * 270 * @return The maximum bounding box coordinate for this geometry element. 271 */ 272 @Override 273 public Point getBoundsMax() { 274 return _p1.max(_p2.max(_p3)); 275 } 276 277 /** 278 * Returns transformed version of this element. The returned object 279 * implements {@link geomss.geom.GeomTransform} and contains this element as 280 * a child. 281 * 282 * @param transform The transformation to apply to this geometry. May not be null. 283 * @return A new triangle that is identical to this one with the 284 * specified transformation applied. 285 * @throws DimensionException if this point is not 3D. 286 */ 287 @Override 288 public TriangleTrans getTransformed(GTransform transform) { 289 return TriangleTrans.newInstance(this, requireNonNull(transform)); 290 } 291 /** 292 * Return a copy of this Triangle converted to the specified number of 293 * physical dimensions. If the number of dimensions is greater than this 294 * element, then zeros are added to the additional dimensions. If the number 295 * of dimensions is less than this element, then the extra dimensions are 296 * simply dropped (truncated). If the new dimensions are the same as the 297 * dimension of this element, then this element is simply returned. 298 * 299 * @param newDim The dimension of the Triangle to return. 300 * @return This Triangle converted to the new dimensions. 301 */ 302 @Override 303 public Triangle toDimension(int newDim) { 304 if (getPhyDimension() == newDim) 305 return this; 306 307 Triangle T = Triangle.valueOf(_p1.toDimension(newDim), _p2.toDimension(newDim), _p3.toDimension(newDim)); 308 309 return copyState(T); // Copy over the super-class state for this object to the new one. 310 } 311 312 /** 313 * Returns the equivalent to this element but stated in the specified unit. 314 * 315 * @param unit the length unit of the element to be returned. May not be null. 316 * @return an equivalent to this element but stated in the specified unit. 317 * @throws ConversionException if the the input unit is not a length unit. 318 */ 319 @Override 320 public Triangle to(Unit<Length> unit) throws ConversionException { 321 if (unit.equals(getUnit())) 322 return this; 323 Triangle T = Triangle.valueOf(_p1.to(unit), _p2.to(unit), _p3.to(unit)); 324 return copyState(T); // Copy over the super-class state for this object to the new one. 325 } 326 327 /** 328 * Returns a copy of this Triangle instance allocated by the calling 329 * thread (possibly on the stack). 330 * 331 * @return an identical and independent copy of this Triangle. 332 */ 333 @Override 334 public Triangle copy() { 335 return copyOf(this); 336 } 337 338 /** 339 * Return a copy of this object with any transformations or subranges 340 * removed (applied). 341 * 342 * @return A copy of this object with any transformations or subranges 343 * removed (applied). 344 */ 345 @Override 346 public Triangle copyToReal() { 347 return copy(); 348 } 349 350 /** 351 * Compares this Triangle against the specified object for strict equality. 352 * 353 * @param obj the object to compare with. 354 * @return <code>true</code> if this Triangle is identical to that 355 * Triangle; <code>false</code> otherwise. 356 */ 357 @Override 358 public boolean equals(Object obj) { 359 if (this == obj) 360 return true; 361 if ((obj == null) || (obj.getClass() != this.getClass())) 362 return false; 363 364 Triangle that = (Triangle)obj; 365 return this._p1.equals(that._p1) 366 && this._p2.equals(that._p2) 367 && this._p3.equals(that._p3) 368 && super.equals(obj); 369 } 370 371 /** 372 * Returns the hash code for this parameter. 373 * 374 * @return the hash code value. 375 */ 376 @Override 377 public int hashCode() { 378 return 31*super.hashCode() + Objects.hash(_p1, _p2, _p3); 379 } 380 381 /** 382 * Holds the default XML representation for this object. 383 */ 384 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 385 protected static final XMLFormat<Triangle> XML = new XMLFormat<Triangle>(Triangle.class) { 386 387 @Override 388 public Triangle newInstance(Class<Triangle> cls, InputElement xml) throws XMLStreamException { 389 return FACTORY.object(); 390 } 391 392 @Override 393 public void read(InputElement xml, Triangle obj) throws XMLStreamException { 394 GeomTriangle.XML.read(xml, obj); // Call parent read. 395 396 obj._p1 = xml.getNext(); 397 obj._p2 = xml.getNext(); 398 obj._p3 = xml.getNext(); 399 400 computeTriData(obj); 401 } 402 403 @Override 404 public void write(Triangle obj, OutputElement xml) throws XMLStreamException { 405 GeomTriangle.XML.write(obj, xml); // Call parent write. 406 407 xml.add(obj._p1); 408 xml.add(obj._p2); 409 xml.add(obj._p3); 410 411 } 412 }; 413 414 /////////////////////// 415 // Factory creation. // 416 /////////////////////// 417 private Triangle() { 418 } 419 420 private static final ObjectFactory<Triangle> FACTORY = new ObjectFactory<Triangle>() { 421 @Override 422 protected Triangle create() { 423 return new Triangle(); 424 } 425 426 @Override 427 protected void cleanup(Triangle obj) { 428 obj.reset(); 429 obj._p1 = null; 430 obj._p2 = null; 431 obj._p3 = null; 432 } 433 }; 434 435 private static Triangle copyOf(Triangle original) { 436 Triangle o = FACTORY.object(); 437 o._p1 = original._p1.copy(); 438 o._p2 = original._p2.copy(); 439 o._p3 = original._p3.copy(); 440 computeTriData(o); 441 original.copyState(o); 442 return o; 443 } 444 445 private static Triangle newInstance() { 446 return FACTORY.object(); 447 } 448 449}