多个物体的包围盒
这是一个可以直接在 VitePress 页面中运行的 Three.js 示例。上方显示运行效果,下方展示对应源码。
运行效果
等待模型加载
原理
单个物体可以用 new THREE.Box3().setFromObject(object) 得到世界空间包围盒。多个物体的整体包围盒可以先创建一个空的 Box3,再遍历每个物体,把每个物体自己的包围盒通过 combinedBox.union(objectBox) 合并进去。
这个示例分别加载 ironman_mk44.glb 和 porsche_911.glb。黄色和蓝色线框表示两个模型各自的包围盒,红色线框表示合并后的整体包围盒。GUI 中可以隐藏单独包围盒、隐藏整体包围盒、切换模型显示,以及移动两个模型观察整体包围盒如何变化。
源码
js
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
const canvas = document.querySelector('canvas')
const demo = canvas.parentElement
const scene = new THREE.Scene()
scene.background = new THREE.Color('#111827')
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 2.5, 6)
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.outputColorSpace = THREE.SRGBColorSpace
renderer.setSize(window.innerWidth, window.innerHeight)
const ambientLight = new THREE.AmbientLight('#ffffff', 0.9)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight('#ffffff', 2)
directionalLight.position.set(4, 6, 5)
scene.add(directionalLight)
const gridHelper = new THREE.GridHelper(12, 12)
scene.add(gridHelper)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
const modelConfigs = [
{
name: 'Iron Man MK44',
path: '/models/ironman_mk44.glb',
position: new THREE.Vector3(-2.2, 0, 0),
boxColor: '#facc15'
},
{
name: 'Porsche 911',
path: '/models/porsche_911.glb',
position: new THREE.Vector3(2.2, 0, 0),
boxColor: '#38bdf8'
}
]
const objectItems = []
const combinedBox = new THREE.Box3()
let combinedBoxHelper = null
const loadModel = (config) => new Promise((resolve, reject) => {
gltfLoader.load(
config.path,
(gltf) => {
const holder = new THREE.Group()
holder.name = config.name
holder.position.copy(config.position)
holder.add(gltf.scene)
scene.add(holder)
const box = new THREE.Box3().setFromObject(holder)
const boxHelper = new THREE.Box3Helper(box, config.boxColor)
scene.add(boxHelper)
resolve({
...config,
holder,
box,
boxHelper
})
},
undefined,
reject
)
})
const updateBoundingBoxes = () => {
combinedBox.makeEmpty()
objectItems.forEach((item) => {
item.box.setFromObject(item.holder)
item.boxHelper.box.copy(item.box)
combinedBox.union(item.box)
})
combinedBoxHelper.box.copy(combinedBox)
}
Promise.all(modelConfigs.map(loadModel)).then((items) => {
objectItems.push(...items)
combinedBoxHelper = new THREE.Box3Helper(combinedBox, '#ef4444')
scene.add(combinedBoxHelper)
updateBoundingBoxes()
})
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene, camera)
}
animate()