上节做了家具导入:

这节我们来做编辑。
其实和之前的 Three.js Editor 一样:

只不过不支持放缩,家具大小是固定的。
酷家乐的家具编辑也是这样:

只不过它是用自己写的控件,我们直接用 TransformControls 也是一样。

引入 TransformControls,把它 的 helper 添加到场景中。
然后 TransformControls 拖动的时候,禁用 OrbitControls,不然两者操作冲突。
const transformControls = new TransformControls(camera, renderer.domElement);
const transformHelper = transformControls.getHelper();
scene.add(transformHelper);
transformControls.addEventListener('dragging-changed', function (event) {
controls.enabled = !event.value;
});
然后点击的时候,把 TransformControls 给 attach 到目标对象上。
我们给所有的家具单独一个 group:

const furnitures = new THREE.Group();
furnitures.name = 'furnitures';
data.furnitures.forEach(furniture => {
const gltfLoader = new GLTFLoader();
gltfLoader.load(furniture.modelUrl, (gltf) => {
furnitures.add(gltf.scene);
gltf.scene.position.set(
furniture.position.x,
furniture.position.y,
furniture.position.z
);
gltf.scene.rotation.x = furniture.rotation.x;
gltf.scene.rotation.y = furniture.rotation.y;
gltf.scene.rotation.z = furniture.rotation.z;
});
})
house.add(furnitures);
这样点击的时候方便查找。
然后给模型的所有子对象标记 target,点击的时候可以找到正确的对象:

gltf.scene.traverse(obj => {
(obj as any).target = gltf.scene;
})
然后来处理下点击:

const furnitures = scene.getObjectByName('furnitures')!;
const intersections2 = rayCaster.intersectObjects(furnitures.children);
if(intersections2.length) {
const obj = intersections2[0].object as any;
if(obj.target) {
transformControls.attach(obj.target);
}
} else {
transformControls.detach();
}
找到所有家具,点击的时候和这些对象做检测。
如果点中了家具对象,就把 TransformControls 给 attach 上,否则 detach
试下效果:

除了位移,还要做旋转:

酷家乐的这个控件是自己写的,可以一起做平移、旋转。
而我们用 TransformControls 需要切换 mode:

transformControls.mode = 'rotate';
transformControls.showX = false;
transformControls.showZ = false;
切换到 rotate 的 mode,然后禁用 X、Z 方向的旋转。
看下效果:

这样就只能做围绕 y 轴的旋转了。
我们导出个 changeMode 方法:

function changeMode(isTranslate: boolean) {
if(isTranslate) {
transformControls.mode = 'translate';
transformControls.showX = true;
transformControls.showZ = true;
} else {
transformControls.mode = 'rotate';
transformControls.showX = false;
transformControls.showZ = false;
}
}
组件里接收下:

const changeModeRef = useRef<(isTranslate: boolean) => void>(null);
changeModeRef.current = changeMode;
把切换按钮先放在之前 2D、3D 的按钮旁边:

<Button
onClick={() => changeModeRef.current?.(true)}
>平移</Button>
<Button
onClick={() => changeModeRef.current?.(false)}
>旋转</Button>

这样,家具的平移、旋转就完成了。
案例代码上传了小册仓库
总结
这节我们实现了家具的平移、旋转操作。
用 TransformControls 来做平移、旋转,点击家具的时候,把它 attach 到目标家具,没点中家具就 detach。
这里为了方便查找,我们把所有家具单独放在一个 group里,并给这个 group 一个 name。点击的时候,射线和这个 group 里的家具做相交判断。
并且我们加了两个按钮来切换 translate、rotate 两种操作模式。
平移、旋转实现了,下节我们继续完善编辑功能。