001: /*
002: * $RCSfile: CubicSplineSegment.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 CubicSplineSegment class creates the representation of a
053: * TCB (Kochanek-Bartels Spline). This class takes 4 key frames as
054: * its input (using TCBKeyFrame). If interpolating between the i<sup>th</sup>
055: * and (i+1)<sup>th</sup> key frame then the four key frames that need to
056: * be specified are the (i-1)<sup>th</sup>, i<sup>th</sup>, (i+1)<sup>th</sup>
057: * and (i+2)<sup>th</sup> keyframes in order. The CubicSegmentClass
058: * then pre-computes the hermite interpolation basis coefficients if the
059: * (i+1)<sup>th</sup> frame has the linear flag set to zero. These are used to
060: * calculate the interpolated position, scale and quaternions when they
061: * requested by the user using the getInterpolated* methods. If the the
062: * (i+1)<sup>th</sup> frame's linear flag is set to 1 then the class uses
063: * linear interpolation to calculate the interpolated position, sccale and
064: * quaternions it returns through the getInterpolated* methods.
065: *
066: * @since Java3D 1.1
067: */
068:
069: public class CubicSplineSegment {
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: TCBKeyFrame[] keyFrame = new TCBKeyFrame[4];
084:
085: // H.C
086: Point3f c0, c1, c2, c3; // coefficients for position
087: Point3f e0, e1, e2, e3; // coefficients for scale
088:
089: // variables for destination derivative
090: float one_minus_t_in;
091: float one_minus_c_in;
092: float one_minus_b_in;
093: float one_plus_c_in;
094: float one_plus_b_in;
095: float ddb;
096: float dda;
097:
098: // variables for source derivative
099: float one_minus_t_out;
100: float one_minus_c_out;
101: float one_minus_b_out;
102: float one_plus_c_out;
103: float one_plus_b_out;
104: float dsb;
105: float dsa;
106:
107: // Length of the spline segment
108: float length;
109:
110: // interpolation type
111: int linear;
112:
113: /**
114: * Default constructor
115: */
116: CubicSplineSegment() {
117:
118: length = 0;
119:
120: }
121:
122: /**
123: * Creates a cubic spline segment between two key frames using the
124: * key frames provided. If creating a spline between the ith frame and
125: * the (i+1)<sup>th</sup> frame then send down the (i - 1)<sup>th</sup>,
126: * i<sup>th</sup> , (i+1)<sup>th</sup> and the (i+2)<sup>th</sup> key
127: * frames.
128: *
129: * @param kf0 (i - 1)<sup>th</sup> Key Frame
130: * @param kf1 i<sup>th</sup> Key Frame
131: * @param kf2 (i + 1)<sup>th</sup> Key Frame
132: * @param kf3 (i + 2)<sup>th</sup> Key Frame
133: */
134:
135: CubicSplineSegment(TCBKeyFrame kf0, TCBKeyFrame kf1,
136: TCBKeyFrame kf2, TCBKeyFrame kf3) {
137:
138: // Copy KeyFrame information
139: keyFrame[0] = new TCBKeyFrame(kf0);
140: keyFrame[1] = new TCBKeyFrame(kf1);
141: keyFrame[2] = new TCBKeyFrame(kf2);
142: keyFrame[3] = new TCBKeyFrame(kf3);
143:
144: // if linear interpolation is requested then just set linear flag
145: // if spline interpolation is needed then compute spline coefficients
146: if (kf2.linear == 1) {
147: this .linear = 1;
148: } else {
149: this .linear = 0;
150: computeCommonCoefficients(kf0, kf1, kf2, kf3);
151: computeHermiteCoefficients(kf0, kf1, kf2, kf3);
152: }
153:
154: length = computeLength(1.0f);
155: // System.out.println ("Segment length = " + length);
156:
157: }
158:
159: // compute the common coefficients
160: private void computeCommonCoefficients(TCBKeyFrame kf0,
161: TCBKeyFrame kf1, TCBKeyFrame kf2, TCBKeyFrame kf3) {
162:
163: // variables for destination derivative
164: float one_minus_t_in = 1.0f - kf1.tension;
165: float one_minus_c_in = 1.0f - kf1.continuity;
166: float one_minus_b_in = 1.0f - kf1.bias;
167: float one_plus_c_in = 1.0f + kf1.continuity;
168: float one_plus_b_in = 1.0f + kf1.bias;
169:
170: // coefficients for the incoming Tangent
171: ddb = one_minus_t_in * one_minus_c_in * one_minus_b_in;
172: dda = one_minus_t_in * one_plus_c_in * one_plus_b_in;
173:
174: // variables for source derivative
175: float one_minus_t_out = 1.0f - kf2.tension;
176: float one_minus_c_out = 1.0f - kf2.continuity;
177: float one_minus_b_out = 1.0f - kf2.bias;
178: float one_plus_c_out = 1.0f + kf2.continuity;
179: float one_plus_b_out = 1.0f + kf2.bias;
180:
181: // coefficients for the outgoing Tangent
182: dsb = one_minus_t_in * one_plus_c_in * one_minus_b_in;
183: dsa = one_minus_t_in * one_minus_c_in * one_plus_b_in;
184: }
185:
186: // compute the hermite interpolation basis coefficients
187: private void computeHermiteCoefficients(TCBKeyFrame kf0,
188: TCBKeyFrame kf1, TCBKeyFrame kf2, TCBKeyFrame kf3) {
189:
190: Point3f deltaP = new Point3f();
191: Point3f deltaS = new Point3f();
192:
193: // Find the difference in position and scale
194: deltaP.x = kf2.position.x - kf1.position.x;
195: deltaP.y = kf2.position.y - kf1.position.y;
196: deltaP.z = kf2.position.z - kf1.position.z;
197:
198: deltaS.x = kf2.scale.x - kf1.scale.x;
199: deltaS.y = kf2.scale.y - kf1.scale.y;
200: deltaS.z = kf2.scale.z - kf1.scale.z;
201:
202: // Incoming Tangent
203: Point3f dd_pos = new Point3f();
204: Point3f dd_scale = new Point3f();
205:
206: // If this is the first keyframe of the animation
207: if (kf0.knot == kf1.knot) {
208:
209: float ddab = 0.5f * (dda + ddb);
210:
211: // Position
212: dd_pos.x = ddab * deltaP.x;
213: dd_pos.y = ddab * deltaP.y;
214: dd_pos.z = ddab * deltaP.z;
215:
216: // Scale
217: dd_scale.x = ddab * deltaS.x;
218: dd_scale.y = ddab * deltaS.y;
219: dd_scale.z = ddab * deltaS.z;
220:
221: } else {
222:
223: float adj0 = (kf1.knot - kf0.knot) / (kf2.knot - kf0.knot);
224:
225: // Position
226: dd_pos.x = adj0
227: * ((ddb * deltaP.x) + (dda * (kf1.position.x - kf0.position.x)));
228: dd_pos.y = adj0
229: * ((ddb * deltaP.y) + (dda * (kf1.position.y - kf0.position.y)));
230: dd_pos.z = adj0
231: * ((ddb * deltaP.z) + (dda * (kf1.position.z - kf0.position.z)));
232:
233: // Scale
234: dd_scale.x = adj0
235: * ((ddb * deltaS.x) + (dda * (kf1.scale.x - kf0.scale.x)));
236: dd_scale.y = adj0
237: * ((ddb * deltaS.y) + (dda * (kf1.scale.y - kf0.scale.y)));
238: dd_scale.z = adj0
239: * ((ddb * deltaS.z) + (dda * (kf1.scale.z - kf0.scale.z)));
240: }
241:
242: // Outgoing Tangent
243: Point3f ds_pos = new Point3f();
244: Point3f ds_scale = new Point3f();
245:
246: // If this is the last keyframe of the animation
247: if (kf2.knot == kf3.knot) {
248:
249: float dsab = 0.5f * (dsa + dsb);
250:
251: // Position
252: ds_pos.x = dsab * deltaP.x;
253: ds_pos.y = dsab * deltaP.y;
254: ds_pos.z = dsab * deltaP.z;
255:
256: // Scale
257: ds_scale.x = dsab * deltaS.x;
258: ds_scale.y = dsab * deltaS.y;
259: ds_scale.z = dsab * deltaS.z;
260:
261: } else {
262:
263: float adj1 = (kf2.knot - kf1.knot) / (kf3.knot - kf1.knot);
264:
265: // Position
266: ds_pos.x = adj1
267: * ((dsb * (kf3.position.x - kf2.position.x)) + (dsa * deltaP.x));
268: ds_pos.y = adj1
269: * ((dsb * (kf3.position.y - kf2.position.y)) + (dsa * deltaP.y));
270: ds_pos.z = adj1
271: * ((dsb * (kf3.position.z - kf2.position.z)) + (dsa * deltaP.z));
272:
273: // Scale
274: ds_scale.x = adj1
275: * ((dsb * (kf3.scale.x - kf2.scale.x)) + (dsa * deltaS.x));
276: ds_scale.y = adj1
277: * ((dsb * (kf3.scale.y - kf2.scale.y)) + (dsa * deltaS.y));
278: ds_scale.z = adj1
279: * ((dsb * (kf3.scale.z - kf2.scale.z)) + (dsa * deltaS.z));
280: }
281:
282: // Calculate the coefficients of the polynomial for position
283: c0 = new Point3f();
284: c0.x = kf1.position.x;
285: c0.y = kf1.position.y;
286: c0.z = kf1.position.z;
287:
288: c1 = new Point3f();
289: c1.x = dd_pos.x;
290: c1.y = dd_pos.y;
291: c1.z = dd_pos.z;
292:
293: c2 = new Point3f();
294: c2.x = 3 * deltaP.x - 2 * dd_pos.x - ds_pos.x;
295: c2.y = 3 * deltaP.y - 2 * dd_pos.y - ds_pos.y;
296: c2.z = 3 * deltaP.z - 2 * dd_pos.z - ds_pos.z;
297:
298: c3 = new Point3f();
299: c3.x = -2 * deltaP.x + dd_pos.x + ds_pos.x;
300: c3.y = -2 * deltaP.y + dd_pos.y + ds_pos.y;
301: c3.z = -2 * deltaP.z + dd_pos.z + ds_pos.z;
302:
303: // Calculate the coefficients of the polynomial for scale
304: e0 = new Point3f();
305: e0.x = kf1.scale.x;
306: e0.y = kf1.scale.y;
307: e0.z = kf1.scale.z;
308:
309: e1 = new Point3f();
310: e1.x = dd_scale.x;
311: e1.y = dd_scale.y;
312: e1.z = dd_scale.z;
313:
314: e2 = new Point3f();
315: e2.x = 3 * deltaS.x - 2 * dd_scale.x - ds_scale.x;
316: e2.y = 3 * deltaS.y - 2 * dd_scale.y - ds_scale.y;
317: e2.z = 3 * deltaS.z - 2 * dd_scale.z - ds_scale.z;
318:
319: e3 = new Point3f();
320: e3.x = -2 * deltaS.x + dd_scale.x + ds_scale.x;
321: e3.y = -2 * deltaS.y + dd_scale.y + ds_scale.y;
322: e3.z = -2 * deltaS.z + dd_scale.z + ds_scale.z;
323: }
324:
325: /**
326: * Computes the length of the curve at a given point between
327: * key frames.
328: * @param u specifies the point between keyframes where 0 <= u <= 1.
329: */
330:
331: public float computeLength(float u) {
332:
333: float result = 0f;
334:
335: // if linear interpolation
336: if (linear == 1) {
337: result = u
338: * keyFrame[2].position
339: .distance(keyFrame[1].position);
340: } else {
341: // Need to transform domain [0,u] to [-1,1]. If 0 <= x <= u
342: // and -1 <= t <= 1, then x = u*(t+1)/2.
343: int degree = 5;
344: for (int i = 0; i < degree; i++)
345: result += (float) modCoeff[i]
346: * computeSpeed(u * (float) modRoot[i]);
347: result *= u;
348: }
349:
350: return result;
351: }
352:
353: // Velocity along curve
354: private float computeSpeed(float u) {
355: Point3f v = new Point3f();
356:
357: v.x = c1.x + u * (2 * c2.x + 3 * u * c3.x);
358: v.y = c1.y + u * (2 * c2.y + 3 * u * c3.y);
359: v.z = c1.z + u * (2 * c2.z + 3 * u * c3.z);
360:
361: return (float) (Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z));
362: }
363:
364: /**
365: * Computes the interpolated quaternion along the curve at
366: * a given point between key frames. This routine uses linear
367: * interpolation if the (i+1)<sup>th</sup> key frame's linear
368: * value is equal to 1.
369: *
370: * @param u specifies the point between keyframes where 0 <= u <= 1.
371: * @param newQuat returns the value of the interpolated quaternion
372: */
373:
374: public void getInterpolatedQuaternion(float u, Quat4f newQuat) {
375:
376: // if linear interpolation
377: if (this .linear == 1) {
378: double quatDot;
379:
380: quatDot = keyFrame[1].quat.x * keyFrame[2].quat.x
381: + keyFrame[1].quat.y * keyFrame[2].quat.y
382: + keyFrame[1].quat.z * keyFrame[2].quat.z
383: + keyFrame[1].quat.w * keyFrame[2].quat.w;
384:
385: if (quatDot < 0) {
386: newQuat.x = keyFrame[1].quat.x
387: + (-keyFrame[2].quat.x - keyFrame[1].quat.x)
388: * u;
389: newQuat.y = keyFrame[1].quat.y
390: + (-keyFrame[2].quat.y - keyFrame[1].quat.y)
391: * u;
392: newQuat.z = keyFrame[1].quat.z
393: + (-keyFrame[2].quat.z - keyFrame[1].quat.z)
394: * u;
395: newQuat.w = keyFrame[1].quat.w
396: + (-keyFrame[2].quat.w - keyFrame[1].quat.w)
397: * u;
398: } else {
399: newQuat.x = keyFrame[1].quat.x
400: + (keyFrame[2].quat.x - keyFrame[1].quat.x) * u;
401: newQuat.y = keyFrame[1].quat.y
402: + (keyFrame[2].quat.y - keyFrame[1].quat.y) * u;
403: newQuat.z = keyFrame[1].quat.z
404: + (keyFrame[2].quat.z - keyFrame[1].quat.z) * u;
405: newQuat.w = keyFrame[1].quat.w
406: + (keyFrame[2].quat.w - keyFrame[1].quat.w) * u;
407: }
408:
409: } else {
410:
411: // TODO:
412: // Currently we just use the great circle spherical interpolation
413: // for quaternions irrespective of the linear flag. Eventually
414: // we might want to do cubic interpolation of quaternions
415: newQuat.interpolate(keyFrame[1].quat, keyFrame[2].quat, u);
416: }
417:
418: }
419:
420: /**
421: * Computes the interpolated scale along the curve at a given point
422: * between key frames and returns a Point3f with the interpolated
423: * x, y, and z scale components. This routine uses linear
424: * interpolation if the (i+1)<sup>th</sup> key frame's linear
425: * value is equal to 1.
426: *
427: * @param u specifies the point between keyframes where 0 <= u <= 1.
428: * @param newScale returns the interpolated x,y,z scale value in a Point3f
429: */
430:
431: public void getInterpolatedScale(float u, Point3f newScale) {
432:
433: // if linear interpolation
434: if (this .linear == 1) {
435:
436: newScale.x = keyFrame[1].scale.x
437: + ((keyFrame[2].scale.x - keyFrame[1].scale.x) * u);
438: newScale.y = keyFrame[1].scale.y
439: + ((keyFrame[2].scale.y - keyFrame[1].scale.y) * u);
440: newScale.z = keyFrame[1].scale.z
441: + ((keyFrame[2].scale.z - keyFrame[1].scale.z) * u);
442:
443: } else {
444:
445: newScale.x = e0.x + u * (e1.x + u * (e2.x + u * e3.x));
446: newScale.y = e0.y + u * (e1.y + u * (e2.y + u * e3.y));
447: newScale.z = e0.z + u * (e1.z + u * (e2.z + u * e3.z));
448:
449: }
450: }
451:
452: /**
453: * Computes the interpolated position along the curve at a given point
454: * between key frames and returns a Point3f with the interpolated
455: * x, y, and z scale components. This routine uses linear
456: * interpolation if the (i+1)<sup>th</sup> key frame's linear
457: * value is equal to 1.
458: *
459: * @param u specifies the point between keyframes where 0 <= u <= 1.
460: * @param newPos returns the interpolated x,y,z position in a Point3f
461: */
462:
463: public void getInterpolatedPosition(float u, Point3f newPos) {
464:
465: // if linear interpolation
466: if (this .linear == 1) {
467: newPos.x = keyFrame[1].position.x
468: + ((keyFrame[2].position.x - keyFrame[1].position.x) * u);
469: newPos.y = keyFrame[1].position.y
470: + ((keyFrame[2].position.y - keyFrame[1].position.y) * u);
471: newPos.z = keyFrame[1].position.z
472: + ((keyFrame[2].position.z - keyFrame[1].position.z) * u);
473: } else {
474:
475: newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x));
476: newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y));
477: newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z));
478:
479: }
480: }
481:
482: /**
483: * Computes the interpolated position along the curve at a given point
484: * between key frames and returns a Vector3f with the interpolated
485: * x, y, and z scale components. This routine uses linear
486: * interpolation if the (i+1)<sup>th</sup> key frame's linear
487: * value is equal to 1.
488: *
489: * @param u specifies the point between keyframes where 0 <= u <= 1.
490: * @param newPos returns the interpolated x,y,z position in a Vector3f.
491: */
492:
493: public void getInterpolatedPositionVector(float u, Vector3f newPos) {
494: // if linear interpolation
495: if (this .linear == 1) {
496: newPos.x = keyFrame[1].position.x
497: + ((keyFrame[2].position.x - keyFrame[1].position.x) * u);
498: newPos.y = keyFrame[1].position.y
499: + ((keyFrame[2].position.y - keyFrame[1].position.y) * u);
500: newPos.z = keyFrame[1].position.z
501: + ((keyFrame[2].position.z - keyFrame[1].position.z) * u);
502: } else {
503:
504: newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x));
505: newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y));
506: newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z));
507:
508: }
509: }
510:
511: /**
512: * Computes the ratio of the length of the spline from the i<sup>th</sup>
513: * key frame to the position specified by u to the length of the entire
514: * spline segment from the i<sup>th</sup> key frame to the (i+1)
515: * <sup>th</sup> key frame. When the (i+1)<sup>th</sup> key frame's linear
516: * value is equal to 1, this is meaninful otherwise it should return u.
517: *
518: * @param u specifies the point between keyframes where 0 <= u <= 1.
519: * @return the interpolated ratio
520: */
521:
522: public float getInterpolatedValue(float u) {
523: return (computeLength(u) / this.length);
524: }
525: }
|