1 /* Copyright 2002-2013 CS Systèmes d'Information 2 * Licensed to CS Systèmes d'Information (CS) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * CS licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.orekit.orbits; 18 19 import java.io.Serializable; 20 21 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; 22 import org.apache.commons.math3.linear.DecompositionSolver; 23 import org.apache.commons.math3.linear.MatrixUtils; 24 import org.apache.commons.math3.linear.QRDecomposition; 25 import org.apache.commons.math3.linear.RealMatrix; 26 import org.apache.commons.math3.util.FastMath; 27 import org.orekit.errors.OrekitException; 28 import org.orekit.errors.OrekitMessages; 29 import org.orekit.frames.Frame; 30 import org.orekit.frames.Transform; 31 import org.orekit.time.AbsoluteDate; 32 import org.orekit.time.TimeInterpolable; 33 import org.orekit.time.TimeShiftable; 34 import org.orekit.time.TimeStamped; 35 import org.orekit.utils.PVCoordinates; 36 import org.orekit.utils.PVCoordinatesProvider; 37 38 /** 39 * This class handles orbital parameters. 40 41 * <p> 42 * For user convenience, both the Cartesian and the equinoctial elements 43 * are provided by this class, regardless of the canonical representation 44 * implemented in the derived class (which may be classical keplerian 45 * elements for example). 46 * </p> 47 * <p> 48 * The parameters are defined in a frame specified by the user. It is important 49 * to make sure this frame is consistent: it probably is inertial and centered 50 * on the central body. This information is used for example by some 51 * force models. 52 * </p> 53 * <p> 54 * The object <code>OrbitalParameters</code> is guaranteed to be immutable. 55 * </p> 56 * @author Luc Maisonobe 57 * @author Guylaine Prat 58 * @author Fabien Maussion 59 * @author Véronique Pommier-Maurussane 60 */ 61 public abstract class Orbit 62 implements TimeStamped, TimeShiftable<Orbit>, TimeInterpolable<Orbit>, 63 Serializable, PVCoordinatesProvider { 64 65 /** Serializable UID. */ 66 private static final long serialVersionUID = 438733454597999578L; 67 68 /** Frame in which are defined the orbital parameters. */ 69 private final Frame frame; 70 71 /** Date of the orbital parameters. */ 72 private final AbsoluteDate date; 73 74 /** Value of mu used to compute position and velocity (m<sup>3</sup>/s<sup>2</sup>). */ 75 private final double mu; 76 77 /** Computed PVCoordinates. */ 78 private transient PVCoordinates pvCoordinates; 79 80 /** Jacobian of the orbital parameters with mean angle with respect to the Cartesian coordinates. */ 81 private transient double[][] jacobianMeanWrtCartesian; 82 83 /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with mean angle. */ 84 private transient double[][] jacobianWrtParametersMean; 85 86 /** Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian coordinates. */ 87 private transient double[][] jacobianEccentricWrtCartesian; 88 89 /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with eccentric angle. */ 90 private transient double[][] jacobianWrtParametersEccentric; 91 92 /** Jacobian of the orbital parameters with true angle with respect to the Cartesian coordinates. */ 93 private transient double[][] jacobianTrueWrtCartesian; 94 95 /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with true angle. */ 96 private transient double[][] jacobianWrtParametersTrue; 97 98 /** Default constructor. 99 * Build a new instance with arbitrary default elements. 100 * @param frame the frame in which the parameters are defined 101 * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame}) 102 * @param date date of the orbital parameters 103 * @param mu central attraction coefficient (m^3/s^2) 104 * @exception IllegalArgumentException if frame is not a {@link 105 * Frame#isPseudoInertial pseudo-inertial frame} 106 */ 107 protected Orbit(final Frame frame, final AbsoluteDate date, final double mu) 108 throws IllegalArgumentException { 109 ensurePseudoInertialFrame(frame); 110 this.date = date; 111 this.mu = mu; 112 this.pvCoordinates = null; 113 this.frame = frame; 114 jacobianMeanWrtCartesian = null; 115 jacobianWrtParametersMean = null; 116 jacobianEccentricWrtCartesian = null; 117 jacobianWrtParametersEccentric = null; 118 jacobianTrueWrtCartesian = null; 119 jacobianWrtParametersTrue = null; 120 } 121 122 /** Set the orbit from Cartesian parameters. 123 * @param pvCoordinates the position and velocity in the inertial frame 124 * @param frame the frame in which the {@link PVCoordinates} are defined 125 * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame}) 126 * @param date date of the orbital parameters 127 * @param mu central attraction coefficient (m^3/s^2) 128 * @exception IllegalArgumentException if frame is not a {@link 129 * Frame#isPseudoInertial pseudo-inertial frame} 130 */ 131 protected Orbit(final PVCoordinates pvCoordinates, final Frame frame, 132 final AbsoluteDate date, final double mu) 133 throws IllegalArgumentException { 134 ensurePseudoInertialFrame(frame); 135 this.date = date; 136 this.mu = mu; 137 this.pvCoordinates = pvCoordinates; 138 this.frame = frame; 139 } 140 141 /** Get the orbit type. 142 * @return orbit type 143 */ 144 public abstract OrbitType getType(); 145 146 /** Ensure the defining frame is a pseudo-inertial frame. 147 * @param frame frame to check 148 * @exception IllegalArgumentException if frame is not a {@link 149 * Frame#isPseudoInertial pseudo-inertial frame} 150 */ 151 private static void ensurePseudoInertialFrame(final Frame frame) 152 throws IllegalArgumentException { 153 if (!frame.isPseudoInertial()) { 154 throw OrekitException.createIllegalArgumentException( 155 OrekitMessages.NON_PSEUDO_INERTIAL_FRAME_NOT_SUITABLE_FOR_DEFINING_ORBITS, 156 frame.getName()); 157 } 158 } 159 160 /** Get the frame in which the orbital parameters are defined. 161 * @return frame in which the orbital parameters are defined 162 */ 163 public Frame getFrame() { 164 return frame; 165 } 166 167 /** Get the semi-major axis. 168 * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p> 169 * @return semi-major axis (m) 170 */ 171 public abstract double getA(); 172 173 /** Get the first component of the equinoctial eccentricity vector. 174 * @return first component of the equinoctial eccentricity vector 175 */ 176 public abstract double getEquinoctialEx(); 177 178 /** Get the second component of the equinoctial eccentricity vector. 179 * @return second component of the equinoctial eccentricity vector 180 */ 181 public abstract double getEquinoctialEy(); 182 183 /** Get the first component of the inclination vector. 184 * @return first component of the inclination vector 185 */ 186 public abstract double getHx(); 187 188 /** Get the second component of the inclination vector. 189 * @return second component of the inclination vector 190 */ 191 public abstract double getHy(); 192 193 /** Get the eccentric longitude argument. 194 * @return E + ω + Ω eccentric longitude argument (rad) 195 */ 196 public abstract double getLE(); 197 198 /** Get the true longitude argument. 199 * @return v + ω + Ω true longitude argument (rad) 200 */ 201 public abstract double getLv(); 202 203 /** Get the mean longitude argument. 204 * @return M + ω + Ω mean longitude argument (rad) 205 */ 206 public abstract double getLM(); 207 208 // Additional orbital elements 209 210 /** Get the eccentricity. 211 * @return eccentricity 212 */ 213 public abstract double getE(); 214 215 /** Get the inclination. 216 * @return inclination (rad) 217 */ 218 public abstract double getI(); 219 220 /** Get the central acceleration constant. 221 * @return central acceleration constant 222 */ 223 public double getMu() { 224 return mu; 225 } 226 227 /** Get the keplerian period. 228 * <p>The keplerian period is computed directly from semi major axis 229 * and central acceleration constant.</p> 230 * @return keplerian period in seconds, or positive infinity for hyperbolic orbits 231 */ 232 public double getKeplerianPeriod() { 233 final double a = getA(); 234 return (a < 0) ? Double.POSITIVE_INFINITY : 2.0 * FastMath.PI * a * FastMath.sqrt(a / mu); 235 } 236 237 /** Get the keplerian mean motion. 238 * <p>The keplerian mean motion is computed directly from semi major axis 239 * and central acceleration constant.</p> 240 * @return keplerian mean motion in radians per second 241 */ 242 public double getKeplerianMeanMotion() { 243 final double absA = FastMath.abs(getA()); 244 return FastMath.sqrt(mu / absA) / absA; 245 } 246 247 /** Get the date of orbital parameters. 248 * @return date of the orbital parameters 249 */ 250 public AbsoluteDate getDate() { 251 return date; 252 } 253 254 /** Get the {@link PVCoordinates} in a specified frame. 255 * @param outputFrame frame in which the position/velocity coordinates shall be computed 256 * @return pvCoordinates in the specified output frame 257 * @exception OrekitException if transformation between frames cannot be computed 258 * @see #getPVCoordinates() 259 */ 260 public PVCoordinates getPVCoordinates(final Frame outputFrame) 261 throws OrekitException { 262 if (pvCoordinates == null) { 263 pvCoordinates = initPVCoordinates(); 264 } 265 266 // If output frame requested is the same as definition frame, 267 // PV coordinates are returned directly 268 if (outputFrame == frame) { 269 return pvCoordinates; 270 } 271 272 // Else, PV coordinates are transformed to output frame 273 final Transform t = frame.getTransformTo(outputFrame, date); 274 return t.transformPVCoordinates(pvCoordinates); 275 } 276 277 /** {@inheritDoc} */ 278 public PVCoordinates getPVCoordinates(final AbsoluteDate otherDate, final Frame otherFrame) 279 throws OrekitException { 280 return shiftedBy(otherDate.durationFrom(getDate())).getPVCoordinates(otherFrame); 281 } 282 283 284 /** Get the {@link PVCoordinates} in definition frame. 285 * @return pvCoordinates in the definition frame 286 * @see #getPVCoordinates(Frame) 287 */ 288 public PVCoordinates getPVCoordinates() { 289 if (pvCoordinates == null) { 290 pvCoordinates = initPVCoordinates(); 291 } 292 return pvCoordinates; 293 } 294 295 /** Compute the position/velocity coordinates from the canonical parameters. 296 * @return computed position/velocity coordinates 297 */ 298 protected abstract PVCoordinates initPVCoordinates(); 299 300 /** Get a time-shifted orbit. 301 * <p> 302 * The orbit can be slightly shifted to close dates. This shift is based on 303 * a simple keplerian model. It is <em>not</em> intended as a replacement 304 * for proper orbit and attitude propagation but should be sufficient for 305 * small time shifts or coarse accuracy. 306 * </p> 307 * @param dt time shift in seconds 308 * @return a new orbit, shifted with respect to the instance (which is immutable) 309 */ 310 public abstract Orbit shiftedBy(final double dt); 311 312 /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters. 313 * <p> 314 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with 315 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter 316 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot. 317 * </p> 318 * @param type type of the position angle to use 319 * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix 320 * is larger than 6x6, only the 6x6 upper left corner will be modified 321 */ 322 public void getJacobianWrtCartesian(final PositionAngle type, final double[][] jacobian) { 323 324 final double[][] cachedJacobian; 325 synchronized (this) { 326 switch (type) { 327 case MEAN : 328 if (jacobianMeanWrtCartesian == null) { 329 // first call, we need to compute the jacobian and cache it 330 jacobianMeanWrtCartesian = computeJacobianMeanWrtCartesian(); 331 } 332 cachedJacobian = jacobianMeanWrtCartesian; 333 break; 334 case ECCENTRIC : 335 if (jacobianEccentricWrtCartesian == null) { 336 // first call, we need to compute the jacobian and cache it 337 jacobianEccentricWrtCartesian = computeJacobianEccentricWrtCartesian(); 338 } 339 cachedJacobian = jacobianEccentricWrtCartesian; 340 break; 341 case TRUE : 342 if (jacobianTrueWrtCartesian == null) { 343 // first call, we need to compute the jacobian and cache it 344 jacobianTrueWrtCartesian = computeJacobianTrueWrtCartesian(); 345 } 346 cachedJacobian = jacobianTrueWrtCartesian; 347 break; 348 default : 349 throw OrekitException.createInternalError(null); 350 } 351 } 352 353 // fill the user provided array 354 for (int i = 0; i < cachedJacobian.length; ++i) { 355 System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length); 356 } 357 358 } 359 360 /** Compute the Jacobian of the Cartesian parameters with respect to the orbital parameters. 361 * <p> 362 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with 363 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter 364 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot. 365 * </p> 366 * @param type type of the position angle to use 367 * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix 368 * is larger than 6x6, only the 6x6 upper left corner will be modified 369 */ 370 public void getJacobianWrtParameters(final PositionAngle type, final double[][] jacobian) { 371 372 final double[][] cachedJacobian; 373 synchronized (this) { 374 switch (type) { 375 case MEAN : 376 if (jacobianWrtParametersMean == null) { 377 // first call, we need to compute the jacobian and cache it 378 jacobianWrtParametersMean = createInverseJacobian(type); 379 } 380 cachedJacobian = jacobianWrtParametersMean; 381 break; 382 case ECCENTRIC : 383 if (jacobianWrtParametersEccentric == null) { 384 // first call, we need to compute the jacobian and cache it 385 jacobianWrtParametersEccentric = createInverseJacobian(type); 386 } 387 cachedJacobian = jacobianWrtParametersEccentric; 388 break; 389 case TRUE : 390 if (jacobianWrtParametersTrue == null) { 391 // first call, we need to compute the jacobian and cache it 392 jacobianWrtParametersTrue = createInverseJacobian(type); 393 } 394 cachedJacobian = jacobianWrtParametersTrue; 395 break; 396 default : 397 throw OrekitException.createInternalError(null); 398 } 399 } 400 401 // fill the user-provided array 402 for (int i = 0; i < cachedJacobian.length; ++i) { 403 System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length); 404 } 405 406 } 407 408 /** Create an inverse Jacobian. 409 * @param type type of the position angle to use 410 * @return inverse Jacobian 411 */ 412 private double[][] createInverseJacobian(final PositionAngle type) { 413 414 // get the direct Jacobian 415 final double[][] directJacobian = new double[6][6]; 416 getJacobianWrtCartesian(type, directJacobian); 417 418 // invert the direct Jacobian 419 final RealMatrix matrix = MatrixUtils.createRealMatrix(directJacobian); 420 final DecompositionSolver solver = new QRDecomposition(matrix).getSolver(); 421 return solver.getInverse().getData(); 422 423 } 424 425 /** Compute the Jacobian of the orbital parameters with mean angle with respect to the Cartesian parameters. 426 * <p> 427 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with 428 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter 429 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot. 430 * </p> 431 * @return 6x6 Jacobian matrix 432 * @see #computeJacobianEccentricWrtCartesian() 433 * @see #computeJacobianTrueWrtCartesian() 434 */ 435 protected abstract double[][] computeJacobianMeanWrtCartesian(); 436 437 /** Compute the Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian parameters. 438 * <p> 439 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with 440 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter 441 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot. 442 * </p> 443 * @return 6x6 Jacobian matrix 444 * @see #computeJacobianMeanWrtCartesian() 445 * @see #computeJacobianTrueWrtCartesian() 446 */ 447 protected abstract double[][] computeJacobianEccentricWrtCartesian(); 448 449 /** Compute the Jacobian of the orbital parameters with true angle with respect to the Cartesian parameters. 450 * <p> 451 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with 452 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter 453 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot. 454 * </p> 455 * @return 6x6 Jacobian matrix 456 * @see #computeJacobianMeanWrtCartesian() 457 * @see #computeJacobianEccentricWrtCartesian() 458 */ 459 protected abstract double[][] computeJacobianTrueWrtCartesian(); 460 461 /** Add the contribution of the Keplerian motion to parameters derivatives 462 * <p> 463 * This method is used by integration-based propagators to evaluate the part of Keplerian 464 * motion to evolution of the orbital state. 465 * </p> 466 * @param type type of the position angle in the state 467 * @param gm attraction coefficient to use 468 * @param pDot array containing orbital state derivatives to update (the Keplerian 469 * part must be <em>added</em> to the array components, as the array may already 470 * contain some non-zero elements corresponding to non-Keplerian parts) 471 */ 472 public abstract void addKeplerContribution(final PositionAngle type, final double gm, double[] pDot); 473 474 /** Fill a Jacobian half row with a single vector. 475 * @param a coefficient of the vector 476 * @param v vector 477 * @param row Jacobian matrix row 478 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set) 479 */ 480 protected static void fillHalfRow(final double a, final Vector3D v, final double[] row, final int j) { 481 row[j] = a * v.getX(); 482 row[j + 1] = a * v.getY(); 483 row[j + 2] = a * v.getZ(); 484 } 485 486 /** Fill a Jacobian half row with a linear combination of vectors. 487 * @param a1 coefficient of the first vector 488 * @param v1 first vector 489 * @param a2 coefficient of the second vector 490 * @param v2 second vector 491 * @param row Jacobian matrix row 492 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set) 493 */ 494 protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2, 495 final double[] row, final int j) { 496 row[j] = a1 * v1.getX() + a2 * v2.getX(); 497 row[j + 1] = a1 * v1.getY() + a2 * v2.getY(); 498 row[j + 2] = a1 * v1.getZ() + a2 * v2.getZ(); 499 } 500 501 /** Fill a Jacobian half row with a linear combination of vectors. 502 * @param a1 coefficient of the first vector 503 * @param v1 first vector 504 * @param a2 coefficient of the second vector 505 * @param v2 second vector 506 * @param a3 coefficient of the third vector 507 * @param v3 third vector 508 * @param row Jacobian matrix row 509 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set) 510 */ 511 protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2, 512 final double a3, final Vector3D v3, 513 final double[] row, final int j) { 514 row[j] = a1 * v1.getX() + a2 * v2.getX() + a3 * v3.getX(); 515 row[j + 1] = a1 * v1.getY() + a2 * v2.getY() + a3 * v3.getY(); 516 row[j + 2] = a1 * v1.getZ() + a2 * v2.getZ() + a3 * v3.getZ(); 517 } 518 519 /** Fill a Jacobian half row with a linear combination of vectors. 520 * @param a1 coefficient of the first vector 521 * @param v1 first vector 522 * @param a2 coefficient of the second vector 523 * @param v2 second vector 524 * @param a3 coefficient of the third vector 525 * @param v3 third vector 526 * @param a4 coefficient of the fourth vector 527 * @param v4 fourth vector 528 * @param row Jacobian matrix row 529 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set) 530 */ 531 protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2, 532 final double a3, final Vector3D v3, final double a4, final Vector3D v4, 533 final double[] row, final int j) { 534 row[j] = a1 * v1.getX() + a2 * v2.getX() + a3 * v3.getX() + a4 * v4.getX(); 535 row[j + 1] = a1 * v1.getY() + a2 * v2.getY() + a3 * v3.getY() + a4 * v4.getY(); 536 row[j + 2] = a1 * v1.getZ() + a2 * v2.getZ() + a3 * v3.getZ() + a4 * v4.getZ(); 537 } 538 539 /** Fill a Jacobian half row with a linear combination of vectors. 540 * @param a1 coefficient of the first vector 541 * @param v1 first vector 542 * @param a2 coefficient of the second vector 543 * @param v2 second vector 544 * @param a3 coefficient of the third vector 545 * @param v3 third vector 546 * @param a4 coefficient of the fourth vector 547 * @param v4 fourth vector 548 * @param a5 coefficient of the fifth vector 549 * @param v5 fifth vector 550 * @param row Jacobian matrix row 551 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set) 552 */ 553 protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2, 554 final double a3, final Vector3D v3, final double a4, final Vector3D v4, 555 final double a5, final Vector3D v5, 556 final double[] row, final int j) { 557 row[j] = a1 * v1.getX() + a2 * v2.getX() + a3 * v3.getX() + a4 * v4.getX() + a5 * v5.getX(); 558 row[j + 1] = a1 * v1.getY() + a2 * v2.getY() + a3 * v3.getY() + a4 * v4.getY() + a5 * v5.getY(); 559 row[j + 2] = a1 * v1.getZ() + a2 * v2.getZ() + a3 * v3.getZ() + a4 * v4.getZ() + a5 * v5.getZ(); 560 } 561 562 /** Fill a Jacobian half row with a linear combination of vectors. 563 * @param a1 coefficient of the first vector 564 * @param v1 first vector 565 * @param a2 coefficient of the second vector 566 * @param v2 second vector 567 * @param a3 coefficient of the third vector 568 * @param v3 third vector 569 * @param a4 coefficient of the fourth vector 570 * @param v4 fourth vector 571 * @param a5 coefficient of the fifth vector 572 * @param v5 fifth vector 573 * @param a6 coefficient of the sixth vector 574 * @param v6 sixth vector 575 * @param row Jacobian matrix row 576 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set) 577 */ 578 protected static void fillHalfRow(final double a1, final Vector3D v1, final double a2, final Vector3D v2, 579 final double a3, final Vector3D v3, final double a4, final Vector3D v4, 580 final double a5, final Vector3D v5, final double a6, final Vector3D v6, 581 final double[] row, final int j) { 582 row[j] = a1 * v1.getX() + a2 * v2.getX() + a3 * v3.getX() + a4 * v4.getX() + a5 * v5.getX() + a6 * v6.getX(); 583 row[j + 1] = a1 * v1.getY() + a2 * v2.getY() + a3 * v3.getY() + a4 * v4.getY() + a5 * v5.getY() + a6 * v6.getY(); 584 row[j + 2] = a1 * v1.getZ() + a2 * v2.getZ() + a3 * v3.getZ() + a4 * v4.getZ() + a5 * v5.getZ() + a6 * v6.getZ(); 585 } 586 587 }