网站开发工具排行,安康免费做网站,网页设计跟网站建设的区别,汽车报价网址创建一个时钟对象:
const clock new THREE.Clock();这行代码创建了一个新的THREE.Clock对象#xff0c;它用于跟踪经过的时间。这在动画和物理模拟中很有用。 2. 创建场景:
const scene new THREE.Scene();这行代码创建了一个新的3D场景。所有的物体#xff08;如模型、灯…创建一个时钟对象:
const clock new THREE.Clock();这行代码创建了一个新的THREE.Clock对象它用于跟踪经过的时间。这在动画和物理模拟中很有用。 2. 创建场景:
const scene new THREE.Scene();这行代码创建了一个新的3D场景。所有的物体如模型、灯光等都会添加到这个场景中。 3. 设置场景的背景和雾:
scene.background new THREE.Color( 0x88ccee );
scene.fog new THREE.Fog( 0x88ccee, 0, 50 );这里设置了场景的背景色为浅蓝色并添加了一个雾效果使远处的物体看起来更模糊。 4. 创建相机:
const camera new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.rotation.order YXZ;这行代码创建了一个新的透视相机它定义了观察3D场景的角度和范围。70是视野角度window.innerWidth / window.innerHeight定义了相机的宽高比0.1和1000是相机的近裁剪面和远裁剪面。camera.rotation.order YXZ;设置了相机旋转的顺序。 5. 添加半球光:
const fillLight1 new THREE.HemisphereLight( 0x8dc1de, 0x00668d, 1.5 );
fillLight1.position.set( 2, 1, 1 );
scene.add( fillLight1 );这部分代码创建了一个半球光并将其添加到场景中。半球光模拟了一个柔和的环境光由一个天空色和一个地面色组成。 6. 添加方向光:
const directionalLight new THREE.DirectionalLight( 0xffffff, 2.5 );
// ... (设置方向光的各种属性)
scene.add( directionalLight );这部分代码创建了一个方向光并设置了其颜色、强度、位置、阴影属性等。方向光是从一个特定方向照射的光通常用于模拟太阳光。 7. 创建渲染器:
const container document.getElementById( container );const renderer new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled true;
renderer.shadowMap.type THREE.VSMShadowMap;
renderer.toneMapping THREE.ACESFilmicToneMapping;
container.appendChild( renderer.domElement );这部分代码首先获取页面上的container元素然后创建一个WebGL渲染器。渲染器用于在网页上显示3D场景。这里还设置了渲染器的抗锯齿、像素比、尺寸、阴影映射类型和色调映射。 8. 添加性能监控:
const stats new Stats();
stats.domElement.style.position absolute;
stats.domElement.style.top 0px;
container.appendChild( stats.domElement );这部分代码添加了一个性能监控器用于显示渲染器的帧率和其他相关信息。
常量定义:
const GRAVITY 30;
const NUM_SPHERES 100;
const SPHERE_RADIUS 0.2;
const STEPS_PER_FRAME 5;这里定义了一些常量用于控制球体的数量、半径、重力加速度和每帧的模拟步骤数。
创建球体几何体和材质:
const sphereGeometry new THREE.IcosahedronGeometry( SPHERE_RADIUS, 5 );
const sphereMaterial new THREE.MeshLambertMaterial( { color: 0xdede8d } );使用THREE.IcosahedronGeometry创建了一个二十面体几何体用于作为球体的基础形状。然后定义了一个Lambert材质这种材质适用于模拟非直接光照的情况并设置了球体的颜色。
创建和添加球体到场景中:
const spheres [];
let sphereIdx 0;for ( let i 0; i NUM_SPHERES; i ) {// 创建球体网格const sphere new THREE.Mesh( sphereGeometry, sphereMaterial );sphere.castShadow true;sphere.receiveShadow true;// 将球体添加到场景中scene.add( sphere );// 将球体和其他相关数据添加到数组中spheres.push({mesh: sphere,collider: new THREE.Sphere( new THREE.Vector3( 0, - 100, 0 ), SPHERE_RADIUS ),velocity: new THREE.Vector3()});
}这段代码循环创建了指定数量的球体并将它们添加到场景中。每个球体都有一个与之关联的碰撞体一个Three.js的球体用于物理模拟以及一个速度向量。
创建八叉树:
const worldOctree new Octree();八叉树Octree是一种用于3D空间分割的数据结构常用于碰撞检测和空间索引。这里创建了一个新的八叉树实例但代码中没有显示其如何使用。
创建玩家碰撞体和速度向量:
const playerCollider new Capsule( new THREE.Vector3( 0, 0.35, 0 ), new THREE.Vector3( 0, 1, 0 ), 0.35 );
const playerVelocity new THREE.Vector3();
const playerDirection new THREE.Vector3();这里创建了玩家的碰撞体一个胶囊形状速度向量和方向向量。注意Capsule可能是一个自定义类不是Three.js库的一部分。
初始化其他变量:
let playerOnFloor false;
let mouseTime 0;
const keyStates {};
const vector1 new THREE.Vector3();
const vector2 new THREE.Vector3();
const vector3 new THREE.Vector3();这些变量可能用于控制玩家的状态如是否在地板上、鼠标交互、键盘输入以及临时存储向量计算的结果。
键盘按键监听:
document.addEventListener( keydown, ( event ) {keyStates[ event.code ] true;
} );document.addEventListener( keyup, ( event ) {keyStates[ event.code ] false;
} );这里为文档对象整个页面添加了两个事件监听器分别用于处理键盘的keydown和keyup事件。keydown事件在用户按下键时触发keyup则在键被释放时触发。event.code提供了被按下或释放的键的标识符。keyStates是一个对象用于存储每个键的当前状态按下或释放。
鼠标按下监听:
container.addEventListener( mousedown, () {document.body.requestPointerLock();mouseTime performance.now();
} );当在container元素上按下鼠标时这段代码请求将指针锁定到页面上这样当鼠标移动时鼠标指针将不再显示而页面的其他部分也不会接收鼠标事件。mouseTime变量存储了鼠标按下的时间可能是为了计算后续鼠标移动的持续时间或速度。
鼠标释放监听:
document.addEventListener( mouseup, () {if ( document.pointerLockElement ! null ) throwBall();
} );当鼠标按钮释放时如果指针已经被锁定即document.pointerLockElement不为null则调用throwBall函数。从这段代码看不出throwBall函数的实现细节但可以猜测它的作用是抛出某种对象可能是一个球体。
鼠标移动监听:
document.body.addEventListener( mousemove, ( event ) {if ( document.pointerLockElement document.body ) {camera.rotation.y - event.movementX / 500;camera.rotation.x - event.movementY / 500;}
} );当鼠标在文档体上移动时如果指针被锁定到文档体上这段代码会更新相机的旋转。event.movementX和event.movementY分别表示鼠标在X和Y轴上的移动量。通过除以500代码将鼠标的移动量转换为较小的相机旋转量从而实现平滑的相机控制。
窗口大小调整监听:
window.addEventListener( resize, onWindowResize );function onWindowResize() {camera.aspect window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );
}这段代码监听窗口的大小调整事件。当窗口大小变化时它会调用onWindowResize函数该函数会更新相机的纵横比并更新其投影矩阵。此外它还调用renderer.setSize来确保渲染器的大小与窗口的大小相匹配。
这段代码是用于处理三维场景中的球体和玩家或相机的交互和移动。具体来说它定义了三个函数throwBall、playerCollisions和updatePlayer。以下是对这些函数的详细解析
throwBall 函数
这个函数用于投掷一个球体可能是一个游戏对象如球。 获取球体: const sphere spheres[ sphereIdx ];从spheres数组中获取一个球体sphereIdx是当前的球体索引。 设置投掷方向: camera.getWorldDirection( playerDirection );
sphere.collider.center.copy( playerCollider.end ).addScaledVector( playerDirection, playerCollider.radius * 1.5 );首先获取相机的世界方向玩家面向的方向然后将球体的位置设置为玩家碰撞体的末端并沿着玩家方向移动一定距离玩家碰撞体半径的1.5倍。 计算投掷力度: const impulse 15 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) );这里计算投掷的力度或冲量。力度基于鼠标按下的时间mouseTime和当前时间performance.now()的差值来计算。时间差越大力度越大。 设置球体的速度和方向: sphere.velocity.copy( playerDirection ).multiplyScalar( impulse );
sphere.velocity.addScaledVector( playerVelocity, 2 );球体的速度被设置为玩家方向并乘以计算出的力度。然后再基于玩家的速度进行微调。 更新球体索引: sphereIdx ( sphereIdx 1 ) % spheres.length;更新球体索引以便下一次投掷时可以使用下一个球体。
playerCollisions 函数
这个函数处理玩家或相机的碰撞体与场景中的其他物体之间的碰撞。 检测碰撞: const result worldOctree.capsuleIntersect( playerCollider );使用worldOctree可能是一个空间划分的数据结构用于加速碰撞检测检测玩家的碰撞体是否与任何物体相交。 判断是否在地面上: playerOnFloor false;
if ( result ) {playerOnFloor result.normal.y 0;// ...
}如果发生碰撞检查碰撞的法线result.normal的y分量是否大于0来判断玩家是否在地面上。 处理非地面碰撞: if ( ! playerOnFloor ) {playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity) );
}如果玩家不在地面上更新玩家的速度以反映碰撞的影响。 移动碰撞体以处理穿透: playerCollider.translate( result.normal.multiplyScalar( result.depth) );移动玩家的碰撞体以处理任何可能发生的穿透。
updatePlayer 函数
这个函数用于更新玩家的位置和速度。 计算阻尼: let damping Math.exp( - 4 * deltaTime ) - 1;阻尼用于模拟玩家速度的逐渐减小例如空气阻力或摩擦。 处理玩家在空中的移动: if ( ! playerOnFloor ) {playerVelocity.y - GRAVITY * deltaTime;damping * 0.1;
}如果玩家不在地面上更新玩家的y速度以模拟重力并减小阻尼。 更新玩家速度: playerVelocity.addScaledVector( playerVelocity, damping );根据阻尼更新玩家的速度。 移动玩家碰撞体: const deltaPosition playerVelocity.clone().multiplyScalar( deltaTime );
playerCollider.translate( deltaPosition );计算玩家应该移动的距离并更新玩家的碰撞体位置。 检测碰撞并更新相机位置: playerCollisions();
camera.position.copy( playerCollider.end );调用playerCollisions函数来处理可能的碰撞然后将相机的位置设置为玩家碰撞体的末端。
playerSphereCollision 函数
这个函数处理玩家球体与另一个球体的碰撞。 计算玩家碰撞体的中心: const center vector1.addVectors( playerCollider.start, playerCollider.end ).multiplyScalar( 0.5 );玩家碰撞体可能是一个胶囊体capsule这里计算其中心点。 获取球体的中心: const sphere_center sphere.collider.center;计算碰撞半径: const r playerCollider.radius sphere.collider.radius;
const r2 r * r;这是玩家碰撞体和球体碰撞体半径之和以及它的平方。 碰撞检测: for ( const point of [ playerCollider.start, playerCollider.end, center ] ) {// ...
}这里将玩家碰撞体近似为三个点开始点、结束点和中心点进行碰撞检测。对于每个点它计算到球体中心的距离平方并检查这个距离是否小于两个碰撞体半径之和的平方。 碰撞响应: 如果发生碰撞代码计算碰撞法线normal然后根据法线和两个物体的速度来计算碰撞后的新速度。同时调整球体的位置以解决任何可能的穿透。
spheresCollisions 函数
这个函数处理球体之间的碰撞。 双层循环遍历球体: for ( let i 0, length spheres.length; i length; i ) {// ...for ( let j i 1; j length; j ) {// ...}
}使用两个嵌套的循环来遍历所有球体对。这样可以确保每个球体对只被检查一次。 计算球体间的距离平方: const d2 s1.collider.center.distanceToSquared( s2.collider.center );检查碰撞: 如果两个球体中心之间的距离小于它们半径之和则发生碰撞。 碰撞响应: 类似于playerSphereCollision函数根据碰撞法线和两个球体的速度来计算新的速度并调整球体的位置。
这段代码实现了三维空间中的碰撞检测和响应。它使用了简化的碰撞检测将玩家碰撞体近似为三个点和基于物理的碰撞响应改变物体的速度和位置。这种碰撞处理在实时渲染和物理模拟中非常常见特别是在游戏开发中。
这段代码主要实现了三维空间中多个球体的更新和碰撞处理。以下是对代码的详细解析
updateSpheres 函数
这个函数负责更新所有球体的位置和速度并处理它们与场景中其他物体的碰撞。 更新球体位置: sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );根据球体的速度和经过的时间deltaTime来更新球体的位置。 检查球体与场景中的碰撞: const result worldOctree.sphereIntersect( sphere.collider );使用worldOctree可能是一个用于空间划分的数据结构来检查球体是否与场景中的其他物体发生碰撞。 处理碰撞: 如果发生碰撞result为真则调整球体的速度和位置以响应碰撞。这里使用了基于物理的碰撞响应通过沿着碰撞法线result.normal反向推动球体来解决穿透问题。 处理重力: 如果球体没有碰撞到任何东西result为假则球体的y轴速度竖直方向会受到重力的影响而减小。 速度阻尼: const damping Math.exp( - 1.5 * deltaTime ) - 1;
sphere.velocity.addScaledVector( sphere.velocity, damping );对球体的速度应用阻尼使其逐渐减小。这可以模拟空气阻力或其他形式的能量损失。 检查与玩家的碰撞: playerSphereCollision( sphere );调用playerSphereCollision函数来检查球体是否与玩家发生碰撞并相应地更新它们的速度和位置。 处理球体间的碰撞: spheresCollisions();调用spheresCollisions函数来处理球体之间的碰撞。 更新球体的视觉表示: sphere.mesh.position.copy( sphere.collider.center );将球体的视觉表示mesh的位置更新为其碰撞体的中心位置以确保视觉和物理状态一致。
getForwardVector 函数
这个函数返回相机或玩家的前方向量。 获取相机的世界方向: camera.getWorldDirection( playerDirection );获取相机在世界坐标系中的方向。 调整方向并归一化: playerDirection.y 0;
playerDirection.normalize();将方向向量的y分量设为0即忽略竖直方向然后归一化向量。
getSideVector 函数
这个函数返回相机或玩家的侧向量。 获取相机的世界方向: camera.getWorldDirection( playerDirection );同样获取相机在世界坐标系中的方向。 计算侧向量: playerDirection.cross( camera.up );通过计算相机方向向量与上方向向量的叉积来得到侧向量。 玩家控制controls 函数: 根据deltaTime上一次渲染到当前渲染的时间差和玩家是否在地面playerOnFloor来设定玩家的移动速度speedDelta。根据按键状态keyStates来更新玩家的速度playerVelocity。具体来说如果按下’W’键玩家会向前移动如果按下’S’键玩家会向后移动如果按下’A’键玩家会向左移动如果按下’D’键玩家会向右移动。如果玩家在地面并且按下空格键玩家的垂直速度y轴会设置为15实现跳跃功能。 模型加载和场景设置: 使用GLTFLoader来加载一个名为collision-world.glb的3D模型。将加载的模型gltf.scene添加到场景scene中。创建一个worldOctree数据结构来存储场景的碰撞信息并通过gltf.scene来初始化它。遍历模型的每一个子节点如果它是网格isMesh则设置它的阴影投射和接收属性并优化其贴图的采样通过设置anisotropy。创建一个OctreeHelper对象这是一个用于可视化worldOctree的辅助对象但默认是隐藏的。使用GUI库来创建一个界面元素用户可以通过这个界面来切换OctreeHelper的可见性。 玩家位置重置teleportPlayerIfOob 函数: 如果相机代表玩家的位置在y轴上的值小于或等于-25可能表示玩家掉出了世界边界则将玩家的碰撞体playerCollider的位置和大小重置并将相机位置设置为新的碰撞体位置同时将相机的旋转重置。这实际上是将玩家“传送”回世界中的某个安全位置。
整体上这段代码为3D场景中的玩家控制、模型加载和场景设置以及玩家位置重置提供了实现。其中controls函数负责根据玩家的输入更新玩家的速度而模型加载和场景设置部分则通过加载一个3D模型并设置其相关属性来初始化场景。最后teleportPlayerIfOob函数提供了一个机制来确保玩家不会掉出世界的边界。
animate 函数是3D渲染应用中常见的动画循环函数用于在每个动画帧中更新场景的状态并渲染场景。以下是对这段代码的详细解析
代码功能概述 计算时间差deltaTime: 通过 clock.getDelta() 获取从上一次动画帧到现在的时间差以秒为单位并限制其最大值为0.05秒。之后将这个时间差除以 STEPS_PER_FRAME得到一个子步长的时间差。 碰撞检测的子步处理: 通过一个循环执行 STEPS_PER_FRAME 次更新操作每次循环中都会调用玩家控制和更新函数以此来降低物体快速移动导致碰撞检测失败的风险。 更新函数: controls( deltaTime ): 根据当前的时间差 deltaTime 更新玩家的速度和位置。updatePlayer( deltaTime ): 更新玩家的状态或位置可能还包括与环境的交互。updateSpheres( deltaTime ): 更新场景中的球体或其他物体的状态。teleportPlayerIfOob(): 如果玩家超出边界则将其传送回安全位置。 渲染场景: 使用 renderer.render( scene, camera ) 渲染当前的场景和相机视图。 更新性能统计: stats.update() 可能用于更新一些性能统计信息比如帧率等。 请求下一帧: 使用 requestAnimationFrame( animate ) 请求浏览器的下一帧动画并将 animate 函数作为回调函数从而形成一个连续的动画循环。
细节分析 deltaTime 的计算考虑了 STEPS_PER_FRAME这通常用于减少快速移动物体错过碰撞检测的机会。通过将总的 deltaTime 分割成多个子步每个子步中更新物体的位置可以提高碰撞检测的准确性。 controls 函数负责根据用户的输入和当前的时间差来更新玩家的移动状态。 updatePlayer 和 updateSpheres 函数分别更新玩家和场景中球体的状态。这些更新可能包括位置、速度、动画状态等。 teleportPlayerIfOob 函数用于处理玩家超出边界的情况确保玩家不会离开游戏世界。 renderer.render( scene, camera ) 是渲染命令它使用当前的场景和相机状态来生成图像。 stats.update() 可能是用于更新性能统计信息的例如帧率、渲染时间等这对于调试和优化非常有用。 requestAnimationFrame( animate ) 确保 animate 函数在每个浏览器动画帧被调用从而保持动画的流畅性和同步性。
总结
这段代码定义了一个 animate 函数它是3D应用中典型的动画循环。它首先计算时间差并在多个子步中更新玩家和场景中物体的状态然后进行渲染和性能统计的更新。最后它使用 requestAnimationFrame 来请求下一帧的动画从而形成一个连续的动画循环。这个循环确保了游戏或应用的流畅运行和实时交互。 全部源码 !DOCTYPE html
html langenheadtitlethree.js - misc - octree collisions/titlemeta charsetutf-8 /meta nameviewport contentwidthdevice-width, user-scalableno, minimum-scale1.0, maximum-scale1.0link typetext/css relstylesheet hrefmain.css/headbodydiv idinfoOctree threejs demo - basic collisions with static triangle meshbr /MOUSE to look around and to throw ballsbr/WASD to move and SPACE to jump/divdiv idcontainer/divscript typeimportmap{imports: {three: ../build/three.module.js,three/addons/: ./jsm/}}/scriptscript typemoduleimport * as THREE from three;import Stats from three/addons/libs/stats.module.js;import { GLTFLoader } from three/addons/loaders/GLTFLoader.js;import { Octree } from three/addons/math/Octree.js;import { OctreeHelper } from three/addons/helpers/OctreeHelper.js;import { Capsule } from three/addons/math/Capsule.js;import { GUI } from three/addons/libs/lil-gui.module.min.js;const clock new THREE.Clock();const scene new THREE.Scene();scene.background new THREE.Color( 0x88ccee );scene.fog new THREE.Fog( 0x88ccee, 0, 50 );const camera new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000 );camera.rotation.order YXZ;const fillLight1 new THREE.HemisphereLight( 0x8dc1de, 0x00668d, 1.5 );fillLight1.position.set( 2, 1, 1 );scene.add( fillLight1 );const directionalLight new THREE.DirectionalLight( 0xffffff, 2.5 );directionalLight.position.set( - 5, 25, - 1 );directionalLight.castShadow true;directionalLight.shadow.camera.near 0.01;directionalLight.shadow.camera.far 500;directionalLight.shadow.camera.right 30;directionalLight.shadow.camera.left - 30;directionalLight.shadow.camera.top 30;directionalLight.shadow.camera.bottom - 30;directionalLight.shadow.mapSize.width 1024;directionalLight.shadow.mapSize.height 1024;directionalLight.shadow.radius 4;directionalLight.shadow.bias - 0.00006;scene.add( directionalLight );const container document.getElementById( container );const renderer new THREE.WebGLRenderer( { antialias: true } );renderer.setPixelRatio( window.devicePixelRatio );renderer.setSize( window.innerWidth, window.innerHeight );renderer.shadowMap.enabled true;renderer.shadowMap.type THREE.VSMShadowMap;renderer.toneMapping THREE.ACESFilmicToneMapping;container.appendChild( renderer.domElement );const stats new Stats();stats.domElement.style.position absolute;stats.domElement.style.top 0px;container.appendChild( stats.domElement );const GRAVITY 30;const NUM_SPHERES 100;const SPHERE_RADIUS 0.2;const STEPS_PER_FRAME 5;const sphereGeometry new THREE.IcosahedronGeometry( SPHERE_RADIUS, 5 );const sphereMaterial new THREE.MeshLambertMaterial( { color: 0xdede8d } );const spheres [];let sphereIdx 0;for ( let i 0; i NUM_SPHERES; i ) {const sphere new THREE.Mesh( sphereGeometry, sphereMaterial );sphere.castShadow true;sphere.receiveShadow true;scene.add( sphere );spheres.push( {mesh: sphere,collider: new THREE.Sphere( new THREE.Vector3( 0, - 100, 0 ), SPHERE_RADIUS ),velocity: new THREE.Vector3()} );}const worldOctree new Octree();const playerCollider new Capsule( new THREE.Vector3( 0, 0.35, 0 ), new THREE.Vector3( 0, 1, 0 ), 0.35 );const playerVelocity new THREE.Vector3();const playerDirection new THREE.Vector3();let playerOnFloor false;let mouseTime 0;const keyStates {};const vector1 new THREE.Vector3();const vector2 new THREE.Vector3();const vector3 new THREE.Vector3();document.addEventListener( keydown, ( event ) {keyStates[ event.code ] true;} );document.addEventListener( keyup, ( event ) {keyStates[ event.code ] false;} );container.addEventListener( mousedown, () {document.body.requestPointerLock();mouseTime performance.now();} );document.addEventListener( mouseup, () {if ( document.pointerLockElement ! null ) throwBall();} );document.body.addEventListener( mousemove, ( event ) {if ( document.pointerLockElement document.body ) {camera.rotation.y - event.movementX / 500;camera.rotation.x - event.movementY / 500;}} );window.addEventListener( resize, onWindowResize );function onWindowResize() {camera.aspect window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );}function throwBall() {const sphere spheres[ sphereIdx ];camera.getWorldDirection( playerDirection );sphere.collider.center.copy( playerCollider.end ).addScaledVector( playerDirection, playerCollider.radius * 1.5 );// throw the ball with more force if we hold the button longer, and if we move forwardconst impulse 15 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) );sphere.velocity.copy( playerDirection ).multiplyScalar( impulse );sphere.velocity.addScaledVector( playerVelocity, 2 );sphereIdx ( sphereIdx 1 ) % spheres.length;}function playerCollisions() {const result worldOctree.capsuleIntersect( playerCollider );playerOnFloor false;if ( result ) {playerOnFloor result.normal.y 0;if ( ! playerOnFloor ) {playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) );}playerCollider.translate( result.normal.multiplyScalar( result.depth ) );}}function updatePlayer( deltaTime ) {let damping Math.exp( - 4 * deltaTime ) - 1;if ( ! playerOnFloor ) {playerVelocity.y - GRAVITY * deltaTime;// small air resistancedamping * 0.1;}playerVelocity.addScaledVector( playerVelocity, damping );const deltaPosition playerVelocity.clone().multiplyScalar( deltaTime );playerCollider.translate( deltaPosition );playerCollisions();camera.position.copy( playerCollider.end );}function playerSphereCollision( sphere ) {const center vector1.addVectors( playerCollider.start, playerCollider.end ).multiplyScalar( 0.5 );const sphere_center sphere.collider.center;const r playerCollider.radius sphere.collider.radius;const r2 r * r;// approximation: player 3 spheresfor ( const point of [ playerCollider.start, playerCollider.end, center ] ) {const d2 point.distanceToSquared( sphere_center );if ( d2 r2 ) {const normal vector1.subVectors( point, sphere_center ).normalize();const v1 vector2.copy( normal ).multiplyScalar( normal.dot( playerVelocity ) );const v2 vector3.copy( normal ).multiplyScalar( normal.dot( sphere.velocity ) );playerVelocity.add( v2 ).sub( v1 );sphere.velocity.add( v1 ).sub( v2 );const d ( r - Math.sqrt( d2 ) ) / 2;sphere_center.addScaledVector( normal, - d );}}}function spheresCollisions() {for ( let i 0, length spheres.length; i length; i ) {const s1 spheres[ i ];for ( let j i 1; j length; j ) {const s2 spheres[ j ];const d2 s1.collider.center.distanceToSquared( s2.collider.center );const r s1.collider.radius s2.collider.radius;const r2 r * r;if ( d2 r2 ) {const normal vector1.subVectors( s1.collider.center, s2.collider.center ).normalize();const v1 vector2.copy( normal ).multiplyScalar( normal.dot( s1.velocity ) );const v2 vector3.copy( normal ).multiplyScalar( normal.dot( s2.velocity ) );s1.velocity.add( v2 ).sub( v1 );s2.velocity.add( v1 ).sub( v2 );const d ( r - Math.sqrt( d2 ) ) / 2;s1.collider.center.addScaledVector( normal, d );s2.collider.center.addScaledVector( normal, - d );}}}}function updateSpheres( deltaTime ) {spheres.forEach( sphere {sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );const result worldOctree.sphereIntersect( sphere.collider );if ( result ) {sphere.velocity.addScaledVector( result.normal, - result.normal.dot( sphere.velocity ) * 1.5 );sphere.collider.center.add( result.normal.multiplyScalar( result.depth ) );} else {sphere.velocity.y - GRAVITY * deltaTime;}const damping Math.exp( - 1.5 * deltaTime ) - 1;sphere.velocity.addScaledVector( sphere.velocity, damping );playerSphereCollision( sphere );} );spheresCollisions();for ( const sphere of spheres ) {sphere.mesh.position.copy( sphere.collider.center );}}function getForwardVector() {camera.getWorldDirection( playerDirection );playerDirection.y 0;playerDirection.normalize();return playerDirection;}function getSideVector() {camera.getWorldDirection( playerDirection );playerDirection.y 0;playerDirection.normalize();playerDirection.cross( camera.up );return playerDirection;}function controls( deltaTime ) {// gives a bit of air controlconst speedDelta deltaTime * ( playerOnFloor ? 25 : 8 );if ( keyStates[ KeyW ] ) {playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) );}if ( keyStates[ KeyS ] ) {playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) );}if ( keyStates[ KeyA ] ) {playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) );}if ( keyStates[ KeyD ] ) {playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) );}if ( playerOnFloor ) {if ( keyStates[ Space ] ) {playerVelocity.y 15;}}}const loader new GLTFLoader().setPath( ./models/gltf/ );loader.load( collision-world.glb, ( gltf ) {scene.add( gltf.scene );worldOctree.fromGraphNode( gltf.scene );gltf.scene.traverse( child {if ( child.isMesh ) {child.castShadow true;child.receiveShadow true;if ( child.material.map ) {child.material.map.anisotropy 4;}}} );const helper new OctreeHelper( worldOctree );helper.visible false;scene.add( helper );const gui new GUI( { width: 200 } );gui.add( { debug: false }, debug ).onChange( function ( value ) {helper.visible value;} );animate();} );function teleportPlayerIfOob() {if ( camera.position.y - 25 ) {playerCollider.start.set( 0, 0.35, 0 );playerCollider.end.set( 0, 1, 0 );playerCollider.radius 0.35;camera.position.copy( playerCollider.end );camera.rotation.set( 0, 0, 0 );}}function animate() {const deltaTime Math.min( 0.05, clock.getDelta() ) / STEPS_PER_FRAME;// we look for collisions in substeps to mitigate the risk of// an object traversing another too quickly for detection.for ( let i 0; i STEPS_PER_FRAME; i ) {controls( deltaTime );updatePlayer( deltaTime );updateSpheres( deltaTime );teleportPlayerIfOob();}renderer.render( scene, camera );stats.update();requestAnimationFrame( animate );}/script/body
/html
本内容来源于小豆包想要更多内容请跳转小豆包 》