Series Introduction
Perhaps, 300 years ago, Sir Isaac Newton (Sir Issac Newton, 1643-1727) had no illusions that physics was widely used in many games and animations today. Why do you use physics in these applications? The author believes that since we were born, we have been experiencing the laws of the physical world, aware of how objects "move" in this world, such as when the ball is parabolic (the spin ball may be made into an arc), and the stone is at the end of a line that swings at a fixed frequency and so on. To make an object in a game or animation realistic, it moves in a way that conforms to our expectation of "normal movement".
Today's game animations use a variety of physical simulation techniques, such as kinematic simulations (kinematics simulation), rigid body dynamics simulations (rigid bodies dynamics simulation), Rope/fabric simulations (string/cloth Simulation), Flexible dynamics simulation (soft body dynamics simulation), Fluid dynamics Simulation (fluid dynamics) and so on. In addition, collision detection (collision detection) is required in many analog systems.
This series is expected to introduce some of the most basic knowledge and continue to use JavaScript as an example to experience in an instant interactive way.
Introduction to this article
As the first of the series, this paper introduces the simplest kinematics simulations, with only two very simple formulas. Kinematics simulations can be used to simulate many object motions (such as Mario jumps, shells, etc.), and this article will make some visual effects with particle systems (particle systems can actually be used to play games, not just visual effects).
Kinematics Simulation
Kinematics (kinematics), which studies the movement of objects, differs from kinetics (dynamics) in that kinematics does not take into account the mass (mass)/moment of inertia (moment of inertia) of objects, and the Forces (force) and torques that are not considered in addition to the object ( Torque).
Let's recall Newton's first law of motion:
When the object is not subjected to an external force, or the resultant force is zero, the original stationary is stationary, and the original motion is constant along the line. This law is also called "inertia". The law states that each object has a linear velocity (linear velocity) in addition to its position (position). However, it is not interesting to simulate objects that are not affected by force. Apart from the concept of force, we can use linear acceleration (linear acceleration) to influence the motion of an object. For example, to compute the y-axis coordinates of a free falling body at any time t, you can use the following analytical solution (analytical solution):
The first coordinates and velocity of the y-axis are t=0, and g is the gravitational acceleration (gravitational acceleration).
This analytical solution is simple, but there are some drawbacks, such as g being constant, which cannot be changed in the simulation process, and it is also difficult to deal with this discontinuity (discontinuity) when an object encounters a barrier that produces a collision.
In computer simulations, continuous object state is usually required. Using the language of the game, is to calculate the state of the first frame, the state of the second frame and so on. Set the state of the object at any time t: The position vector is, the velocity vector is, the acceleration vector is. We want to calculate the state of the next simulation time from the state of the time. The simplest approach is to use Euler (Euler method) as a numerical integral (numerical integration):
The Euler method is very simple, but has the accuracy and the stability problem, this article will first ignore these questions. The example in this article uses two-dimensional space, we first implement a JavaScript two-dimensional vector class:
Copy Code code as follows:
Vector2.js
Vector2 = function (x, y) {this.x = x; this.y = y;};
Vector2.prototype = {
Copy:function () {Return to 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 to 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 process of simulation:
Copy Code code 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= "height=" ></canvas></td>
<TD width= "Ten" > </td>
<TD width= "100%" valign= "Top" >
<li> Change start position </li>
<li> change start speed (including direction) </li>
<li> Change Acceleration </li>
</td>
</tr>
</tbody>
</table>
The core of this program is the first two lines of the step () function. It's simple, isn't it?
Particle systems
Particle systems (particle system) are common effects in graphics. The particle system can use kinematics simulation to do many different effects. Particle systems are often used in games and animations to make different visual effects, such as raindrops, Sparks, smoke, explosions, and so on. Sometimes, there are some game-related functions, such as when the enemy is defeated, they emit some flash, the protagonist can absorb them.
The definition of a particle
The particle system simulates a large number of particles and usually renders the particles in some ways. Particles usually have the following characteristics:
<li> particles are independent, the particles do not affect each other (no collisions, no force) </li>
<li> particles have a life cycle, the end of life will disappear </li>
<li> particles can be understood as a point of space, sometimes can also set the radius as a sphere and environmental collision </li>
<li> particles with motion and other appearance states (such as color, image, etc.) </li>
<li> particles can only be linear motion without regard to rotational motion (with exceptions) </li>
The following are the particle classes implemented in this example:
Copy Code code 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 usually be divided into three cycles:
Launch particle
Simulated particles (particle aging, collisions, kinematics simulations, etc.)
Render particles
In the game loop (game loop), you need to perform the above three steps for each particle system.
Life and Death
In the example of this article, a JavaScript array is used to store all the live particles particles. Creating a particle just adds it to the end of the array. The code snippet is as follows:
Copy Code code as follows:
Particlesystem.js
function Particlesystem () {
Private fields
var that = this;
var particles = new Array ();
Public fields
this.gravity = new Vector2 (0, 100);
This.effectors = new Array ();
Public methods
This.emit = function (particle) {
Particles.push (particle);
};
// ...
}
When the particles are initialized, age is set to zero, and life is fixed. Age and the unit of life are all seconds. Each simulation step, the particle aging, that is, to increase the age of <span class= "math" >\delta t</span>, older than life, will die. The code snippet is as follows:
Copy Code code as follows:
function Particlesystem () {
// ...
This.simulate = function (dt) {
Aging (DT);
Applygravity ();
Applyeffectors ();
kinematics (DT);
};
// ...
Private methods
function aging (DT) {
for (var i = 0; i < particles.length;) {
var p = particles[i];
P.age + = DT;
if (p.age >= p.life)
Kill (i);
Else
i++;
}
}
function Kill (Index) {
if (Particles.length > 1)
Particles[index] = particles[particles.length-1];
Particles.pop ();
}
// ...
}
In the function Kill (), a trick is used. Because the order of the particles in the array is not important, to remove the middle one, you only need to copy the last particle to that element, and the pop () to remove the bottom particle. This is usually faster than removing the elements in the middle of the array directly (using arrays or std::vector in C + +).
Kinematics Simulation
Apply the most important two-sentence kinematics simulation code to all the particles. In addition, the gravitational acceleration is first written to the acceleration of the particle each time the simulation is made. This is done in order to change the acceleration each time in the future (the sequel talks).
Copy Code code as follows:
function Particlesystem () {
// ...
function applygravity () {
for (var i in particles)
Particles[i].acceleration = that.gravity;
}
function kinematics (DT) {
for (var i in particles) {
var p = particles[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, segments (current position and previous position), images, sprites, and so on. This article uses a circle and controls the transparency of the circle by age-life ratio, and the code snippet is as follows:
Copy Code code as follows:
function Particlesystem () {
// ...
This.render = function (CTX) {
for (var i in particles) {
var p = particles[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 ();
}
}
// ...
}
Elementary particle System Complete
In the following example, each frame emits a particle in the middle of the canvas (200,200), the launch direction is 360 degrees, the rate is 100, life is 1 seconds, red, and the radius is 5 pixels.
Copy Code code as follows:
var ps = new Particlesystem ();
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), sampledirection (). Multiply (1, color.red, 5));
Ps.simulate (DT);
Clearcanvas ();
Ps.render (CTX);
}
Start ("Basicparticlesystemcanvas", step);
<button onclick= "eval (document.getElementById (' Basicparticlesystemcode '). Value" type= "button" >run</ Button>
<button onclick= "Stop ()" type= "button" >Stop</button>
<table border= "0" style= "width:100%;" >
<tbody>
<tr>
<td><canvas id= "Basicparticlesystemcanvas" width= "height=" ></canvas></td>
<TD width= "Ten" > </td>
<TD width= "100%" valign= "Top" >
<li> Change Launch Position </li>
<li> up, launch range within 90 degrees </li>
<li> Change Life </li>
<li> Change Radius </li>
<li> emit 5 particles per frame </li>
</td>
</tr>
</tbody>
</table>
Simple collision
In order to illustrate the advantages of the numerical integral relative to the analytical solution, a simple collision on the particle system is presented. We want to add a requirement that when the particle touches the inner wall of the rectangular chamber (which can be set to the entire canvas size), it bounces back and the collision is completely elastic (perfectly elastic collision).
In the program design, I use the callback method to carry out this function. The Particlesystem class has a effectors array that executes the Apply () function for each Effectors object before kinematics simulation:
And that's how the rectangular chamber is implemented:
Copy Code code 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 the particle is detected beyond the inner wall, the velocity component of the direction is reversed.
In addition, the main loop of this example does not empty the entire canvas at a time, but instead draws a translucent black rectangle per frame to simulate the effect of dynamic blur (motion blur). The color of the particles is also randomly sampled from two colors.
Copy Code code as follows:
var ps = new Particlesystem ();
Ps.effectors.push (New Chamberbox (0, 0, 400, 400)); The most important thing is that the sentence is more
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), Sampledirection (Math.PI * 1.75, Math.PI * 2). Multiply (a), 3, Samplecolor (Co Lor.blue, Color.purple), 5);
Ps.simulate (DT);
ctx.fillstyle= "Rgba (0, 0, 0, 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= "height=" ></canvas>
Interactive launch
The last example adds an interactive function that emits particles in the mouse position, with the particle in the direction of moving the mouse and adding a bit of noise (noise). The particle size and life are all random.
Copy Code code as follows:
var ps = new Particlesystem ();
Ps.effectors.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, 0, 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.target.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= "height=" ></canvas>
Summarize
This paper introduces the simplest kinematics simulation, uses Euler method to make numerical integral, and realizes a particle system with simple collision by this method. The essence of this article is that there are only two simple formulas (only two addends and two multipliers) to make the reader understand that physical simulations can be very simple. Although this example is in two-dimensional space, this example can be extended to three-dimensional space, only to replace Vector2 with Vector3. This article full source code can be downloaded.
The continuation talks and the addition of other physical phenomena on this basis have the opportunity to join other physical simulation subjects. I hope you will support me and give me more advice.