Created
September 29, 2018 21:57
-
-
Save valex/23e9a7d407759458a3def9bfed51acb3 to your computer and use it in GitHub Desktop.
Using constraints to limit movement of objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
body { | |
/* set margin to 0 and overflow to hidden, to go fullscreen */ | |
margin: 0; | |
overflow: hidden; | |
background-color: #000000; | |
} | |
</style> | |
<title>Physijs Constraints</title> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script> | |
<!-- https://github.com/mrdoob/three.js/tree/master/examples/js/renderers --> | |
<script type="text/javascript" src="../libs96/Projector.js"></script> | |
<!-- http://chandlerprall.github.io/Physijs/ --> | |
<script type="text/javascript" src="../libs96/physi.js"></script> | |
<!-- https://github.com/gka/chroma.js/ --> | |
<script type="text/javascript" src="../libs96/chroma.min.js"></script> | |
<script type="text/javascript"> | |
'use strict'; | |
Physijs.scripts.worker = '../libs96/physijs_worker.js'; | |
// https://github.com/kripken/ammo.js/ | |
Physijs.scripts.ammo = '../libs96/ammo.js'; | |
var scale = chroma.scale(['white', 'blue', 'red', 'yellow']); | |
var initScene, render, applyForce, setMousePosition, mouse_position, | |
ground_material, box_material, | |
projector, renderer, render_stats, physics_stats, scene, ground, light, camera, box, boxes = []; | |
initScene = function () { | |
projector = new THREE.Projector; | |
renderer = new THREE.WebGLRenderer({antialias: true}); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.setClearColor(new THREE.Color(0x000000)); | |
renderer.shadowMap.enabled = true; | |
document.getElementById('viewport').appendChild(renderer.domElement); | |
render_stats = new Stats(); | |
render_stats.domElement.style.position = 'absolute'; | |
render_stats.domElement.style.top = '1px'; | |
render_stats.domElement.style.right = '1px'; | |
render_stats.domElement.style.zIndex = 100; | |
document.getElementById('viewport').appendChild(render_stats.domElement); | |
scene = new Physijs.Scene({reportSize: 10, fixedTimeStep: 1 / 60}); | |
scene.setGravity(new THREE.Vector3(0, -10, 0)); | |
camera = new THREE.PerspectiveCamera( | |
35, | |
window.innerWidth / window.innerHeight, | |
1, | |
1000 | |
); | |
camera.position.set(85, 65, 65); | |
camera.lookAt(new THREE.Vector3(0, 0, 0)); | |
scene.add(camera); | |
// Light | |
light = new THREE.SpotLight(0xFFFFFF); | |
light.position.set(20, 50, 50); | |
light.castShadow = true; | |
light.shadowMapDebug = true; | |
light.shadow.camera.near = 10; | |
light.shadow.camera.far = 100; | |
scene.add(light); | |
var meshes = []; | |
createGround(); | |
var flipperLeftConstraint = createLeftFlipper(); | |
var flipperRightConstraint = createRightFlipper(); | |
var sliderBottomConstraint = createSliderBottom(); | |
var sliderTopConstraint = createSliderTop(); | |
var coneTwistConstraint = createConeTwist(); | |
var point2point = createPointToPoint(true); | |
var controls = new function () { | |
this.enableMotor = false; | |
this.acceleration = 2; | |
this.velocity = -10; | |
this.enableConeTwistMotor = false; | |
this.motorTargetX = 0; | |
this.motorTargetY = 0; | |
this.motorTargetZ = 0; | |
this.updateCone = function () { | |
if (controls.enableConeTwistMotor) { | |
coneTwistConstraint.enableMotor(); | |
coneTwistConstraint.setMotorTarget(new THREE.Vector3(controls.motorTargetX, controls.motorTargetY, controls.motorTargetZ)); | |
} else { | |
coneTwistConstraint.disableMotor(); | |
} | |
}; | |
this.updateMotor = function () { | |
if (controls.enableMotor) { | |
// velocity is the velocity we are going for. | |
// acceleration is how fast we're going to reach it | |
flipperLeftConstraint.disableMotor(); | |
flipperLeftConstraint.enableAngularMotor(controls.velocity, controls.acceleration); | |
flipperRightConstraint.disableMotor(); | |
flipperRightConstraint.enableAngularMotor(-1 * controls.velocity, controls.acceleration); | |
} else { | |
flipperLeftConstraint.disableMotor(); | |
flipperRightConstraint.disableMotor(); | |
} | |
}; | |
this.sliderLeft = function () { | |
sliderBottomConstraint.disableLinearMotor(); | |
sliderBottomConstraint.enableLinearMotor(controls.velocity, controls.acceleration); | |
sliderTopConstraint.disableLinearMotor(); | |
sliderTopConstraint.enableLinearMotor(controls.velocity, controls.acceleration); | |
}; | |
this.sliderRight = function () { | |
sliderBottomConstraint.disableLinearMotor(); | |
sliderBottomConstraint.enableLinearMotor(-1 * controls.velocity, controls.acceleration); | |
sliderTopConstraint.disableLinearMotor(); | |
sliderTopConstraint.enableLinearMotor(-1 * controls.velocity, controls.acceleration); | |
}; | |
this.clearMeshes = function () { | |
meshes.forEach(function (e) { | |
scene.remove(e); | |
}); | |
meshes = []; | |
}; | |
this.addSpheres = function () { | |
var colorSphere = scale(Math.random()).hex(); | |
for (var i = 0; i < 5; i++) { | |
box = new Physijs.SphereMesh( | |
new THREE.SphereGeometry(2, 20), | |
Physijs.createMaterial( | |
new THREE.MeshPhongMaterial( | |
{ | |
color: colorSphere, | |
opacity: 0.8, | |
transparent: true | |
// map: THREE.ImageUtils.loadTexture( '../assets/textures/general/floor-wood.jpg' ) | |
}), | |
controls.sphereFriction, | |
controls.sphereRestitution | |
) | |
, 0.1); | |
box.castShadow = true; | |
box.receiveShadow = true; | |
box.position.set( | |
Math.random() * 50 - 25, | |
20 + Math.random() * 5, | |
Math.random() * 5 | |
); | |
meshes.push(box); | |
scene.add(box); | |
} | |
}; | |
}; | |
controls.updateMotor(); | |
var gui = new dat.GUI(); | |
gui.domElement.style.position = 'absolute'; | |
gui.domElement.style.top = '20px'; | |
gui.domElement.style.left = '20px'; | |
var generalFolder = gui.addFolder('general'); | |
generalFolder.add(controls, "acceleration", 0, 15).onChange(controls.updateMotor); | |
generalFolder.add(controls, "velocity", -10, 10).onChange(controls.updateMotor); | |
var hingeFolder = gui.addFolder('hinge'); | |
hingeFolder.add(controls, "enableMotor").onChange(controls.updateMotor); | |
var sliderFolder = gui.addFolder('sliders'); | |
sliderFolder.add(controls, "sliderLeft").onChange(controls.sliderLeft); | |
sliderFolder.add(controls, "sliderRight").onChange(controls.sliderRight); | |
var coneTwistFolder = gui.addFolder('coneTwist'); | |
coneTwistFolder.add(controls, "enableConeTwistMotor").onChange(controls.updateCone); | |
coneTwistFolder.add(controls, "motorTargetX", -Math.PI / 2, Math.PI / 2).onChange(controls.updateCone); | |
coneTwistFolder.add(controls, "motorTargetY", -Math.PI / 2, Math.PI / 2).onChange(controls.updateCone); | |
coneTwistFolder.add(controls, "motorTargetZ", -Math.PI / 2, Math.PI / 2).onChange(controls.updateCone); | |
var spheresFolder = gui.addFolder('spheres'); | |
spheresFolder.add(controls, "clearMeshes").onChange(controls.updateMotor); | |
spheresFolder.add(controls, "addSpheres").onChange(controls.updateMotor); | |
requestAnimationFrame(render); | |
scene.simulate(); | |
}; | |
function createGround() { | |
// Materials | |
ground_material = Physijs.createMaterial( | |
new THREE.MeshPhongMaterial( | |
{ | |
// color: 0xaaaaaa, | |
map: new THREE.TextureLoader().load('../assets96/textures/general/floor-wood.jpg') | |
}), | |
.9, // high friction | |
.7 // low restitution | |
); | |
// Ground | |
ground = new Physijs.BoxMesh( | |
new THREE.BoxGeometry(60, 1, 65), | |
ground_material, | |
0 // mass | |
); | |
ground.receiveShadow = true; | |
var borderLeft = new Physijs.BoxMesh( | |
new THREE.BoxGeometry(2, 6, 65), | |
ground_material, | |
0 // mass | |
); | |
borderLeft.position.x = -31; | |
borderLeft.position.y = 2; | |
borderLeft.receiveShadow = true; | |
ground.add(borderLeft); | |
var borderRight = new Physijs.BoxMesh(new THREE.BoxGeometry(2, 6, 65), | |
ground_material, | |
0 // mass | |
); | |
borderRight.position.x = 31; | |
borderRight.position.y = 2; | |
borderRight.receiveShadow = true; | |
ground.add(borderRight); | |
var borderBottom = new Physijs.BoxMesh( | |
new THREE.BoxGeometry(64, 6, 2), | |
ground_material, | |
0 // mass | |
); | |
borderBottom.position.z = 32; | |
borderBottom.position.y = 1.5; | |
borderBottom.receiveShadow = true; | |
ground.add(borderBottom); | |
var borderTop = new Physijs.BoxMesh( | |
new THREE.BoxGeometry(64, 6, 2), | |
ground_material, | |
0 // mass | |
); | |
borderTop.position.z = -32; | |
borderTop.position.y = 2; | |
borderTop.receiveShadow = true; | |
ground.add(borderTop); | |
ground.receiveShadow = true; | |
scene.add(ground); | |
} | |
function createConeTwist() { | |
var baseMesh = new THREE.SphereGeometry(1); | |
var armMesh = new THREE.BoxGeometry(2, 12, 3); | |
var objectOne = new Physijs.BoxMesh(baseMesh, Physijs.createMaterial( | |
new THREE.MeshPhongMaterial({color: 0x4444ff, transparent: true, opacity: 0.7}), 0, 0), 0); | |
objectOne.position.z = 0; | |
objectOne.position.x = 20; | |
objectOne.position.y = 15.5; | |
objectOne.castShadow = true; | |
scene.add(objectOne); | |
var objectTwo = new Physijs.SphereMesh(armMesh, Physijs.createMaterial( | |
new THREE.MeshPhongMaterial({color: 0x4444ff, transparent: true, opacity: 0.7}), 0, 0), 10); | |
objectTwo.position.z = 0; | |
objectTwo.position.x = 20; | |
objectTwo.position.y = 7.5; | |
scene.add(objectTwo); | |
objectTwo.castShadow = true; | |
//position is the position of the axis, relative to the ref, based on the current position | |
var constraint = new Physijs.ConeTwistConstraint(objectOne, objectTwo, objectOne.position); | |
scene.addConstraint(constraint/*, true*/); | |
// set limit to quarter circle for each axis | |
constraint.setLimit(0.5 * Math.PI, 0.5 * Math.PI, 0.5 * Math.PI); | |
constraint.setMaxMotorImpulse(1); | |
constraint.setMotorTarget(new THREE.Vector3(0, 0, 0)); // desired rotation | |
return constraint; | |
} | |
function createPointToPoint() { | |
var obj1 = new THREE.SphereGeometry(2); | |
var obj2 = new THREE.SphereGeometry(2); | |
var objectOne = new Physijs.SphereMesh(obj1, Physijs.createMaterial( | |
new THREE.MeshPhongMaterial({color: 0xff4444, transparent: true, opacity: 0.7}), 0, 0)); | |
objectOne.position.z = -18; | |
objectOne.position.x = -10; | |
objectOne.position.y = 2; | |
objectOne.castShadow = true; | |
scene.add(objectOne); | |
var objectTwo = new Physijs.SphereMesh(obj2, Physijs.createMaterial( | |
new THREE.MeshPhongMaterial({color: 0xff4444, transparent: true, opacity: 0.7}), 0, 0)); | |
objectTwo.position.z = -5; | |
objectTwo.position.x = -20; | |
objectTwo.position.y = 2; | |
objectTwo.castShadow = true; | |
scene.add(objectTwo); | |
// if no position two, its fixed to a position. Else fixed to objectTwo and both will move | |
var constraint = new Physijs.PointConstraint(objectOne, objectTwo, objectTwo.position); | |
scene.addConstraint(constraint/*, true*/); | |
} | |
function createSliderBottom() { | |
var sliderCube = new THREE.BoxGeometry(12, 2, 2); | |
var sliderMesh = new Physijs.BoxMesh(sliderCube, Physijs.createMaterial( | |
new THREE.MeshPhongMaterial({color: 0x44ff44, opacity: 0.6, transparent: true}), 0, 0), 0.01); | |
sliderMesh.position.z = 20; | |
sliderMesh.position.x = 6; | |
sliderMesh.position.y = 1.5; | |
sliderMesh.castShadow = true; | |
scene.add(sliderMesh); | |
var constraint = new Physijs.SliderConstraint(sliderMesh, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0)); | |
scene.addConstraint(constraint/*, true*/); | |
constraint.setLimits(-10, 10, 0, 0); | |
constraint.setRestitution(0.1, 0.1); | |
return constraint; | |
} | |
function createSliderTop() { | |
var sliderSphere = new THREE.BoxGeometry(7, 2, 7); | |
var sliderMesh = new Physijs.BoxMesh(sliderSphere, Physijs.createMaterial( | |
new THREE.MeshPhongMaterial({color: 0x44ff44, transparent: true, opacity: 0.5}), 0, 0), 10); | |
sliderMesh.position.z = -15; | |
sliderMesh.position.x = -20; | |
sliderMesh.position.y = 1.5; | |
scene.add(sliderMesh); | |
sliderMesh.castShadow = true; | |
//position is the position of the axis, relative to the ref, based on the current position | |
var constraint = new Physijs.SliderConstraint(sliderMesh, new THREE.Vector3(-10, 0, 20), new THREE.Vector3(Math.PI / 2, 0, 0)); | |
scene.addConstraint(constraint/*, true*/); | |
constraint.setLimits(-20, 10, 0.5, -0, 5); | |
constraint.setRestitution(0.2, 0.1); | |
return constraint; | |
} | |
function createLeftFlipper() { | |
var flipperLeft = new Physijs.BoxMesh( | |
new THREE.BoxGeometry(12, 2, 2), Physijs.createMaterial(new THREE.MeshPhongMaterial( | |
{opacity: 0.6, transparent: true} | |
)), 0.3 | |
); | |
flipperLeft.position.x = -6; | |
flipperLeft.position.y = 2; | |
flipperLeft.position.z = 0; | |
flipperLeft.castShadow = true; | |
scene.add(flipperLeft); | |
var flipperLeftPivot = new Physijs.SphereMesh( | |
new THREE.BoxGeometry(1, 1, 1), ground_material, 0); | |
flipperLeftPivot.position.y = 1; | |
flipperLeftPivot.position.x = -15; | |
flipperLeftPivot.position.z = 0; | |
flipperLeftPivot.rotation.y = 1.4; | |
flipperLeftPivot.castShadow = true; | |
scene.add(flipperLeftPivot); | |
// when looking at the axis, the axis of object two are used. | |
// so as long as that one is the same as the scene, no problems | |
// rotation and axis are relative to object2. If position == cube2.position it works as expected | |
var constraint = new Physijs.HingeConstraint(flipperLeft, flipperLeftPivot, flipperLeftPivot.position, new THREE.Vector3(0, 1, 0)); | |
scene.addConstraint(constraint/*, true*/); | |
constraint.setLimits( | |
-2.2, // minimum angle of motion, in radians, from the point object 1 starts (going back) | |
-0.6, // maximum angle of motion, in radians, from the point object 1 starts (going forward) | |
0.1, // applied as a factor to constraint error, how big the kantelpunt is moved when a constraint is hit | |
0 // controls bounce at limit (0.0 == no bounce) | |
); | |
return constraint; | |
} | |
function createRightFlipper() { | |
var flipperright = new Physijs.BoxMesh( | |
new THREE.BoxGeometry(12, 2, 2), Physijs.createMaterial(new THREE.MeshPhongMaterial( | |
{opacity: 0.6, transparent: true} | |
)), 0.3 | |
); | |
flipperright.position.x = 8; | |
flipperright.position.y = 2; | |
flipperright.position.z = 0; | |
flipperright.castShadow = true; | |
scene.add(flipperright); | |
var flipperLeftPivot = new Physijs.SphereMesh( | |
new THREE.BoxGeometry(1, 1, 1), ground_material, 0); | |
flipperLeftPivot.position.y = 2; | |
flipperLeftPivot.position.x = 15; | |
flipperLeftPivot.position.z = 0; | |
flipperLeftPivot.rotation.y = 1.4; | |
flipperLeftPivot.castShadow = true; | |
scene.add(flipperLeftPivot); | |
// when looking at the axis, the axis of object two are used. | |
// so as long as that one is the same as the scene, no problems | |
// rotation and axis are relative to object2. If position == cube2.position it works as expected | |
var constraint = new Physijs.HingeConstraint(flipperright, flipperLeftPivot, flipperLeftPivot.position, new THREE.Vector3(0, 1, 0)); | |
// var constraint = new Physijs.HingeConstraint(cube1, new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0)); | |
scene.addConstraint(constraint/*, true*/); | |
constraint.setLimits( | |
-2.2, // minimum angle of motion, in radians, from the point object 1 starts (going back) | |
-0.6, // maximum angle of motion, in radians, from the point object 1 starts (going forward) | |
0.1, // applied as a factor to constraint error, how big the kantelpunt is moved when a constraint is hit | |
0 // controls bounce at limit (0.0 == no bounce) | |
); | |
return constraint; | |
} | |
var direction = 1; | |
render = function () { | |
requestAnimationFrame(render); | |
renderer.render(scene, camera); | |
render_stats.update(); | |
ground.__dirtyRotation = true; | |
scene.simulate(undefined, 2); | |
}; | |
window.onload = initScene; | |
</script> | |
</head> | |
<body> | |
<div id="viewport"></div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment