ShadowEditor/web/test/webgl_physics_volume.html
2020-05-01 09:07:57 +08:00

415 lines
17 KiB
HTML

<html lang="en">
<head>
<title>Ammo.js softbody volume demo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #61443e;
font-family: Monospace;
font-size: 13px;
text-align: center;
background-color: #bfd1e5;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
}
a {
color: #a06851;
}
</style>
</head>
<body>
<div id="info">Ammo.js physics soft body volume demo
<br>Click to throw a ball</div>
<div id="container">
<br />
<br />
<br />
<br />
<br />Loading...</div>
<script src="../assets/js/three.js"></script>
<script src="../assets/js/libs/ammo.js"></script>
<script src="../assets/js/controls/OrbitControls.js"></script>
<script src="../assets/js/libs/stats.min.js"></script>
<script>
// Graphics variables
var container, stats;
var camera, controls, scene, renderer;
var textureLoader;
var clock = new THREE.Clock();
var clickRequest = false;
var mouseCoords = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var ballMaterial = new THREE.MeshPhongMaterial({
color: 0x202020
});
var pos = new THREE.Vector3();
var quat = new THREE.Quaternion();
// Physics variables
var gravityConstant = -9.8;
var physicsWorld;
var rigidBodies = [];
var softBodies = [];
var margin = 0.05;
var transformAux1 = new Ammo.btTransform();
var softBodyHelpers = new Ammo.btSoftBodyHelpers();
init();
animate();
function init() {
initGraphics();
initPhysics();
createObjects();
initInput();
}
function initGraphics() {
container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.2, 2000);
scene = new THREE.Scene();
scene.background = new THREE.Color(0xbfd1e5);
camera.position.set(-7, 5, 8);
controls = new THREE.OrbitControls(camera);
controls.target.set(0, 2, 0);
controls.update();
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
textureLoader = new THREE.TextureLoader();
var ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-10, 10, 5);
light.castShadow = true;
var d = 20;
light.shadow.camera.left = -d;
light.shadow.camera.right = d;
light.shadow.camera.top = d;
light.shadow.camera.bottom = -d;
light.shadow.camera.near = 2;
light.shadow.camera.far = 50;
light.shadow.mapSize.x = 1024;
light.shadow.mapSize.y = 1024;
scene.add(light);
container.innerHTML = '';
container.appendChild(renderer.domElement);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function initPhysics() {
// Physics configuration
var collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
var broadphase = new Ammo.btDbvtBroadphase();
var solver = new Ammo.btSequentialImpulseConstraintSolver();
var softBodySolver = new Ammo.btDefaultSoftBodySolver();
physicsWorld = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration,
softBodySolver);
physicsWorld.setGravity(new Ammo.btVector3(0, gravityConstant, 0));
physicsWorld.getWorldInfo().set_m_gravity(new Ammo.btVector3(0, gravityConstant, 0));
}
function createObjects() {
// Ground
pos.set(0, -0.5, 0);
quat.set(0, 0, 0, 1);
var ground = createParalellepiped(40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial({
color: 0xFFFFFF
}));
ground.castShadow = true;
ground.receiveShadow = true;
textureLoader.load("textures/grid.png", function (texture) {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(40, 40);
ground.material.map = texture;
ground.material.needsUpdate = true;
});
// Create soft volumes
var volumeMass = 15;
var sphereGeometry = new THREE.SphereBufferGeometry(1.5, 40, 25);
sphereGeometry.translate(5, 5, 0);
createSoftVolume(sphereGeometry, volumeMass, 250);
var boxGeometry = new THREE.BoxBufferGeometry(1, 1, 5, 4, 4, 20);
boxGeometry.translate(-2, 5, 0);
createSoftVolume(boxGeometry, volumeMass, 120);
// Ramp
pos.set(3, 1, 0);
quat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), 30 * Math.PI / 180);
var obstacle = createParalellepiped(10, 1, 4, 0, pos, quat, new THREE.MeshPhongMaterial({
color: 0x606060
}));
obstacle.castShadow = true;
obstacle.receiveShadow = true;
}
function processGeometry(bufGeometry) {
// Obtain a Geometry
var geometry = new THREE.Geometry().fromBufferGeometry(bufGeometry);
// Merge the vertices so the triangle soup is converted to indexed triangles
geometry.mergeVertices();
// Convert again to BufferGeometry, indexed
var indexedBufferGeom = createIndexedBufferGeometryFromGeometry(geometry);
// Create index arrays mapping the indexed vertices to bufGeometry vertices
mapIndices(bufGeometry, indexedBufferGeom);
}
function createIndexedBufferGeometryFromGeometry(geometry) {
var numVertices = geometry.vertices.length;
var numFaces = geometry.faces.length;
var bufferGeom = new THREE.BufferGeometry();
var vertices = new Float32Array(numVertices * 3);
var indices = new(numFaces * 3 > 65535 ? Uint32Array : Uint16Array)(numFaces * 3);
for (var i = 0; i < numVertices; i++) {
var p = geometry.vertices[i];
var i3 = i * 3;
vertices[i3] = p.x;
vertices[i3 + 1] = p.y;
vertices[i3 + 2] = p.z;
}
for (var i = 0; i < numFaces; i++) {
var f = geometry.faces[i];
var i3 = i * 3;
indices[i3] = f.a;
indices[i3 + 1] = f.b;
indices[i3 + 2] = f.c;
}
bufferGeom.setIndex(new THREE.BufferAttribute(indices, 1));
bufferGeom.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
return bufferGeom;
}
function isEqual(x1, y1, z1, x2, y2, z2) {
var delta = 0.000001;
return Math.abs(x2 - x1) < delta &&
Math.abs(y2 - y1) < delta &&
Math.abs(z2 - z1) < delta;
}
function mapIndices(bufGeometry, indexedBufferGeom) {
// Creates ammoVertices, ammoIndices and ammoIndexAssociation in bufGeometry
var vertices = bufGeometry.attributes.position.array;
var idxVertices = indexedBufferGeom.attributes.position.array;
var indices = indexedBufferGeom.index.array;
var numIdxVertices = idxVertices.length / 3;
var numVertices = vertices.length / 3;
bufGeometry.ammoVertices = idxVertices;
bufGeometry.ammoIndices = indices;
bufGeometry.ammoIndexAssociation = [];
for (var i = 0; i < numIdxVertices; i++) {
var association = [];
bufGeometry.ammoIndexAssociation.push(association);
var i3 = i * 3;
for (var j = 0; j < numVertices; j++) {
var j3 = j * 3;
if (isEqual(idxVertices[i3], idxVertices[i3 + 1], idxVertices[i3 + 2],
vertices[j3], vertices[j3 + 1], vertices[j3 + 2])) {
association.push(j3);
}
}
}
}
function createSoftVolume(bufferGeom, mass, pressure) {
processGeometry(bufferGeom);
var volume = new THREE.Mesh(bufferGeom, new THREE.MeshPhongMaterial({
color: 0xFFFFFF
}));
volume.castShadow = true;
volume.receiveShadow = true;
volume.frustumCulled = false;
scene.add(volume);
textureLoader.load("textures/colors.png", function (texture) {
volume.material.map = texture;
volume.material.needsUpdate = true;
});
// Volume physic object
var volumeSoftBody = softBodyHelpers.CreateFromTriMesh(
physicsWorld.getWorldInfo(),
bufferGeom.ammoVertices,
bufferGeom.ammoIndices,
bufferGeom.ammoIndices.length / 3,
true);
var sbConfig = volumeSoftBody.get_m_cfg();
sbConfig.set_viterations(40);
sbConfig.set_piterations(40);
// Soft-soft and soft-rigid collisions
sbConfig.set_collisions(0x11);
// Friction
sbConfig.set_kDF(0.1);
// Damping
sbConfig.set_kDP(0.01);
// Pressure
sbConfig.set_kPR(pressure);
// Stiffness
volumeSoftBody.get_m_materials().at(0).set_m_kLST(0.9);
volumeSoftBody.get_m_materials().at(0).set_m_kAST(0.9);
volumeSoftBody.setTotalMass(mass, false);
Ammo.castObject(volumeSoftBody, Ammo.btCollisionObject).getCollisionShape().setMargin(margin);
physicsWorld.addSoftBody(volumeSoftBody, 1, -1);
volume.userData.physicsBody = volumeSoftBody;
// Disable deactivation
volumeSoftBody.setActivationState(4);
softBodies.push(volume);
}
function createParalellepiped(sx, sy, sz, mass, pos, quat, material) {
var threeObject = new THREE.Mesh(new THREE.BoxBufferGeometry(sx, sy, sz, 1, 1, 1), material);
var shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5));
shape.setMargin(margin);
createRigidBody(threeObject, shape, mass, pos, quat);
return threeObject;
}
function createRigidBody(threeObject, physicsShape, mass, pos, quat) {
threeObject.position.copy(pos);
threeObject.quaternion.copy(quat);
var transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
var motionState = new Ammo.btDefaultMotionState(transform);
var localInertia = new Ammo.btVector3(0, 0, 0);
physicsShape.calculateLocalInertia(mass, localInertia);
var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia);
var body = new Ammo.btRigidBody(rbInfo);
threeObject.userData.physicsBody = body;
scene.add(threeObject);
if (mass > 0) {
rigidBodies.push(threeObject);
// Disable deactivation
body.setActivationState(4);
}
physicsWorld.addRigidBody(body);
return body;
}
function initInput() {
window.addEventListener('mousedown', function (event) {
if (!clickRequest) {
mouseCoords.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) *
2 + 1
);
clickRequest = true;
}
}, false);
}
function processClick() {
if (clickRequest) {
raycaster.setFromCamera(mouseCoords, camera);
// Creates a ball
var ballMass = 3;
var ballRadius = 0.4;
var ball = new THREE.Mesh(new THREE.SphereBufferGeometry(ballRadius, 18, 16), ballMaterial);
ball.castShadow = true;
ball.receiveShadow = true;
var ballShape = new Ammo.btSphereShape(ballRadius);
ballShape.setMargin(margin);
pos.copy(raycaster.ray.direction);
pos.add(raycaster.ray.origin);
quat.set(0, 0, 0, 1);
var ballBody = createRigidBody(ball, ballShape, ballMass, pos, quat);
ballBody.setFriction(0.5);
pos.copy(raycaster.ray.direction);
pos.multiplyScalar(14);
ballBody.setLinearVelocity(new Ammo.btVector3(pos.x, pos.y, pos.z));
clickRequest = false;
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
render();
stats.update();
}
function render() {
var deltaTime = clock.getDelta();
updatePhysics(deltaTime);
processClick();
renderer.render(scene, camera);
}
function updatePhysics(deltaTime) {
// Step world
physicsWorld.stepSimulation(deltaTime, 10);
// Update soft volumes
for (var i = 0, il = softBodies.length; i < il; i++) {
var volume = softBodies[i];
var geometry = volume.geometry;
var softBody = volume.userData.physicsBody;
var volumePositions = geometry.attributes.position.array;
var volumeNormals = geometry.attributes.normal.array;
var association = geometry.ammoIndexAssociation;
var numVerts = association.length;
var nodes = softBody.get_m_nodes();
for (var j = 0; j < numVerts; j++) {
var node = nodes.at(j);
var nodePos = node.get_m_x();
var x = nodePos.x();
var y = nodePos.y();
var z = nodePos.z();
var nodeNormal = node.get_m_n();
var nx = nodeNormal.x();
var ny = nodeNormal.y();
var nz = nodeNormal.z();
var assocVertex = association[j];
for (var k = 0, kl = assocVertex.length; k < kl; k++) {
var indexVertex = assocVertex[k];
volumePositions[indexVertex] = x;
volumeNormals[indexVertex] = nx;
indexVertex++;
volumePositions[indexVertex] = y;
volumeNormals[indexVertex] = ny;
indexVertex++;
volumePositions[indexVertex] = z;
volumeNormals[indexVertex] = nz;
}
}
geometry.attributes.position.needsUpdate = true;
geometry.attributes.normal.needsUpdate = true;
}
// Update rigid bodies
for (var i = 0, il = rigidBodies.length; i < il; i++) {
var objThree = rigidBodies[i];
var objPhys = objThree.userData.physicsBody;
var ms = objPhys.getMotionState();
if (ms) {
ms.getWorldTransform(transformAux1);
var p = transformAux1.getOrigin();
var q = transformAux1.getRotation();
objThree.position.set(p.x(), p.y(), p.z());
objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
}
}
}
</script>
</body>
</html>