btSoftRigidDynamicsWorld 类是 Ammo.js 物理库中的一个类,表示一个动态世界,用于处理软体和刚体物体的物理模拟。

news2024/11/30 10:35:11

demo案例
在这里插入图片描述

btSoftRigidDynamicsWorld 类是 Ammo.js 物理库中的一个类,表示一个动态世界,用于处理软体和刚体物体的物理模拟。让我们按照输入参数、输出、属性和方法来详细解释其 API:

输入参数:

  1. dispatcher:这是一个 btCollisionDispatcher 实例,负责分派碰撞检测和处理。
  2. broadphase:一个 btBroadphaseInterface 实例,用于广义相交检测,例如高效地识别潜在的碰撞对。
  3. solver:一个 btConstraintSolver 实例,负责解决刚体和软体之间的约束。
  4. collisionConfiguration:一个 btCollisionConfiguration 实例,配置碰撞检测算法的行为。
  5. softBodySolver:一个 btSoftBodySolver 实例,负责解决软体物理模拟。

输出:

一个 btSoftRigidDynamicsWorld 实例,代表着模拟软体和刚体物理的动态世界。

属性:

  • 重力(Gravity):表示作用于世界中物体的重力。
  • 世界信息(World Info):世界的配置信息,如重力和模拟参数等。

方法:

  1. addSoftBody(softBody, collisionFilterGroup, collisionFilterMask):将软体添加到世界中进行模拟。collisionFilterGroupcollisionFilterMask 参数确定软体属于哪些碰撞组,并且与哪些碰撞组进行交互。

  2. removeSoftBody(softBody):从世界中移除软体。

  3. addRigidBody(rigidBody, collisionFilterGroup, collisionFilterMask):将刚体添加到世界中进行模拟。collisionFilterGroupcollisionFilterMask 参数确定刚体属于哪些碰撞组,并且与哪些碰撞组进行交互。

  4. removeRigidBody(rigidBody):从世界中移除刚体。

  5. setGravity(gravityVector):设置作用于世界中物体的重力。

  6. stepSimulation(deltaTime, maxSubSteps, fixedTimeStep):按照指定的时间步长 (deltaTime) 推进模拟。maxSubSteps 确定要执行的最大子步数,fixedTimeStep 指定模拟的固定时间步长。

  7. getGravity():获取当前作用于世界中物体的重力。

  8. getWorldInfo():获取包含世界配置设置的世界信息对象。

  9. getSoftBodyArray():获取当前世界中所有软体的数组。

  10. getRigidBodyArray():获取当前世界中所有刚体的数组。

  11. setInternalTickCallback(callback, worldUserInfo):设置在每个模拟步骤期间内部调用的回调函数。这可用于自定义处理或调试目的。

  12. setWorldUserInfo(worldUserInfo):设置与世界关联的用户定义信息。

  13. getWorldUserInfo():获取与世界关联的用户定义信息。

这些方法允许您操纵和控制动态世界,添加和移除对象,设置重力,推进模拟,并获取有关世界及其内容的信息。

demo 源码

<html lang="en">
	<head>
		<title>Ammo.js softbody cloth 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 cloth demo<br>Press Q or A to move the arm.</div> <!-- 显示提示信息 -->
		<div id="container"></div> <!-- 用于渲染 Three.js 场景 -->

		<script src="jsm/libs/ammo.wasm.js"></script> <!-- 引入 Ammo.js 的 WASM 文件 -->

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js", <!-- 导入 Three.js -->
					"three/addons/": "./jsm/" <!-- 导入 Three.js 的附加模块 -->
				}
			}
		</script>

		<script type="module">

			import * as THREE from 'three'; <!-- 导入 Three.js 库 -->

			import Stats from 'three/addons/libs/stats.module.js'; <!-- 导入性能统计模块 -->

			import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; <!-- 导入 OrbitControls 控制器 -->

			// Graphics variables
			let container, stats; <!-- 定义图形相关的变量 -->
			let camera, controls, scene, renderer; <!-- 定义相机、控制器、场景、渲染器 -->
			let textureLoader; <!-- 定义纹理加载器 -->
			const clock = new THREE.Clock(); <!-- 创建时钟对象 -->

			// Physics variables
			const gravityConstant = - 9.8; <!-- 定义重力常数 -->
			let physicsWorld; <!-- 定义物理世界对象 -->
			const rigidBodies = []; <!-- 用于存储刚体的数组 -->
			const margin = 0.05; <!-- 定义边距 -->
			let hinge; <!-- 定义铰链约束对象 -->
			let cloth; <!-- 定义布料对象 -->
			let transformAux1; <!-- 定义辅助变换对象 -->

			let armMovement = 0; <!-- 定义臂的移动量,默认为0 -->

			Ammo().then( function ( AmmoLib ) { <!-- 异步加载 Ammo.js 库并初始化 -->

				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( - 12, 7, 4 ); <!-- 设置相机位置 -->

				renderer = new THREE.WebGLRenderer( { antialias: true } ); <!-- 创建 WebGL 渲染器 -->
				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(); <!-- 创建纹理加载器 -->

				const ambientLight = new THREE.AmbientLight( 0xbbbbbb ); <!-- 创建环境光 -->
				scene.add( ambientLight ); <!-- 将环境光添加到场景中 -->

				const light = new THREE.DirectionalLight( 0xffffff, 3 ); <!-- 创建平行光 -->
				light.position.set( - 7, 10, 15 ); <!-- 设置光源位置 -->
				light.castShadow = true; <!-- 启用阴影 -->
				const d = 10;
				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; <!-- 设置阴影贴图高度 -->

				light.shadow.bias = - 0.003; <!-- 设置阴影偏移 -->
				scene.add( light ); <!-- 将平行光添加到场景中 -->

				stats = new Stats(); <!-- 创建性能统计器 -->
				stats.domElement.style.position = 'absolute'; <!-- 设置性能统计器位置样式 -->
				stats.domElement.style.top = '0px'; <!-- 设置性能统计器顶部样式 -->
				container.appendChild( stats.domElement ); <!-- 将性能统计器元素添加到容器中 -->

				window.addEventListener( 'resize', onWindowResize ); <!-- 监听窗口大小变化事件 -->

			}

			function initPhysics() {

				// Physics configuration

				const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); <!-- 创建碰撞配置对象 -->
				const dispatcher = new Ammo.btCollisionDispatcher

( collisionConfiguration ); <!-- 创建碰撞调度器对象 -->
				const broadphase = new Ammo.btDbvtBroadphase(); <!-- 创建宽相对象 -->
				const solver = new Ammo.btSequentialImpulseConstraintSolver(); <!-- 创建顺序脉冲约束求解器对象 -->
				const 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(); <!-- 创建辅助变换对象 -->

			}

			function createObjects() {

				const pos = new THREE.Vector3(); <!-- 创建位置向量 -->
				const quat = new THREE.Quaternion(); <!-- 创建四元数对象 -->

				// Ground
				pos.set( 0, - 0.5, 0 ); <!-- 设置地面位置 -->
				quat.set( 0, 0, 0, 1 ); <!-- 设置地面四元数 -->
				const 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.colorSpace = THREE.SRGBColorSpace; <!-- 设置纹理颜色空间 -->
					texture.wrapS = THREE.RepeatWrapping; <!-- 设置纹理水平重复方式 -->
					texture.wrapT = THREE.RepeatWrapping; <!-- 设置纹理垂直重复方式 -->
					texture.repeat.set( 40, 40 ); <!-- 设置纹理重复次数 -->
					ground.material.map = texture; <!-- 设置地面纹理 -->
					ground.material.needsUpdate = true;

				} );

				// Wall
				const brickMass = 0.5; <!-- 设置砖块质量 -->
				const brickLength = 1.2; <!-- 设置砖块长度 -->
				const brickDepth = 0.6; <!-- 设置砖块深度 -->
				const brickHeight = brickLength * 0.5; <!-- 计算砖块高度 -->
				const numBricksLength = 6; <!-- 设置砖块长度数量 -->
				const numBricksHeight = 8; <!-- 设置砖块高度数量 -->
				const z0 = - numBricksLength * brickLength * 0.5; <!-- 计算初始位置 -->
				pos.set( 0, brickHeight * 0.5, z0 ); <!-- 设置初始位置 -->
				quat.set( 0, 0, 0, 1 ); <!-- 设置初始四元数 -->
				for ( let j = 0; j < numBricksHeight; j ++ ) { <!-- 创建砖块墙 -->

					const oddRow = ( j % 2 ) == 1;

					pos.z = z0;

					if ( oddRow ) {

						pos.z -= 0.25 * brickLength;

					}

					const nRow = oddRow ? numBricksLength + 1 : numBricksLength;

					for ( let i = 0; i < nRow; i ++ ) {

						let brickLengthCurrent = brickLength;
						let brickMassCurrent = brickMass;

						if ( oddRow && ( i == 0 || i == nRow - 1 ) ) {

							brickLengthCurrent *= 0.5;
							brickMassCurrent *= 0.5;

						}

						const brick = createParalellepiped( brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, createMaterial() );
						brick.castShadow = true;
						brick.receiveShadow = true;

						if ( oddRow && ( i == 0 || i == nRow - 2 ) ) {

							pos.z += 0.75 * brickLength;

						} else {

							pos.z += brickLength;

						}

					}

					pos.y += brickHeight;

				}

				// The cloth
				// Cloth graphic object
				const clothWidth = 4; <!-- 设置布料宽度 -->
				const clothHeight = 3; <!-- 设置布料高度 -->
				const clothNumSegmentsZ = clothWidth * 5; <!-- 设置布料 Z 轴分段数 -->
				const clothNumSegmentsY = clothHeight * 5; <!-- 设置布料 Y 轴分段数 -->
				const clothPos = new THREE.Vector3( - 3, 3, 2 ); <!-- 设置布料位置 -->

				const clothGeometry = new THREE.PlaneGeometry( clothWidth, clothHeight, clothNumSegmentsZ, clothNumSegmentsY ); <!-- 创建布料几何体 -->
				clothGeometry.rotateY( Math.PI * 0.5 ); <!-- 旋转布料几何体 -->
				clothGeometry.translate( clothPos.x, clothPos.y + clothHeight * 0.5, clothPos.z - clothWidth * 0.5 ); <!-- 平移布料几何体 -->

				const clothMaterial = new THREE.MeshLambertMaterial( { color: 0xFFFFFF, side: THREE.DoubleSide } ); <!-- 创建布料材质 -->
				cloth = new THREE.Mesh( clothGeometry, clothMaterial ); <!-- 创建布料对象 -->
				cloth.castShadow = true; <!-- 启用布料阴影 -->
				cloth.receiveShadow = true; <!-- 启用布料接收阴影 -->
				scene.add( cloth ); <!-- 将布料添加到场景中 -->
				textureLoader.load( 'textures/grid.png', function ( texture ) { <!-- 加载纹理 -->

					texture.colorSpace = THREE.SRGBColorSpace; <!-- 设置纹理颜色空间 -->
					texture.wrapS = THREE.RepeatWrapping; <!-- 设置纹理水平重复方式 -->
					texture.wrapT = THREE.RepeatWrapping; <!-- 设置纹理垂直重复方式 -->
					texture.repeat.set( clothNumSegmentsZ, clothNumSegmentsY ); <!-- 设置纹理重复次数 -->
					cloth.material.map = texture; <!-- 设置布料纹理 -->
					cloth.material.needsUpdate = true;

				} );

				// Cloth physic object
				const softBodyHelpers = new Ammo.btSoftBodyHelpers

(); <!-- 创建软体助手对象 -->
				const clothCorner00 = new Ammo.btVector3( clothPos.x, clothPos.y + clothHeight, clothPos.z ); <!-- 设置布料顶点位置 -->
				const clothCorner01 = new Ammo.btVector3( clothPos.x, clothPos.y + clothHeight, clothPos.z - clothWidth ); <!-- 设置布料顶点位置 -->
				const clothCorner10 = new Ammo.btVector3( clothPos.x, clothPos.y, clothPos.z ); <!-- 设置布料顶点位置 -->
				const clothCorner11 = new Ammo.btVector3( clothPos.x, clothPos.y, clothPos.z - clothWidth ); <!-- 设置布料顶点位置 -->
				const clothSoftBody = softBodyHelpers.CreatePatch( physicsWorld.getWorldInfo(), clothCorner00, clothCorner01, clothCorner10, clothCorner11, clothNumSegmentsZ + 1, clothNumSegmentsY + 1, 0, true ); <!-- 创建布料软体对象 -->
				const sbConfig = clothSoftBody.get_m_cfg(); <!-- 获取软体配置对象 -->
				sbConfig.set_viterations( 10 ); <!-- 设置速度迭代次数 -->
				sbConfig.set_piterations( 10 ); <!-- 设置位置迭代次数 -->

				clothSoftBody.setTotalMass( 0.9, false ); <!-- 设置总质量 -->
				Ammo.castObject( clothSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin * 3 ); <!-- 设置碰撞形状边距 -->
				physicsWorld.addSoftBody( clothSoftBody, 1, - 1 ); <!-- 将布料软体添加到物理世界 -->
				cloth.userData.physicsBody = clothSoftBody; <!-- 将物理软体对象存储在布料对象的自定义数据中 -->
				// Disable deactivation
				clothSoftBody.setActivationState( 4 ); <!-- 禁用物理软体的休眠状态 -->

				// The base
				const armMass = 2; <!-- 设置臂的质量 -->
				const armLength = 3 + clothWidth; <!-- 计算臂的长度 -->
				const pylonHeight = clothPos.y + clothHeight; <!-- 计算支柱高度 -->
				const baseMaterial = new THREE.MeshPhongMaterial( { color: 0x606060 } ); <!-- 创建基座材质 -->
				pos.set( clothPos.x, 0.1, clothPos.z - armLength ); <!-- 设置基座位置 -->
				quat.set( 0, 0, 0, 1 ); <!-- 设置基座四元数 -->
				const base = createParalellepiped( 1, 0.2, 1, 0, pos, quat, baseMaterial ); <!-- 创建基座 -->
				base.castShadow = true; <!-- 启用基座阴影 -->
				base.receiveShadow = true; <!-- 启用基座接收阴影 -->
				pos.set( clothPos.x, 0.5 * pylonHeight, clothPos.z - armLength ); <!-- 设置支柱位置 -->
				const pylon = createParalellepiped( 0.4, pylonHeight, 0.4, 0, pos, quat, baseMaterial ); <!-- 创建支柱 -->
				pylon.castShadow = true; <!-- 启用支柱阴影 -->
				pylon.receiveShadow = true; <!-- 启用支柱接收阴影 -->
				pos.set( clothPos.x, pylonHeight + 0.2, clothPos.z - 0.5 * armLength ); <!-- 设置臂位置 -->
				const arm = createParalellepiped( 0.4, 0.4, armLength + 0.4, armMass, pos, quat, baseMaterial ); <!-- 创建臂 -->
				arm.castShadow = true; <!-- 启用臂阴影 -->
				arm.receiveShadow = true; <!-- 启用臂接收阴影 -->

				// Glue the cloth to the arm
				const influence = 0.5; <!-- 设置影响因子 -->
				clothSoftBody.appendAnchor( 0, arm.userData.physicsBody, false, influence ); <!-- 添加布料到臂的锚点 -->
				clothSoftBody.appendAnchor( clothNumSegmentsZ, arm.userData.physicsBody, false, influence ); <!-- 添加布料到臂的锚点 -->

				// Hinge constraint to move the arm
				const pivotA = new Ammo.btVector3( 0, pylonHeight * 0.5, 0 ); <!-- 设置铰链约束 A 点位置 -->
				const pivotB = new Ammo.btVector3( 0, - 0.2, - armLength * 0.5 ); <!-- 设置铰链约束 B 点位置 -->
				const axis = new Ammo.btVector3( 0, 1, 0 ); <!-- 设置铰链约束轴向 -->
				hinge = new Ammo.btHingeConstraint( pylon.userData.physicsBody, arm.userData.physicsBody, pivotA, pivotB, axis, axis, true ); <!-- 创建铰链约束对象 -->
				physicsWorld.addConstraint( hinge, true ); <!-- 将铰链约束添加到物理世界 -->

			}

			function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {

				const threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material ); <!-- 创建立方体网格对象 -->
				const 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

 ); <!-- 复制四元数 */

				const 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 ) ); <!-- 设置变换对象的旋转 */

				const motionState = new Ammo.btDefaultMotionState( transform ); <!-- 创建默认运动状态 */
				const localInertia = new Ammo.btVector3( 0, 0, 0 ); <!-- 创建局部惯性矩阵 */

				physicsShape.calculateLocalInertia( mass, localInertia ); <!-- 计算局部惯性矩阵 */
				const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia ); <!-- 创建刚体信息对象 */
				const body = new Ammo.btRigidBody( rbInfo ); <!-- 创建刚体对象 */

				threeObject.userData.physicsBody = body; <!-- 存储刚体对象到网格对象的自定义数据中 */

				scene.add( threeObject ); <!-- 将网格对象添加到场景中 */

				if ( mass > 0 ) {

					rigidBodies.push( threeObject ); <!-- 如果质量大于0,将网格对象添加到刚体数组中 */
					// Disable deactivation
					body.setActivationState( 4 ); <!-- 禁用休眠状态 */

				}

				physicsWorld.addRigidBody( body ); <!-- 将刚体添加到物理世界中 */

				return body;

			}

			function initInput() {

				window.addEventListener( 'keydown', function ( event ) { <!-- 监听按键事件 */

					switch ( event.keyCode ) { <!-- 根据按键码执行不同操作 */

						case 81: // Q <!-- 按 Q 键,左移臂 -->
							armMovement = 1;
							break;
						case 65: // A <!-- 按 A 键,右移臂 */
							armMovement = - 1;
							break;

					}

				} );

				window.addEventListener( 'keyup', function ( event ) { <!-- 监听松开按键事件 */

					switch ( event.keyCode ) { <!-- 根据按键码执行不同操作 */

						case 81: // Q <!-- 松开 Q 键,停止臂的移动 */
						case 65: // A <!-- 松开 A 键,停止臂的移动 */
							armMovement = 0;
							break;

					}

				} );

			}

			function animate() {

				requestAnimationFrame( animate ); <!-- 请求动画帧 */

				const deltaTime = clock.getDelta(); <!-- 获取两帧之间的时间差 */
				if ( deltaTime > 0 ) {

					updatePhysics( deltaTime ); <!-- 更新物理 */
					updateArmMovement(); <!-- 更新臂的移动 */

				}

				render(); <!-- 渲染场景 */
				stats.update(); <!-- 更新性能统计器 */

			}

			function updatePhysics( deltaTime ) {

				// Step world
				physicsWorld.stepSimulation( deltaTime, 10 ); <!-- 更新物理世界 */

				// Update cloth
				const softBody = cloth.userData.physicsBody; <!-- 获取布料软体对象 */
				const clothPositions = cloth.geometry.attributes.position.array; <!-- 获取布料几何体的位置数组 */
				const numVerts = clothPositions.length / 3; <!-- 获取布料顶点数量 */
				const nodes = softBody.get_m_nodes(); <!-- 获取布料软体的节点对象 */

				for ( let i = 0; i < numVerts; i ++ ) {

					const node = nodes.at( i ); <!-- 获取当前节点 */
					const nodePos = node.get_m_x(); <!-- 获取当前节点的位置 */
					clothPositions[ i * 3 ] = nodePos.x(); <!-- 更新布料顶点位置 */
					clothPositions[ i * 3 + 1 ] = nodePos.y(); <!-- 更新布料顶点位置 */
					clothPositions[ i * 3 + 2 ] = nodePos.z(); <!-- 更新布料顶点位置 */

				}

				cloth.geometry.attributes.position.needsUpdate = true; <!-- 标记位置属性需要更新 */

			}

			function updateArmMovement() {

				if ( armMovement !== 0 ) { <!-- 如果臂的移动量不为0 */

					const hingeConstraint = hinge; <!-- 获取铰链约束对象 */
					const angle = hingeConstraint.getHingeAngle(); <!-- 获取铰链约束角度 */
					const targetAngle = angle + armMovement * 0.05; <!-- 计算目标角度 */
					hingeConstraint.enableAngularMotor( true, 1.5, 50 ); <!-- 启用角动力器 */
					hingeConstraint.setMotorTarget( targetAngle, 0.8 ); <!-- 设置角动力器目标角度 */
					hingeConstraint.setMaxMotorImpulse( 0.8 ); <!-- 设置角动力器最大冲量 */

				} else { <!-- 如果臂的移动量为0 */

					hinge.enableAngularMotor( false, 0, 0 ); <!-- 禁用角动力器 */

				}

			}

			function render() {

				renderer.render( scene, camera ); <!-- 渲染场景 */

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight; <!-- 更新相机宽高比 */
				camera.updateProjectionMatrix(); <!-- 更新投影矩阵 */
				renderer.setSize( window.innerWidth, window.innerHeight ); <!-- 更新渲染器尺寸 */

			}

		</script>

	</body>
</html>

本内容来源于小豆包,想要更多内容请跳转小豆包 》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1586279.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多线程的学习

多线程编辑&#xff1a; 可以简单理解进程是一个软件 而线程就是一个软件中多个可以同时运行的功能 实现多线程的第一种方式&#xff1a;使用Thead类我们再自己创造一个类继承于这个类我们在对Thead方法进行重写&#xff0c;注意我们再重写的时候一定要加上Override这行 我犯下…

Linux下使用C语言实现高并发服务器

高并发服务器 这一个课程的笔记 相关文章 协议 Socket编程 高并发服务器实现 线程池 使用多进程并发服务器时要考虑以下几点&#xff1a; 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)系统内创建进程个数(与内存大小相关)进程创建过多是否降低整体…

《看漫画学C++》第12章 可大可小的“容器”——向量

在C编程的世界里&#xff0c;数组是一种基础且广泛使用的数据结构。然而&#xff0c;传统的静态数组在大小固定、管理不便等方面的局限性&#xff0c;常常让开发者感到束手束脚。幸运的是&#xff0c;C标准库中的vector类为我们提供了一种更加灵活、高效的动态数组解决方案。 …

Day96:云上攻防-云原生篇Docker安全系统内核版本漏洞CDK自动利用容器逃逸

目录 云原生-Docker安全-容器逃逸&系统内核漏洞 云原生-Docker安全-容器逃逸&docker版本漏洞 CVE-2019-5736 runC容器逃逸(需要管理员配合触发) CVE-2020-15257 containerd逃逸(启动容器时有前提参数) 云原生-Docker安全-容器逃逸&CDK自动化 知识点&#xff1…

2017NOIP普及组真题 4. 跳房子

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1417\ 核心思想 首先、本题中提到 “ 至少 要花多少金币改造机器人&#xff0c;能获得 至少 k分 ”。看到这样的话语&#xff0c;基本可以考虑要使用 二分答案。 那么&#xff0c;本题中…

缓存穿透、缓存雪崩、缓存击穿的区别

缓存三兄弟&#xff08;穿透、雪崩、击穿&#xff09; 缓存穿透 定义 查询一个redis和数据库都不存在的值&#xff0c;数据库不存在则不会将数据写入缓存&#xff0c;所以这些请求每次都达到数据库。 解决办法是缓存空对象&#xff0c;如果数据库不存在则将空对象存入缓存&…

政安晨:【深度学习神经网络基础】(三)—— 激活函数

目录 线性激活函数 阶跃激活函数 S型激活函数 双曲正切激活函数 修正线性单元 Softmax激活函数 偏置扮演什么角色&#xff1f; 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨…

[RK3399 Linux] 移植Linux 5.2.8内核详解

背景是在RK3399上面移植Rockchip官方提供的u-boot 2017.09 一、linux内核 1.1 源码下载 内核源码下载地址为:《https://www.kernel.org/》: 也可以到内核镜像网址下载https://mirrors.edge.kernel.org/pub/linux/kernel/,这里下载速度更快。 如果下载速度太慢,无法下载,…

理解 编译和链接

目录 1. 翻译环境和运行环境 2. 翻译环境 2.1 预处理&#xff08;预编译&#xff09; 2.2 编译 2.2.1 词法分析&#xff1a; 2.2.2 语法分析 2.2.3 语义分析 2.3 汇编 2.4 链接 3. 运行环境 1. 翻译环境和运行环境 在ANSI C的任何一种实现中&#xff0c;存在两个不同…

git修改本地提交历史邮箱地址

1、Git&#xff08;Git&#xff09; 2、修改Git本地提交历史中的邮箱地址 使用 git rebase 命令进行交互式重置。 具体步骤如下&#xff1a;&#xff08;https://git-scm.com/docs/git-rebase&#xff09; 1、查看提交历史&#xff1a; 使用 git log 命令列出提交历史&#x…

完整的项目源码!在线考试完整系统源码(可接私活)

最近有一些读者问我有没有完整的基于SpringbootVue的项目源码&#xff0c;今天给大家整理了一下&#xff0c;并且录制了搭建的教程&#xff0c;无偿分享给大家。 一、系统运行图 1、登陆页面 2、后台管理 3、全套环境资源 ​源码文件部分截图&#xff0c;带视频教程 ​ 在实际…

WPS的JS宏如何批量实现文字的超链接

表格中需要对文字进行超链接&#xff0c;每个链接指引到不同的地址。例如&#xff1a; 实现如下表格中&#xff0c;文件名称超级链接到对应的文件路径上&#xff0c;点击对应的文件名称&#xff0c;即可打开对应的文件。 序号文件名称文件路径1变更申请与处理表.xls文档\系统…

java八股——消息队列MQ

上一篇传送门&#xff1a;点我 目前只学习了RabbitMQ&#xff0c;后续学习了其他MQ后会继续补充。 MQ有了解过吗&#xff1f;说说什么是MQ&#xff1f; MQ是Message Queue的缩写&#xff0c;也就是消息队列的意思。它是一种应用程序对应用程序的通信方法&#xff0c;使得应用…

多线程回答的滚瓜烂熟,面试官问我虚线程了解吗?我说不太了解!

Java虚拟线程&#xff08;Virtual Threads&#xff09;标志着Java在并发编程领域的一次重大飞跃&#xff0c;特别是从Java 21版本开始。这项新技术的引入旨在克服传统多线程和线程池存在的挑战。 多线程和线程池 在Java中&#xff0c;传统的多线程编程依赖于Thread类或实现Ru…

Python(1):认识Python并且了解一些简单函数

文章目录 一、Python的优势及其使用场景二、Python环境的安装三、Python中的变量及其命名四、Python中的注释五、一些简单常见的函数和认识ASCII表六、Python导入模块的方式 一、Python的优势及其使用场景 优点&#xff1a; 开发效率高&#xff1a;Python具有非常强大的第三方…

Java——数组练习

目录 一.数组转字符串 二.数组拷贝 三.求数组中元素的平均值 四.查找数组中指定元素(顺序查找) 五.查找数组中指定元素(二分查找) 六.数组排序(冒泡排序) 七.数组逆序 一.数组转字符串 代码示例&#xff1a; import java.util.Arrays int[] arr {1,2,3,4,5,6}; String…

220 基于matlab的考虑直齿轮热弹耦合的动力学分析

基于matlab的考虑直齿轮热弹耦合的动力学分析&#xff0c;输入主动轮、从动轮各类参数&#xff0c;考虑润滑油温度、润滑油粘度系数等参数&#xff0c;输出接触压力、接触点速度、摩擦系数、对流传热系数等结果。程序已调通&#xff0c;可直接运行。 220直齿轮热弹耦合 接触压力…

出游旅行,不能错过的华为nova 12 Ultra4个AI小技巧

随着AI功能的快速普及&#xff0c;让我们的日常生活和工作借助这些工具变得越来越高效。今天就分享4个超级实用的华为nova 12 Ultra自带的AI小技巧&#xff1a;小艺智能魔法抠图、AI消除、图库搜索、小艺帮写&#xff0c;看看有哪些还是你不知道的&#xff1f; 1. 小艺智能…

Windows:Redis数据库图形化中文工具软件——RESP(3)

这个是用于连接redis数据库的软件工具&#xff0c;安装在windows上的图形化界面&#xff0c;并且支持中文&#xff0c;是在github上的一个项目 1.获取安装包 发布 lework/RedisDesktopManager-Windows (github.com)https://github.com/lework/RedisDesktopManager-Windows/rel…

紧急 CCF-C ICPR 2024摘要投稿日期延期至4月10日 速投速成就科研梦

会议之眼 快讯 第27届ICPR&#xff08;The International Conference on Pattern Recognition&#xff09;即国际模式识别会议将于 2024年 12月1日-5日在印度加尔各答的比斯瓦孟加拉会议中心举行&#xff01;ICPR是国际模式识别协会的旗舰会议&#xff0c;也是模式识别、计算机…