cannon-es-control
Version:
A lightweight 3D physics engine written in JavaScript with control system tools
383 lines (341 loc) • 13.3 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>cannon.js - ragdoll 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'
const demo = new Demo()
demo.addScene('No cone joints', () => {
const world = setupWorld(demo)
// Add a sphere to land on
const sphereBody = createStaticSphere()
world.addBody(sphereBody)
demo.addVisual(sphereBody)
// Create the ragdoll
// It returns an array of body parts and their constraints
const { bodies, constraints } = createRagdoll({
scale: 3,
angle: Math.PI,
angleShoulders: Math.PI,
twistAngle: Math.PI,
})
bodies.forEach((body) => {
// Move the ragdoll up
const position = new CANNON.Vec3(0, 10, 0)
body.position.vadd(position, body.position)
world.addBody(body)
demo.addVisual(body)
})
constraints.forEach((constraint) => {
world.addConstraint(constraint)
})
})
demo.addScene('Normal cone joints', () => {
const world = setupWorld(demo)
// Add a sphere to land on
const sphereBody = createStaticSphere()
world.addBody(sphereBody)
demo.addVisual(sphereBody)
// Create the ragdoll
// It returns an array of body parts and their constraints
const { bodies, constraints } = createRagdoll({
scale: 3,
angle: Math.PI / 4,
angleShoulders: Math.PI / 3,
twistAngle: Math.PI / 8,
})
bodies.forEach((body) => {
// Move the ragdoll up
const position = new CANNON.Vec3(0, 10, 0)
body.position.vadd(position, body.position)
world.addBody(body)
demo.addVisual(body)
})
constraints.forEach((constraint) => {
world.addConstraint(constraint)
})
})
demo.addScene('Thin cone joints', () => {
const world = setupWorld(demo)
// Add a sphere to land on
const sphereBody = createStaticSphere()
world.addBody(sphereBody)
demo.addVisual(sphereBody)
// Create the ragdoll
// It returns an array of body parts and their constraints
const { bodies, constraints } = createRagdoll({
scale: 3,
angle: 0,
angleShoulders: 0,
twistAngle: 0,
})
bodies.forEach((body) => {
// Move the ragdoll up
const position = new CANNON.Vec3(0, 10, 0)
body.position.vadd(position, body.position)
world.addBody(body)
demo.addVisual(body)
})
constraints.forEach((constraint) => {
world.addConstraint(constraint)
})
})
demo.start()
function createRagdoll({ scale = 1, angle, angleShoulders, twistAngle }) {
const bodies = []
const constraints = []
const shouldersDistance = 0.5 * scale
const upperArmLength = 0.4 * scale
const lowerArmLength = 0.4 * scale
const upperArmSize = 0.2 * scale
const lowerArmSize = 0.2 * scale
const neckLength = 0.1 * scale
const headRadius = 0.25 * scale
const upperBodyLength = 0.6 * scale
const pelvisLength = 0.4 * scale
const upperLegLength = 0.5 * scale
const upperLegSize = 0.2 * scale
const lowerLegSize = 0.2 * scale
const lowerLegLength = 0.5 * scale
const headShape = new CANNON.Sphere(headRadius)
const upperArmShape = new CANNON.Box(
new CANNON.Vec3(upperArmLength * 0.5, upperArmSize * 0.5, upperArmSize * 0.5)
)
const lowerArmShape = new CANNON.Box(
new CANNON.Vec3(lowerArmLength * 0.5, lowerArmSize * 0.5, lowerArmSize * 0.5)
)
const upperBodyShape = new CANNON.Box(
new CANNON.Vec3(shouldersDistance * 0.5, lowerArmSize * 0.5, upperBodyLength * 0.5)
)
const pelvisShape = new CANNON.Box(
new CANNON.Vec3(shouldersDistance * 0.5, lowerArmSize * 0.5, pelvisLength * 0.5)
)
const upperLegShape = new CANNON.Box(
new CANNON.Vec3(upperLegSize * 0.5, lowerArmSize * 0.5, upperLegLength * 0.5)
)
const lowerLegShape = new CANNON.Box(
new CANNON.Vec3(lowerLegSize * 0.5, lowerArmSize * 0.5, lowerLegLength * 0.5)
)
// Lower legs
const lowerLeftLeg = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(shouldersDistance / 2, 0, lowerLegLength / 2),
})
const lowerRightLeg = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(-shouldersDistance / 2, 0, lowerLegLength / 2),
})
lowerLeftLeg.addShape(lowerLegShape)
lowerRightLeg.addShape(lowerLegShape)
bodies.push(lowerLeftLeg)
bodies.push(lowerRightLeg)
// Upper legs
const upperLeftLeg = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(
shouldersDistance / 2,
0,
lowerLeftLeg.position.z + lowerLegLength / 2 + upperLegLength / 2
),
})
const upperRightLeg = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(
-shouldersDistance / 2,
0,
lowerRightLeg.position.z + lowerLegLength / 2 + upperLegLength / 2
),
})
upperLeftLeg.addShape(upperLegShape)
upperRightLeg.addShape(upperLegShape)
bodies.push(upperLeftLeg)
bodies.push(upperRightLeg)
// Pelvis
const pelvis = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 0, upperLeftLeg.position.z + upperLegLength / 2 + pelvisLength / 2),
})
pelvis.addShape(pelvisShape)
bodies.push(pelvis)
// Upper body
const upperBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 0, pelvis.position.z + pelvisLength / 2 + upperBodyLength / 2),
})
upperBody.addShape(upperBodyShape)
bodies.push(upperBody)
// Head
const head = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 0, upperBody.position.z + upperBodyLength / 2 + headRadius + neckLength),
})
head.addShape(headShape)
bodies.push(head)
// Upper arms
const upperLeftArm = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(
shouldersDistance / 2 + upperArmLength / 2,
0,
upperBody.position.z + upperBodyLength / 2
),
})
const upperRightArm = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(
-shouldersDistance / 2 - upperArmLength / 2,
0,
upperBody.position.z + upperBodyLength / 2
),
})
upperLeftArm.addShape(upperArmShape)
upperRightArm.addShape(upperArmShape)
bodies.push(upperLeftArm)
bodies.push(upperRightArm)
// Lower arms
const lowerLeftArm = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(
upperLeftArm.position.x + lowerArmLength / 2 + upperArmLength / 2,
0,
upperLeftArm.position.z
),
})
const lowerRightArm = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(
upperRightArm.position.x - lowerArmLength / 2 - upperArmLength / 2,
0,
upperRightArm.position.z
),
})
lowerLeftArm.addShape(lowerArmShape)
lowerRightArm.addShape(lowerArmShape)
bodies.push(lowerLeftArm)
bodies.push(lowerRightArm)
// Neck joint
const neckJoint = new CANNON.ConeTwistConstraint(head, upperBody, {
pivotA: new CANNON.Vec3(0, 0, -headRadius - neckLength / 2),
pivotB: new CANNON.Vec3(0, 0, upperBodyLength / 2),
axisA: CANNON.Vec3.UNIT_Z,
axisB: CANNON.Vec3.UNIT_Z,
angle,
twistAngle,
})
constraints.push(neckJoint)
// Knee joints
const leftKneeJoint = new CANNON.ConeTwistConstraint(lowerLeftLeg, upperLeftLeg, {
pivotA: new CANNON.Vec3(0, 0, lowerLegLength / 2),
pivotB: new CANNON.Vec3(0, 0, -upperLegLength / 2),
axisA: CANNON.Vec3.UNIT_Z,
axisB: CANNON.Vec3.UNIT_Z,
angle,
twistAngle,
})
const rightKneeJoint = new CANNON.ConeTwistConstraint(lowerRightLeg, upperRightLeg, {
pivotA: new CANNON.Vec3(0, 0, lowerLegLength / 2),
pivotB: new CANNON.Vec3(0, 0, -upperLegLength / 2),
axisA: CANNON.Vec3.UNIT_Z,
axisB: CANNON.Vec3.UNIT_Z,
angle,
twistAngle,
})
constraints.push(leftKneeJoint)
constraints.push(rightKneeJoint)
// Hip joints
const leftHipJoint = new CANNON.ConeTwistConstraint(upperLeftLeg, pelvis, {
pivotA: new CANNON.Vec3(0, 0, upperLegLength / 2),
pivotB: new CANNON.Vec3(shouldersDistance / 2, 0, -pelvisLength / 2),
axisA: CANNON.Vec3.UNIT_Z,
axisB: CANNON.Vec3.UNIT_Z,
angle,
twistAngle,
})
const rightHipJoint = new CANNON.ConeTwistConstraint(upperRightLeg, pelvis, {
pivotA: new CANNON.Vec3(0, 0, upperLegLength / 2),
pivotB: new CANNON.Vec3(-shouldersDistance / 2, 0, -pelvisLength / 2),
axisA: CANNON.Vec3.UNIT_Z,
axisB: CANNON.Vec3.UNIT_Z,
angle,
twistAngle,
})
constraints.push(leftHipJoint)
constraints.push(rightHipJoint)
// Spine
const spineJoint = new CANNON.ConeTwistConstraint(pelvis, upperBody, {
pivotA: new CANNON.Vec3(0, 0, pelvisLength / 2),
pivotB: new CANNON.Vec3(0, 0, -upperBodyLength / 2),
axisA: CANNON.Vec3.UNIT_Z,
axisB: CANNON.Vec3.UNIT_Z,
angle,
twistAngle,
})
constraints.push(spineJoint)
// Shoulders
const leftShoulder = new CANNON.ConeTwistConstraint(upperBody, upperLeftArm, {
pivotA: new CANNON.Vec3(shouldersDistance / 2, 0, upperBodyLength / 2),
pivotB: new CANNON.Vec3(-upperArmLength / 2, 0, 0),
axisA: CANNON.Vec3.UNIT_X,
axisB: CANNON.Vec3.UNIT_X,
angle: angleShoulders,
})
const rightShoulder = new CANNON.ConeTwistConstraint(upperBody, upperRightArm, {
pivotA: new CANNON.Vec3(-shouldersDistance / 2, 0, upperBodyLength / 2),
pivotB: new CANNON.Vec3(upperArmLength / 2, 0, 0),
axisA: CANNON.Vec3.UNIT_X,
axisB: CANNON.Vec3.UNIT_X,
angle: angleShoulders,
twistAngle,
})
constraints.push(leftShoulder)
constraints.push(rightShoulder)
// Elbow joint
const leftElbowJoint = new CANNON.ConeTwistConstraint(lowerLeftArm, upperLeftArm, {
pivotA: new CANNON.Vec3(-lowerArmLength / 2, 0, 0),
pivotB: new CANNON.Vec3(upperArmLength / 2, 0, 0),
axisA: CANNON.Vec3.UNIT_X,
axisB: CANNON.Vec3.UNIT_X,
angle,
twistAngle,
})
const rightElbowJoint = new CANNON.ConeTwistConstraint(lowerRightArm, upperRightArm, {
pivotA: new CANNON.Vec3(lowerArmLength / 2, 0, 0),
pivotB: new CANNON.Vec3(-upperArmLength / 2, 0, 0),
axisA: CANNON.Vec3.UNIT_X,
axisB: CANNON.Vec3.UNIT_X,
angle,
twistAngle,
})
constraints.push(leftElbowJoint)
constraints.push(rightElbowJoint)
return { bodies, constraints }
}
function setupWorld(demo) {
const world = demo.getWorld()
world.gravity.set(0, -5, 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
}
function createStaticSphere() {
const sphereShape = new CANNON.Sphere(4)
const sphereBody = new CANNON.Body({ mass: 0 })
sphereBody.addShape(sphereShape)
sphereBody.position.set(0, -1, 0)
return sphereBody
}
</script>
</body>
</html>