UNPKG

cannon-es-control

Version:

A lightweight 3D physics engine written in JavaScript with control system tools

372 lines (318 loc) 13.3 kB
<!DOCTYPE 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>