001: /*
002: * $RCSfile: LwsMotion.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:09 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.loaders.lw3d;
046:
047: import java.io.*;
048: import java.util.Enumeration;
049: import java.util.Vector;
050: import javax.media.j3d.*;
051: import javax.vecmath.*;
052: import com.sun.j3d.utils.behaviors.interpolators.*;
053: import com.sun.j3d.internal.J3dUtilsI18N;
054: import com.sun.j3d.loaders.ParsingErrorException;
055: import com.sun.j3d.loaders.IncorrectFormatException;
056:
057: /**
058: * This class is responsible for parsing the data in a Scene file related to
059: * an object's animation and constructing the appropriate Java3D
060: * Behavior objects. For each keyframe defined for the animation in the
061: * Lightwave file, this class creates a LwsFrame object to parse that
062: * keyframe data and create the appropriate data structures. Then for
063: * each of those LwsFrame objects created, LwsMotion creates a knot
064: * value for a PathInterpolator and fills in the appropriate field. Finally,
065: * the class creates a RotPosScalePathInterpolator with all of the data
066: * from the animation. There are also some utility functions in this
067: * class for dealing with special cases of animations, such as animations
068: * that begin after the first frame of the scene and animations that
069: * define frames in a way that Java3D cannot easily interpret.
070: */
071:
072: class LwsMotion extends TextfileParser {
073:
074: // data from the file
075: String motionName;
076: LwsFrame frames[];
077: int numFrames;
078: int numChannels;
079: boolean loop;
080: float totalTime;
081: int firstFrame;
082: int totalFrames;
083: Behavior behaviors;
084:
085: /**
086: * Constructor
087: */
088: LwsMotion(StreamTokenizer st, int frames, float time) {
089: this (st, 0, frames, time, EXCEPTION);
090:
091: }
092:
093: /**
094: * Constructor: takes tokenizer, 1st frame of this animation, total
095: * number of frames, total time of animation, and the debug settings
096: */
097: LwsMotion(StreamTokenizer st, int firstFrame, int frames,
098: float time, int debugVals) throws ParsingErrorException,
099: IncorrectFormatException {
100:
101: debugPrinter.setValidOutput(debugVals);
102: numFrames = 0;
103: totalTime = time;
104: this .firstFrame = firstFrame;
105: totalFrames = frames;
106: debugOutputLn(LINE_TRACE, "about to get motion name");
107: motionName = getName(st);
108: debugOutputLn(LINE_TRACE, "about to get motion");
109: getMotion(st);
110: }
111:
112: /**
113: * This method parses the tokenizer and creates the data structures
114: * that hold the data from that file. For each separate keyframe,
115: * this method calls LwsFrame to parse and interpret that data.
116: */
117: void getMotion(StreamTokenizer st) throws ParsingErrorException,
118: IncorrectFormatException {
119: debugOutputLn(TRACE, "getMotion()");
120: numChannels = (int) getNumber(st);
121: if (numChannels != 9) {
122: throw new IncorrectFormatException(J3dUtilsI18N
123: .getString("LwsMotion0"));
124: }
125: debugOutputLn(LINE_TRACE, "got channels");
126:
127: numFrames = (int) getNumber(st);
128: frames = new LwsFrame[numFrames];
129: debugOutputLn(VALUES, "got frames" + numFrames);
130:
131: for (int i = 0; i < numFrames; ++i) {
132: frames[i] = new LwsFrame(st);
133: }
134:
135: debugOutput(LINE_TRACE, "got all frames");
136:
137: getAndCheckString(st, "EndBehavior");
138: int repeatVal = (int) getNumber(st);
139: if (repeatVal == 1)
140: loop = false;
141: else
142: loop = true;
143:
144: // need to make sure frame info is in sycn with j3d
145: // fixFrames();
146: }
147:
148: /**
149: * The previous version of this method looked for sucessive frames with
150: * the same rotation value (mod 2PI). If it found such frames, it would
151: * divide that interval into 4 separate frames.
152: * This fix is not sufficient for various rotation cases, though. For
153: * instance, if the rotation difference between two frames is more than
154: * 2PI, the rotation will not case a flag to be fixed and the resulting
155: * rotation will only be between the delta of the two rotations, mod 2PI.
156: * The real fix should behave as follows:
157: * - Iterate through all sucessive frames
158: * - For any two frames that have rotation components that differ by more
159: * than PI/2 (one quarter rotation - no reason for this, but let's pick a
160: * small value to give our resulting path interpolations a better chance
161: * of behaving correctly), figure out how many frames we need to create to
162: * get increments of <= PI/2 between each frame.
163: * - Create these new frames
164: * - Set the odl frames pointer to the new frames structures.
165: */
166:
167: void fixFrames() {
168:
169: boolean addedFrames = false;
170: Vector newFramesList = new Vector();
171: double halfPI = (float) (Math.PI / 2);
172: LwsFrame finalFrame = null;
173:
174: for (int i = 1; i < numFrames; ++i) {
175: LwsFrame prevFrame;
176: LwsFrame lastFrame = frames[i - 1];
177: LwsFrame this Frame = frames[i];
178: LwsFrame nextFrame;
179:
180: finalFrame = this Frame;
181: newFramesList.add(lastFrame);
182:
183: double largestAngleDifference = 0;
184: double this Angle = this Frame.getHeading();
185: double lastAngle = lastFrame.getHeading();
186: double angleDifference = Math.abs(this Angle - lastAngle);
187: if (angleDifference > largestAngleDifference)
188: largestAngleDifference = angleDifference;
189:
190: this Angle = this Frame.getPitch();
191: lastAngle = lastFrame.getPitch();
192: angleDifference = Math.abs(this Angle - lastAngle);
193: if (angleDifference > largestAngleDifference)
194: largestAngleDifference = angleDifference;
195:
196: this Angle = this Frame.getBank();
197: lastAngle = lastFrame.getBank();
198: angleDifference = Math.abs(this Angle - lastAngle);
199: if (angleDifference > largestAngleDifference)
200: largestAngleDifference = angleDifference;
201:
202: if (largestAngleDifference > halfPI) {
203: // Angles too big - create new frames
204: addedFrames = true;
205: int numNewFrames = (int) (largestAngleDifference / halfPI);
206: double increment = 1.0 / (double) (numNewFrames + 1);
207: double currentRatio = increment;
208:
209: double totalf = frames[numFrames - 1].getFrameNum();
210: double tlength = (this Frame.getFrameNum() - lastFrame
211: .getFrameNum())
212: / totalf;
213: double adj0;
214: double adj1;
215:
216: // get the previous and next frames
217: if ((i - 1) < 1) {
218: prevFrame = frames[i - 1];
219: adj0 = 0.0;
220: } else {
221: prevFrame = frames[i - 2];
222: adj0 = tlength
223: / ((this Frame.getFrameNum() - prevFrame
224: .getFrameNum()) / totalf);
225: }
226:
227: if ((i + 1) < numFrames) {
228: nextFrame = frames[i + 1];
229: adj1 = tlength
230: / ((nextFrame.getFrameNum() - lastFrame
231: .getFrameNum()) / totalf);
232: } else {
233: nextFrame = frames[i];
234: adj1 = 1.0;
235: }
236:
237: for (int j = 0; j < numNewFrames; ++j) {
238:
239: LwsFrame newFrame;
240:
241: // if linear interpolation
242: if (this Frame.linearValue == 1) {
243: newFrame = new LwsFrame(lastFrame, this Frame,
244: currentRatio);
245:
246: // if spline interpolation
247: } else {
248: newFrame = new LwsFrame(prevFrame, lastFrame,
249: this Frame, nextFrame, currentRatio,
250: adj0, adj1);
251: }
252:
253: currentRatio += increment;
254: newFramesList.add(newFrame);
255: }
256: }
257: }
258:
259: // Now add in final frame
260: if (finalFrame != null)
261: newFramesList.add(finalFrame);
262: if (addedFrames) {
263:
264: // Recreate frames array from newFramesList
265: LwsFrame newFrames[] = new LwsFrame[newFramesList.size()];
266: Enumeration elements = newFramesList.elements();
267: int index = 0;
268: while (elements.hasMoreElements()) {
269: newFrames[index++] = (LwsFrame) elements.nextElement();
270: }
271: frames = newFrames;
272: numFrames = frames.length;
273: for (int i = 0; i < numFrames; ++i) {
274: debugOutputLn(VALUES, "frame " + i + " = " + frames[i]);
275: frames[i].printVals();
276: }
277: }
278: }
279:
280: /**
281: * Utility for getting integer mod value
282: */
283: int intMod(int divisee, int divisor) {
284: int tmpDiv = divisee;
285: int tmpDivisor = divisor;
286: if (tmpDiv < 0)
287: tmpDiv = -tmpDiv;
288: if (tmpDivisor < 0)
289: tmpDivisor = -tmpDivisor;
290: while (tmpDiv > tmpDivisor) {
291: tmpDiv -= tmpDivisor;
292: }
293: return tmpDiv;
294: }
295:
296: /**
297: * Class that associates a particular frame with its effective frame
298: * number (which accounts for animations that start after frame 1)
299: */
300: class FrameHolder {
301: double frameNumber;
302: LwsFrame frame;
303:
304: FrameHolder(LwsFrame theFrame, double number) {
305: frame = theFrame;
306: frameNumber = number;
307: }
308: }
309:
310: /**
311: * This method was added to account for animations that start after
312: * the first frame (e.g., Juggler.lws starts at frame 30). We need
313: * to alter some of the information for the frames in this "frame subset"
314: */
315: void playWithFrameTimes(Vector framesVector) {
316: debugOutputLn(TRACE, "playWithFrameTimes: firstFrame = "
317: + firstFrame);
318: if (firstFrame == 1) {
319: return;
320: } else if (frames[numFrames - 1].getFrameNum() < totalFrames) {
321: // First, create a vector that holds all LwsFrame's in frame
322: // increasing order (where order is started at firstFrame Modulo
323: // this motion's last frame
324: int motionLastFrame = (int) (frames[numFrames - 1]
325: .getFrameNum() + .4999999);
326: int newFirstFrame = intMod(firstFrame, motionLastFrame);
327: int newLastFrame = intMod(totalFrames, motionLastFrame);
328: int index = 0;
329: while (index < numFrames) {
330: if (frames[index].getFrameNum() >= newFirstFrame)
331: break;
332: ++index;
333: }
334: int startIndex = index;
335: if (frames[startIndex].getFrameNum() > firstFrame
336: && startIndex > 0)
337: startIndex--; // Actually, should interpolate
338: index = startIndex;
339: if (newFirstFrame < newLastFrame) {
340: while (index < numFrames
341: && frames[index].getFrameNum() <= newLastFrame) {
342: FrameHolder frameHolder = new FrameHolder(
343: frames[index], frames[index].getFrameNum()
344: - newFirstFrame);
345: framesVector.addElement(frameHolder);
346: ++index;
347: }
348: } else {
349: double currentNewFrameNumber = -1.0;
350: while (index < numFrames) {
351: currentNewFrameNumber = frames[index].getFrameNum()
352: - newFirstFrame;
353: FrameHolder frameHolder = new FrameHolder(
354: frames[index], currentNewFrameNumber);
355: framesVector.addElement(frameHolder);
356: ++index;
357: }
358: index = 0;
359: while (index <= startIndex
360: && frames[index].getFrameNum() <= newLastFrame) {
361: if (index == 0) {
362: LwsFrame newFrame = new LwsFrame(
363: frames[index],
364: frames[index + 1],
365: 1.0 / (frames[index + 1].getFrameNum() - frames[index]
366: .getFrameNum()));
367: FrameHolder frameHolder = new FrameHolder(
368: newFrame, newFrame.getFrameNum()
369: + currentNewFrameNumber);
370: framesVector.addElement(frameHolder);
371: } else {
372: FrameHolder frameHolder = new FrameHolder(
373: frames[index], frames[index]
374: .getFrameNum()
375: + currentNewFrameNumber);
376: framesVector.addElement(frameHolder);
377: }
378: ++index;
379: }
380: }
381: } else {
382: int index = 0;
383: while (index < numFrames) {
384: if (frames[index].getFrameNum() >= firstFrame)
385: break;
386: ++index;
387: }
388: int startIndex = index;
389: if (frames[startIndex].getFrameNum() > firstFrame
390: && startIndex > 0) {
391: // Interpolate to first frame
392: double ratio = (double) firstFrame
393: / (frames[startIndex].getFrameNum() - frames[startIndex - 1]
394: .getFrameNum());
395: LwsFrame newFrame = new LwsFrame(
396: frames[startIndex - 1], frames[startIndex],
397: ratio);
398: FrameHolder frameHolder = new FrameHolder(newFrame,
399: newFrame.getFrameNum() - firstFrame);
400: framesVector.addElement(frameHolder);
401: }
402: index = startIndex;
403: while (index < numFrames
404: && frames[index].getFrameNum() <= totalFrames) {
405: FrameHolder frameHolder = new FrameHolder(
406: frames[index], frames[index].getFrameNum()
407: - firstFrame);
408: framesVector.addElement(frameHolder);
409: ++index;
410: }
411: if (frames[index - 1].getFrameNum() < totalFrames) {
412: // Interpolate to last frame
413: double ratio = (double) (totalFrames - frames[index - 1]
414: .getFrameNum())
415: / (frames[index].getFrameNum() - frames[index - 1]
416: .getFrameNum());
417: LwsFrame newFrame = new LwsFrame(frames[index - 1],
418: frames[index], ratio);
419: FrameHolder frameHolder = new FrameHolder(newFrame,
420: totalFrames - firstFrame);
421: framesVector.addElement(frameHolder);
422: }
423: }
424: }
425:
426: /**
427: * Normally, we just create j3d behaviors from the frames. But if the
428: * animation's first frame is after frame number one, then we have to
429: * shuffle things around to account for playing/looping on this subset
430: * of the total frames of the animation
431: */
432: void createJava3dBehaviorsForFramesSubset(TransformGroup target) {
433:
434: debugOutputLn(TRACE, "createJava3dBehaviorsForFramesSubset");
435: Vector frameHolders = new Vector();
436: playWithFrameTimes(frameHolders);
437: long alphaAtOne = 0;
438:
439: // determine looping
440: int loopCount;
441: if (loop)
442: loopCount = -1;
443: else
444: loopCount = 1;
445: loopCount = -1;
446:
447: int numFrames = frameHolders.size();
448:
449: debugOutputLn(VALUES, "totalTime = " + totalTime);
450: debugOutputLn(VALUES, "loopCount = " + loopCount);
451:
452: FrameHolder lastFrameHolder = (FrameHolder) frameHolders
453: .elementAt(frameHolders.size() - 1);
454: LwsFrame lastFrame = lastFrameHolder.frame;
455: float animTime = 1000.0f
456: * totalTime
457: * (float) (lastFrameHolder.frameNumber / (float) (totalFrames - firstFrame));
458: debugOutputLn(VALUES, " anim time: " + animTime);
459: debugOutputLn(VALUES, " totalFrames = " + totalFrames);
460:
461: if (!loop)
462: alphaAtOne = (long) (1000.0 * totalTime - animTime);
463: Alpha theAlpha = new Alpha(loopCount, Alpha.INCREASING_ENABLE,
464: 0, 0, (long) animTime, 0, alphaAtOne, 0, 0, 0);
465:
466: float knots[] = new float[numFrames];
467: Point3f[] positions = new Point3f[numFrames];
468: Quat4f[] quats = new Quat4f[numFrames];
469: Point3f[] scales = new Point3f[numFrames];
470: Transform3D yAxis = new Transform3D();
471: Matrix4d mat = new Matrix4d();
472: KBKeyFrame[] keyFrames = new KBKeyFrame[numFrames];
473:
474: for (int i = 0; i < numFrames; ++i) {
475:
476: FrameHolder frameHolder = (FrameHolder) frameHolders
477: .elementAt(i);
478: LwsFrame frame = frameHolder.frame;
479:
480: // copy position
481: positions[i] = frame.getPosition();
482:
483: // copy scale
484: // Used to hardcode no-scale: scales[i] = 1.0f, 1.0f, 1.0f;
485: // Note that we can't do non-uniform scaling in the current Path
486: // interpolators. The interpolator just uses the x scale.
487: // getScale makes sure that we don't have any zero scale component
488: scales[i] = frame.getScale();
489:
490: // copy rotation information
491: frame.setRotationMatrix(mat);
492: debugOutputLn(VALUES, "LwsMotion::createj3dbeh, mat = "
493: + mat);
494: quats[i] = new Quat4f();
495: quats[i].set(mat);
496: debugOutputLn(VALUES, " and quat = " + quats[i]);
497:
498: // calculate knot points from frame numbers
499: if (i == 0)
500: knots[i] = 0.0f;
501: else
502: knots[i] = (float) (frameHolder.frameNumber)
503: / (float) (lastFrameHolder.frameNumber);
504:
505: // Create KB key frames
506: keyFrames[i] = new KBKeyFrame(knots[i], frame.linearValue,
507: positions[i], (float) frame.heading,
508: (float) frame.pitch, (float) frame.bank, scales[i],
509: (float) frame.tension, (float) frame.continuity,
510: (float) frame.bias);
511:
512: debugOutputLn(VALUES, "pos, knots, quat = " + positions[i]
513: + knots[i] + quats[i]);
514: }
515:
516: // Pass the KeyFrames to the interpolator an let it do its thing
517: KBRotPosScaleSplinePathInterpolator b = new KBRotPosScaleSplinePathInterpolator(
518: theAlpha, target, yAxis, keyFrames);
519: if (b != null) {
520: behaviors = b;
521: BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,
522: 0.0, 0.0), 1000000.0);
523: b.setSchedulingBounds(bounds);
524: target.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
525: target.addChild(behaviors);
526: }
527: }
528:
529: /**
530: * Create j3d behaviors for the data stored in this animation. This is
531: * done by creating a RotPosScalePathInterpolator object that contains
532: * all of the position, orientation, scale data for each keyframe.
533: */
534: void createJava3dBehaviors(TransformGroup target) {
535:
536: if (numFrames <= 1)
537: behaviors = null;
538: else {
539: if (firstFrame > 1) {
540: createJava3dBehaviorsForFramesSubset(target);
541: return;
542: }
543:
544: long alphaAtOne = 0;
545:
546: // determine looping
547: int loopCount;
548: if (loop)
549: loopCount = -1;
550: else
551: loopCount = 1;
552: loopCount = -1;
553:
554: debugOutputLn(VALUES, "totalTime = " + totalTime);
555: debugOutputLn(VALUES, "loopCount = " + loopCount);
556:
557: float animTime = 1000.0f
558: * totalTime
559: * (float) (frames[numFrames - 1].getFrameNum() / (float) totalFrames);
560:
561: debugOutputLn(VALUES, " anim time: " + animTime);
562: debugOutputLn(VALUES, " totalFrames = " + totalFrames);
563: debugOutputLn(VALUES, " lastFrame = "
564: + frames[numFrames - 1].getFrameNum());
565:
566: if (!loop)
567: alphaAtOne = (long) (1000.0 * totalTime - animTime);
568: Alpha theAlpha = new Alpha(loopCount,
569: Alpha.INCREASING_ENABLE, 0, 0, (long) animTime, 0,
570: alphaAtOne, 0, 0, 0);
571:
572: float knots[] = new float[numFrames];
573: Point3f[] positions = new Point3f[numFrames];
574: Quat4f[] quats = new Quat4f[numFrames];
575: Point3f[] scales = new Point3f[numFrames];
576: Transform3D yAxis = new Transform3D();
577: Matrix4d mat = new Matrix4d();
578: KBKeyFrame[] keyFrames = new KBKeyFrame[numFrames];
579:
580: for (int i = 0; i < numFrames; ++i) {
581:
582: // copy position
583: positions[i] = frames[i].getPosition();
584:
585: // copy scale
586: // Used to hardcode no-scale: scales[i] = 1.0f, 1.0f, 1.0f;
587: // Note that we can't do non-uniform scaling in the current Path
588: // interpolators. The interpolator just uses the x scale.
589: // getScale makes sure that we don't have any 0 scale component
590: scales[i] = frames[i].getScale();
591:
592: // copy rotation information
593: frames[i].setRotationMatrix(mat);
594: debugOutputLn(VALUES, "LwsMotion::createj3dbeh, mat = "
595: + mat);
596: quats[i] = new Quat4f();
597: quats[i].set(mat);
598: debugOutputLn(VALUES, " and quat = " + quats[i]);
599:
600: // calculate knot points from frame numbers
601: if (i == 0)
602: knots[i] = 0.0f;
603: else
604: knots[i] = (float) (frames[i].getFrameNum())
605: / (float) (frames[numFrames - 1]
606: .getFrameNum());
607:
608: // Create KB key frames
609: keyFrames[i] = new KBKeyFrame(knots[i],
610: frames[i].linearValue, positions[i],
611: (float) frames[i].heading,
612: (float) frames[i].pitch,
613: (float) frames[i].bank, scales[i],
614: (float) frames[i].tension,
615: (float) frames[i].continuity,
616: (float) frames[i].bias);
617:
618: debugOutputLn(VALUES, "pos, knots, quat = "
619: + positions[i] + knots[i] + quats[i]);
620: }
621:
622: // Pass the KeyFrames to the interpolator an let it do its thing
623: KBRotPosScaleSplinePathInterpolator b = new KBRotPosScaleSplinePathInterpolator(
624: theAlpha, target, yAxis, keyFrames);
625: if (b != null) {
626: behaviors = b;
627: BoundingSphere bounds = new BoundingSphere(new Point3d(
628: 0.0, 0.0, 0.0), 1000000.0);
629: b.setSchedulingBounds(bounds);
630: target
631: .setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
632: target.addChild(behaviors);
633: }
634: }
635:
636: }
637:
638: /**
639: * Returns the Behavior object created for this animation
640: */
641: Behavior getBehaviors() {
642: return behaviors;
643: }
644:
645: /**
646: * Returns the first LwsFrame object (which contains the initial
647: * setup for a given object)
648: */
649: LwsFrame getFirstFrame() {
650: if (numFrames > 0)
651: return frames[0];
652: else
653: return null;
654: }
655:
656: /**
657: * Utility function for printing values
658: */
659: void printVals() {
660: debugOutputLn(VALUES, " motionName = " + motionName);
661: debugOutputLn(VALUES, " numChannels = " + numChannels);
662: debugOutputLn(VALUES, " numFrames = " + numFrames);
663: debugOutputLn(VALUES, " loop = " + loop);
664: for (int i = 0; i < numFrames; ++i) {
665: debugOutputLn(VALUES, " FRAME " + i);
666: frames[i].printVals();
667: }
668: }
669:
670: }
|