Skip to content

几何体居中与获取中心点

这是一个可以直接在 VitePress 页面中运行的 Three.js 示例。上方显示运行效果,下方展示对应源码。

运行效果

中心点:等待模型加载

原理

获取模型中心点时,常用 Box3.setFromObject(model) 先计算模型在世界空间中的包围盒,然后调用 box.getCenter(center) 得到包围盒中心点。

如果要把模型中心移动到世界原点,可以将模型整体反向移动中心点向量:model.position.sub(center)。这样模型的包围盒中心就会从原来的位置移动到 (0, 0, 0)

这个示例加载 public/models/ironman_mk44.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(8, 8)
scene.add(gridHelper)

const axesHelper = new THREE.AxesHelper(2)
axesHelper.setColors(0xff0000, 0x00ff00, 0x0000ff)
scene.add(axesHelper)

const centerMarker = new THREE.Mesh(
	new THREE.SphereGeometry(0.08, 16, 12),
	new THREE.MeshBasicMaterial({ color: '#facc15' })
)
scene.add(centerMarker)

const box = new THREE.Box3()
const center = new THREE.Vector3()
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')

const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)

let model = null
let boxHelper = null
let originalPosition = new THREE.Vector3()

const guiState = {
	centerModel: false,
	showCenterPoint: true,
	showBox: true
}

const updateCenterInfo = () => {
	box.setFromObject(model)
	box.getCenter(center)
	centerMarker.position.copy(center)
	centerMarker.visible = guiState.showCenterPoint

	if (boxHelper) {
		boxHelper.box.copy(box)
		boxHelper.visible = guiState.showBox
	}
}

const moveModelCenterToOrigin = () => {
	box.setFromObject(model)
	box.getCenter(center)

	// 将模型整体反向移动中心点向量,中心点就会落到世界原点。
	model.position.sub(center)
}

const applyCenterState = () => {
	model.position.copy(originalPosition)

	if (guiState.centerModel) {
		moveModelCenterToOrigin()
	}

	updateCenterInfo()
}

gltfLoader.load('/models/ironman_mk44.glb', (gltf) => {
	model = gltf.scene
	originalPosition = model.position.clone()
	scene.add(model)

	box.setFromObject(model)
	boxHelper = new THREE.Box3Helper(box, '#facc15')
	scene.add(boxHelper)

	updateCenterInfo()
})

const gui = new GUI({
	title: '中心点控制',
	autoPlace: false
})

gui.domElement.classList.add('three-gui')
demo.appendChild(gui.domElement)

gui
	.add(guiState, 'centerModel')
	.name('居中到原点')
	.onChange(applyCenterState)

gui
	.add(guiState, 'showCenterPoint')
	.name('显示中心点')
	.onChange(updateCenterInfo)

gui
	.add(guiState, 'showBox')
	.name('显示包围盒')
	.onChange(updateCenterInfo)

function animate() {
	requestAnimationFrame(animate)

	controls.update()
	renderer.render(scene, camera)
}

animate()