Skip to content

101. 实战:酷家乐装修编辑器(五)

Published:

上节在 json 里添加了墙的数据,并且把它绘制了出来:

2025-05-24 20.16.12.gif

这节我们来加上门窗。

先看下酷家乐的:

比如这样的门窗:

image.png

在 2d 平面会有对应的标识:

image.png

这个门窗是可以调整大小的:

2025-06-16 15.11.48.gif

可以看到,在 2d 平面了调整了窗户大小,3d 视图的窗户也小了。

而且窗户可以更换:

2025-06-16 15.14.28.gif

可以移动位置:

2025-06-16 15.15.14.gif

那如何在墙上画出这样的门窗呢?

首先,我们用 ExtrudeGeometry 拉伸出墙壁的时候,要留下窗户和门的洞,这个洞的大小位置也要记录下来。

然后在洞的位置加载一个门窗的模型,调整门窗大小的话就是设置模型的 scale。

我们来写一下:

之前我们是由底部的 4 个点向上拉伸出墙体:

image.png

但我们要挖洞的话,应该是从墙面向一侧水平拉伸。

所以我们改一下存储的结构:

比如这样一面墙:

image.png

image.png

image.png

我们只存储底部的两个端点的位置:

大概是这样的结构:

image.png

墙底部的左右端点位置,墙的高度、厚度。

窗户的洞的左下角位置,窗户宽高。

有了这些就知道怎么画了。

walls: [
    {
        left: {x: 0, z: 0},
        right: {x: 500, z: 0},
        height: 500,
        depth: 30,
        windows: [
            {
                leftBottomPosition: {
                    x: 100,
                    z: 100
                },
                width: 300,
                height: 300
            }
        ]
    }
]

类型也要改一下:

image.png

interface Wall {
    left: {
        x: number,
        z: number
    },
    right: {
        x: number,
        z: number
    },
    height: number,
    depth: number,
    windows: [
        {
            leftBottomPosition: {
                x: number,
                z: number
            },
            width: number,
            height: number
        }
    ]
}

把之前绘制 2d 视图的代码注释掉:

image.png

我们先绘制 3d 的:

image.png

const scene = scene3DRef.current!;
const walls = data.walls.map(item => {
    const shape = new THREE.Shape();
    shape.moveTo(item.left.x, item.left.z);
    shape.lineTo(item.right.x, item.right.z);
    shape.lineTo(item.right.x, item.right.z + item.height);
    shape.lineTo(item.left.x, item.left.z + item.height);
    shape.lineTo(item.left.x, item.left.z);

    const geometry = new THREE.ExtrudeGeometry(shape, {
        depth: item.depth
    });
    const material = new THREE.MeshPhongMaterial({
        color: 'white'
    })
    const wall =  new THREE.Mesh(geometry, material);
    // wall.rotateX(-Math.PI/2);
    return wall;
});

scene.add(...walls);

同样是从左下角位置、右下角位置、右上角位置、左上角位置,最后回到左下角位置。

按照这个顺序来画墙的形状,然后拉伸一定的厚度。

这样拉伸就不用旋转了。

看下效果:

image.png

这样墙就画出来了。

然后我们来挖孔:

image.png

item.windows.forEach(win => {
    const path = new THREE.Path();

    const { x, z } = win.leftBottomPosition;

    path.moveTo(x, z);
    path.lineTo(x + win.width, z);
    path.lineTo(x + win.width, z + win.height);
    path.lineTo(x, z + win.height);
    path.lineTo(x, z);
    shape.holes.push(path);
})

同样是按照从左下角到右下角、右上角、左上角来转一圈的顺序来画。

把它添加到墙的 holes 里。

看下效果:

image.png

这样洞就挖好了。

AxesHelper 加长一下:

image.png

image.png

改一下数据试试:

image.png

2025-06-16 16.12.57.gif

image.png

2025-06-16 16.14.16.gif

没啥问题。

案例代码上传了小册仓库

总结

这节我们实现了门窗的存储和绘制。

上节的墙体数据存储有些问题,我们改了一下:

存储墙的两个端点还有墙的高度、厚度信息,并且存储 holes 的信息,包括左下角坐标,洞的宽度、高度。

这样,门窗的洞就预留好了,下节我们在洞的位置放上门窗。

评论