Imagine you intend to drive a spaceship through a J3D scene, lets say with constant speed. The camera is fixed at the ship. What would be the best way to do that really smooth? Esp. on Win32? I tried to create a Behavior that works on the getViewingPlatform().getViewPlatformTransform(). The behavior wakes up every frame and the code is very intuitive. I tried to measure time with System.currentTimeMillis() or with an own clock, that averages a couple of frames depending on timer resolution (50ms on Win32). This clock is triggered within Canvas3D.preRender(). But the movement is far from being smooth! Another way could be to synchronize the renderer with a update cycle. Could that be better? When I change a transformgroup within an behavior, when can I expect the change to be valid? Next frame? I append my FrameClock and TimerAccuracy classes for convenience. And my TestBehavior to show how to use them. Any thoughts on smooth animation?? - J Joerg 'Herkules' Plewe HARDCODE Development http://www.hardcode.de
// TimerAccuracy.java //********************************************************************************************** // // // (C) Copyright 2000 by Dipl. Phys. Joerg Plewe, HARDCODE Development // All rights reserved. Copying, modification, // distribution or publication without the prior written // consent of the author is prohibited. // // // Filename: TimerAccuracy.java // Author: Herkules // Created: 20000119 // Version: $Version$ // // Module: $Module$ // Dependecies: $Dependencies$ // // History: // $Date$ $Who$ $What$ // /* Purpose: $Describe purpose of module$ Measure the resolution of the System timer */ //********************************************************************************************** package HARDCODE.Util; /** * Measure the accuracy of the timer */ // // This classes 'measure' method has to be run at highest thread priority! // public class TimerAccuracy { final public long measure( long _timeToMeasure ) { // Max./min. ticker difference maxRes = 0; minRes = 999999; // Get current ms ticker long ms = System.currentTimeMillis(); // Calculate time to stop long EndMs = ms + _timeToMeasure; while( ms < EndMs ) { long oldms = ms; ms = System.currentTimeMillis(); long diff = ms - oldms; if( diff > maxRes ) maxRes = diff; if( diff > 0 && diff < minRes ) minRes = diff; } System.out.println( "Worst timer resolution: " + maxRes ); System.out.println( "Best timer resolution: " + minRes ); return minRes; } //------------------------------------------------------------------------- //------------------------------------------------------------------------- //------------------------------------------------------------------------- // // Variables // public long minRes; public long maxRes; }
// FrameClock.java //********************************************************************************************** // // // (C) Copyright 2000 by Dipl. Phys. Joerg Plewe, HARDCODE Development // All rights reserved. Copying, modification, // distribution or publication without the prior written // consent of the author is prohibited. // // // Filename: FrameClock.java // Author: Herkules // Created: 20000119 // Version: $Version$ // // Module: $Module$ // Dependecies: $Dependencies$ // // History: // $Date$ $Who$ $What$ // /* Purpose: $Describe purpose of module$ Provide an adjustable (->network) clock This class is a singleton. Every method is static */ //********************************************************************************************** package HARDCODE.Util; public class FrameClock { //-------------------------------------------------------------------------Adjust // Herkules // Adjust the clock with an offset // public static void adjust( int _Adjustment ) { // Shift the adjustment adjust -= _Adjustment; // reset the timer reset(); } //-------------------------------------------------------------------------maintainFrameTiming public static void maintainFrameTiming( ) { int currentMillis = (int)System.currentTimeMillis() - adjust; int FrameTimeDiff = currentMillis - lastFrameMillis; FrameTimer[ FrameTimerIndex ] = FrameTimeDiff; FrameTimerIndex++; FrameTimerIndex %= FrameTimerCount; if( FrameTimeDiff > 0 ) { lastFrameMillis = currentMillis; countNoTimerChange = 0; } else countNoTimerChange++; // Remember an average FPS value at reasonables distances if( FrameTimerIndex == 0) { updateAverages(); // System.out.println( getAverageFPS() ); } } //-------------------------------------------------------------------------getTimeNextFrame public static int getTimeNextFrame() { return lastFrameMillis + ( countNoTimerChange * averageFrameTime ); } //-------------------------------------------------------------------------getFPS public static float getFPS() { return averageFPS; } //-------------------------------------------------------------------------getAverageFrameTime public static float getAverageFrameTime() { return (float)averageFrameTime; } //-------------------------------------------------------------------------reset public static void reset() { FrameTimerCount = (int)new TimerAccuracy().measure( 200 ) / 2; if( FrameTimerCount < 5 ) FrameTimerCount = 5; if( FrameTimerCount >= maxFrameTimerCount ) FrameTimerCount = maxFrameTimerCount - 1; for( int i = 0; i < FrameTimerCount; ++i ) { FrameTimer[ i ] = averageFrameTime; } lastFrameMillis = (int)System.currentTimeMillis() - adjust; updateAverages(); } //-------------------------------------------------------------------------updateAverages private static void updateAverages() { averageFrameTime = 0; for( int i = 0; i < FrameTimerCount; ++i ) { averageFrameTime += FrameTimer[ i ]; } averageFrameTime /= FrameTimerCount; averageFPS = 1000.0f / (float)averageFrameTime; } //********************************************************************************************* // Variables //********************************************************************************************* public static int lastSnap = -1; private static int adjust = 0; private final static int maxFrameTimerCount = 100; private static int FrameTimerCount = maxFrameTimerCount; private static int FrameTimer[] = new int[ maxFrameTimerCount ]; private static int lastFrameMillis; private static int FrameTimerIndex = 0; private static int averageFrameTime = 40; private static int countNoTimerChange = 0; private static float averageFPS = 25.0f; }
/* * TestBehavior.java * * Created on 2. Juli 2000, 22:50 */ package HARDCODE.TGAW.Lib3D; import javax.media.j3d.*; import javax.vecmath.*; import HARDCODE.Util.FrameClock; /** * * @author Herkules * @version */ public class TestBehavior extends Behavior { /** Creates new TestBehavior */ public TestBehavior( TransformGroup tg ) { m_TransformGroup = tg; } public void initialize() { m_StartTime = FrameClock.getTimeNextFrame(); wakeupOn( m_wakeup ); } public void processStimulus( java.util.Enumeration criteria ) { // System.out.println( "Wake up!" ); m_TransformGroup.getTransform( m_T3D ); angle += FrameClock.getAverageFrameTime() / 2000.0f; // int TimeNextFrame = FrameClock.getTimeNextFrame(); // angle = ( TimeNextFrame - m_StartTime ) / 2000.0f ; AxisAngle4f aa4f = new AxisAngle4f(new Vector3f(1.0f, 1.0f, 1.0f), angle ); m_T3D.setRotation( aa4f ); m_TransformGroup.setTransform( m_T3D ); wakeupOn( m_wakeup ); } private WakeupOnElapsedFrames m_wakeup = new WakeupOnElapsedFrames( 0 ); private TransformGroup m_TransformGroup; private Transform3D m_T3D = new Transform3D(); private float angle = 0.0f; private int m_StartTime; }