001: /*
002: * $RCSfile: KBCubicSplineSegment.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.4 $
041: * $Date: 2007/02/09 17:20:11 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.utils.behaviors.interpolators;
046:
047: import javax.media.j3d.*;
048: import java.util.*;
049: import javax.vecmath.*;
050:
051: /**
052: * The KBCubicSplineSegment class creates the representation of a
053: * Kochanek-Bartel's (also known as the TCB or Tension-Continuity-Bias
054: * Spline. This class takes 4 key frames as its input (using KBKeyFrame).
055: * If interpolating between the i<sup>th</sup> and (i+1)<sup>th</sup> key
056: * frame then the four key frames that need to be specified are the
057: * (i-1)<sup>th</sup>, i<sup>th</sup>, (i+1)<sup>th</sup>
058: * and (i+2)<sup>th</sup> keyframes in order. The KBCubicSegmentClass
059: * then pre-computes the hermite interpolation basis coefficients if the
060: * (i+1)<sup>th</sup> frame has the linear flag set to zero. These are used to
061: * calculate the interpolated position, scale and quaternions when they
062: * requested by the user using the getInterpolated* methods. If the the
063: * (i+1)<sup>th</sup> frame's linear flag is set to 1 then the class uses
064: * linear interpolation to calculate the interpolated position, scale, heading
065: * pitch and bank it returns through the getInterpolated* methods.
066: *
067: * @since Java3D 1.2
068: */
069: public class KBCubicSplineSegment {
070:
071: // Legendre polynomial information for Gaussian quadrature of speed
072: // for the domain [0,u], 0 <= u <= 1.
073:
074: // Legendre roots mapped to (root+1)/2
075: static final double modRoot[] = { 0.046910077, 0.230765345, 0.5,
076: 0.769234655, 0.953089922 };
077:
078: // original coefficients divided by 2
079: static final double modCoeff[] = { 0.118463442, 0.239314335,
080: 0.284444444, 0.239314335, 0.118463442 };
081:
082: // Key Frames
083: KBKeyFrame[] keyFrame = new KBKeyFrame[4];
084:
085: // H.C
086: Point3f c0, c1, c2, c3; // coefficients for position
087: Point3f e0, e1, e2, e3; // coefficients for scale
088: float h0, h1, h2, h3; // coefficients for heading
089: float p0, p1, p2, p3; // coefficients for pitch
090: float b0, b1, b2, b3; // coefficients for bank
091:
092: // variables for destination derivative
093: float one_minus_t_in;
094: float one_minus_c_in;
095: float one_minus_b_in;
096: float one_plus_c_in;
097: float one_plus_b_in;
098: float ddb;
099: float dda;
100:
101: // variables for source derivative
102: float one_minus_t_out;
103: float one_minus_c_out;
104: float one_minus_b_out;
105: float one_plus_c_out;
106: float one_plus_b_out;
107: float dsb;
108: float dsa;
109:
110: // Length of the spline segment
111: float length;
112:
113: // interpolation type
114: int linear;
115:
116: // Default constructor
117: KBCubicSplineSegment() {
118:
119: length = 0;
120:
121: }
122:
123: /**
124: * Creates a cubic spline segment between two key frames using the
125: * key frames provided. If creating a spline between the ith frame and
126: * the (i+1)<sup>th</sup> frame then send down the (i - 1)<sup>th</sup>,
127: * i<sup>th</sup> , (i+1)<sup>th</sup> and the (i+2)<sup>th</sup> key
128: * frames.
129: *
130: * @param kf0 (i - 1)<sup>th</sup> Key Frame
131: * @param kf1 i<sup>th</sup> Key Frame
132: * @param kf2 (i + 1)<sup>th</sup> Key Frame
133: * @param kf3 (i + 2)<sup>th</sup> Key Frame
134: */
135:
136: KBCubicSplineSegment(KBKeyFrame kf0, KBKeyFrame kf1,
137: KBKeyFrame kf2, KBKeyFrame kf3) {
138:
139: // Copy KeyFrame information
140: keyFrame[0] = new KBKeyFrame(kf0);
141: keyFrame[1] = new KBKeyFrame(kf1);
142: keyFrame[2] = new KBKeyFrame(kf2);
143: keyFrame[3] = new KBKeyFrame(kf3);
144:
145: // if linear interpolation is requested then just set linear flag
146: // if spline interpolation is needed then compute spline coefficients
147: if (kf2.linear == 1) {
148: this .linear = 1;
149: } else {
150: this .linear = 0;
151: computeCommonCoefficients(kf0, kf1, kf2, kf3);
152: computeHermiteCoefficients(kf0, kf1, kf2, kf3);
153: }
154:
155: length = computeLength(1.0f);
156: // System.out.println ("Segment length = " + length);
157:
158: }
159:
160: // compute the common coefficients
161: private void computeCommonCoefficients(KBKeyFrame kf0,
162: KBKeyFrame kf1, KBKeyFrame kf2, KBKeyFrame kf3) {
163:
164: // variables for destination derivative
165: float one_minus_t_in = 1.0f - kf1.tension;
166: float one_minus_c_in = 1.0f - kf1.continuity;
167: float one_minus_b_in = 1.0f - kf1.bias;
168: float one_plus_c_in = 1.0f + kf1.continuity;
169: float one_plus_b_in = 1.0f + kf1.bias;
170:
171: // coefficients for the incoming Tangent
172: ddb = one_minus_t_in * one_minus_c_in * one_minus_b_in;
173: dda = one_minus_t_in * one_plus_c_in * one_plus_b_in;
174:
175: // variables for source derivative
176: float one_minus_t_out = 1.0f - kf2.tension;
177: float one_minus_c_out = 1.0f - kf2.continuity;
178: float one_minus_b_out = 1.0f - kf2.bias;
179: float one_plus_c_out = 1.0f + kf2.continuity;
180: float one_plus_b_out = 1.0f + kf2.bias;
181:
182: // coefficients for the outgoing Tangent
183: dsb = one_minus_t_in * one_plus_c_in * one_minus_b_in;
184: dsa = one_minus_t_in * one_minus_c_in * one_plus_b_in;
185: }
186:
187: // compute the hermite interpolation basis coefficients
188: private void computeHermiteCoefficients(KBKeyFrame kf0,
189: KBKeyFrame kf1, KBKeyFrame kf2, KBKeyFrame kf3) {
190:
191: Point3f deltaP = new Point3f();
192: Point3f deltaS = new Point3f();
193: float deltaH;
194: float deltaT;
195: float deltaB;
196:
197: // Find the difference in position and scale
198: deltaP.x = kf2.position.x - kf1.position.x;
199: deltaP.y = kf2.position.y - kf1.position.y;
200: deltaP.z = kf2.position.z - kf1.position.z;
201:
202: deltaS.x = kf2.scale.x - kf1.scale.x;
203: deltaS.y = kf2.scale.y - kf1.scale.y;
204: deltaS.z = kf2.scale.z - kf1.scale.z;
205:
206: // Find the difference in heading, pitch, and bank
207: deltaH = kf2.heading - kf1.heading;
208: deltaT = kf2.pitch - kf1.pitch;
209: deltaB = kf2.bank - kf1.bank;
210:
211: // Incoming Tangent
212: Point3f dd_pos = new Point3f();
213: Point3f dd_scale = new Point3f();
214: float dd_heading, dd_pitch, dd_bank;
215:
216: // If this is the first keyframe of the animation
217: if (kf0.knot == kf1.knot) {
218:
219: float ddab = 0.5f * (dda + ddb);
220:
221: // Position
222: dd_pos.x = ddab * deltaP.x;
223: dd_pos.y = ddab * deltaP.y;
224: dd_pos.z = ddab * deltaP.z;
225:
226: // Scale
227: dd_scale.x = ddab * deltaS.x;
228: dd_scale.y = ddab * deltaS.y;
229: dd_scale.z = ddab * deltaS.z;
230:
231: // Heading, Pitch and Bank
232: dd_heading = ddab * deltaH;
233: dd_pitch = ddab * deltaT;
234: dd_bank = ddab * deltaB;
235:
236: } else {
237:
238: float adj0 = (kf1.knot - kf0.knot) / (kf2.knot - kf0.knot);
239:
240: // Position
241: dd_pos.x = adj0
242: * ((ddb * deltaP.x) + (dda * (kf1.position.x - kf0.position.x)));
243: dd_pos.y = adj0
244: * ((ddb * deltaP.y) + (dda * (kf1.position.y - kf0.position.y)));
245: dd_pos.z = adj0
246: * ((ddb * deltaP.z) + (dda * (kf1.position.z - kf0.position.z)));
247:
248: // Scale
249: dd_scale.x = adj0
250: * ((ddb * deltaS.x) + (dda * (kf1.scale.x - kf0.scale.x)));
251: dd_scale.y = adj0
252: * ((ddb * deltaS.y) + (dda * (kf1.scale.y - kf0.scale.y)));
253: dd_scale.z = adj0
254: * ((ddb * deltaS.z) + (dda * (kf1.scale.z - kf0.scale.z)));
255:
256: // Heading, Pitch and Bank
257: dd_heading = adj0
258: * ((ddb * deltaH) + (dda * (kf1.heading - kf0.heading)));
259: dd_pitch = adj0
260: * ((ddb * deltaT) + (dda * (kf1.pitch - kf0.pitch)));
261: dd_bank = adj0
262: * ((ddb * deltaB) + (dda * (kf1.bank - kf0.bank)));
263: }
264:
265: // Outgoing Tangent
266: Point3f ds_pos = new Point3f();
267: Point3f ds_scale = new Point3f();
268: float ds_heading, ds_pitch, ds_bank;
269:
270: // If this is the last keyframe of the animation
271: if (kf2.knot == kf3.knot) {
272:
273: float dsab = 0.5f * (dsa + dsb);
274:
275: // Position
276: ds_pos.x = dsab * deltaP.x;
277: ds_pos.y = dsab * deltaP.y;
278: ds_pos.z = dsab * deltaP.z;
279:
280: // Scale
281: ds_scale.x = dsab * deltaS.x;
282: ds_scale.y = dsab * deltaS.y;
283: ds_scale.z = dsab * deltaS.z;
284:
285: // Heading, Pitch and Bank
286: ds_heading = dsab * deltaH;
287: ds_pitch = dsab * deltaT;
288: ds_bank = dsab * deltaB;
289:
290: } else {
291:
292: float adj1 = (kf2.knot - kf1.knot) / (kf3.knot - kf1.knot);
293:
294: // Position
295: ds_pos.x = adj1
296: * ((dsb * (kf3.position.x - kf2.position.x)) + (dsa * deltaP.x));
297: ds_pos.y = adj1
298: * ((dsb * (kf3.position.y - kf2.position.y)) + (dsa * deltaP.y));
299: ds_pos.z = adj1
300: * ((dsb * (kf3.position.z - kf2.position.z)) + (dsa * deltaP.z));
301:
302: // Scale
303: ds_scale.x = adj1
304: * ((dsb * (kf3.scale.x - kf2.scale.x)) + (dsa * deltaS.x));
305: ds_scale.y = adj1
306: * ((dsb * (kf3.scale.y - kf2.scale.y)) + (dsa * deltaS.y));
307: ds_scale.z = adj1
308: * ((dsb * (kf3.scale.z - kf2.scale.z)) + (dsa * deltaS.z));
309:
310: // Heading, Pitch and Bank
311: ds_heading = adj1
312: * ((dsb * (kf3.heading - kf2.heading)) + (dsa * deltaH));
313: ds_pitch = adj1
314: * ((dsb * (kf3.pitch - kf2.pitch)) + (dsa * deltaT));
315: ds_bank = adj1
316: * ((dsb * (kf3.bank - kf2.bank)) + (dsa * deltaB));
317: }
318:
319: // Calculate the coefficients of the polynomial for position
320: c0 = new Point3f();
321: c0.x = kf1.position.x;
322: c0.y = kf1.position.y;
323: c0.z = kf1.position.z;
324:
325: c1 = new Point3f();
326: c1.x = dd_pos.x;
327: c1.y = dd_pos.y;
328: c1.z = dd_pos.z;
329:
330: c2 = new Point3f();
331: c2.x = 3 * deltaP.x - 2 * dd_pos.x - ds_pos.x;
332: c2.y = 3 * deltaP.y - 2 * dd_pos.y - ds_pos.y;
333: c2.z = 3 * deltaP.z - 2 * dd_pos.z - ds_pos.z;
334:
335: c3 = new Point3f();
336: c3.x = -2 * deltaP.x + dd_pos.x + ds_pos.x;
337: c3.y = -2 * deltaP.y + dd_pos.y + ds_pos.y;
338: c3.z = -2 * deltaP.z + dd_pos.z + ds_pos.z;
339:
340: // Calculate the coefficients of the polynomial for scale
341: e0 = new Point3f();
342: e0.x = kf1.scale.x;
343: e0.y = kf1.scale.y;
344: e0.z = kf1.scale.z;
345:
346: e1 = new Point3f();
347: e1.x = dd_scale.x;
348: e1.y = dd_scale.y;
349: e1.z = dd_scale.z;
350:
351: e2 = new Point3f();
352: e2.x = 3 * deltaS.x - 2 * dd_scale.x - ds_scale.x;
353: e2.y = 3 * deltaS.y - 2 * dd_scale.y - ds_scale.y;
354: e2.z = 3 * deltaS.z - 2 * dd_scale.z - ds_scale.z;
355:
356: e3 = new Point3f();
357: e3.x = -2 * deltaS.x + dd_scale.x + ds_scale.x;
358: e3.y = -2 * deltaS.y + dd_scale.y + ds_scale.y;
359: e3.z = -2 * deltaS.z + dd_scale.z + ds_scale.z;
360:
361: // Calculate the coefficients of the polynomial for heading, pitch
362: // and bank
363: h0 = kf1.heading;
364: p0 = kf1.pitch;
365: b0 = kf1.bank;
366:
367: h1 = dd_heading;
368: p1 = dd_pitch;
369: b1 = dd_bank;
370:
371: h2 = 3 * deltaH - 2 * dd_heading - ds_heading;
372: p2 = 3 * deltaT - 2 * dd_pitch - ds_pitch;
373: b2 = 3 * deltaB - 2 * dd_bank - ds_bank;
374:
375: h3 = -2 * deltaH + dd_heading + ds_heading;
376: p3 = -2 * deltaT + dd_pitch + ds_pitch;
377: b3 = -2 * deltaB + dd_bank + ds_bank;
378: }
379:
380: /**
381: * Computes the length of the curve at a given point between
382: * key frames.
383: * @param u specifies the point between keyframes where 0 <= u <= 1.
384: */
385:
386: public float computeLength(float u) {
387:
388: float result = 0f;
389:
390: // if linear interpolation
391: if (linear == 1) {
392: result = u
393: * keyFrame[2].position
394: .distance(keyFrame[1].position);
395: } else {
396: // Need to transform domain [0,u] to [-1,1]. If 0 <= x <= u
397: // and -1 <= t <= 1, then x = u*(t+1)/2.
398: int degree = 5;
399: for (int i = 0; i < degree; i++)
400: result += (float) modCoeff[i]
401: * computeSpeed(u * (float) modRoot[i]);
402: result *= u;
403: }
404:
405: return result;
406: }
407:
408: // Velocity along curve
409: private float computeSpeed(float u) {
410: Point3f v = new Point3f();
411:
412: v.x = c1.x + u * (2 * c2.x + 3 * u * c3.x);
413: v.y = c1.y + u * (2 * c2.y + 3 * u * c3.y);
414: v.z = c1.z + u * (2 * c2.z + 3 * u * c3.z);
415:
416: return (float) (Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z));
417: }
418:
419: /**
420: * Computes the interpolated scale along the curve at a given point
421: * between key frames and returns a Point3f with the interpolated
422: * x, y, and z scale components. This routine uses linear
423: * interpolation if the (i+1)<sup>th</sup> key frame's linear
424: * value is equal to 1.
425: *
426: * @param u specifies the point between keyframes where 0 <= u <= 1.
427: * @param newScale returns the interpolated x,y,z scale value in a Point3f
428: */
429:
430: public void getInterpolatedScale(float u, Point3f newScale) {
431:
432: // if linear interpolation
433: if (this .linear == 1) {
434:
435: newScale.x = keyFrame[1].scale.x
436: + ((keyFrame[2].scale.x - keyFrame[1].scale.x) * u);
437: newScale.y = keyFrame[1].scale.y
438: + ((keyFrame[2].scale.y - keyFrame[1].scale.y) * u);
439: newScale.z = keyFrame[1].scale.z
440: + ((keyFrame[2].scale.z - keyFrame[1].scale.z) * u);
441:
442: } else {
443:
444: newScale.x = e0.x + u * (e1.x + u * (e2.x + u * e3.x));
445: newScale.y = e0.y + u * (e1.y + u * (e2.y + u * e3.y));
446: newScale.z = e0.z + u * (e1.z + u * (e2.z + u * e3.z));
447:
448: }
449: }
450:
451: /**
452: * Computes the interpolated position along the curve at a given point
453: * between key frames and returns a Point3f with the interpolated
454: * x, y, and z scale components. This routine uses linear
455: * interpolation if the (i+1)<sup>th</sup> key frame's linear
456: * value is equal to 1.
457: *
458: * @param u specifies the point between keyframes where 0 <= u <= 1.
459: * @param newPos returns the interpolated x,y,z position in a Point3f
460: */
461:
462: public void getInterpolatedPosition(float u, Point3f newPos) {
463:
464: // if linear interpolation
465: if (this .linear == 1) {
466: newPos.x = keyFrame[1].position.x
467: + ((keyFrame[2].position.x - keyFrame[1].position.x) * u);
468: newPos.y = keyFrame[1].position.y
469: + ((keyFrame[2].position.y - keyFrame[1].position.y) * u);
470: newPos.z = keyFrame[1].position.z
471: + ((keyFrame[2].position.z - keyFrame[1].position.z) * u);
472: } else {
473:
474: newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x));
475: newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y));
476: newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z));
477:
478: }
479: }
480:
481: /**
482: * Computes the interpolated position along the curve at a given point
483: * between key frames and returns a Vector3f with the interpolated
484: * x, y, and z scale components. This routine uses linear
485: * interpolation if the (i+1)<sup>th</sup> key frame's linear
486: * value is equal to 1.
487: *
488: * @param u specifies the point between keyframes where 0 <= u <= 1.
489: * @param newPos returns the interpolated x,y,z position in a Vector3f.
490: */
491:
492: public void getInterpolatedPositionVector(float u, Vector3f newPos) {
493: // if linear interpolation
494: if (this .linear == 1) {
495: newPos.x = keyFrame[1].position.x
496: + ((keyFrame[2].position.x - keyFrame[1].position.x) * u);
497: newPos.y = keyFrame[1].position.y
498: + ((keyFrame[2].position.y - keyFrame[1].position.y) * u);
499: newPos.z = keyFrame[1].position.z
500: + ((keyFrame[2].position.z - keyFrame[1].position.z) * u);
501: } else {
502:
503: newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x));
504: newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y));
505: newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z));
506:
507: }
508: }
509:
510: /**
511: * Computes the interpolated heading along the curve at a given point
512: * between key frames and returns the interpolated value as a float
513: * This routine uses linear interpolation if the (i+1)<sup>th</sup>
514: * key frame's linear value is equal to 1.
515: *
516: * @param u specifies the point between keyframes where 0 <= u <= 1.
517: * @return returns the interpolated heading value
518: */
519:
520: public float getInterpolatedHeading(float u) {
521:
522: float newHeading;
523:
524: // if linear interpolation
525: if (this .linear == 1) {
526:
527: newHeading = keyFrame[1].heading
528: + ((keyFrame[2].heading - keyFrame[1].heading) * u);
529: } else {
530:
531: newHeading = h0 + u * (h1 + u * (h2 + u * h3));
532:
533: }
534:
535: return newHeading;
536: }
537:
538: /**
539: * Computes the interpolated pitch along the curve at a given point
540: * between key frames and returns the interpolated value as a float
541: * This routine uses linear interpolation if the (i+1)<sup>th</sup>
542: * key frame's linear value is equal to 1.
543: *
544: * @param u specifies the point between keyframes where 0 <= u <= 1.
545: * @return returns the interpolated pitch value
546: */
547:
548: public float getInterpolatedPitch(float u) {
549:
550: float newPitch;
551:
552: // if linear interpolation
553: if (this .linear == 1) {
554:
555: newPitch = keyFrame[1].pitch
556: + ((keyFrame[2].pitch - keyFrame[1].pitch) * u);
557: } else {
558:
559: newPitch = p0 + u * (p1 + u * (p2 + u * p3));
560:
561: }
562:
563: return newPitch;
564: }
565:
566: /**
567: * Computes the interpolated bank along the curve at a given point
568: * between key frames and returns the interpolated value as a float
569: * This routine uses linear interpolation if the (i+1)<sup>th</sup>
570: * key frame's linear value is equal to 1.
571: *
572: * @param u specifies the point between keyframes where 0 <= u <= 1.
573: * @return returns the interpolated bank value
574: */
575:
576: public float getInterpolatedBank(float u) {
577:
578: float newBank;
579:
580: // if linear interpolation
581: if (this .linear == 1) {
582:
583: newBank = keyFrame[1].bank
584: + ((keyFrame[2].bank - keyFrame[1].bank) * u);
585: } else {
586:
587: newBank = b0 + u * (b1 + u * (b2 + u * b3));
588:
589: }
590:
591: return newBank;
592: }
593:
594: /**
595: * Computes the ratio of the length of the spline from the i<sup>th</sup>
596: * key frame to the position specified by u to the length of the entire
597: * spline segment from the i<sup>th</sup> key frame to the (i+1)
598: * <sup>th</sup> key frame. When the (i+1)<sup>th</sup> key frame's linear
599: * value is equal to 1, this is meaninful otherwise it should return u.
600: *
601: * @param u specifies the point between keyframes where 0 <= u <= 1.
602: * @return the interpolated ratio
603: */
604:
605: public float getInterpolatedValue(float u) {
606: return (computeLength(u) / this.length);
607: }
608: }
|