Skip to content

Instantly share code, notes, and snippets.

@marcofugaro
Last active September 27, 2018 22:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcofugaro/aa8a759914601110563bb200c24b6180 to your computer and use it in GitHub Desktop.
Save marcofugaro/aa8a759914601110563bb200c24b6180 to your computer and use it in GitHub Desktop.
Nature of code notes

Nature of code notes

https://www.youtube.com/user/shiffman/playlists?shelf_id=6&sort=dd&view=50

There are many distribution functions, such as random, gaussian, perlin noise, or some custom function.

Custom distribution

const x = random(0, 1)
const ny = random(0, 1)
const y = x**x // the custom function
if (ny < y) { // if it's under our custom distribution in the xy graph
  // yes! return
  return x
} else {
  // otherwise, retry, usually there is a while() loop
}

Vectors!!

A vector is defined by two things, magnitude and direction!

the basic function to do operations with the vectors are

  • add adds x to the x, and y to the y
  • subtract, the same of adds, but the second vector is INVERTED (180 degrees), because giving minus to a vector means inverting it. This is useful because this IS THE DISTANCE between 2 vectors
  • multiply or scale for example V * 2, means to affect its length
  • limit if the magnitude is greater that the limit, set it to the limit
  • magnitude is the length of the vector, we calculate it with the Pitagora theorem: Math.sqrt(xx + yy)
  • normalize a vector means keeping its direction, but giving it the magnitude of 1, the formula is x = x / magnitude, and x = y / magnitude

This is the mothod of a Mover class called in requestAnimationFrame:

update() {
  this.velocity.add(this.acceleration) // acceleration if ANY change in velocity, adjusts magnitude or direction
  this.position.add(this.velocity) // velocity if how much the position chenges overtime
}

Velocity, position, acceleration are vectors! This is called Euler Integration

Forces

A force is a vector which cause an object with mass to accelerate.

The force often corresponds to the acceleration because of newton's second law:

A = F / m

Let's write an applyForce method!

applyForce(forceVector) {
  this.acceleration = forceVector
}

But what if we want multiple forces?? We need the NET FORCE!

// called every frame by every force
applyForce(forceVector) {
  this.acceleration.add(forceVector)
}
update() {
  this.velocity.add(this.acceleration)
  this.position.add(this.velocity)
  // reset the acceleration vector
  this.acceleration.multiply(0)
}

Mass is the amount of matter in an object.

Following newton's second law:

// clone the wector so we don't mutate the original one
applyForce(forceVector) {
  this.acceleration.add(forceVector.clone().divide(this.mass))
}
// this.mass is a number

If we apply a gravity force to an object, let's first multiply the gravity by the objects mass, since ignoring other things we consider constants (earth's mass and distance from the object)

const gravity = new Vector(0, 8.91)
mover.applyForce(gravity.clone().mult(mover.mass))

But how we transform a mathematical formula into code? For example Friction.

Let's take the formula Friction = -1 * μ * getMagnitude(Normal force) * normalize(velocity)

The direction is given by: -1 * normalize(velocity). And the magnitude by: μ * getMagnitude(Normal force).

The coefficient of friction μ is given by the materials, it's a constant.

So the code of friction would be

const friction = velocity.clone()
friction.normalize()
friction.multiply(-1)
const c = 0.01 // this is our magnitude
friction.multiply(c)

The drag force (of a liquid/air resistance) instead has a formula of Force of drag = -1/2 * ρ * getMagnitude(velocity)**2 * A * Coefficent of drag * normalize(velocity)

ρ is the density of the material it's moving through, a constant.

getMagnitude(velocity)**2 means the drag force is directly proportional to the velocity, also exponentially.

A is surface area, a constant.

Cd is also a constant.

The direction is the opposite of the velocity (minus)

To simplyfy the formula, setting many things at 1

Fd = - Constant * getMagnitude(velocity)**2 * normalize(velocity)

So the code of drag would be

const drag = velocity.clone()
drag.normalize()
drag.multiply(-1)

const speed = velocity.magnitude()
const c = 0.01 // this is our magnitude
drag.multiply(c * speed**2)

Another force is the Gravitational attraction. The force an object has when it's attracted to another Fg = (Gravity constant * massObj1 * massObj2 * normalize(r) / distance ** 2 where r is the vector that starts from the onject where the force is applied and finishes into the other object.

Here is the attract method, it returns the gravity attraction vector:

attract(objectAttracted) {
  const objectFixed = this
  
  const distanceVector = subtractVectors(objectFixed.position, objectAttracted.position)
  const direction = normalize(distanceVector)
  const distance = getMagnitude(distanceVector)
  const magnitude = (G * objectAttracted.mass * objectFixed.mass) / d**2

  return direction.mult(magnitude)
}

But this doesn't work because the distance is so great if measured in pixels (the unit in thet formula is meters), we can bypass this weirdness by doing constrain(distance, 5, 25)

Angular Motion

An angle it's like the position, except it isn't a vector. It can have angular velocity and angular acceleration.

update() {
  angularAcceleration = // magic goes here
  angle += angularVelocity
  angularVelocity += angularAcceleration
}

There are the most useful functions in trigonometry! We need them when we have to calculate some adjacent vectors length and stuff.

How is this useful also?

We can now switch to the Polar Coordinates! They describe a point not by its (x, y) coordinates, but by its (r, θ) coordinates

So we now think in polar coordinates and we switch to cartesian like this

Armonic Motion

We can use the sin to simulate the armonic motion, because it is SMOOTH. Taken a sine wave, these are its values, the PERIOD is how much time the curve takes to do a full oscillation, by default it's 2 PI.

This formula is rapresented as

x = amplitude * sin(frameCount * frequency)

Framecount is incremental in time, we often use something incremental, Date.now() is also fine. OR we could increment it by hand every update, something like this, utilizing angular velocity.

angularAcceleration = 0.05
angle += angularVelocity
angularVelocity += angularAcceleration

x = amplitude * sin(angle)

Springs!

Springs' force follows Hook's law, which is rapresented by the formula F = - koefficent of elasticity * displacement. Spring has a rest length, the magnitude of the force vector is the difference between the current length and the rest length. The direction goes from the pendulum to the origin of the spring.

update() {
  const springDirection = subtract(pendulum.position, origin)
  const currentLength = getMagnitude(springDirection)
  const stretch = currentLength - stretchLength

  const k = 0.01
  const springMagnitude = -k * stretch
}

Particle Systems

A particle system is usually a class keeping track in an array of all the particle it generates. In this array there can be different types of particles, StarParticles, SquareParticles, and this is ok as long as they extend the same class Particle, which is also the type of the array. This is called polymorphism.

The particle systems usually proxies its applyed forces to all the particles it contains.

In real-word scenarios, particle systems use textures with the blend mode ADD instead of the default one.

Box2D

Box2D is used in 2D physics games like angry birds.

In Box2D you have a world that simulates real-life properties. You init the body once at the start, you add a shape to the body (using a fixture), and put the body in the world. Then you read its position from the world and display it.

The only issue is that Box2D doesn't wanna know about pixels, so you have to transform everything in "world" coordinates.

The axes of Box2D start from the center, like a cartesian plate, so we have to be careful because our pixel coordinates may start from the top left. So we need to flip the Y axis upside-down.

You can make DYNAMIC, STATIC and KINEMATIC bodies. You can control the kinematic body by assigning a velocity. Watch out! A kinematic body can't collide with other kinematic bodies.

You can make a terrain using a chain shape, you pass it an array of all the points that form the chain, and automatically below them a static rigid body will form.

Alas, if you need a custom shaped body in the physics world, you just give Box2D an array vertices, pointing from the center of the body. But make sure to pass only convex shape!

Or you can pass Box2D multiple shapes with an offset, and he will weld them together.

Instead, to connect them dynamically, you use joints. The basic joins is the distance joint, you can set its springiness and other parameters for the joint (rest length, frequencyHz, dampingRatio).

A revolute joint instead, takes an anchor point in common between the two bodies, and lets the body REVOLUTE around it. It also features a MOTOR, so you can simulate things like a car/windmill.

A mouse joint is used to make a body follow the mouse, basically you attach the body with a distance joint to the ground body which holds the position of the mouse. During update you call setTarget on the joint with the mouse coordinates.

You can apply forces to a body using applyForce(), and specifying where on the body the force should be applied.

If you want to fire a function when two object collide, you can use the global function beginContact(). Other hooks available are endContact(), preSolve(), postSolve(). In the arguments there are the fixtures of the two bodies that collided. From those you can derive the original bodies.

Toxiclibs

Toxiclibs has some advantages/differences over Box2D. First it supports 3D, second is MUCH easier to use. Howerver, it doesn't handle collisions, just springs and relations between objects. For example is really good at simulating a cloth. Like this one, or the gravity attraction app with the white dots.

Toxiclibs uses Verlet Physics, which handle integration from force to velocity to position. This is called Euler Integration, here specifically we're using Verlet Integration, which is complex math used to determine how a body has moved between two frames. Verlet integration is a cool methodology because it integrates from force to position without passing through velocity, it uses previous location and current location of the body.

In Toxiclibs the world is called Verlet Physics, a body is a particle, and particles are connected with springs.

The way you write Toxiclib code, is that you extend VelvecParticle with a class, which on its part is already extending a Vec2D! So you have available in the class the x and y of the position.

A Spring between two objects has two parameters, the rest length (in px this time!!) and the strength, 0 to 1.

Some real application of Verlet Physics are network graphs, simulated cloths, and springy creatures!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment