上节把编辑后的家具数据保存到了 store 并做了持久化:


这节我们在 2D 视图把它绘制出来。
我们把 2D 的坐标轴辅助线加长一下:


家具是添加到 house 里的,我们也可视化一下 house 的局部坐标系:

const helper = new THREE.AxesHelper(30000);
house.add(helper);

可以看到,house 的局部坐标系。
家具是添加到 house 下的,位置是相对这个局部坐标系做偏移。
这个忘了的同学可以回顾下局部坐标、世界坐标那一节。
然后就可以绘制家具了:

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;
gltf.scene.traverse(obj => {
(obj as any).target = gltf.scene;
});
gltf.scene.name = furniture.id;
});
})
house.add(furnitures);
看下效果:

家具绘制出来了,但是位置不对,放到它的镜像位置刚刚好。
这个是我们之前调整墙的绘制问题的时候,统一给位置都设置了相反数。
所以家具的位置也是相反数。



这样就对了。
然后更新的时候也是同样的逻辑:

const houseObj = scene.getObjectByName('house')!;
if(houseObj) {
data.furnitures.forEach(furniture => {
const obj = houseObj.getObjectByName(furniture.id);
if(obj) {
obj.position.set(
-furniture.position.x,
-furniture.position.y,
-furniture.position.z
);
obj.rotation.x = furniture.rotation.x;
obj.rotation.y = furniture.rotation.y;
obj.rotation.z = furniture.rotation.z;
}
})
return;
}
试一下:

没啥问题。
不过家具没必要 y 轴方向编辑,改一下:


transformControls.showY = false;
transformControls.showY = true;
其实应该在 2D 视图里也可以拖拽,然后在 3D 视图看效果。
我们也在 2D 视图里加一下这些逻辑:
首先初始化的时候传入 updateForniture 方法:


updateFurniture: Action['updateFurniture']
然后也加一段同样的 TransformControls 的逻辑:

const transformControls = new TransformControls(camera, renderer.domElement);
transformControls.showY = false;
const transformHelper = transformControls.getHelper();
scene.add(transformHelper);
transformControls.addEventListener('dragging-changed', function (event) {
controls.enabled = !event.value;
});
transformControls.addEventListener('change', () => {
const obj = transformControls.object;
if(obj) {
if(transformControls.mode === 'translate') {
updateFurniture(obj.name, 'position',new THREE.Vector3(
-obj.position.x,
-obj.position.y,
-obj.position.z
));
} else if(transformControls.mode === 'rotate'){
updateFurniture(obj.name, 'rotation', new THREE.Vector3(
obj.rotation.x,
obj.rotation.y,
obj.rotation.z
));
}
}
});
要注意的是,位置是相反的,所以需要取反之后再保存。
处理下点击事件:

renderer.domElement.addEventListener('click', (e) => {
const y = -((e.offsetY / height) * 2 - 1);
const x = (e.offsetX / width) * 2 - 1;
const rayCaster = new THREE.Raycaster();
rayCaster.setFromCamera(new THREE.Vector2(x, y), camera);
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();
}
});
试下效果:

然后再处理下旋转。
同样加一个切换 mode 的方法:

function changeMode(isTranslate: boolean) {
if(isTranslate) {
transformControls.mode = 'translate';
transformControls.showX = true;
transformControls.showZ = true;
transformControls.showY = false;
} else {
transformControls.mode = 'rotate';
transformControls.showX = false;
transformControls.showZ = false;
transformControls.showY = true;
}
}
加一个 ref 来接收:

const changeMode2DRef = useRef<(isTranslate: boolean) => void>(null);

changeMode2DRef.current = changeMode;
调用下:

<Button
onClick={() => {
changeModeRef.current?.(true);
changeMode2DRef.current?.(true);
}}
>平移</Button>
<Button
onClick={() => {
changeModeRef.current?.(false);
changeMode2DRef.current?.(false);
}}
>旋转</Button>
试下效果:

这样,2D 视图的家具渲染、编辑就完成了。
案例代码上传了小册仓库
总结
这节我们在 2D 视图绘制了家具,并且实现了编辑功能。
用同样的数据在 2D 视图里渲染家具,只不过位置因为之前调整墙绘制问题需要设置相反数。
编辑也是用 TransformControls 来实现,只是不需要 y 轴的编辑,在 2D 视图改了数据,会同步更新 3D 视图。
这样,我们就可以在 2D 或者 3D 视图编辑家具位置、旋转角度了。