cannon-es-control
Version:
A lightweight 3D physics engine written in JavaScript with control system tools
372 lines (318 loc) • 13.3 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8" />
<title>cannon.js - constraints demo</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
</head>
<body>
<script type="module">
import * as CANNON from '../dist/cannon-es-control.js'
import { Demo } from './js/Demo.js'
/**
* In this demo, we demonstrate some constraint types. Constraints
* removes degrees of freedom from bodies and forces them to move in
* a way defined by the constraint.
*/
const demo = new Demo()
demo.addScene('Lock', () => {
const world = setupWorld(demo)
world.gravity.set(0, -10, 0)
world.solver.iterations = 20
const size = 0.5
const mass = 1
const space = size * 0.1
const boxShape = new CANNON.Box(new CANNON.Vec3(size, size, size))
const N = 10
let previous
for (let i = 0; i < N; i++) {
// Create a box
const boxBody = new CANNON.Body({
mass: mass,
shape: boxShape,
position: new CANNON.Vec3(-(N - i - N / 2) * (size * 2 + 2 * space), size * 6 + space, 0),
})
world.addBody(boxBody)
demo.addVisual(boxBody)
if (previous) {
// Connect the current body to the last one
const lockConstraint = new CANNON.LockConstraint(boxBody, previous)
world.addConstraint(lockConstraint)
}
// To keep track of which body was added last
previous = boxBody
}
// Create stands
const body1 = new CANNON.Body({
mass: 0,
shape: boxShape,
position: new CANNON.Vec3(-(-N / 2 + 1) * (size * 2 + 2 * space), size * 3, 0),
})
world.addBody(body1)
demo.addVisual(body1)
const body2 = new CANNON.Body({
mass: 0,
shape: boxShape,
position: new CANNON.Vec3(-(N / 2) * (size * 2 + space * 2), size * 3, 0),
})
world.addBody(body2)
demo.addVisual(body2)
})
// We link together boxes in a chain
demo.addScene('Links', () => {
const world = setupWorld(demo)
world.gravity.set(0, -20, -1)
const size = 1
let mass = 0
const space = size * 0.1
const boxShape = new CANNON.Box(new CANNON.Vec3(size, size, size * 0.1))
const N = 10
let previous
for (let i = 0; i < N; i++) {
// Create a box
const boxBody = new CANNON.Body({ mass })
boxBody.addShape(boxShape)
boxBody.position.set(0, (N - i) * (size * 2 + space * 2) + size * 2 + space, 0)
boxBody.linearDamping = 0.01 // Damping makes the movement slow down with time
boxBody.angularDamping = 0.01
world.addBody(boxBody)
demo.addVisual(boxBody)
if (i != 0) {
// Connect the current body to the last one
// We connect two corner points to each other.
const pointConstraint1 = new CANNON.PointToPointConstraint(
boxBody,
new CANNON.Vec3(size, size + space, 0),
previous,
new CANNON.Vec3(size, -size - space, 0)
)
const pointConstraint2 = new CANNON.PointToPointConstraint(
boxBody,
new CANNON.Vec3(-size, size + space, 0),
previous,
new CANNON.Vec3(-size, -size - space, 0)
)
world.addConstraint(pointConstraint1)
world.addConstraint(pointConstraint2)
} else {
// First body is now static. The rest should be dynamic.
mass = 0.3
}
// To keep track of which body was added last
previous = boxBody
}
})
// Particle cloth on sphere
demo.addScene('Cloth on sphere', () => {
const world = setupWorld(demo)
const dist = 0.2
const mass = 0.5
// To construct the cloth we need rows*cols particles.
const rows = 15
const cols = 15
const bodies = {} // bodies['i j'] => particle
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
// Create a new body
const body = new CANNON.Body({ mass })
body.addShape(new CANNON.Particle())
body.position.set(-(i - cols * 0.5) * dist, 5, (j - rows * 0.5) * dist)
bodies[`${i} ${j}`] = body
world.addBody(body)
demo.addVisual(body)
}
}
// To connect two particles, we use a distance constraint. This forces the particles to be at a constant distance from each other.
function connect(i1, j1, i2, j2) {
const distanceConstraint = new CANNON.DistanceConstraint(bodies[`${i1} ${j1}`], bodies[`${i2} ${j2}`], dist)
world.addConstraint(distanceConstraint)
}
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
// Connect particle at position (i,j) to (i+1,j) and to (i,j+1).
if (i < cols - 1) connect(i, j, i + 1, j)
if (j < rows - 1) connect(i, j, i, j + 1)
}
}
// Add the static sphere we throw the cloth on top of
const sphere = new CANNON.Sphere(1.5)
const body = new CANNON.Body({ mass: 0 })
body.addShape(sphere)
body.position.set(0, 3.5, 0)
world.addBody(body)
demo.addVisual(body)
})
// A pendulum made out of two spheres using a PointToPointConstraint
demo.addScene('Sphere pendulum', () => {
const world = setupWorld(demo)
const size = 1
const mass = 1
const sphereShape = new CANNON.Sphere(size)
const spherebody = new CANNON.Body({ mass })
spherebody.addShape(sphereShape)
spherebody.position.set(0, size * 3, 0)
spherebody.velocity.set(-5, 0, 0)
spherebody.linearDamping = 0
spherebody.angularDamping = 0
world.addBody(spherebody)
demo.addVisual(spherebody)
const spherebody2 = new CANNON.Body({ mass: 0 })
spherebody2.addShape(sphereShape)
spherebody2.position.set(0, size * 7, 0)
world.addBody(spherebody2)
demo.addVisual(spherebody2)
// Connect this body to the last one
const pointConstraint = new CANNON.PointToPointConstraint(
spherebody,
new CANNON.Vec3(0, size * 2, 0),
spherebody2,
new CANNON.Vec3(0, -size * 2, 0)
)
world.addConstraint(pointConstraint)
})
// Sphere chain
demo.addScene('Sphere chain', () => {
const world = setupWorld(demo)
// world.solver.setSpookParams(1e20, 3)
const size = 0.5
const dist = size * 2 + 0.12
const mass = 1
const N = 20
world.solver.iterations = N // To be able to propagate force throw the chain of N spheres, we need at least N solver iterations.
const sphereShape = new CANNON.Sphere(size)
let previous
for (let i = 0; i < N; i++) {
// Create a new body
const sphereBody = new CANNON.Body({ mass: i === 0 ? 0 : mass })
sphereBody.addShape(sphereShape)
sphereBody.position.set(0, dist * (N - i), 0)
sphereBody.velocity.x = -i
world.addBody(sphereBody)
demo.addVisual(sphereBody)
// Connect this body to the last one added
if (previous) {
const distanceConstraint = new CANNON.DistanceConstraint(sphereBody, previous, dist)
world.addConstraint(distanceConstraint)
}
// Keep track of the lastly added body
previous = sphereBody
}
})
// Particle cloth. Same as the previous cloth but here we make the first row of particles static, nailing the cloth it in space
demo.addScene('Particle cloth', () => {
const world = setupWorld(demo)
// world.solver.setSpookParams(1e20, 3)
world.solver.iterations = 18
const dist = 0.2
const mass = 0.5
const rows = 15
const cols = 15
const bodies = {} // bodies['i j'] => particle
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
// Create a new body
const body = new CANNON.Body({ mass: j == rows - 1 ? 0 : mass })
body.addShape(new CANNON.Particle())
body.position.set(-dist * i, dist * j + 5, 0)
body.velocity.set(0, 0, (Math.sin(i * 0.1) + Math.sin(j * 0.1)) * 3)
bodies[`${i} ${j}`] = body
world.addBody(body)
demo.addVisual(body)
}
}
function connect(i1, j1, i2, j2) {
const distanceConstraint = new CANNON.DistanceConstraint(bodies[`${i1} ${j1}`], bodies[`${i2} ${j2}`], dist)
world.addConstraint(distanceConstraint)
}
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
if (i < cols - 1) connect(i, j, i + 1, j)
if (j < rows - 1) connect(i, j, i, j + 1)
}
}
})
// Particle 3d object
// Distance constraints can be used to construct even cooler things, like this 3d block.
demo.addScene('3D cloth structure', () => {
const world = setupWorld(demo)
// Max solver iterations: Use more for better force propagation, but keep in mind that it's not very computationally cheap!
world.solver.iterations = 10
const dist = 1
const mass = 1
const Nx = 6
const Ny = 3
const Nz = 3
const bodies = {} // bodies['i j k'] => particle
for (let i = 0; i < Nx; i++) {
for (let j = 0; j < Ny; j++) {
for (let k = 0; k < Nz; k++) {
// Create a new body
const body = new CANNON.Body({ mass })
body.addShape(new CANNON.Particle())
body.position.set(-dist * i, dist * k + dist * Nz * 0.3 + 1, dist * j)
body.velocity.set(0, 0, (Math.sin(i * 0.1) + Math.sin(j * 0.1)) * 30)
bodies[`${i} ${j} ${k}`] = body
world.addBody(body)
demo.addVisual(body)
}
}
}
function connect(i1, j1, k1, i2, j2, k2, distance) {
const distanceConstraint = new CANNON.DistanceConstraint(
bodies[`${i1} ${j1} ${k1}`],
bodies[`${i2} ${j2} ${k2}`],
distance
)
world.addConstraint(distanceConstraint)
}
for (let i = 0; i < Nx; i++) {
for (let j = 0; j < Ny; j++) {
for (let k = 0; k < Nz; k++) {
// normal directions
if (i < Nx - 1) connect(i, j, k, i + 1, j, k, dist)
if (j < Ny - 1) connect(i, j, k, i, j + 1, k, dist)
if (k < Nz - 1) connect(i, j, k, i, j, k + 1, dist)
// Diagonals
if (i < Nx - 1 && j < Ny - 1 && k < Nz - 1) {
// 3d diagonals
connect(i, j, k, i + 1, j + 1, k + 1, Math.sqrt(3) * dist)
connect(i + 1, j, k, i, j + 1, k + 1, Math.sqrt(3) * dist)
connect(i, j + 1, k, i + 1, j, k + 1, Math.sqrt(3) * dist)
connect(i, j, k + 1, i + 1, j + 1, k, Math.sqrt(3) * dist)
}
// 2d diagonals
if (i < Nx - 1 && j < Ny - 1) {
connect(i + 1, j, k, i, j + 1, k, Math.sqrt(2) * dist)
connect(i, j + 1, k, i + 1, j, k, Math.sqrt(2) * dist)
}
if (i < Nx - 1 && k < Nz - 1) {
connect(i + 1, j, k, i, j, k + 1, Math.sqrt(2) * dist)
connect(i, j, k + 1, i + 1, j, k, Math.sqrt(2) * dist)
}
if (j < Ny - 1 && k < Nz - 1) {
connect(i, j + 1, k, i, j, k + 1, Math.sqrt(2) * dist)
connect(i, j, k + 1, i, j + 1, k, Math.sqrt(2) * dist)
}
}
}
}
})
demo.start()
function setupWorld(demo) {
// Create world
const world = demo.getWorld()
world.gravity.set(0, -40, 0)
// Static ground plane
const groundShape = new CANNON.Plane()
const groundBody = new CANNON.Body({ mass: 0 })
groundBody.addShape(groundShape)
groundBody.position.set(0, 1, 0)
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
world.addBody(groundBody)
demo.addVisual(groundBody)
return world
}
</script>
</body>
</html>