案例展示
摩擦力模拟
<template>
<div id="TO"></div>
</template>
<script setup>
import { onMounted } from 'vue'
// 模型数据
const modelList = [
{
modelName: '立方体-1',
modelType: 'cube',
scale: { x: 1, y: 1, z: 1 },
position: { x: -3, y: 0.5, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
base: {
depth: 1,
width: 1,
height: 1
},
material: {
color: 'yellow'
},
userData: {
physics: {
rigidType: 'dynamic', // 刚体类型: dynamic动态, static静态
shapeType: 'cube', // 形状类型: cube立方体
helperLines: {
active: true,
cubeBase: {
width: 1,
height: 1,
depth: 1
}
},
material: {
staticFriction: 0,
dynamicFriction: 0,
restitution: 1
},
mass: 1,
linearVelocity: {
x: 5,
y: 0,
z: 0
}
}
}
},
{
modelName: '立方体-2',
modelType: 'cube',
scale: { x: 1, y: 1, z: 1 },
position: { x: 3, y: 0.5, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
base: {
depth: 1,
width: 1,
height: 1
},
material: {
color: 'yellow'
},
userData: {
physics: {
rigidType: 'dynamic', // 刚体类型: dynamic动态, static静态
shapeType: 'cube', // 形状类型: cube立方体
helperLines: {
active: true,
cubeBase: {
width: 1,
height: 1,
depth: 1
}
},
material: {
staticFriction: 1, //静摩擦力
dynamicFriction: 1, //动摩擦力
restitution: 1 //弹性系数
},
mass: 1,
linearVelocity: {
x: 5,
y: 0,
z: 0
}
}
}
},
{
modelName: '平面',
modelType: 'cube',
scale: { x: 1, y: 1, z: 1 },
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
base: {
depth: 20,
width: 20,
height: 0.1
},
material: {
color: 0x525252
},
userData: {
physics: {
rigidType: 'static', // 刚体类型: dynamic动态, static静态
shapeType: 'cube', // 形状类型: cube立方体
helperLines: {
active: true,
cubeBase: {
width: 20,
height: 0.1,
depth: 20
}
},
material: {
staticFriction: 0.5,
dynamicFriction: 0,
restitution: 0
},
mass: 1,
linearVelocity: {
x: 0,
y: 0,
z: 0
}
}
}
}
]
// 场景参数
const sceneData = {
camera: {
position: {
x: 0,
y: 10,
z: 30
}
},
helper: {
axes: {
active: false
},
grid: {
active: false
}
},
modelList: modelList
}
let pxScene
// 存储物理对象与Three.js模型的映射关系
const physicsObjects = [] // 格式: { name: 名称, pxActor: 物理对象, threeMesh: 渲染模型, pxShape?: 形状对象 }
let physics
let PhysXInstance
let tmpFilterData
let shapeFlags
// 控制物理模拟的状态
let animationFrameId = null
let TO = null
onMounted(() => {
//初始化场景
TO = new ThingOrigin('easyBuild', document.getElementById('TO'), sceneData, (TO) => {
initPhysx()
})
})
// 初始化物理场景
const initPhysx = async () => {
const result = await TO.physx.initPhysx()
if (result.success === false) {
console.warn('[initPhysx] PhysX 未加载,后续物理逻辑已跳过', result.error)
return
}
;({ physics, PhysXInstance } = result)
pxScene = TO.physx.createScene({
x: 0,
y: -9.81,
z: 0
})
tmpFilterData = new PhysXInstance.PxFilterData(1, 1, 0, 0)
shapeFlags = TO.physx.createShapeFlags({
sceneQueryShape: true,
simulationShape: true
})
initPhysxBody()
}
// 初始化物理对象
const initPhysxBody = () => {
modelList.forEach((item) => {
loadPhysxModel(item)
})
simulationLoop()
}
//加载物理刚体模型
const loadPhysxModel = (item) => {
const modelInfo = item
const tmpVec = TO.physx.createVec3(modelInfo.position)
var tmpPose = new PhysXInstance.PxTransform(PhysXInstance.PxIDENTITYEnum.PxIdentity) // 变换
tmpPose.set_p(tmpVec)
const physicsData = modelInfo.userData.physics
const boxMaterial = TO.physx.createMaterial(physicsData.material)
shapeFlags = TO.physx.createShapeFlags({
sceneQueryShape: true,
simulationShape: true
})
let shape = null
if (physicsData.shapeType === 'cube') {
shape = TO.physx.createBoxShape(physicsData.helperLines.cubeBase, shapeFlags, boxMaterial)
} else if (physicsData.shapeType === 'sphere') {
shape = TO.physx.createSphereShape(physicsData.helperLines.sphereBase, shapeFlags, boxMaterial)
} else if (physicsData.shapeType === 'capsule') {
shape = TO.physx.createCapsuleShape(physicsData.helperLines.capsuleBase, shapeFlags, boxMaterial)
}
const actor =
physicsData.rigidType === 'dynamic'
? physics.createRigidDynamic(tmpPose)
: physics.createRigidStatic(tmpPose)
shape.setSimulationFilterData(tmpFilterData)
actor.attachShape(shape)
if (physicsData.rigidType === 'dynamic') {
actor.setMass(physicsData.mass)
let linearVelocity = TO.physx.createVec3(physicsData.linearVelocity)
actor.setLinearVelocity(linearVelocity)
}
pxScene.addActor(actor)
// 对应的Three.js模型
const boxMesh = TO.scene.getObjectByName(modelInfo.modelName)
// 添加物理对象到物理对象列表
physicsObjects.push({
name: modelInfo.modelName,
pxActor: actor,
threeMesh: boxMesh,
pxShape: shape,
modelInfo: modelInfo // 保存完整的modelInfo以便重新创建物理对象
})
}
// 物理模拟与渲染循环
const simulationLoop = () => {
let lastFrame = 0
function loop(hrTime) {
var timeStep = Math.min(0.03, (hrTime - lastFrame) / 1000)
lastFrame = hrTime // 重要:更新 lastFrame
// 1. 执行物理模拟
pxScene.simulate(timeStep)
pxScene.fetchResults(true)
// 2. 同步物理对象到Three.js模型
physicsObjects.forEach(({ pxActor, threeMesh, name }) => {
const pose = pxActor.getGlobalPose()
const position = pose.get_p()
const rotation = pose.get_q() // 四元数
// 同步位置
threeMesh.position.set(position.get_x(), position.get_y(), position.get_z())
// 同步旋转(PhysX四元数格式为(x,y,z,w),Three.js为(x,y,z,w),直接对应)
threeMesh.quaternion.set(rotation.get_x(), rotation.get_y(), rotation.get_z(), rotation.get_w())
})
// 3. 渲染场景
animationFrameId = requestAnimationFrame(loop)
}
animationFrameId = requestAnimationFrame(loop)
}
</script>
<style scoped>
#TO {
width: 100%;
height: 400px;
position: relative;
}
</style>