Dr Manuel Carcenac - European University of Lefke Advanced topics in animation - presented with JOGL kinematic animation: time interpolation of degrees of freedom dynamic animation: time integration of degrees of freedom dynamic animation: all-pairs n-body problem and gravitation References: http://jogamp.org/ (for JOGL 2.0) http://download.java.net/media/jogl/jogl-2.x-docs/ http://www.glprogramming.com/red/ http://eng.eul.edu.tr/manuel/Course_on_Graphics_in_Java/Course_on_Graphics_in_Java.htm (for practical introduction to JOGL 2.0) Advanced Animation and Rendering Techniques – Theory and Practice Alan Watt, Mark Watt; Addison-Wesley, ACM press Michael Thomas Flanagan's Java Scientific and Numerical Library http://www.ee.ucl.ac.uk/~mflanaga/java/index.html http://www.ee.ucl.ac.uk/~mflanaga/java/CubicSpline.html (for cubic spline interpolation) http://farside.ph.utexas.edu/teaching/329/lectures/node35.html for Runge-Kutta method http://www.ast.cam.ac.uk/~sverre/web/pages/nbody.htm for n-body problem http://www.scholarpedia.org/article/N-body_simulations_(gravitational) Dr Manuel Carcenac - European University of Lefke kinematic animation: time interpolation of degrees of freedom cubic spline interpolation: install Flanagan's scientific library: ♦ put flanagan.jar into folder C:\Program Files\Java\jdk1.6.0_23\jre\lib\ext ♦ add to system variable CLASSPATH (already exists, was created for JOGL) ♦ Computer Properties Advanced system settings Environment Variables ♦ edit system variable CLASSPATH (already exists, was created for JOGL) add at the end of it: ;C:\Program Files\Java\jdk1.6.0_23\jre\lib\ext\flanagan.jar import statement: import flanagan.interpolation.*; cubic spline with Nk control points ( tk , xk ) : double[] xk = new double[Nk]; double[] tk = new double[Nk]; CubicSpline csx = new CubicSpline(tk , xk); interpolation of function x(t) : double x = csx.interpolate(t); for every control point k , csx.interpolate(tk[k]) yields value xk[k] Dr Manuel Carcenac - European University of Lefke example: interpolated curve with interactive selection and modification of control points import flanagan.interpolation.*; public class P { public static void main(String[] arg) { Gui gui = new Gui(); } } class Gui { JFrame f; DrawingPanel p; JPanel ps; JSlider skx , sky , skz , skt; JLabel lkx , lky , lkz , lkt; int Nk = 8; int ks = 0; // ks = k of selected key double[] xk = new double[Nk] , yk = new double[Nk] , zk = new double[Nk] , tk = new double[Nk]; int N = 60; double L = 100.0 , T = 100.0; { for (int k = ; k < Nk ; k++) { xk[k] = 0.0; yk[k] = - L / + L * k / (Nk - 1); zk[k] = 0.0; tk[k] = T * k / (Nk - 1); } } class DrawingPanel extends GLJPanel { GLU glu; GLUquadric quad; float[] RED = new float[] { 1.0f , 0.0f , 0.0f , 1.0f } , GREEN = new float[] { 0.0f , 1.0f , 0.0f , 1.0f } , WHITE = new float[] { 1.0f , 1.0f , 1.0f , 1.0f }; DrawingPanel() { super(new GLCapabilities(GLProfile.getDefault())); this.addGLEventListener(new GLEventListener() { public void display(GLAutoDrawable drawable) { //**** DISPLAY //// - KEY POINTS for (int k = ; k < Nk ; k++) { gl.glMaterialfv(GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , (k == ks ? RED : GREEN) , 0); gl.glPushMatrix(); gl.glTranslatef((float)xk[k] , (float)yk[k] , (float)zk[k]); glu.gluSphere(quad , 2.0f , 10 , 10); gl.glPopMatrix(); } Dr Manuel Carcenac - European University of Lefke //// - INTERPOLATED POINTS gl.glMaterialfv(GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , WHITE , 0); CubicSpline csx = new CubicSpline(tk , xk) , csy = new CubicSpline(tk , yk) , csz = new CubicSpline(tk , zk); for (int k = ; k < N ; k++) { double t = T * k / (N - 1); double x = csx.interpolate(t) , y = csy.interpolate(t) , z = csz.interpolate(t); gl.glPushMatrix(); gl.glTranslatef((float)x , (float)y , (float)z); glu.gluSphere(quad , 1.0f , 10 , 10); gl.glPopMatrix(); } gl.glFlush(); } } ); } } Dr Manuel Carcenac - European University of Lefke Gui() { f = new JFrame(); f.setFocusable(true); f.setVisible(true); p = new DrawingPanel(); f.getContentPane().add(p , BorderLayout.CENTER); f.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { int bt = e.getButton(); switch (bt) { case MouseEvent.BUTTON1: ks ; if (ks == -1) ks = Nk - 1; case MouseEvent.BUTTON3: ks++; if (ks == Nk) ks = 0; break; break; } skx.setValue((int)(1000 * (0.5 + xk[ks] / L))); sky.setValue((int)(1000 * (0.5 + yk[ks] / L))); skz.setValue((int)(1000 * (0.5 + zk[ks] / L))); skt.setValue((int)(1000 * tk[ks] / T)); f.repaint(); } } ); ps = new JPanel(); ps.setLayout(new GridLayout(0 , 2)); f.getContentPane().add(ps , BorderLayout.EAST); skx = new JSlider(JSlider.HORIZONTAL , , 1000 , 0); ps.add(skx); lkx = new JLabel(" x of selected key"); ps.add(lkx); sky = new JSlider(JSlider.HORIZONTAL , , 1000 , 0); ps.add(sky); lky = new JLabel(" y of selected key"); ps.add(lky); skz = new JSlider(JSlider.HORIZONTAL , , 1000 , 0); ps.add(skz); lkz = new JLabel(" z of selected key"); ps.add(lkz); skt = new JSlider(JSlider.HORIZONTAL , , 1000 , 0); ps.add(skt); lkt = new JLabel(" t of selected key"); ps.add(lkt); skx.addChangeListener( { xk[ks] = -L/2+L*skx.getValue()/1000; f.repaint(); } } ); sky.addChangeListener( { yk[ks] = -L/2+L*sky.getValue()/1000; f.repaint(); } } ); skz.addChangeListener( { zk[ks] = -L/2+L*skz.getValue()/1000; f.repaint(); } } ); skt.addChangeListener( { if (ks != && ks != Nk - 1) tk[ks] = T*skt.getValue()/1000; if (ks > && tk[ks - 1] + T / 100 > tk[ks]) { tk[ks] = tk[ks - 1] + T / 100; } if (ks < Nk - && tk[ks] > tk[ks + 1] - T / 100) { tk[ks] = tk[ks + 1] - T / 100; } f.repaint(); } } ); f.setSize(new Dimension(900 + 16 , 600 + 38)); } } Dr Manuel Carcenac - European University of Lefke Dr Manuel Carcenac - European University of Lefke example: interpolated trajectory of a ball: interactive creation of key frames over time followed by spline driven animation import flanagan.interpolation.*; public class P { public static void main(String[] arg) { Gui gui = new Gui(); while (true) { if (gui.read_perform_anim()) { gui.animation(); gui.write_perform_anim(false); } Thread.yield(); } } } class Gui { JFrame f; DrawingPanel p; JPanel ps; JSlider skx , sky , skz; JLabel lkx , lky , lkz; JButton b_new_key , b_perform_anim; int Nk , nk; double[] xk , yk , zk , tk; double L = 100.0 , T = 20.0; double time; boolean perform_anim = false; synchronized void write_perform_anim(boolean value) { perform_anim = value; } synchronized boolean read_perform_anim() { return perform_anim; } void animation() { time = 0; long dt = 50; // 0.05s while (time + dt * 0.001 < T) { long t_start = System.currentTimeMillis(); f.repaint(); time += dt * 0.001; long dt_real = System.currentTimeMillis() - t_start; if (dt_real < dt) try {Thread.sleep(dt - dt_real);} catch(InterruptedException ee){} else System.out.println("PC too slow; please increase dt"); } } Dr Manuel Carcenac - European University of Lefke class DrawingPanel extends GLJPanel { GLU glu; GLUquadric quad; float[] RED = new float[] { 1.0f , 0.0f , 0.0f , 1.0f } , GREEN = new float[] { 0.0f , 1.0f , 0.0f , 1.0f } , WHITE = new float[] { 1.0f , 1.0f , 1.0f , 1.0f }; DrawingPanel() { super(new GLCapabilities(GLProfile.getDefault())); this.addGLEventListener(new GLEventListener() { public void display(GLAutoDrawable drawable) { //**** DISPLAY //// - KEY POINTS for (int k = ; k < nk ; k++) { gl.glMaterialfv(GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , (k == nk - ? RED : GREEN) , 0); gl.glPushMatrix(); gl.glTranslatef((float)xk[k] , (float)yk[k] , (float)zk[k]); glu.gluSphere(quad , 4.0f , 10 , 10); gl.glPopMatrix(); } //// - ANIMATED POINT if (read_perform_anim()) { gl.glMaterialfv(GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , WHITE , 0); CubicSpline csx = new CubicSpline(tk , xk) , csy = new CubicSpline(tk , yk) , csz = new CubicSpline(tk , zk); double x = csx.interpolate(time) , y = csy.interpolate(time) , z = csz.interpolate(time); gl.glPushMatrix(); gl.glTranslatef((float)x , (float)y , (float)z); glu.gluSphere(quad , 4.0f , 10 , 10); gl.glPopMatrix(); } gl.glFlush(); } } ); } } Dr Manuel Carcenac - European University of Lefke Gui() { f = new JFrame(); f.setFocusable(true); f.setVisible(true); p = new DrawingPanel(); f.getContentPane().add(p , BorderLayout.CENTER); String s = JOptionPane.showInputDialog(f , "number of ctrl pts over " + T + "s ; at least 3"); Nk = Integer.valueOf(s).intValue(); if (Nk 0) { xk[nk - 1] = L * skx.getValue() / 1000; f.repaint(); } } } ); sky.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (nk > 0) { yk[nk - 1] = L * sky.getValue() / 1000; f.repaint(); } } } ); skz.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (nk > 0) { zk[nk - 1] = L * skz.getValue() / 1000; f.repaint(); } } } ); b_new_key = new JButton(); b_new_key.setText("record new key"); b_new_key.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (nk + Para.H / 2); dpv[i] = new PositionVelocity(); pv_[i] = new PositionVelocity(); } } 26 Dr Manuel Carcenac - European University of Lefke time integration with Runge-Kutta: void evolve_over_time_step(double dt) { derivatives_positions_velocities(pv); increment_positions_velocities(pv_ , pv , dpv , dt / 2); D1 s(t) + D1 × ∆ t / derivatives_positions_velocities(pv_); D2 increment_positions_velocities(pv , pv , dpv , dt); s(t) + D2 × ∆ t t += dt; } void increment_positions_velocities( PositionVelocity[] pv1 , PositionVelocity[] pv0 , PositionVelocity[] dpv , double k) { for (int i = ; i < n ; i++) { pv1[i].x = pv0[i].x + dpv[i].x * k; pv1[i].y = pv0[i].y + dpv[i].y * k; pv1[i].z = pv0[i].z + dpv[i].z * k; pv1[i].vx = pv0[i].vx + dpv[i].vx * k; pv1[i].vy = pv0[i].vy + dpv[i].vy * k; pv1[i].vz = pv0[i].vz + dpv[i].vz * k; } } 27 Dr Manuel Carcenac - European University of Lefke time derivatives of positions and velocities: //// calculate dpv for pv = state.pv or state.pv_ void derivatives_positions_velocities(PositionVelocity[] pv) { double pipjx , pipjy , pipjz , d2 , q; for (int i = ; i < n ; i++) { dpv[i].x = pv[i].vx; dpv[i].y = pv[i].vy; dpv[i].z = pv[i].vz; dpv[i].vx = 0.0; dpv[i].vy = 0.0; dpv[i].vz = 0.0; for (int j = ; j < n ; j++) if (j != i) { pipjx = pv[j].x - pv[i].x; pipjy = pv[j].y - pv[i].y; pipjz = pv[j].z - pv[i].z; d2 = pipjx * pipjx + pipjy * pipjy + pipjz * pipjz + Para.EPS * Para.EPS; q = m[j] / d2 / Math.sqrt(d2); dpv[i].vx += q * pipjx; dpv[i].vy += q * pipjy; dpv[i].vz += q * pipjz; } dpv[i].vx *= Para.G; dpv[i].vy *= Para.G; dpv[i].vz *= Para.G; } } 28 Dr Manuel Carcenac - European University of Lefke displaying the state: void display(GL2 gl , GLU glu , GLUquadric quad) { for (int i = ; i < n ; i++) { if (i < n / 2) gl.glMaterialfv( GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , Gui.COLOR1 , 0); else gl.glMaterialfv( GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , Gui.COLOR2 , 0); gl.glPushMatrix(); gl.glTranslatef( (float)(pv[i].x / Para.H) , (float)(pv[i].y / Para.H) , (float)(pv[i].z / Para.H)); glu.gluSphere(quad , 0.01f , 10 , 10); gl.glPopMatrix(); } } 29 Dr Manuel Carcenac - European University of Lefke main class and Gui class: public class P { public static void main(String[] arg) { Gui gui = new Gui(); gui.animation(); } } class Gui { JFrame f; DrawingPanel p; State state; void animation() { while (true) { state.evolve_over_time_step(Para.DT); f.repaint(); } } class DrawingPanel extends GLJPanel { public void display(GLAutoDrawable drawable) { state.display(gl , glu , quad); } } Gui() { state = new State(n); } } 30 Dr Manuel Carcenac - European University of Lefke example: simulation with n = 4000 31 Dr Manuel Carcenac - European University of Lefke 32