001/** 002 * TFISurface -- A transfinite interpolation surface defined by four boundary curves. 003 * 004 * Copyright (C) 2013-2017, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 */ 018package geomss.geom; 019 020import geomss.geom.nurbs.BasicNurbsSurface; 021import geomss.geom.nurbs.NurbsSurface; 022import geomss.geom.nurbs.SurfaceFactory; 023import jahuwaldt.js.param.Parameter; 024import java.text.MessageFormat; 025import java.util.List; 026import java.util.Objects; 027import static java.util.Objects.isNull; 028import static java.util.Objects.requireNonNull; 029import javax.measure.converter.ConversionException; 030import javax.measure.quantity.Length; 031import javax.measure.unit.Unit; 032import javax.swing.event.ChangeListener; 033import javolution.context.ObjectFactory; 034import javolution.context.StackContext; 035import javolution.lang.Immutable; 036import javolution.text.Text; 037import javolution.text.TextBuilder; 038import javolution.util.FastTable; 039import javolution.xml.XMLFormat; 040import javolution.xml.stream.XMLStreamException; 041 042/** 043 * Represents a bi-linearly blended transfinite interpolation (TFI) or Coons patch surface 044 * defined from four boundary curves. The boundary curves must meet at each of the 4 045 * corners of the patch. It is assumed that all the input curves are oriented in an 046 * appropriate direction and order to create the desired TFI surface. 047 * 048 * <p> Modified by: Joseph A. Huwaldt </p> 049 * 050 * @author Joseph A. Huwaldt, Date: April 17, 2013 051 * @version October 29, 2017 052 */ 053@SuppressWarnings({"serial", "CloneableImplementsClone"}) 054public class TFISurface extends AbstractSurface<TFISurface> { 055 056 // The four boundary curves 057 private Curve s0crv, s1crv; 058 private Curve t0crv, t1crv; 059 060 /** 061 * Reference to a change listener for this object's child curves. 062 */ 063 private ChangeListener _childChangeListener = new ForwardingChangeListener(this); 064 065 /** 066 * Return a TFISurface made up of the specified boundary {@link Curve} objects. It is 067 * assumed that the boundary curves are ordered properly and that the end points are 068 * coincident. No check for this is made. 069 * 070 * @param s0 The curve that serves as the s=0 boundary of this surface. May not be 071 * null. 072 * @param t0 The curve that serves as the t=0 boundary of this surface. May not be 073 * null. 074 * @param s1 The curve that serves as the s=1 boundary of this surface. May not be 075 * null. 076 * @param t1 The curve that serves as the t=1 boundary of this surface. May not be 077 * null. 078 * @return The TFISurface with the specified boundary curves. 079 */ 080 public static TFISurface newInstance(Curve s0, Curve t0, Curve s1, Curve t1) { 081 return newInstance(null, s0, t0, s1, t1); 082 } 083 084 /** 085 * Return a TFISurface made up of the specified boundary {@link Curve} objects. It is 086 * assumed that the boundary curves are ordered properly and that the end points are 087 * coincident. No check for this is made. 088 * 089 * @param name The name to be assigned to this surface (may be <code>null</code>). 090 * @param s0 The curve that serves as the s=0 boundary of this surface. May not be 091 * null. 092 * @param t0 The curve that serves as the t=0 boundary of this surface. May not be 093 * null. 094 * @param s1 The curve that serves as the s=1 boundary of this surface. May not be 095 * null. 096 * @param t1 The curve that serves as the t=1 boundary of this surface. May not be 097 * null. 098 * @return The TFISurface with the specified boundary curves. 099 */ 100 public static TFISurface newInstance(String name, Curve s0, Curve t0, Curve s1, Curve t1) { 101 102 // Make sure that each input curve is the same dimension. 103 int numDims = GeomUtil.maxPhyDimension(s0, t0, s1, t1); 104 s0 = s0.toDimension(numDims); 105 s1 = s1.toDimension(numDims); 106 t0 = t0.toDimension(numDims); 107 t1 = t1.toDimension(numDims); 108 109 // Convert all the curves to the same units. 110 Unit<Length> unit = s0.getUnit(); 111 s1 = s1.to(unit); 112 t0 = t0.to(unit); 113 t1 = t1.to(unit); 114 115 TFISurface o = FACTORY.object(); 116 o.setName(name); 117 o.s0crv = s0; 118 o.s1crv = s1; 119 o.t0crv = t0; 120 o.t1crv = t1; 121 122 if (!(s0 instanceof Immutable)) 123 s0.addChangeListener(o._childChangeListener); 124 if (!(s1 instanceof Immutable)) 125 s1.addChangeListener(o._childChangeListener); 126 if (!(t0 instanceof Immutable)) 127 t0.addChangeListener(o._childChangeListener); 128 if (!(t1 instanceof Immutable)) 129 t1.addChangeListener(o._childChangeListener); 130 131 return o; 132 } 133 134 /** 135 * Returns the number of physical dimensions of the geometry element. This 136 * implementation always returns the physical dimension of the underlying 137 * {@link Curve} objects. 138 * 139 * @return The number of physical dimensions of the geometry element. 140 */ 141 @Override 142 public int getPhyDimension() { 143 return s0crv.getPhyDimension(); 144 } 145 146 /** 147 * Return the equivalent of this surface converted to the specified number of physical 148 * dimensions. If the number of dimensions is greater than this element, then zeros 149 * are added to the additional dimensions. If the number of dimensions is less than 150 * this element, then the extra dimensions are simply dropped (truncated). If the new 151 * dimensions are the same as the dimension of this element, then this element is 152 * simply returned. 153 * 154 * @param newDim The dimension of the surface to return. 155 * @return This surface converted to the new dimensions. 156 */ 157 @Override 158 public TFISurface toDimension(int newDim) { 159 if (getPhyDimension() == newDim) 160 return this; 161 162 // Convert the curves. 163 Curve ns0crv = s0crv.toDimension(newDim); 164 Curve ns1crv = s1crv.toDimension(newDim); 165 Curve nt0crv = t0crv.toDimension(newDim); 166 Curve nt1crv = t1crv.toDimension(newDim); 167 168 TFISurface srf = TFISurface.newInstance(getName(), ns0crv, ns1crv, nt0crv, nt1crv); 169 copyState(srf); 170 171 return srf; 172 } 173 174 /** 175 * Returns the number of child-elements that make up this geometry element. This 176 * implementation always returns 4 for the four boundary curves. 177 * 178 * @return The number of child-elements that make up this geometry element. 179 */ 180 @Override 181 public int size() { 182 return 4; 183 } 184 185 /** 186 * Calculate a point on the surface for the given parametric position on the surface. 187 * 188 * @param s 1st parametric dimension distance to calculate a point for (0.0 to 1.0 189 * inclusive). 190 * @param t 2nd parametric dimension distance to calculate a point for (0.0 to 1.0 191 * inclusive). 192 * @return The calculated point on the surface at the specified parameter values. 193 * @throws IllegalArgumentException if there is any problem with the parameter values. 194 */ 195 @Override 196 public Point getRealPoint(double s, double t) { 197 validateParameter(s, t); 198 StackContext.enter(); 199 try { 200 // Reference: http://en.wikipedia.org/wiki/Coons_surface 201 // Lt = b0(t)*p(s,0) + b1(t)*p(s,1) 202 // Ls = c0(s)*p(0,t) + c1(s)*p(1,t) 203 // B = p00*c0(s)*b0(t) + p01*c1(s)*b0(t) + p10*c0(s)*b1(t) + p11*b1(t)*c1(s) 204 // p = Lt + Ls - B 205 206 double b0t = 1 - t; 207 double b1t = t; 208 double c0s = 1 - s; 209 double c1s = s; 210 Point ps0 = t0crv.getRealPoint(s); 211 Point ps1 = t1crv.getRealPoint(s); 212 Point p0t = s0crv.getRealPoint(t); 213 Point p1t = s1crv.getRealPoint(t); 214 215 Point Lt = ps0.times(b0t).plus(ps1.times(b1t)); 216 Point Ls = p0t.times(c0s).plus(p1t.times(c1s)); 217 Point p00 = t0crv.getRealPoint(0); 218 Point p01 = t0crv.getRealPoint(1); 219 Point p10 = t1crv.getRealPoint(0); 220 Point p11 = t1crv.getRealPoint(1); 221 Point B = p00.times(c0s * b0t).plus(p01.times(c1s * b0t)).plus(p10.times(c0s * b1t)).plus(p11.times(c1s * b1t)); 222 223 // Now sum up the pieces. 224 Point p = Lt.plus(Ls).minus(B); 225 return StackContext.outerCopy(p); 226 227 } finally { 228 StackContext.exit(); 229 } 230 } 231 232 /** 233 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 234 * respect to parametric s-position on the surface for the given parametric position 235 * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>. 236 * <p> 237 * Example:<br> 238 * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br> 239 * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc. 240 * </p> 241 * 242 * @param s 1st parametric dimension distance to calculate derivative for (0.0 to 243 * 1.0 inclusive). 244 * @param t 2nd parametric dimension distance to calculate derivative for (0.0 to 245 * 1.0 inclusive). 246 * @param grade The maximum grade to calculate the u-derivatives for (1=1st 247 * derivative, 2=2nd derivative, etc) 248 * @param scaled Pass <code>true</code> for properly scaled derivatives or 249 * <code>false</code> if the magnitude of the derivative vector is not 250 * required -- this sometimes results in faster calculation times. 251 * @return A list of s-derivatives up to the specified grade of the surface at the 252 * specified parametric position. 253 * @throws IllegalArgumentException if the grade is < 0 or the parameter values are 254 * invalid. 255 */ 256 @Override 257 public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) { 258 validateParameter(s, t); 259 if (grade < 0) 260 throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr")); 261 262 // Check for easy cases. 263 if (parNearStart(t, TOL_ST)) 264 return t0crv.getSDerivatives(s, grade); 265 else if (parNearEnd(t, TOL_ST)) 266 return t1crv.getSDerivatives(s, grade); 267 268 /* 269 P = Lt + Ls - B 270 Lt = b0(t)*P(s,0) + b1(t)*P(s,1) 271 Ls = c0(s)*P(0,t) + c1(s)*P(1,t) 272 B = P(0,0)*c0(s)*b0(t) + P(0,1)*c1(s)*b0(t) + P(1,0)*c0(s)*b1(t) + P(1,1)*b1(t)*c1(s) 273 So: 274 dP/ds = d(Lt)/ds + d(Ls)/ds - d(B)/ds 275 Ps = dP/ds, Lts = d(Lt)/ds, Lss = d(Ls)/ds, and Bs = d(B)/ds 276 Ps = Lts + Lss - Bs 277 Lts = b0(t)*Ps(s,0) + b1(t)*Ps(s,1) 278 Lss = P(0,t)*d(c0(s))/ds + P(1,t)*d(c1(s))/ds 279 c0ss = d(c0(s))/ds and c1ss = d(c1(s))/ds 280 Lss = P(0,t)*c0ss + P(1,t)*c1ss 281 Bs = P(0,0)*b0(t)*c0ss + P(0,1)*b0(t)*c1ss + P(1,0)*b1(t)*c0ss + P(1,1)*b1(t)*c1ss 282 283 For higher derivatives, c0ss and c1ss = 0. 284 */ 285 FastTable<Vector<Length>> ders = FastTable.newInstance(); 286 287 // Get the actual point on the surface. 288 Point Pst = getRealPoint(s, t); 289 ders.add(Pst.toGeomVector()); 290 291 StackContext.enter(); 292 try { 293 double b0t = 1 - t; 294 double b1t = t; 295 double c0ss = -1; 296 double c1ss = 1; 297 298 // Determine derivatives in the S-direction at the t=0 and t=1 boundaries. 299 // These are: Ps(s,0) and Ps(s,1) 300 List<Vector<Length>> Pss0 = t0crv.getSDerivatives(s, grade); 301 List<Vector<Length>> Pss1 = t1crv.getSDerivatives(s, grade); 302 303 // Determine the points on the s=0 and s=1 boundaries. 304 // These are: P(0,t) and P(1,t) 305 Point P0t = s0crv.getRealPoint(t); 306 Point P1t = s1crv.getRealPoint(t); 307 308 // Get the corner points. 309 Point P00 = t0crv.getRealPoint(0); 310 Point P01 = t0crv.getRealPoint(1); 311 Point P10 = t1crv.getRealPoint(0); 312 Point P11 = t1crv.getRealPoint(1); 313 314 // Combine the different elements to get the total interpolated derivatives. 315 for (int g = 1; g <= grade; ++g) { 316 Vector<Length> Pss0v = Pss0.get(g); 317 Vector<Length> Pss1v = Pss1.get(g); 318 Vector<Length> Lts = Pss0v.times(b0t).plus(Pss1v.times(b1t)); 319 Point Lss = P0t.times(c0ss).plus(P1t.times(c1ss)); 320 Point Bs = P00.times(c0ss * b0t); 321 Bs = Bs.plus(P01.times(c1ss * b0t)); 322 Bs = Bs.plus(P10.times(c0ss * b1t)); 323 Bs = Bs.plus(P11.times(c1ss * b1t)); 324 Vector<Length> d = Lts.plus(Lss.minus(Bs).toGeomVector()); 325 ders.add(StackContext.outerCopy(d)); 326 327 // Prepare for next grade (derivative). 328 c0ss = 0; 329 c1ss = 0; 330 } 331 332 } finally { 333 StackContext.exit(); 334 } 335 336 // Set the origin for the derivative vectors. 337 for (int i = 1; i <= grade; ++i) { 338 ders.get(i).setOrigin(Pst); 339 } 340 341 return ders; 342 } 343 344 /** 345 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 346 * respect to parametric t-position on the surface for the given parametric position 347 * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>. 348 * <p> 349 * Example:<br> 350 * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br> 351 * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc. 352 * </p> 353 * 354 * @param s 1st parametric dimension distance to calculate derivative for (0.0 to 355 * 1.0 inclusive). 356 * @param t 2nd parametric dimension distance to calculate derivative for (0.0 to 357 * 1.0 inclusive). 358 * @param grade The maximum grade to calculate the v-derivatives for (1=1st 359 * derivative, 2=2nd derivative, etc) 360 * @param scaled Pass <code>true</code> for properly scaled derivatives or 361 * <code>false</code> if the magnitude of the derivative vector is not 362 * required -- this sometimes results in faster calculation times. 363 * @return A list of t-derivatives up to the specified grade of the surface at the 364 * specified parametric position. 365 * @throws IllegalArgumentException if the grade is < 0 or the parameter values are 366 * invalid. 367 */ 368 @Override 369 public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) { 370 validateParameter(s, t); 371 if (grade < 0) 372 throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr")); 373 374 // Check for easy cases. 375 if (parNearStart(s, TOL_ST)) 376 return s0crv.getSDerivatives(t, grade); 377 else if (parNearEnd(s, TOL_ST)) 378 return s1crv.getSDerivatives(t, grade); 379 380 /* 381 P = Lt + Ls - B 382 Lt = b0(t)*P(s,0) + b1(t)*P(s,1) 383 Ls = c0(s)*P(0,t) + c1(s)*P(1,t) 384 B = P(0,0)*c0(s)*b0(t) + P(0,1)*c1(s)*b0(t) + P(1,0)*c0(s)*b1(t) + P(1,1)*b1(t)*c1(s) 385 So: 386 dP/dt = d(Lt)/dt + d(Ls)/dt - d(B)/dt 387 Pt = dP/dt, Ltt = d(Lt)/dt, Lst = d(Ls)/dt, and Bt = d(B)/dt 388 Pt = Ltt + Lst - Bt 389 Ltt = d(b0(t))/dt*P(s,0) + d(b1(t))/dt*P(s,1) 390 b0tt = d(b0(t))/dt and b1tt = d(b1(t))/dt 391 Ltt = b0tt*P(s,0) + b1tt*P(s,1) 392 Lst = Pt(0,t)*c0(s) + Pt(1,t)*c1(s) 393 Bt = P(0,0)*b0tt*c0(s) + P(0,1)*b0tt*c1(s) + P(1,0)*b1tt*c0(s) + P(1,1)*b1tt*c1(s) 394 395 For higher derivatives, b0tt and b1tt = 0. 396 */ 397 FastTable<Vector<Length>> ders = FastTable.newInstance(); 398 399 // Get the actual point on the surface. 400 Point Pst = getRealPoint(s, t); 401 ders.add(Pst.toGeomVector()); 402 403 StackContext.enter(); 404 try { 405 double b0tt = -1; 406 double b1tt = 1; 407 double c0s = 1 - s; 408 double c1s = s; 409 410 // Determine derivatives in the T-direction at the s=0 and s=1 boundaries. 411 // These are: Pt(0,t) and Pt(1,t) 412 List<Vector<Length>> Pt0t = s0crv.getSDerivatives(t, grade); 413 List<Vector<Length>> Pt1t = s1crv.getSDerivatives(t, grade); 414 415 // Determine the points on the t=0 and t=1 boundaries. 416 // These are: P(s,0) and P(s,1) 417 Point Ps0 = t0crv.getRealPoint(s); 418 Point Ps1 = t1crv.getRealPoint(s); 419 420 // Get the corner points. 421 Point P00 = t0crv.getRealPoint(0); 422 Point P01 = t0crv.getRealPoint(1); 423 Point P10 = t1crv.getRealPoint(0); 424 Point P11 = t1crv.getRealPoint(1); 425 426 // Combine the different elements to get the total interpolated derivatives. 427 for (int g = 1; g <= grade; ++g) { 428 Vector<Length> Pt0tv = Pt0t.get(g); 429 Vector<Length> Pt1tv = Pt1t.get(g); 430 Vector<Length> Ltt = Pt0tv.times(c0s).plus(Pt1tv.times(c1s)); 431 Point Lst = Ps0.times(b0tt).plus(Ps1.times(b1tt)); 432 Point Bt = P00.times(c0s * b0tt); 433 Bt = Bt.plus(P01.times(c1s * b0tt)); 434 Bt = Bt.plus(P10.times(c0s * b1tt)); 435 Bt = Bt.plus(P11.times(c1s * b1tt)); 436 Vector<Length> d = Ltt.plus(Lst.minus(Bt).toGeomVector()); 437 ders.add(StackContext.outerCopy(d)); 438 439 // Prepare for next grade (derivative). 440 b0tt = 0; 441 b1tt = 0; 442 } 443 444 } finally { 445 StackContext.exit(); 446 } 447 448 // Set the origin for the derivative vectors. 449 for (int i = 1; i <= grade; ++i) { 450 ders.get(i).setOrigin(Pst); 451 } 452 453 return ders; 454 } 455 456 /** 457 * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the 458 * specified position on this surface. 459 * 460 * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to 461 * 1.0 inclusive). 462 * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to 463 * 1.0 inclusive). 464 * @return The twist vector of this surface at the specified parametric position. 465 * @throws IllegalArgumentException if the parameter values are invalid. 466 */ 467 @Override 468 public Vector<Length> getTwistVector(double s, double t) { 469 validateParameter(s, t); 470 471 /* 472 P = Lt + Ls - B 473 Lt = b0(t)*P(s,0) + b1(t)*P(s,1) 474 Ls = c0(s)*P(0,t) + c1(s)*P(1,t) 475 B = P(0,0)*c0(s)*b0(t) + P(0,1)*c1(s)*b0(t) + P(1,0)*c0(s)*b1(t) + P(1,1)*b1(t)*c1(s) 476 477 So: 478 dP/ds = d(Lt)/ds + d(Ls)/ds - d(B)/ds 479 Ps = dP/ds, Lts = d(Lt)/ds, Lss = d(Ls)/ds, and Bs = d(B)/ds 480 Ps = Lts + Lss - Bs 481 Lts = b0(t)*Ps(s,0) + b1(t)*Ps(s,1) 482 Lss = P(0,t)*d(c0(s))/ds + P(1,t)*d(c1(s))/ds 483 c0ss = d(c0(s))/ds and c1ss = d(c1(s))/ds 484 Lss = P(0,t)*c0ss + P(1,t)*c1ss 485 Bs = P(0,0)*b0(t)*c0ss + P(0,1)*b0(t)*c1ss + P(1,0)*b1(t)*c0ss + P(1,1)*b1(t)*c1ss 486 487 So: 488 d^2P/(ds*dt) = d(dP/ds)/dt = d(Ps)/dt = dPsdt = d(Lts)/dt + d(Lss)/dt - d(Bs)/dt = Ltst + Lsst - Bst 489 d(Lts)/dt = Ltst = d(b0(t))/dt*Ps(s,0) + d(b1(t))/dt*Ps(s,1) 490 b0tt = d(b0(t))/dt and b1tt = d(b1(t))/dt 491 Ltst = Ps(s,0)*b0tt + Ps(s,1)*b1tt 492 d(Lss)/dt = Lsst = Pt(0,t)*c0ss + Pt(1,t)*c1ss 493 d(Bs)/dt = Bst = P(0,0)*b0tt*c0ss + P(0,1)*b0tt*c1ss + P(1,0)*b1tt*c0ss + P(1,1)*b1tt*c1ss 494 495 */ 496 StackContext.enter(); 497 try { 498 Unit<Length> unit = getUnit(); 499 double b0tt = -1; 500 double b1tt = 1; 501 double c0ss = -1; 502 double c1ss = 1; 503 504 // Determine derivatives in the S-direction at the t=0 and t=1 boundaries. 505 // These are: Ps(s,0) and Ps(s,1) 506 Vector<Length> Pss0 = t0crv.getSDerivative(s, 1); 507 Vector<Length> Pss1 = t1crv.getSDerivative(s, 1); 508 Point nullPnt = Point.newInstance(getPhyDimension(), unit); 509 Pss0.setOrigin(nullPnt); 510 Pss1.setOrigin(nullPnt); 511 512 // Determine derivatives in the T-direction at the s=0 and s=1 boundaries. 513 // These are: Pt(0,t) and Pt(1,t) 514 Vector<Length> Pt0t = s0crv.getSDerivative(t, 1); 515 Vector<Length> Pt1t = s1crv.getSDerivative(t, 1); 516 Pt0t.setOrigin(nullPnt); 517 Pt1t.setOrigin(nullPnt); 518 519 // Get the corner points. 520 Point P00 = t0crv.getRealPoint(0); 521 Point P01 = t0crv.getRealPoint(1); 522 Point P10 = t1crv.getRealPoint(0); 523 Point P11 = t1crv.getRealPoint(1); 524 525 // Get the actual point on the surface. 526 Point Pst = getRealPoint(s, t); 527 528 // Ltst = Ps(s,0)*b0tt + Ps(s,1)*b1tt 529 Vector<Length> Ltst = Pss0.times(b0tt).plus(Pss1.times(b1tt)); 530 531 // Lsst = Pt(0,t)*c0ss + Pt(1,t)*c1ss 532 Vector<Length> Lsst = Pt0t.times(c0ss).plus(Pt1t.times(c1ss)); 533 534 // Bst = P(0,0)*b0tt*c0ss + P(0,1)*b0tt*c1ss + P(1,0)*b1tt*c0ss + P(1,1)*b1tt*c1ss 535 Point Bst = P00.times(b0tt * c0ss); 536 Bst = Bst.plus(P01.times(b0tt * c1ss)); 537 Bst = Bst.plus(P10.times(b1tt * c0ss)); 538 Bst = Bst.plus(P11.times(b1tt * c1ss)); 539 540 // dPsdt = Ltst + Lsst - Bst 541 Vector<Length> dPsdt = Ltst.plus(Lsst).minus(Bst.toGeomVector()); 542 dPsdt.setOrigin(Pst); 543 544 return StackContext.outerCopy(dPsdt); 545 546 } finally { 547 StackContext.exit(); 548 } 549 } 550 551 /** 552 * Return a new surface that is identical to this one, but with the S-parameterization 553 * reversed. 554 * 555 * @return A new surface that is identical to this one, but with the 556 * S-parameterization reversed. 557 * @see #reverseT 558 */ 559 @Override 560 public TFISurface reverseS() { 561 Curve s0 = s0crv.reverse(); 562 Curve s1 = s1crv.reverse(); 563 Curve t0 = t1crv; 564 Curve t1 = t0crv; 565 566 TFISurface srf = TFISurface.newInstance(s0, s1, t0, t1); 567 copyState(srf); // Copy over the super-class state for this surface to the new one. 568 569 return srf; 570 } 571 572 /** 573 * Return a new surface that is identical to this one, but with the T-parameterization 574 * reversed. 575 * 576 * @return A new surface that is identical to this one, but with the 577 * T-parameterization reversed. 578 * @see #reverseS 579 */ 580 @Override 581 public TFISurface reverseT() { 582 Curve s0 = s1crv; 583 Curve s1 = s0crv; 584 Curve t0 = t0crv.reverse(); 585 Curve t1 = t1crv.reverse(); 586 587 TFISurface srf = TFISurface.newInstance(s0, s1, t0, t1); 588 copyState(srf); // Copy over the super-class state for this surface to the new one. 589 590 return srf; 591 } 592 593 /** 594 * Return a new surface that is identical to this one but with the transpose of the 595 * parameterization of this surface. The S and T directions will be swapped. 596 * 597 * @return A new surface that is identical to this one but with the transpose of the 598 * parameterization of this surface. 599 * @see #reverseT 600 * @see #reverseS 601 */ 602 @Override 603 public TFISurface transpose() { 604 Curve s0 = t0crv; 605 Curve s1 = t1crv; 606 Curve t0 = s0crv; 607 Curve t1 = s1crv; 608 609 TFISurface srf = TFISurface.newInstance(s0, s1, t0, t1); 610 copyState(srf); // Copy over the super-class state for this surface to the new one. 611 612 return srf; 613 } 614 615 /** 616 * Split this {@link SubrangeSurface} at the specified parametric S-position returning 617 * a list containing two new surfaces (a lower surface with smaller S-parametric 618 * positions than "s" and an upper surface with larger S-parametric positions). 619 * 620 * @param s The S-parametric position where this surface should be split (must not be 621 * 0 or 1!). 622 * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper 623 * surface. 624 */ 625 @Override 626 public GeomList<TFISurface> splitAtS(double s) { 627 validateParameter(s, 0); 628 if (parNearEnds(s, TOL_ST)) 629 throw new IllegalArgumentException(MessageFormat.format( 630 RESOURCES.getString("canNotSplitAtEnds"), "surface")); 631 632 // Get the dividing curve from the NURBS representation. 633 // TODO: Figure out how to do this direclty on a TFI. 634 BasicNurbsSurface nsrf = createNurbsSurface(); 635 SubrangeCurve splitC = nsrf.getSCurve(s); 636 637 // Split the edge curves. 638 GeomList<Curve> t0crvs = t0crv.splitAt(s); 639 GeomList<Curve> t1crvs = t1crv.splitAt(s); 640 641 // Create the new surface pieces. 642 TFISurface srfL = TFISurface.newInstance(s0crv, t0crvs.get(0), splitC, t1crvs.get(0)); 643 TFISurface srfU = TFISurface.newInstance(splitC, t0crvs.get(1), s1crv, t1crvs.get(1)); 644 645 // Create the output list. 646 GeomList<TFISurface> output = GeomList.valueOf(srfL, srfU); 647 return output; 648 } 649 650 /** 651 * Split this {@link SubrangeSurface} at the specified parametric T-position returning 652 * a list containing two new surfaces (a lower surface with smaller T-parametric 653 * positions than "t" and an upper surface with larger T-parametric positions). 654 * 655 * @param t The T-parametric position where this surface should be split (must not be 656 * 0 or 1!). 657 * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper 658 * surface. 659 */ 660 @Override 661 public GeomList<TFISurface> splitAtT(double t) { 662 validateParameter(0, t); 663 if (parNearEnds(t, TOL_ST)) 664 throw new IllegalArgumentException(MessageFormat.format( 665 RESOURCES.getString("canNotSplitAtEnds"), "surface")); 666 667 // Get the dividing curve from the NURBS representation. 668 // TODO: Figure out how to do this direclty on a TFI. 669 BasicNurbsSurface nsrf = createNurbsSurface(); 670 SubrangeCurve splitC = nsrf.getTCurve(t); 671 BasicNurbsSurface.recycle(nsrf); 672 673 // Split the edge curves. 674 GeomList<Curve> s0crvs = s0crv.splitAt(t); 675 GeomList<Curve> s1crvs = s1crv.splitAt(t); 676 677 // Create the new surface pieces. 678 TFISurface srfL = TFISurface.newInstance(s0crvs.get(0), t0crv, s1crvs.get(0), splitC); 679 TFISurface srfU = TFISurface.newInstance(s0crvs.get(1), splitC, s1crvs.get(1), t1crv); 680 681 // Create the output list. 682 GeomList<TFISurface> output = GeomList.valueOf(srfL, srfU); 683 return output; 684 } 685 686 private BasicNurbsSurface createNurbsSurface() { 687 // Determine tolerance from size of bounding box. 688 Point range = getBoundsMax().minus(getBoundsMin()); 689 Parameter<Length> tol = range.norm().divide(1000); 690 BasicNurbsSurface srf = SurfaceFactory.createTFISurface(s0crv, t0crv, s1crv, t1crv, tol); 691 return srf; 692 } 693 694 /** 695 * Return the T=0 Boundary for this surface as a curve. 696 * 697 * @return The T=0 Boundary for this surface as a curve. 698 */ 699 @Override 700 public Curve getT0Curve() { 701 return t0crv; 702 } 703 704 /** 705 * Return the T=1 Boundary for this surface as a curve. 706 * 707 * @return The T=1 Boundary for this surface as a curve. 708 */ 709 @Override 710 public Curve getT1Curve() { 711 return t1crv; 712 } 713 714 /** 715 * Return the S=0 Boundary for this surface as a curve. 716 * 717 * @return The S=0 Boundary for this surface as a curve. 718 */ 719 @Override 720 public Curve getS0Curve() { 721 return s0crv; 722 } 723 724 /** 725 * Return the S=1 Boundary for this surface as a curve. 726 * 727 * @return The S=1 Boundary for this surface as a curve. 728 */ 729 @Override 730 public Curve getS1Curve() { 731 return s1crv; 732 } 733 734 /** 735 * Returns transformed version of this element. The returned object implements 736 * {@link GeomTransform} and contains this element as a child. 737 * 738 * @param transform The transformation to apply to this geometry. May not be null. 739 * @return A new triangle that is identical to this one with the specified 740 * transformation applied. 741 * @throws DimensionException if this point is not 3D. 742 */ 743 @Override 744 public TFISurface getTransformed(GTransform transform) { 745 Curve s0 = (Curve)s0crv.getTransformed(requireNonNull(transform)); 746 Curve s1 = (Curve)s1crv.getTransformed(transform); 747 Curve t0 = (Curve)t0crv.getTransformed(transform); 748 Curve t1 = (Curve)t1crv.getTransformed(transform); 749 750 return TFISurface.newInstance(s0, t0, s1, t1); 751 } 752 753 /** 754 * Return the coordinate point representing the minimum bounding box corner of this 755 * geometry element (e.g.: min X, min Y, min Z). 756 * 757 * @return The minimum bounding box coordinate for this geometry element. 758 * @throws IndexOutOfBoundsException if this list contains no geometry. 759 */ 760 @Override 761 public Point getBoundsMin() { 762 763 Point minPoint = s0crv.getBoundsMin(); 764 minPoint = minPoint.min(s1crv.getBoundsMin()); 765 minPoint = minPoint.min(t0crv.getBoundsMin()); 766 minPoint = minPoint.min(t1crv.getBoundsMin()); 767 768 return minPoint; 769 } 770 771 /** 772 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 773 * X, max Y, max Z). 774 * 775 * @return The maximum bounding box coordinate for this geometry element. 776 * @throws IndexOutOfBoundsException if this list contains no elements. 777 */ 778 @Override 779 public Point getBoundsMax() { 780 781 Point maxPoint = s0crv.getBoundsMax(); 782 maxPoint = maxPoint.max(s1crv.getBoundsMax()); 783 maxPoint = maxPoint.max(t0crv.getBoundsMax()); 784 maxPoint = maxPoint.max(t1crv.getBoundsMax()); 785 786 return maxPoint; 787 } 788 789 /** 790 * Returns the unit in which this surface is stated. 791 * 792 * @return The unit in which this surface is stated. 793 */ 794 @Override 795 public Unit<Length> getUnit() { 796 return s0crv.getUnit(); 797 } 798 799 /** 800 * Returns the equivalent to this surface but stated in the specified unit. 801 * 802 * @param unit the length unit of the surface to be returned. May not be null. 803 * @return an equivalent to this surface but stated in the specified unit. 804 * @throws ConversionException if the the input unit is not a length unit. 805 */ 806 @Override 807 public TFISurface to(Unit<Length> unit) throws ConversionException { 808 if (unit.equals(getUnit())) 809 return this; 810 811 // Convert the curves. 812 TFISurface srf = FACTORY.object(); 813 copyState(srf); 814 815 srf.s0crv = s0crv.to(unit); 816 srf.s1crv = s1crv.to(unit); 817 srf.t0crv = t0crv.to(unit); 818 srf.t1crv = t1crv.to(unit); 819 820 return srf; 821 } 822 823 /** 824 * Return a NURBS surface representation of this surface to within the specified 825 * tolerance. If the curves making up this surface are NURBS curves, then the 826 * resulting NURBS surface is an exact representation of this surface (and tol is 827 * ignored). However, if any of the member curves are not NURBS curves, then tol is 828 * used to make a NURBS approximation of that curve (and this surface). 829 * 830 * @param tol The greatest possible difference between this surface and the NURBS 831 * representation returned. May not be null. 832 * @return A NURBS surface that represents this surface to within the specified 833 * tolerance. 834 */ 835 @Override 836 public NurbsSurface toNurbs(Parameter<Length> tol) { 837 838 // Go off and create a skinned NURBS surface. 839 BasicNurbsSurface srf = SurfaceFactory.createTFISurface(s0crv, t0crv, s1crv, t1crv, requireNonNull(tol)); 840 copyState(srf); // Copy over the super-class state for this surface to the new one. 841 842 return srf; 843 } 844 845 /** 846 * Return <code>true</code> if this TFISurface contains valid and finite numerical 847 * components. A value of <code>false</code> will be returned if any of the member 848 * curves are not valid. 849 * 850 * @return true if this TFISurface contains valid and finite numerical components. 851 */ 852 @Override 853 public boolean isValid() { 854 return s0crv.isValid() && s1crv.isValid() && t0crv.isValid() && t1crv.isValid(); 855 } 856 857 /** 858 * Return <code>true</code> if this surface is degenerate (i.e.: has area less than 859 * the specified tolerance squared). 860 * 861 * @param tol The tolerance for determining if this surface is degenerate. May not be 862 * null. 863 * @return true if this surface is degenerate. 864 */ 865 @Override 866 public boolean isDegenerate(Parameter<Length> tol) { 867 requireNonNull(tol); 868 // A TFI is degenerate if all of it's bounding curves are degenerate. 869 return s0crv.isDegenerate(tol) && s1crv.isDegenerate(tol) 870 && t0crv.isDegenerate(tol) && t1crv.isDegenerate(tol); 871 } 872 873 /** 874 * Compares the specified object with this <code>TIFSurface</code> for equality. 875 * Returns true if and only if both surfaces are of the same type and both contain the 876 * same boundary curves in the same order. 877 * 878 * @param obj the object to compare with. 879 * @return <code>true</code> if this surface is identical to that surface; 880 * <code>false</code> otherwise. 881 */ 882 @Override 883 public boolean equals(Object obj) { 884 if (this == obj) 885 return true; 886 if ((obj == null) || (obj.getClass() != this.getClass())) 887 return false; 888 889 TFISurface that = (TFISurface)obj; 890 return this.s0crv.equals(that.s0crv) 891 && this.s1crv.equals(that.s1crv) 892 && this.t0crv.equals(that.t0crv) 893 && this.t1crv.equals(that.t1crv) 894 && super.equals(obj); 895 } 896 897 /** 898 * Returns the hash code for this <code>TFISurface</code>. 899 * 900 * @return the hash code value. 901 */ 902 @Override 903 public int hashCode() { 904 return 31*super.hashCode() + Objects.hash(s0crv, s1crv, t0crv, t1crv); 905 } 906 907 /** 908 * Return a copy of this object with any transformations or subranges removed 909 * (applied). 910 * 911 * @return A copy of this object with any transformations or subranges removed. 912 */ 913 @Override 914 public TFISurface copyToReal() { 915 TFISurface newSrf = FACTORY.object(); 916 copyState(newSrf); 917 918 newSrf.s0crv = s0crv.copyToReal(); 919 newSrf.s1crv = s1crv.copyToReal(); 920 newSrf.t0crv = t0crv.copyToReal(); 921 newSrf.t1crv = t1crv.copyToReal(); 922 923 return newSrf; 924 } 925 926 /** 927 * Returns a copy of this <code>TFISurface</code> instance 928 * {@link javolution.context.AllocatorContext allocated} by the calling thread 929 * (possibly on the stack). 930 * 931 * @return an identical and independent copy of this object. 932 */ 933 @Override 934 public TFISurface copy() { 935 return copyOf(this); 936 } 937 938 /** 939 * Returns the text representation of this geometry element. 940 * 941 * @return The text representation of this geometry element. 942 */ 943 @Override 944 public Text toText() { 945 TextBuilder tmp = TextBuilder.newInstance(); 946 String className = this.getClass().getName(); 947 tmp.append(className.substring(className.lastIndexOf(".") + 1)); 948 tmp.append(": {\n"); 949 tmp.append(s0crv.toText()); 950 tmp.append(",\n"); 951 tmp.append(s1crv.toText()); 952 tmp.append(",\n"); 953 tmp.append(t0crv.toText()); 954 tmp.append(",\n"); 955 tmp.append(t1crv.toText()); 956 tmp.append("\n"); 957 958 tmp.append("}"); 959 Text txt = tmp.toText(); 960 TextBuilder.recycle(tmp); 961 return txt; 962 } 963 964 /** 965 * Holds the default XML representation for this object. 966 */ 967 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 968 protected static final XMLFormat<TFISurface> XML = new XMLFormat<TFISurface>(TFISurface.class) { 969 970 @Override 971 public TFISurface newInstance(Class<TFISurface> cls, XMLFormat.InputElement xml) throws XMLStreamException { 972 TFISurface obj = FACTORY.object(); 973 return obj; 974 } 975 976 @Override 977 public void read(XMLFormat.InputElement xml, TFISurface obj) throws XMLStreamException { 978 AbstractSurface.XML.read(xml, obj); // Call parent read. 979 980 obj.s0crv = xml.get("S0"); 981 if (isNull(obj.s0crv)) 982 throw new XMLStreamException( 983 MessageFormat.format(RESOURCES.getString("crvMissingXML"), "S0")); 984 obj.s1crv = xml.get("S1"); 985 if (isNull(obj.s1crv)) 986 throw new XMLStreamException( 987 MessageFormat.format(RESOURCES.getString("crvMissingXML"), "S1")); 988 obj.t0crv = xml.get("T0"); 989 if (isNull(obj.t0crv)) 990 throw new XMLStreamException( 991 MessageFormat.format(RESOURCES.getString("crvMissingXML"), "T0")); 992 obj.t1crv = xml.get("T1"); 993 if (isNull(obj.t1crv)) 994 throw new XMLStreamException( 995 MessageFormat.format(RESOURCES.getString("crvMissingXML"), "T1")); 996 997 if (!(obj.s0crv instanceof Immutable)) 998 obj.s0crv.addChangeListener(obj._childChangeListener); 999 if (!(obj.s1crv instanceof Immutable)) 1000 obj.s1crv.addChangeListener(obj._childChangeListener); 1001 if (!(obj.t0crv instanceof Immutable)) 1002 obj.t0crv.addChangeListener(obj._childChangeListener); 1003 if (!(obj.t1crv instanceof Immutable)) 1004 obj.t1crv.addChangeListener(obj._childChangeListener); 1005 1006 } 1007 1008 @Override 1009 public void write(TFISurface obj, XMLFormat.OutputElement xml) throws XMLStreamException { 1010 AbstractSurface.XML.write(obj, xml); // Call parent write. 1011 1012 xml.add(obj.s0crv, "S0"); 1013 xml.add(obj.s1crv, "S1"); 1014 xml.add(obj.t0crv, "T0"); 1015 xml.add(obj.t1crv, "T1"); 1016 } 1017 }; 1018 1019 ////////////////////// 1020 // Factory Creation // 1021 ////////////////////// 1022 /** 1023 * Do not allow the default constructor to be used except by subclasses. 1024 */ 1025 protected TFISurface() { } 1026 1027 private static final ObjectFactory<TFISurface> FACTORY = new ObjectFactory<TFISurface>() { 1028 @Override 1029 protected TFISurface create() { 1030 return new TFISurface(); 1031 } 1032 1033 @Override 1034 protected void cleanup(TFISurface obj) { 1035 obj.reset(); 1036 if (!(obj.s0crv instanceof Immutable)) 1037 obj.s0crv.removeChangeListener(obj._childChangeListener); 1038 if (!(obj.s1crv instanceof Immutable)) 1039 obj.s1crv.removeChangeListener(obj._childChangeListener); 1040 if (!(obj.t0crv instanceof Immutable)) 1041 obj.t0crv.removeChangeListener(obj._childChangeListener); 1042 if (!(obj.t1crv instanceof Immutable)) 1043 obj.t1crv.removeChangeListener(obj._childChangeListener); 1044 obj.s0crv = null; 1045 obj.s1crv = null; 1046 obj.t0crv = null; 1047 obj.t1crv = null; 1048 } 1049 }; 1050 1051 /** 1052 * Recycles a case instance immediately (on the stack when executing in a 1053 * StackContext). 1054 * 1055 * @param instance The instance to be recycled. 1056 */ 1057 public static void recycle(TFISurface instance) { 1058 FACTORY.recycle(instance); 1059 } 1060 1061 @SuppressWarnings("unchecked") 1062 private static TFISurface copyOf(TFISurface original) { 1063 TFISurface obj = FACTORY.object(); 1064 obj.s0crv = original.s0crv.copy(); 1065 obj.s1crv = original.s1crv.copy(); 1066 obj.t0crv = original.t0crv.copy(); 1067 obj.t1crv = original.t1crv.copy(); 1068 1069 if (!(obj.s0crv instanceof Immutable)) 1070 obj.s0crv.addChangeListener(obj._childChangeListener); 1071 if (!(obj.s1crv instanceof Immutable)) 1072 obj.s1crv.addChangeListener(obj._childChangeListener); 1073 if (!(obj.t0crv instanceof Immutable)) 1074 obj.t0crv.addChangeListener(obj._childChangeListener); 1075 if (!(obj.t1crv instanceof Immutable)) 1076 obj.t1crv.addChangeListener(obj._childChangeListener); 1077 1078 original.copyState(obj); 1079 return obj; 1080 } 1081}