threejs(9)-应用物理引擎设置物体相互作用

2023-10-31 05:04

本文主要是介绍threejs(9)-应用物理引擎设置物体相互作用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、认识物理引擎与cannon安装

Cannon.js是一个开源的3D物理引擎,用于在WebGL中创建3D物理模拟。它提供了一个灵活的API,可以应用于许多WebGL场景。

我们需要了解一些基本概念,包括物理实体、碰撞、物理变换、物理世界和物理实体之间的约束等。

在这里插入图片描述

官网:https://pmndrs.github.io/cannon-es/

npm: https://www.npmjs.com/package/cannon-es

npm i --save cannon-es

cannon-es是一个轻量级且易于使用的网络 3D 物理引擎。它的灵感来自three.js的简单 API,并基于ammo.js和Bullet 物理引擎。
首先要设置的是我们的物理世界,它将容纳我们所有的物理实体并推动模拟向前发展。
让我们用地球引力创造一个世界。请注意,cannon.js 使用SI 单位(米、千克、秒等)。

const world = new CANNON.World({gravity: new CANNON.Vec3(0, -9.82, 0), // m/s²
})

为了推进模拟,我们必须调用world.fixedStep()每一帧。作为第一个参数,我们可以传递我们希望模拟运行的固定时间步长,默认值是1 / 60meaning 60fps。 world.fixedStep()跟踪上次调用它以独立于帧率保持相同速度的模拟,因为requestAnimationFrame调用可能因不同设备而异,或者如果存在性能问题。在此处阅读有关固定模拟步进的更多信息。

function animate() {requestAnimationFrame(animate)// Run the simulation independently of framerate every 1 / 60 msworld.fixedStep()
}
// Start the simulation loop
animate()

如果你想打发自上次通话以来的时间(dt在游戏世界中),你可以使用更高级的world.step().
查看高级世界步进示例

const timeStep = 1 / 60 // seconds
let lastCallTime
function animate() {requestAnimationFrame(animate)const time = performance.now() / 1000 // secondsif (!lastCallTime) {world.step(timeStep)} else {const dt = time - lastCallTimeworld.step(timeStep, dt)}lastCallTime = time
}
// Start the simulation loop
animate()

刚体是将在世界中模拟的实体,它们可以是简单的形状,如Sphere、Box、Plane、Cylinder,或更复杂的形状,如ConvexPolyhedron、Particle、Heightfield、Trimesh。
让我们创建一个基本的球体。

const radius = 1 // m
const sphereBody = new CANNON.Body({mass: 5, // kgshape: new CANNON.Sphere(radius),
})
sphereBody.position.set(0, 10, 0) // m
world.addBody(sphereBody)

如您所见,我们指定了一个质量属性,质量定义了身体在受力影响时的行为。
当物体具有质量并受到力的影响时,它们被称为动态物体。还有不受力影响但可以具有速度并四处移动的运动学实体。第三种类型的物体是静态物体,它们只能在世界中定位并且不受力或速度的影响。
如果将质量为 0 的物体传递给物体,则该物体会自动标记为静态物体。您还可以在主体选项中明确主体类型。例如,让我们创建一个静态地面。

const groundBody = new CANNON.Body({type: CANNON.Body.STATIC,shape: new CANNON.Plane(),
})
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) // make it face up
world.addBody(groundBody)

以下是所有以前的片段组合在一个完整的示例中。

import * as CANNON from 'cannon-es'// Setup our physics world
const world = new CANNON.World({gravity: new CANNON.Vec3(0, -9.82, 0), // m/s²
})// Create a sphere body
const radius = 1 // m
const sphereBody = new CANNON.Body({mass: 5, // kgshape: new CANNON.Sphere(radius),
})
sphereBody.position.set(0, 10, 0) // m
world.addBody(sphereBody)// Create a static plane for the ground
const groundBody = new CANNON.Body({type: CANNON.Body.STATIC, // can also be achieved by setting the mass to 0shape: new CANNON.Plane(),
})
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) // make it face up
world.addBody(groundBody)// Start the simulation loop
function animate() {requestAnimationFrame(animate)world.fixedStep()// the sphere y position shows the sphere fallingconsole.log(`Sphere y position: ${sphereBody.position.y}`)
}
animate()

请注意,cannon 不负责将任何内容渲染到屏幕上,它只是计算模拟的数学。要在屏幕上实际显示某些内容,您必须使用渲染库,例如three.js。让我们看看如何实现这一目标。
首先,你必须在 three.js 中创建 body 的对应实体。例如,这里是如何在 three.js 中创建一个球体。

const radius = 1 // m
const geometry = new THREE.SphereGeometry(radius)
const material = new THREE.MeshNormalMaterial()
const sphereMesh = new THREE.Mesh(geometry, material)
scene.add(sphereMesh)

然后,您必须将 three.js 网格与 cannon.js 主体连接起来。要做到这一点,您需要在走过世界后每帧将位置和旋转数据从身体复制到网格。

function animate() {requestAnimationFrame(animate)// world stepping...sphereMesh.position.copy(sphereBody.position)sphereMesh.quaternion.copy(sphereBody.quaternion)// three.js render...
}
animate()

二、使用物理引擎关联Threejs物体

在这里插入图片描述
一个物理引擎,一边渲染引擎,渲染引擎从物理引擎中获取数据进行渲染
在这里插入图片描述

const world = new CANNON.World(); // 创建物理世界
world.gravity.set(0, -9.8, 0);  // 设置重力方向// 创建物理小球形状
const sphereShape = new CANNON.Sphere(1);//设置物体材质
const sphereWorldMaterial = new CANNON.Material();// 创建物理世界的物体
const sphereBody = new CANNON.Body({shape: sphereShape,position: new CANNON.Vec3(0, 0, 0),//   小球质量mass: 1,//   物体材质material: sphereWorldMaterial,
});// 将物体添加至物理世界
world.addBody(sphereBody);

物理引擎如何与渲染引擎关联

 // 更新物理引擎里世界的物体world.step(1 / 120, deltaTime); // 更新sphere.position.copy(sphereBody.position); // 渲染引擎复制物理引擎中的数据 做渲染 自由落体

自由落体

import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 导入动画库
import gsap from "gsap";
// 导入dat.gui
import * as dat from "dat.gui";
// 导入connon引擎
import * as CANNON from "cannon-es";// 目标:使用cannon引擎
console.log(CANNON);// const gui = new dat.GUI();
// 1、创建场景
const scene = new THREE.Scene();// 2、创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,300
);// 设置相机位置
camera.position.set(0, 0, 18);
scene.add(camera);// 创建球和平面
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true; // 阴影
scene.add(sphere);const floor = new THREE.Mesh(new THREE.PlaneBufferGeometry(20, 20),new THREE.MeshStandardMaterial()
);floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;// 接收阴影
scene.add(floor);// 创建物理世界
// const world = new CANNON.World({ gravity: 9.8 });
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);
// 创建物理小球形状
const sphereShape = new CANNON.Sphere(1);//设置物体材质
const sphereWorldMaterial = new CANNON.Material();// 创建物理世界的物体
const sphereBody = new CANNON.Body({shape: sphereShape,position: new CANNON.Vec3(0, 0, 0),//   小球质量mass: 1,//   物体材质material: sphereWorldMaterial,
});// 将物体添加至物理世界
world.addBody(sphereBody);//添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.castShadow = true; // 阴影
scene.add(dirLight);// 初始化渲染器
// 渲染器透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);// // 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 设置时钟
const clock = new THREE.Clock();function render() {//   let time = clock.getElapsedTime();let deltaTime = clock.getDelta();// 更新物理引擎里世界的物体world.step(1 / 120, deltaTime); // 更新sphere.position.copy(sphereBody.position); // 渲染引擎复制物理引擎中的数据renderer.render(scene, camera);//   渲染下一帧的时候就会调用render函数requestAnimationFrame(render);
}render();// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {//   console.log("画面变化了");// 更新摄像头camera.aspect = window.innerWidth / window.innerHeight;//   更新摄像机的投影矩阵camera.updateProjectionMatrix();//   更新渲染器renderer.setSize(window.innerWidth, window.innerHeight);//   设置渲染器的像素比renderer.setPixelRatio(window.devicePixelRatio);
});

三、设置固定不动的地面与小球碰撞

小球如何撞到地面之后停下来

那么也要创建一个物理世界地面

// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(floorBody);

这样小球打在地面上就会停下来
在这里插入图片描述

import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 导入动画库
import gsap from "gsap";
// 导入dat.gui
import * as dat from "dat.gui";
// 导入connon引擎
import * as CANNON from "cannon-es";// 目标:使用cannon引擎
console.log(CANNON);// const gui = new dat.GUI();
// 1、创建场景
const scene = new THREE.Scene();// 2、创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,300
);// 设置相机位置
camera.position.set(0, 0, 18);
scene.add(camera);// 创建球和平面
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true;
scene.add(sphere);const floor = new THREE.Mesh(new THREE.PlaneBufferGeometry(20, 20),new THREE.MeshStandardMaterial()
);floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);// 创建物理世界
// const world = new CANNON.World({ gravity: 9.8 });
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);
// 创建物理小球形状
const sphereShape = new CANNON.Sphere(1);//设置物体材质
const sphereWorldMaterial = new CANNON.Material();// 创建物理世界的物体
const sphereBody = new CANNON.Body({shape: sphereShape,position: new CANNON.Vec3(0, 0, 0),//   小球质量mass: 1,//   物体材质material: sphereWorldMaterial,
});// 将物体添加至物理世界
world.addBody(sphereBody);// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(floorBody);//添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.castShadow = true;
scene.add(dirLight);// 初始化渲染器
// 渲染器透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);// // 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 设置时钟
const clock = new THREE.Clock();function render() {//   let time = clock.getElapsedTime();let deltaTime = clock.getDelta();// 更新物理引擎里世界的物体world.step(1 / 120, deltaTime);sphere.position.copy(sphereBody.position);renderer.render(scene, camera);//   渲染下一帧的时候就会调用render函数requestAnimationFrame(render);
}render();// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {//   console.log("画面变化了");// 更新摄像头camera.aspect = window.innerWidth / window.innerHeight;//   更新摄像机的投影矩阵camera.updateProjectionMatrix();//   更新渲染器renderer.setSize(window.innerWidth, window.innerHeight);//   设置渲染器的像素比renderer.setPixelRatio(window.devicePixelRatio);
});

三、监听碰撞事件和控制碰撞音效

在这里插入图片描述

// 创建击打声音
const hitSound = new Audio("assets/metalHit.mp3");
// 添加监听碰撞事件
function HitEvent(e) {// 获取碰撞的强度//   console.log("hit", e);const impactStrength = e.contact.getImpactVelocityAlongNormal();console.log(impactStrength); // 获取碰撞的强度if (impactStrength > 2) {//   重新从零开始播放hitSound.currentTime = 0;hitSound.play();}
}
sphereBody.addEventListener("collide", HitEvent);

免费音乐素材下载地址,爱给网:https://www.aigei.com/

import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 导入动画库
import gsap from "gsap";
// 导入dat.gui
import * as dat from "dat.gui";
// 导入connon引擎
import * as CANNON from "cannon-es";// 目标:使用cannon引擎
console.log(CANNON);// const gui = new dat.GUI();
// 1、创建场景
const scene = new THREE.Scene();// 2、创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,300
);// 设置相机位置
camera.position.set(0, 0, 18);
scene.add(camera);// 创建球和平面
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true;
scene.add(sphere);const floor = new THREE.Mesh(new THREE.PlaneBufferGeometry(20, 20),new THREE.MeshStandardMaterial()
);floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);// 创建物理世界
// const world = new CANNON.World({ gravity: 9.8 });
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);
// 创建物理小球形状
const sphereShape = new CANNON.Sphere(1);//设置物体材质
const sphereWorldMaterial = new CANNON.Material("sphere");// 创建物理世界的物体
const sphereBody = new CANNON.Body({shape: sphereShape,position: new CANNON.Vec3(0, 0, 0),//   小球质量mass: 1,//   物体材质material: sphereWorldMaterial,
});// 将物体添加至物理世界
world.addBody(sphereBody);// 创建击打声音
const hitSound = new Audio("assets/metalHit.mp3");
// 添加监听碰撞事件
function HitEvent(e) {// 获取碰撞的强度//   console.log("hit", e);const impactStrength = e.contact.getImpactVelocityAlongNormal();console.log(impactStrength); // 获取碰撞的强度if (impactStrength > 2) {//   重新从零开始播放hitSound.currentTime = 0;hitSound.play();}
}
sphereBody.addEventListener("collide", HitEvent);// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
const floorMaterial = new CANNON.Material("floor");
floorBody.material = floorMaterial;
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(floorBody);// 设置2种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(sphereMaterial,floorMaterial,{//   摩擦力friction: 0.1,// 弹性restitution: 0.7,}
);// 讲材料的关联设置添加的物理世界
world.addContactMaterial(defaultContactMaterial);//添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.castShadow = true;
scene.add(dirLight);// 初始化渲染器
// 渲染器透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);// // 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 设置时钟
const clock = new THREE.Clock();function render() {//   let time = clock.getElapsedTime();let deltaTime = clock.getDelta();// 更新物理引擎里世界的物体world.step(1 / 120, deltaTime);sphere.position.copy(sphereBody.position);renderer.render(scene, camera);//   渲染下一帧的时候就会调用render函数requestAnimationFrame(render);
}render();// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {//   console.log("画面变化了");// 更新摄像头camera.aspect = window.innerWidth / window.innerHeight;//   更新摄像机的投影矩阵camera.updateProjectionMatrix();//   更新渲染器renderer.setSize(window.innerWidth, window.innerHeight);//   设置渲染器的像素比renderer.setPixelRatio(window.devicePixelRatio);
});

四、关联材质设置摩擦与弹性系数

//设置物体材质
const cubeWorldMaterial = new CANNON.Material("cube");
// 设置地面材质
const floorMaterial = new CANNON.Material("floor");
floorBody.material = floorMaterial;// 设置2种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(cubeWorldMaterial,floorMaterial,{//   摩擦力friction: 0.1,// 弹性restitution: 0.7,}
);// 讲材料的关联设置添加的物理世界
world.addContactMaterial(defaultContactMaterial);// 设置世界碰撞的默认材料,如果材料没有设置,都用这个
world.defaultContactMaterial = defaultContactMaterial;

五、立方体相互碰撞后旋转效果

// window每点击一次创建一个物体
在这里插入图片描述

window.addEventListener("click", createCube);const cubeArr = [];
//设置物体材质
const cubeWorldMaterial = new CANNON.Material("cube");function createCube() {// 创建立方体和平面const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1);const cubeMaterial = new THREE.MeshStandardMaterial();const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);cube.castShadow = true; //阴影scene.add(cube);// 创建物理cube形状const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));// 创建物理世界的物体const cubeBody = new CANNON.Body({shape: cubeShape,position: new CANNON.Vec3(0, 0, 0),//   小球质量mass: 1,//   物体材质material: cubeWorldMaterial,});cubeBody.applyLocalForce(new CANNON.Vec3(300, 0, 0), //添加的力的大小和方向new CANNON.Vec3(0, 0, 0) //施加的力所在的位置);// 将物体添加至物理世界world.addBody(cubeBody);// 添加监听碰撞事件function HitEvent(e) {// 获取碰撞的强度//   console.log("hit", e);const impactStrength = e.contact.getImpactVelocityAlongNormal();console.log(impactStrength);if (impactStrength > 2) {//   重新从零开始播放hitSound.currentTime = 0;hitSound.volume = impactStrength / 12;hitSound.play();}}cubeBody.addEventListener("collide", HitEvent);cubeArr.push({mesh: cube,body: cubeBody,});
}

// 下落后物体旋转

  cubeArr.forEach((item) => {item.mesh.position.copy(item.body.position);// 设置渲染的物体跟随物理的物体旋转item.mesh.quaternion.copy(item.body.quaternion);});

撞击后声音逐渐减弱

  // 添加监听碰撞事件function HitEvent(e) {// 获取碰撞的强度//   console.log("hit", e);const impactStrength = e.contact.getImpactVelocityAlongNormal();console.log(impactStrength);if (impactStrength > 2) {//   重新从零开始播放hitSound.currentTime = 0;hitSound.volume = impactStrength / 12;hitSound.play();}}cubeBody.addEventListener("collide", HitEvent);

六、给物体施加作用力

  cubeBody.applyLocalForce(new CANNON.Vec3(300, 0, 0), //添加的力的大小和方向new CANNON.Vec3(0, 0, 0) //施加的力所在的位置);
import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 导入动画库
import gsap from "gsap";
// 导入dat.gui
import * as dat from "dat.gui";
// 导入connon引擎
import * as CANNON from "cannon-es";// 目标:设置cube跟着旋转
console.log(CANNON);// const gui = new dat.GUI();
// 1、创建场景
const scene = new THREE.Scene();// 2、创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,300
);// 设置相机位置
camera.position.set(0, 0, 18);
scene.add(camera);const cubeArr = [];
//设置物体材质
const cubeWorldMaterial = new CANNON.Material("cube");function createCube() {// 创建立方体和平面const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1);const cubeMaterial = new THREE.MeshStandardMaterial();const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);cube.castShadow = true; //阴影scene.add(cube);// 创建物理cube形状const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));// 创建物理世界的物体const cubeBody = new CANNON.Body({shape: cubeShape,position: new CANNON.Vec3(0, 0, 0),//   小球质量mass: 1,//   物体材质material: cubeWorldMaterial,});cubeBody.applyLocalForce(new CANNON.Vec3(300, 0, 0), //添加的力的大小和方向new CANNON.Vec3(0, 0, 0) //施加的力所在的位置);// 将物体添加至物理世界world.addBody(cubeBody);// 添加监听碰撞事件function HitEvent(e) {// 获取碰撞的强度//   console.log("hit", e);const impactStrength = e.contact.getImpactVelocityAlongNormal();console.log(impactStrength);if (impactStrength > 2) {//   重新从零开始播放hitSound.currentTime = 0;hitSound.volume = impactStrength / 12;hitSound.play();}}cubeBody.addEventListener("collide", HitEvent);cubeArr.push({mesh: cube,body: cubeBody,});
}window.addEventListener("click", createCube);// 平面
const floor = new THREE.Mesh(new THREE.PlaneBufferGeometry(20, 20),new THREE.MeshStandardMaterial()
);floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true; // 接收阴影
scene.add(floor);// 创建物理世界
// const world = new CANNON.World({ gravity: 9.8 });
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0); // 重力方向// 创建击打声音
const hitSound = new Audio("assets/metalHit.mp3");// 物理世界创建地面
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body();
// 设置地面材质
const floorMaterial = new CANNON.Material("floor");
floorBody.material = floorMaterial;
// 当质量为0的时候,可以使得物体保持不动
floorBody.mass = 0;
floorBody.addShape(floorShape);
// 地面位置
floorBody.position.set(0, -5, 0);
// 旋转地面的位置
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(floorBody);// 设置2种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(cubeWorldMaterial,floorMaterial,{//   摩擦力friction: 0.1,// 弹性restitution: 0.7,}
);// 讲材料的关联设置添加的物理世界
world.addContactMaterial(defaultContactMaterial);// 设置世界碰撞的默认材料,如果材料没有设置,都用这个
world.defaultContactMaterial = defaultContactMaterial;//添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.castShadow = true; // 投放阴影
scene.add(dirLight);// 初始化渲染器
// 渲染器透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);// // 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 设置时钟
const clock = new THREE.Clock();function render() {//   let time = clock.getElapsedTime();let deltaTime = clock.getDelta();// 更新物理引擎里世界的物体world.step(1 / 120, deltaTime); // 更新//   cube.position.copy(cubeBody.position);cubeArr.forEach((item) => {item.mesh.position.copy(item.body.position);// 设置渲染的物体跟随物理的物体旋转item.mesh.quaternion.copy(item.body.quaternion);});renderer.render(scene, camera);//   渲染下一帧的时候就会调用render函数requestAnimationFrame(render);
}render();// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {//   console.log("画面变化了");// 更新摄像头camera.aspect = window.innerWidth / window.innerHeight;//   更新摄像机的投影矩阵camera.updateProjectionMatrix();//   更新渲染器renderer.setSize(window.innerWidth, window.innerHeight);//   设置渲染器的像素比renderer.setPixelRatio(window.devicePixelRatio);
});

七、cannon流体模拟

流体模拟是一种用于模拟流体运动和形态的技术,常用于计算机图形学、动画、物理学、流体力学等领域。
在计算机图形学中,流体模拟可以用来创建各种真实的水、火、烟、云等效果。如海浪、河流、火焰、烟雾、雨雪等。
在动画制作中,流体模拟可以用来制作角色的衣服、头发、发型等效果,还可以用来制作特效动画,如爆炸、火焰等。
在物理学和流体力学中,流体模拟可以用来研究流体运动的规律,如水流、气流、火焰等,对于工程设计和模拟有很大的帮助。
在游戏开发中,流体模拟可以用来制作游戏中的水、火、烟等效果,让游戏画面更加真实。
流体模拟技术还可以应用于医学影像、纺织工业、液压机械、化学工程等其他领域。
SPH (Smooth Particle Hydrodynamics) 是一种流体模拟算法,它可以模拟流体的运动和形态。SPH算法的核心思想是将流体分成许多粒子,然后用这些粒子来模拟流体的运动。
SPH算法的主要步骤如下:

  1. 初始化:在初始状态下,将流体划分成许多粒子,并计算每个粒子的密度、速度和位置。
  2. 计算力学量:在每一时刻,根据流体的物理性质和流体周围粒子的密度、速度和位置计算每个粒子的力学量,如加速度、速度和位置。
  3. 更新粒子状态:根据力学量更新粒子的速度和位置。
  4. 渲染:将粒子的位置和密度渲染成流体的形态。

SPH算法还有很多优化和变种,如模拟不同流体(如气体和液体)、多相流体、多孔介质、粘性流体、非牛顿流体等等。

需要注意的是,在实际应用中,SPH算法需要解决的问题非常复杂,需要对数学和物理有很好的理解。如果不熟悉这些知识,可能需要学习一些基础知识。
Cannon.js是一个开源的物理引擎,它支持WebGL和JavaScript。Cannon.js中的SPHSystem类是用来模拟流体的一个类,它使用了SPH算法。
使用Cannon.js中的SPHSystem类,可以很容易地在WebGL中模拟流体。下面是一个简单的例子,说明了如何使用SPHSystem类:

// 创建一个SPH系统
var sph = new CANNON.SPHSystem();// 添加一些粒子
for (var i = 0; i < 100; i++) {var p = new CANNON.SPHSystem.Particle();p.position.set(Math.random(), Math.random(), Math.random());sph.addParticle(p);
}// 每一帧更新粒子状态
function update() {sph.step();requestAnimationFrame(update);
}
update();

这样就可以在WebGL中模拟流体。需要注意的是,还需要使用WebGL来渲染粒子,并且需要设置相应的参数才能达到期望的效果。
CANNON.SPHSystem 还支持设置流体的密度、粘性、阻力等参数,还支持给粒子施加力,添加障碍物等等。需要根据实际需求来设置相应的参数。
CANNON.SPHSystem类中有一些属性来控制流体的表现,这四个属性分别是:
● density : 流体的密度,它用来控制流体的粘度和阻力。
● particles : 包含所有粒子的数组。
● smoothingRadius : 用来控制粒子之间交互的距离,粒子之间的距离小于这个值时,它们之间会产生影响。
● viscosity : 流体的粘度,它用来控制流体的阻力。
其中 density 和 viscosity 是控制流体行为的重要参数。密度越高,流体就会越粘稠,阻力也会越大。粘度越高,流体就会越阻力,移动越困难。
particles 属性是粒子数组,可以通过它访问到粒子的属性,如位置、速度等。
smoothingRadius 是交互半径,这个值越大,粒子之间的交互就越广,越小,粒子之间的交互就越窄,根据实际需求来调整。
如果想要模拟出真实的流体效果,需要根据实际场景来调整这些参数。

八、车辆模拟

RaycastVehicle
RaycastVehicle是cannon.js提供了一个车辆对象。并且官方给我们提供了demo。

何为Raycast
为何称为RaycastVehicle呢?这与该对象的物理模拟原理有关系。该对象使用刚体(CANNON.Body)作为车身,从刚体的四个角处向下发射固定长度的射线,射线与地面的交叉点作为车辆与地面的接触点,在该点为车身刚体施加纵向的悬挂弹力与横向的牵引摩擦力。

在这里插入图片描述
其核心计算在updateVehicle方法中。

使用方法

使用方法可以参考官方示例。
1、首先构造一个RaycastVehicle对象:

vehicle = new CANNON.RaycastVehicle({chassisBody: chassisBody,indexRightAxis: 0,indexForwardAxis: 2,indexUpAxis: 1,
});
vehicle.addToWorld(world);

其中,chassisBody是代表车身的刚体,indexRightAxis、indexForwardAxis、indexUpAxis官方示例中并没有用到,他们分别代表车的右、前、上轴,0、1、2分别代表x、y、z轴。
2、添加逻辑车轮:

vehicle.addWheel(options);

options对象为车轮及悬挂的参数:

chassisConnectionPointLocal: new Vec3(),// 车轮连接点,相对于chassisBody(也是发射射线的起点)directionLocal: new Vec3(),// 车轮的下方方向(垂直车身向下)axleLocal: new Vec3(),// 车轴方向suspensionRestLength: 1,// 悬挂长度(未受任何力)suspensionMaxLength: 2,// 悬挂最大长度,限制计算出的suspensionLengthsuspensionStiffness: 100,// 悬挂刚度dampingCompression: 10,// 悬挂压缩阻尼dampingRelaxation: 10,// 悬挂复原阻尼maxSuspensionForce: Number.MAX_VALUE, // 限制计算出的suspensionForcemaxSuspensionTravel: 1,// 悬挂可伸长或压缩的最大距离radius: 1,// 车轮半径frictionSlip: 10000,// 滑动摩檫系数(用于计算车轮所能提供的最大摩檫力)rollInfluence: 0.01,// 施加侧向力时的位置系数,越小越接近车身,防止侧翻

添加车轮碰撞。添加好逻辑车轮后,还要在world中添加刚体以产生碰撞:
在cannon-es的RaycastVehicle类中,设置轮子的对象包含以下属性:

  1. radius:轮子的半径,表示轮子的大小。
  2. directionLocal:轮子方向的本地坐标,指定轮子相对于车身的朝向。
  3. suspensionStiffness:悬挂硬度,指定悬挂系统的硬度,影响轮子的弹性。
  4. suspensionRestLength:悬挂系统的静态长度,指定悬挂系统在不受载荷时的长度。
  5. frictionSlip:滑动摩擦,指定轮子与地面的滑动摩擦。
  6. dampingRelaxation:弛性阻尼,指定悬挂系统的弛性阻尼,影响轮子的悬挂效果。
  7. dampingCompression:压缩阻尼,指定悬挂系统的压缩阻尼,影响轮子的悬挂效果。
  8. maxSuspensionForce:最大悬挂力,指定悬挂系统所能承受的最大悬挂力。
  9. rollInfluence:滚动影响,指定轮子的滚动影响,影响车辆的滚动效果。
  10. axleLocal:轴的本地坐标,指定轴相对于车身的位置。
  11. chassisConnectionPointLocal:车身连接点的本地坐标,指定轮子与车身的连接点的位置。
  12. maxSuspensionTravel:最大悬挂旅行,指定悬挂系统可以移动的最大距离,也就是轮子可以上下移动的最大距离。这个属性用来限制轮子的移动范围,有助于模拟轮子的物理行为。
for (var i = 0; i < vehicle.wheelInfos.length; i++) {var wheel = vehicle.wheelInfos[i];var cylinderShape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);var wheelBody = new CANNON.Body({mass: 0});wheelBody.type = CANNON.Body.KINEMATIC;wheelBody.collisionFilterGroup = 0; // turn off collisionsvar q = new CANNON.Quaternion();q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);// 把竖着的圆柱体放倒作为车轮wheelBody.addShape(cylinderShape, new CANNON.Vec3(), q);wheelBodies.push(wheelBody);world.addBody(wheelBody);

对于车轮碰撞,还要实时更新其Transom:

world.addEventListener('postStep', function () {for (var i = 0; i < vehicle.wheelInfos.length; i++) {var t = vehicle.wheelInfos[i].worldTransform;var wheelBody = wheelBodies[i];wheelBody.position.copy(t.position);wheelBody.quaternion.copy(t.quaternion);}
});

车辆控制。可以使用RaycastVehicle的如下函数控制车辆:

applyEngineForce = function(value, wheelIndex) // 施加牵引力
setSteeringValue = function(value, wheelIndex) // 设置转向角(弧度)
setBrake = function(brake, wheelIndex) // 刹车

实现动画。在每帧渲染之前,把车身、车轮的Transom拷贝给three.js或Bayalon.js图形,就实现了动画.

在这里插入图片描述

实现漂移

在赛车游戏中,漂移能够很大程度上增加游戏的娱乐性。要理解漂移,首先要了解汽车的转向。
后轮无滑移转向时,前轮与后轮的瞬心即为转向中心.
无滑移状态下,转向中心静止不变,车辆将沿转向中心做圆周运动。当漂移时,转向中心也做圆周运动,车辆运动的圆周比无滑移转向时的半径小:
在这里插入图片描述
实现漂移,可以在用户按下漂移键后,修改后轮的摩擦系数:

vehicle.wheelInfos[2].frictionSlip= up ? 3.5: 1.5;
vehicle.wheelInfos[3].frictionSlip= up ? 3.5: 1.5;

但是,此时很容易出现漂移过度,车子打一个圈:
在这里插入图片描述
前文中介绍了转向半径,这里再利用一下。设转向半径为r,轴距为l,轮距为w:
在这里插入图片描述
根据阿克曼条件,两轮的转向角为:

在这里插入图片描述

r = 6 + Math.abs(vehicle.currentVehicleSpeedKmHour) / 10switch (event.keyCode) {// 。。。case 39: // rightvehicle.setSteeringValue(up ? 0 : -Math.atan(2 / (r + 1 / 2)), 0);vehicle.setSteeringValue(up ? 0 : -Math.atan(2 / (r - 1 / 2)), 1);break;case 37: // leftvehicle.setSteeringValue(up ? 0 : Math.atan(2 / (r - 1 / 2)), 0);vehicle.setSteeringValue(up ? 0 : Math.atan(2 / (r + 1 / 2)), 1);break;case 67:vehicle.wheelInfos[2].frictionSlip = up ? 3.5 : 1.4;vehicle.wheelInfos[3].frictionSlip = up ? 3.5 : 1.4;

在这里插入图片描述

此外,在卡丁车类游戏中,也可以使用参考文献[1]中,给车身施加侧向力的方法让车辆产生更顺滑的漂移

这篇关于threejs(9)-应用物理引擎设置物体相互作用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/313006

相关文章

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2

将Python应用部署到生产环境的小技巧分享

《将Python应用部署到生产环境的小技巧分享》文章主要讲述了在将Python应用程序部署到生产环境之前,需要进行的准备工作和最佳实践,包括心态调整、代码审查、测试覆盖率提升、配置文件优化、日志记录完... 目录部署前夜:从开发到生产的心理准备与检查清单环境搭建:打造稳固的应用运行平台自动化流水线:让部署像

四种简单方法 轻松进入电脑主板 BIOS 或 UEFI 固件设置

《四种简单方法轻松进入电脑主板BIOS或UEFI固件设置》设置BIOS/UEFI是计算机维护和管理中的一项重要任务,它允许用户配置计算机的启动选项、硬件设置和其他关键参数,该怎么进入呢?下面... 随着计算机技术的发展,大多数主流 PC 和笔记本已经从传统 BIOS 转向了 UEFI 固件。很多时候,我们也

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

使用C#如何创建人名或其他物体随机分组

《使用C#如何创建人名或其他物体随机分组》文章描述了一个随机分配人员到多个团队的代码示例,包括将人员列表随机化并根据组数分配到不同组,最后按组号排序显示结果... 目录C#创建人名或其他物体随机分组此示例使用以下代码将人员分配到组代码首先将lstPeople ListBox总结C#创建人名或其他物体随机分组

SpringBoot项目引入token设置方式

《SpringBoot项目引入token设置方式》本文详细介绍了JWT(JSONWebToken)的基本概念、结构、应用场景以及工作原理,通过动手实践,展示了如何在SpringBoot项目中实现JWT... 目录一. 先了解熟悉JWT(jsON Web Token)1. JSON Web Token是什么鬼