效果如下:
点击体验3D物理引擎bullet的javascript版本。源码参考了:https://github.com/THISISAGOODNAME/learn-ammojs,感谢原作者!
<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"> <link type="text/css" rel="stylesheet" href="main.css"> <style> body { color: #333; }</style> </head> <body> <div id="info"> Ammo.js physics soft body volume demo<br/> Click to throw a ball </div> <div id="container"></div>
<script src="js/libs/ammo.js"></script>
<script type="module">
import * as THREE from '../build/three.module.js';
import Stats from './jsm/libs/stats.module.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js'; import { BufferGeometryUtils } from './jsm/utils/BufferGeometryUtils.js';
// 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; var softBodyHelpers;
Ammo().then( function ( AmmoLib ) {
Ammo = AmmoLib;
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 );
renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.shadowMap.enabled = true; container.appendChild( renderer.domElement );
controls = new OrbitControls( camera, renderer.domElement ); controls.target.set( 0, 2, 0 ); controls.update();
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 );
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 ) );
transformAux1 = new Ammo.btTransform(); softBodyHelpers = new Ammo.btSoftBodyHelpers();
}
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 ) {
// Ony consider the position values when merging the vertices var posOnlyBufGeometry = new THREE.BufferGeometry(); posOnlyBufGeometry.setAttribute( 'position', bufGeometry.getAttribute( 'position' ) ); posOnlyBufGeometry.setIndex( bufGeometry.getIndex() );
// Merge the vertices so the triangle soup is converted to indexed triangles var indexedBufferGeom = BufferGeometryUtils.mergeVertices( posOnlyBufGeometry );
// Create index arrays mapping the indexed vertices to bufGeometry vertices mapIndices( bufGeometry, indexedBufferGeom );
}
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>
先不说了,广告时间又到了,现在植入广告:几个《传热学》相关的小程序总结如下,可在微信中点击体验:
《传热学》相关小程序演示动画如下(其中下图1D非稳态导热计算发散,调小时间步长后重新计算,结果收敛!):
黑体单色辐射力如下图,可见温度越高,同频率辐射力越大:
《(计算)流体力学》中的几个小程序,可在微信中点击体验:
关于《(计算)流体力学》相关的几个小程序演示动画如下:
LBM(=Lattice Boltzmann Method)计算得到的圆柱绕流“卡门涡街”演示(由于网格较少,分辨率低,圆柱近乎正方形):
顺便,《(热工过程)自动控制》中关于PID控制器的仿真可点击此处体验:PID控制演示小程序,(PID控制相关视频见:基础/整定/重要补充)。动画如下:
(正文完!)
本文分享自 传输过程数值模拟学习笔记 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!