Skip to content

116. 实战:酷家乐装修编辑器(二十)

Published:

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

2025-06-29 21.02.43.gif

image.png

这节我们在 2D 视图把它绘制出来。

我们把 2D 的坐标轴辅助线加长一下:

image.png

image.png

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

image.png

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

image.png

可以看到,house 的局部坐标系。

家具是添加到 house 下的,位置是相对这个局部坐标系做偏移。

这个忘了的同学可以回顾下局部坐标、世界坐标那一节。

然后就可以绘制家具了:

image.png

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);

看下效果:

image.png

家具绘制出来了,但是位置不对,放到它的镜像位置刚刚好。

这个是我们之前调整墙的绘制问题的时候,统一给位置都设置了相反数。

所以家具的位置也是相反数。

image.png

image.png

image.png

这样就对了。

然后更新的时候也是同样的逻辑:

image.png

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;
}

试一下:

2025-07-02 14.33.27.gif

没啥问题。

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

image.png

image.png

transformControls.showY = false;
transformControls.showY = true;

其实应该在 2D 视图里也可以拖拽,然后在 3D 视图看效果。

我们也在 2D 视图里加一下这些逻辑:

首先初始化的时候传入 updateForniture 方法:

image.png

image.png

updateFurniture: Action['updateFurniture']

然后也加一段同样的 TransformControls 的逻辑:

image.png

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
            ));
        }
    }
});

要注意的是,位置是相反的,所以需要取反之后再保存。

处理下点击事件:

image.png

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();
    }
});

试下效果:

2025-07-02 17.19.04.gif

然后再处理下旋转。

同样加一个切换 mode 的方法:

image.png

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 来接收:

image.png

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

image.png

changeMode2DRef.current = changeMode;

调用下: image.png

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

试下效果:

2025-07-02 17.29.44.gif

这样,2D 视图的家具渲染、编辑就完成了。

案例代码上传了小册仓库

总结

这节我们在 2D 视图绘制了家具,并且实现了编辑功能。

用同样的数据在 2D 视图里渲染家具,只不过位置因为之前调整墙绘制问题需要设置相反数。

编辑也是用 TransformControls 来实现,只是不需要 y 轴的编辑,在 2D 视图改了数据,会同步更新 3D 视图。

这样,我们就可以在 2D 或者 3D 视图编辑家具位置、旋转角度了。

评论