Advanced topicsin animation kinematic animation: time interpolation of degrees of freedom dynamic animation: time integration of degrees of freedom dynamic animation: all-pairs n-body pr
Trang 1Advanced topics
in animation
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
(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)
Trang 2kinematic 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]
Trang 3example: 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];
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 };
, 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();
}
Trang 4//// - 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)
Trang 5Gui()
{
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; break;
case MouseEvent.BUTTON3: ks++; if (ks == Nk) ks = 0; break; }
skx = new JSlider(JSlider.HORIZONTAL , 0 , 1000 , 0); ps.add(skx);
lkx = new JLabel(" x of selected key"); ps.add(lkx);
sky = new JSlider(JSlider.HORIZONTAL , 0 , 1000 , 0); ps.add(sky);
lky = new JLabel(" y of selected key"); ps.add(lky);
skz = new JSlider(JSlider.HORIZONTAL , 0 , 1000 , 0); ps.add(skz);
lkz = new JLabel(" z of selected key"); ps.add(lkz);
skt = new JSlider(JSlider.HORIZONTAL , 0 , 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(
Trang 7example: 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();
boolean perform_anim = false;
synchronized void write_perform_anim(boolean value) { perform_anim = value; }
synchronized boolean read_perform_anim() { return perform_anim; }
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"); }
}
Trang 8class 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 };
, GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , (k == nk - 1 ? RED : GREEN) , 0);
gl.glPushMatrix(); gl.glTranslatef((float)xk[k] , (float)yk[k] , (float)zk[k]);
CubicSpline csx = new CubicSpline(tk , xk)
, csy = new CubicSpline(tk , yk)
Trang 9Gui()
{
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<3) { System.out.println("Nk < 3"); System.exit(0); }
nk = 0;
xk = new double[Nk]; yk = new double[Nk]; zk = new double[Nk]; tk = new double[Nk];
ps = new JPanel(); ps.setLayout(new GridLayout(0 , 2));
f.getContentPane().add(ps , BorderLayout.EAST);
skx = new JSlider(JSlider.HORIZONTAL , 0 , 1000 , 0); ps.add(skx);
lkx = new JLabel(" x of new key"); ps.add(lkx);
sky = new JSlider(JSlider.HORIZONTAL , 0 , 1000 , 0); ps.add(sky);
lky = new JLabel(" y of new key"); ps.add(lky);
skz = new JSlider(JSlider.HORIZONTAL , 0 , 1000 , 0); ps.add(skz);
lkz = new JLabel(" z of new key"); ps.add(lkz);
skx.addChangeListener(new ChangeListener()
{ public void stateChanged(ChangeEvent e)
{ 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(); } } } );
{ public void actionPerformed(ActionEvent e)
{ if (nk < Nk) JOptionPane.showMessageDialog(f , "you have not yet recorded all the keys!"); else write_perform_anim(true); } } );
f.setSize(new Dimension(1000 + 16 , 600 + 38));
}
}
Trang 11example : angles of an articulated structure: 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();
JFrame f; DrawingPanel p; JPanel ps;
JSlider[] ska = new JSlider[Na]; JButton b_new_key , b_perform_anim;
boolean perform_anim = false;
synchronized void write_perform_anim(boolean value) { perform_anim = value; }
synchronized boolean read_perform_anim() { return perform_anim; }
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"); }
}
Trang 12class DrawingPanel extends GLJPanel
float[] ai = new float[Na];
for (int a = 0 ; a < Na ; a++) ai[a] = (float)(ak[a][nk - 1]);
articulated_arm(gl , glu , quad , ai);
}
//// - ANIMATED ARM
if (read_perform_anim())
{
CubicSpline[] csa = new CubicSpline[Na];
for (int a = 0 ; a < Na ; a++) csa[a] = new CubicSpline(tk , ak[a]);
float[] ai = new float[Na];
for (int a = 0 ; a < Na ; a++) ai[a] = (float)(csa[a].interpolate(time));
articulated_arm(gl , glu , quad , ai);
Trang 13Gui()
{
f = new JFrame(); f.setFocusable(true); f.setVisible(true);
p = new DrawingPanel(); f.getContentPane().add(p , BorderLayout.CENTER);
String s = JOptionPane.showInputDialog(f , "n of ctrl pts over " + T + "s ; at least 3");
Nk = Integer.valueOf(s).intValue(); if (Nk<3) { System.out.println("Nk<3"); System.exit(0); }
nk = 0;
ak = new double[Na][Nk]; tk = new double[Nk];
ps = new JPanel(); ps.setLayout(new GridLayout(0 , 1));
f.getContentPane().add(ps , BorderLayout.EAST);
for (int a = 0 ; a < Na ; a++)
{ ska[a] = new JSlider(JSlider.HORIZONTAL , 0 , 90 , 0); ps.add(ska[a]);
{ public void actionPerformed(ActionEvent e)
{ if (nk < Nk) JOptionPane.showMessageDialog(f , "you have not yet recorded all the keys!"); else write_perform_anim(true); } } );
f.setSize(new Dimension(800 + 16 , 600 + 38));
}
Trang 14void articulated_arm(GL2 gl , GLU glu , GLUquadric quad , float[] ai)
{
gl.glMaterialfv(GL.GL_FRONT_AND_BACK , GLLightingFunc.GL_AMBIENT_AND_DIFFUSE , new float[] { 1.0f , 1.0f , 0.0f , 1.0f } , 0);
gl.glRotatef(ai[0] , 0.0f , 0.0f , 1.0f); gl.glRotatef( 90 - ai[1] , 1.0f , 0.0f , 0.0f);
Trang 16dynamic animation: time integration of degrees of freedom
example: balls under the influence of gravity and bouncing off the floor
.
public class P
{ public static void main(String[] arg)
{ Gui gui = new Gui();
this.x = x; this.y = y; this.z = z;
this.vx = vx; this.vy = vy; this.vz = vz; }
}
class Gui
{
JFrame f; DrawingPanel p;
int N; Ball[] ball;
double V = 30.0 , DELTA = 10.0 , ALPHA = 0.9; //// V in m/s; DELTA in degrees; ALPHA <= 1.0 void animation()
{
double delta_t = 0.05; long dt = (long)(delta_t * 1000);
while (true)
{ long t_start = System.currentTimeMillis();
for (int i = 0 ; i < N ; i++)
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"); }
Trang 17float[] RED = new float[] { 1.0f , 0.0f , 0.0f , 1.0f } , GREEN = new float[] { 0.0f , 1.0f , 0.0f , 1.0f }
, BLUE = new float[] { 0.0f , 0.0f , 1.0f , 1.0f }, WHITE = new float[] { 1.0f , 1.0f , 1.0f , 1.0f };
class DrawingPanel extends GLJPanel
Trang 18Gui()
{
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 balls; at least 1");
N = Integer.valueOf(s).intValue(); if (N < 1) { System.out.println("N < 1"); System.exit(0); } ball = new Ball[N];
for (int i = 0 ; i < N ; i++)
{ double v = V * (0.2 * Math.random() + 0.8);
double a = 2 * Math.PI * Math.random();
double b = DELTA * (Math.PI / 180) * Math.random();
ball[i] = new Ball( (i%3==0 ? RED : i%3==1 ? GREEN : BLUE)
Trang 20dynamic animation: all-pairs n-body problem and gravitation
all-pairs n-body problem:
n bodies fully interacting with each other:
position pi and velocity vi of body i :
time derivative of the system's global state:
i j
i
i i
vz vy vx z y x
v
p pv
pv pv
D
pv pv
dt d
1 n
0
1 n 0
Trang 21time integration:
global state s(t) is known at time t
Euler method:
inaccurate + numerical instability: errors pile up over time
second-order Runge-Kutta method:
accurate + numerically stable = excellent
fourth-order Runge-Kutta method is often preferred
D
D1=
( ) ( s t D Δt / 2 , t Δt / 2 )
Trang 22m v
v p
dt
d t
i i
ε
i j
i j
p p
p p G
Trang 23case study: collision of two galaxies - simulation parameters and initial state:
total mass of the two galaxies = 4×1042 kg
initial diameter of each galaxy H = 1021 m
Trang 24class encapsulating the simulation parameters:
class Para
{
static double G = 6.67384e-11;
static double M = 4.0e42; // total mass of the two galaxies
static double H = 1.0e21; // diameter of each galaxy
static double EPS = H / 100;
static double DX = H; // distance between the two galaxies over X axis
static double DY = H; // distance between the two galaxies over Y axis
static double V = 350.0 * 1.e3; // velocity between the two galaxies along X axis
static double DT = 1.0e5 * 31536000; // 100,000 years
}
Trang 25classes describing the state of the n-body system:
PositionVelocity(double x , double y ,double z , double vx , double vy , double vz)
{ this.x = x; this.y = y; this.z = z;
this.vx = vx; this.vy = vy; this.vz = vz; }
Trang 26initialization of the state:
if (i < n / 2) { gal_x = - Para.DX / 2; gal_y = - Para.DY / 2; gal_z = 0.0;
gal_vx = Para.V / 2; gal_vy = 0.0; gal_vz = 0.0; }
else { gal_x = Para.DX / 2; gal_y = Para.DY / 2; gal_z = 0.0;
gal_vx = - Para.V / 2; gal_vy = 0.0; gal_vz = 0.0; }
m[i] = Para.M / n;
do
{
pv[i] = new PositionVelocity( gal_x + Para.H * ( -0.5 + Math.random())
, gal_y + Para.H * ( -0.5 + Math.random())
, gal_z + Para.H * ( -0.5 + Math.random())
, gal_vx , gal_vy , gal_vz);
h = Math.sqrt( (pv[i].x - gal_x) * (pv[i].x - gal_x)
+ (pv[i].y - gal_y) * (pv[i].y - gal_y)
+ (pv[i].z - gal_z) * (pv[i].z - gal_z));
}
while (h > Para.H / 2);
dpv[i] = new PositionVelocity();
pv_[i] = new PositionVelocity();
}
}
Trang 27time integration with Runge-Kutta:
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;
}
}
D 1 s(t) + D
1 × ∆t / 2
D 2 s(t) + D 2 × ∆t
Trang 28time 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 = 0 ; i < n ; i++)