Series Introduction
Perhaps Sir Isaac Newton (Sir Issac Newton, three hundred-1643), 1727 years ago, has not imagined that physics is widely used in many games and animations today. Why should physics be used in these applications? I believe that since we were born, we have been feeling the laws of the physical world, realizing how objects are "moving normally" in this world ", for example, when the ball is shot, the ball is parabolic (the Spin Ball may be made into a arc ball), and the stones will swing at the end of a line at a fixed frequency. To make the objects in a game or animation realistic, the Movement method must meet our expectation for "normal movement.
Today's game animation uses a variety of physical simulation technologies, such as kinematics simulation, rigid body dynamics simulation, and string/cloth simulation) soft body dynamics simulation, fluid dynamics simulation, and so on. In addition, collision detection is required in many simulation systems.
This series hopes to introduce some of the most basic knowledge in this area. We will continue to use JavaScript as an example to experience it through real-time interaction.
Introduction
As the first article in the series, this article describes the simplest kinematics simulation, with only two very simple formulas. Kinematics Simulation can be used to simulate the movement of many objects (such as Mario's jump and shells). This article will work with the particle system to make some visual effects (the particle system can also be used to play games, instead of visual effects ).
Kinematics Simulation
Kinematics is different from dynamics in studying the movement of an object in that the kinematics does not consider the mass of the object (mass)/the moment of inertia (moment of inertia ), and does not consider the force and torque of an object ).
Let's first recall Newton's first law of motion:
When the object is not affected by external force, or when the force is zero, the original static is constant static, and the original motion is constant moving along a straight line. This law is also known as the Law of inertia 」. This law states that each object has a linear speed (linear velocity) State in addition to its position. However, it is not interesting to simulate only objects without force impact. Linear acceleration can be used to influence the motion of an object. For example, to calculate the Y axis coordinate of a free falling body at any time t, you can use the following analytical solution ):
Where, and are the start coordinates and velocity of the Y axis when t = 0, while g is the acceleration of gravity (gravitational acceleration ).
This analysis solution is simple, but has some disadvantages. For example, g is a constant and cannot be changed during simulation. In addition, when an object encounters an obstacle and produces a collision, this formula also makes it difficult to deal with such discontinuity ).
In computer simulation, it is usually necessary to calculate the state of consecutive objects. In terms of games, the state of the first frame and the state of the second frame are calculated. Set the state of an object at any time t: position vector, velocity vector, and acceleration vector. We want to calculate the status of the next simulation time from the time state. The simplest method is to use the Euler method for numerical integration ):
The Euler's method is very simple, but there are problems with accuracy and stability. This article will ignore these problems first. The example in this article uses two-dimensional space. We first implement a JavaScript two-dimensional Vector class:
Copy codeThe Code is as follows:
// Vector2.js
Vector2 = function (x, y) {this. x = x; this. y = y ;};
Vector2.prototype = {
Copy: function () {return new Vector2 (this. x, this. y );},
Length: function () {return Math. sqrt (this. x * this. x + this. y * this. y );},
SqrLength: function () {return this. x * this. x + this. y * this. y ;},
Normalize: function () {var inv = 1/this. length (); return new Vector2 (this. x * inv, this. y * inv );},
Negate: function () {return new Vector2 (-this. x,-this. y );},
Add: function (v) {return new Vector2 (this. x + v. x, this. y + v. y );},
Subtract: function (v) {return new Vector2 (this. x-v. x, this. y-v. y );},
Multiply: function (f) {return new Vector2 (this. x * f, this. y * f );},
Divide: function (f) {var invf = 1/f; return new Vector2 (this. x * invf, this. y * invf );},
Dot: function (v) {return this. x * v. x + this. y * v. y ;}
};
Vector2.zero = new Vector2 (0, 0 );
Then, you can use HTML5 Canvas to describe the simulation process:
Copy codeThe Code is as follows:
Var position = new Vector2 (10,200 );
Var velocity = new Vector2 (50,-50 );
Var acceleration = new Vector2 (0, 10 );
Var dt = 0.1;
Function step (){
Position = position. add (velocity. multiply (dt ));
Velocity = velocity. add (acceleration. multiply (dt ));
Ctx. strokeStyle = "#000000 ";
Ctx. fillStyle = "# FFFFFF ";
Ctx. beginPath ();
Ctx. arc (position. x, position. y, 5, 0, Math. PI * 2, true );
Ctx. closePath ();
Ctx. fill ();
Ctx. stroke ();
}
Start ("kinematicsCancas", step );
<Button onclick = "eval (document. getElementById ('kinematicscode'). value)" type = "button"> Run </button>
<Button onclick = "stop ();" type = "button"> Stop </button>
<Button onclick = "clearCanvas ();" type = "button"> Clear </button>
<Table border = "0" style = "width: 100%;">
<Tbody>
<Tr>
<Td> <canvas id = "kinematicsCancas" width = "400" height = "400"> </canvas> </td>
<Td width = "10"> </td>
& Lt; td width = "100%" valign = "top" & gt;
<H4> try to modify the code <Li> change the starting position </li>
<Li> change the start speed (including the direction) </li>
<Li> change acceleration </li>
</Td>
</Tr>
</Tbody>
</Table>
The core of this program is the first two lines of code of the step () function. Is it easy?
Particle System
Particle system is a special effect commonly used in graphics. The particle system can apply kinematics simulation to achieve many different effects. In games and animations, particle systems are often used for different visual effects such as rain, Sparks, smoke, and explosion. Sometimes, some gameplay-related functions will also be made. For example, after an enemy is defeated, it will emit some flashes, and the protagonist can absorb them.
Particle Definition
Particle systems simulate a large number of particles and usually use some methods to render the particles. Particles generally have the following features:
<Li> particles are independent, and they do not affect each other (no collision or force) </li>
<Li> the lifecycle of a particle disappears after the lifecycle ends. </li>
<Li> A particle can be understood as a point in space, and sometimes a radius can be set as a sphere and an environment collision </li>
<Li> particles are in motion and other appearance states (such as colors and images) </li>
<Li> the particle can only perform linear motion without considering the rotation motion (or exceptions) </li>
The following is the particle class implemented in this example:
Copy codeThe Code is as follows: // Particle. js
Particle = function (position, velocity, life, color, size ){
This. position = position;
This. velocity = velocity;
This. acceleration = Vector2.zero;
This. age = 0;
This. life = life;
This. color = color;
This. size = size;
};
Game loop
Particle systems can generally be divided into three cycles:
Emit particles
Simulated particles (particle aging, collision, kinematics simulation, etc)
Rendering Particles
In game loop, you need to perform the preceding three steps for each particle system.
Life and death
In the example in this article, a JavaScript array is used to store all living particles. To generate a particle, add it to the end of the array. The code snippet is as follows:
Copy codeThe Code is as follows: // fig. js
Function participant system (){
// Private fields
Var that = this;
Var participant = new Array ();
// Public fields
This. gravity = new Vector2 (0,100 );
This. Variables = new Array ();
// Public methods
This. emit = function (particle ){
Particle. push (particle );
};
//...
}
When a particle is initialized, age is set to zero, and life is fixed. The unit of age and life is second. In each simulated step, the particle ages, that is, the age is increased by <span class = "math"> \ Delta t </span>. If the age exceeds life, the particle dies. The code snippet is as follows:
Copy codeThe Code is as follows: function participant system (){
//...
This. simulate = function (dt ){
Aging (dt );
ApplyGravity ();
ApplyEffectors ();
Kinematics (dt );
};
//...
// Private methods
Function aging (dt ){
For (var I = 0; I <participant. length ;){
Var p = participant [I];
P. age + = dt;
If (p. age> = p. life)
Kill (I );
Else
I ++;
}
}
Function kill (index ){
If (participant. length> 1)
Participant [index] = participant [participant. length-1];
Participant. pop ();
}
//...
}
In the kill () function, a technique is used. Because the order of particles in the array is not important, to delete a particle in the middle, you only need to copy the last particle to that element and use pop () to remove the last particle. This is usually faster than directly deleting the elements in the middle of the array (using arrays or std: vector in C ++ is also ).
Kinematics Simulation
Apply the most important two pieces of kinematics simulation code in this article to all particles. In addition, each simulation first writes the gravity acceleration into the particle's acceleration. This is done to change the acceleration every time in the future (this is part of the continued talks ).
Copy codeThe Code is as follows: function participant system (){
//...
Function applyGravity (){
For (var I in participant)
Participant [I]. acceleration = that. gravity;
}
Function kinematics (dt ){
For (var I in participant ){
Var p = participant [I];
P. position = p. position. add (p. velocity. multiply (dt ));
P. velocity = p. velocity. add (p. acceleration. multiply (dt ));
}
}
//...
}
Rendering
Particles can be rendered in many different ways, such as circles, line segments (current and previous positions), images, and genie. This article uses a circle and controls the transparency of the circle based on the ratio of age to life. The code snippet is as follows:
Copy codeThe Code is as follows: function participant system (){
//...
This. render = function (ctx ){
For (var I in participant ){
Var p = participant [I];
Var alpha = 1-p. age/p. life;
Ctx. fillStyle = "rgba ("
+ Math. floor (p.color. r * 255) + ","
+ Math. floor (p. color. g * 255) + ","
+ Math. floor (p. color. B * 255) + ","
+ Alpha. toFixed (2) + ")";
Ctx. beginPath ();
Ctx. arc (p. position. x, p. position. y, p. size, 0, Math. PI * 2, true );
Ctx. closePath ();
Ctx. fill ();
}
}
//...
}
Basic particle system completed
In the following example, each frame emits a particle in the middle of the canvas (200,200). The emission direction is 360 degrees, the rate is 100, and the life is 1 second, red, with a radius of 5 pixels.
Copy codeThe Code is as follows:
Var ps = new participant system ();
Var dt = 0.01;
Function sampleDirection (){
Var theta = Math. random () * 2 * Math. PI;
Return new Vector2 (Math. cos (theta), Math. sin (theta ));
}
Function step (){
Ps. emit (new Particle (new Vector2 (200,200), sampleDirection (). multiply (100), 1, Color. red, 5 ));
Ps. simulate (dt );
ClearCanvas ();
Ps. render (ctx );
}
Start ("basicparticle systemcanvas", step );
<Button onclick = "eval (document. getElementById ('basicdetailsystemcode'). value)" type = "button"> Run </button>
<Button onclick = "stop ();" type = "button"> Stop </button>
<Table border = "0" style = "width: 100%;">
<Tbody>
<Tr>
<Td> <canvas id = "basicparticipant lesystemcanvas" width = "400" height = "400"> </canvas> </td>
<Td width = "10"> </td>
& Lt; td width = "100%" valign = "top" & gt;
<H4> try to modify the code <Li> change the launch position </li>
<Li> up-launch, with a launch range of 90 degrees </li>
<Li> change life </li>
<Li> change radius </li>
<Li> five particles are emitted each frame </li>
</Td>
</Tr>
</Tbody>
</Table>
Simple collision
In order to describe the advantages of using numerical integral to the analytical solution, a simple collision is added to the particle system. We want to add a requirement that when a particle encounters the inner wall of a rectangular room (which can be set to the entire Canvas size), it will crash and rebound, and the collision is completely elastic (perfectly elastic collision ).
In programming, I use the callback method for this function. The particle system class has an array of variables. Before performing a kinematics simulation, run the apply () function for each of the variables object:
The rectangular room is implemented as follows:
Copy codeThe Code is as follows: // ChamberBox. js
Function ChamberBox (x1, y1, x2, y2 ){
This. apply = function (particle ){
If (particle. position. x-particle. size <x1 | particle. position. x + particle. size> x2)
Particle. velocity. x =-particle. velocity. x;
If (particle. position. y-particle. size <y1 | particle. position. y + particle. size> y2)
Particle. velocity. y =-particle. velocity. y;
};
}
This is actually when we detect that the particle is out of the inner wall, we will reverse the velocity component in this direction.
In addition, the main loop in this example no longer clears the entire Canvas each time, but draws a translucent black rectangle each frame to simulate the effect of Dynamic Fuzzy motion (motion blur. The color of the particle is also randomly sampled from two colors.
Copy codeThe Code is as follows:
Var ps = new participant system ();
Ps. schedultors. push (new ChamberBox (0, 0,400,400); // The most important thing is that this statement is added.
Var dt = 0.01;
Function sampleDirection (angle1, angle2 ){
Var t = Math. random ();
Var theta = angle1 * t + angle2 * (1-t );
Return new Vector2 (Math. cos (theta), Math. sin (theta ));
}
Function sampleColor (color1, color2 ){
Var t = Math. random ();
Return color1.multiply (t). add (color2.multiply (1-t ));
}
Function step (){
Ps. emit (new Particle (new Vector2 (200,200), sampleDirection (Math. PI * 1.75, Math. PI * 2 ). multiply (250), 3, sampleColor (Color. blue, Color. purple), 5 ));
Ps. simulate (dt );
Ctx. fillStyle = "rgba (0, 0, zero, 0.1 )";
Ctx. fillRect (0, 0, canvas. width, canvas. height );
Ps. render (ctx );
}
Start ("collisionChamberCanvas", step );
<Button onclick = "eval (document. getElementById ('collisionchambercode'). value)" type = "button"> Run </button>
<Button onclick = "stop ();" type = "button"> Stop </button>
<Canvas id = "collisionChamberCanvas" width = "400" height = "400"> </canvas>
Interactive launch
In the last example, the interaction function is added to emit particles at the mouse position. The direction of the particles is to move at the speed of the mouse with a little noise (noise ). The particle size and life are both random.
Copy codeThe Code is as follows: var ps = new participant system ();
Ps. schedultors. push (new ChamberBox (0, 0,400,400 ));
Var dt = 0.01;
Var oldMousePosition = Vector2.zero, newMousePosition = Vector2.zero;
Function sampleDirection (angle1, angle2 ){
Var t = Math. random ();
Var theta = angle1 * t + angle2 * (1-t );
Return new Vector2 (Math. cos (theta), Math. sin (theta ));
}
Function sampleColor (color1, color2 ){
Var t = Math. random ();
Return color1.multiply (t). add (color2.multiply (1-t ));
}
Function sampleNumber (value1, value2 ){
Var t = Math. random ();
Return value1 * t + value2 * (1-t );
}
Function step (){
Var velocity = newMousePosition. subtract (oldMousePosition). multiply (10 );
Velocity = velocity. add (sampleDirection (0, Math. PI * 2). multiply (20 ));
Var color = sampleColor (Color. red, Color. yellow );
Var life = sampleNumber (1, 2 );
Var size = sampleNumber (2, 4 );
Ps. emit (new Particle (newMousePosition, velocity, life, color, size ));
OldMousePosition = newMousePosition;
Ps. simulate (dt );
Ctx. fillStyle = "rgba (0, 0, zero, 0.1 )";
Ctx. fillRect (0, 0, canvas. width, canvas. height );
Ps. render (ctx );
}
Start ("interactiveEmitCanvas", step );
Canvas. onmousemove = function (e ){
If (e. layerX | e. layerX = 0) {// Firefox
E.tar get. style. position = 'relative ';
NewMousePosition = new Vector2 (e. layerX, e. layerY );
}
Else
NewMousePosition = new Vector2 (e. offsetX, e. offsetY );
};
<Button onclick = "eval (document. getElementById ('interactiveemitcode'). value)" type = "button"> Run </button>
<Button onclick = "stop ();" type = "button"> Stop </button>
<Canvas id = "interactiveEmitCanvas" width = "400" height = "400"> </canvas>
Summary
This article introduces the simplest kinematics simulation, uses Euler's method for numerical integration, and uses this method to implement a particle system with a simple collision. The essence of this article is actually only two simple formulas (only two adders and two multiplier). I hope readers can understand that physical simulation can be very simple. Although the example in this article is in a two-dimensional space, but this example can be extended to a three-dimensional space, you only need to replace Vector2 with Vector3. Complete source code in this article can be downloaded.
We will continue the talks and add other physical phenomena on this basis, and have the opportunity to join other physical simulation topics. I hope you can support it and give me more comments.