这节我们来总结一下我们做这个项目的过程。
首先,我们选择了 React + TypeScript 来写项目。
前端框架用 vue 或者 react 都行,是为了写网页交互的部分,大家可以换 vue,核心还是 3D 部分。
我们先写了布局:

然后需要用 json 存储数据,所以选择 react 生态的 zustand 来做全局状态管理。
这个 json 存储是必须的,比如酷家乐,它是怎么存储各种户型数据的?它是怎么把编辑好的装修效果保存到服务端的?明显也是用 json 存储。
我们在 json 里保存了 walls、floors、ceilings、furnitures 数据。
然后在 2D、3D、preview 这三个场景里把它同步画出来。
墙的存储格式我们试过了好几种,最终是记录 position、width、height、depth 是最简单的:

用 Shape + ExtrudeGeometry 绘制墙,门窗就是上面的 holes,记录左下角的位置 leftBottomPosition 以及宽高就可以了。
天花板和地板是 Shape + ShapeGeometry 来绘制。
绘制好 3D 场景后,再绘制 2D 场景的时候我们遇到一个问题:

这个是绘制 Shape 的时候方向反了导致的,有两种解决方式:
- 反过来换个顺序绘制墙的 Shape
- 不用反着画,直接旋转 180 度就好了
我们选择了旋转 180 度的方式,这导致了位置都要用负的,并且 y 轴旋转角度要旋转 180 度:

这样确实不大好,最好还是用第一种方式,改一下绘制 Shape 的顺序就好了。

解决 2D 视图绘制的问题之后,我们给它加上了尺寸、门窗标注:


用 LineSegments 来绘制线段,指定几个点,LineSegments 会两两连接。
并且中间用 three-spritetext 来画文字。
我们给每面墙记录了法线方向:

和相机方向做向量点积,实现了面向摄像机的墙隐藏:

户型绘制好后,接下来是家具部分:
从 sketchfab.com 找的门窗、家具模型。
用 TransformControls 来做家具的编辑:


只支持位移、旋转,两个视图会同步。
然后做了家具列表,通过家具列表可以拖拽家具到 3D 场景:

原理和点击的处理差不多,拿到拖拽到的位置距离 canvas 左上角的 offset。
然后用 RayCaster 来计算和地板的相交位置,在那个位置添加家具。
这里拖拽的实现用了 react-dnd,给拖拽的目标(img 图片)添加 useDrag,拖拽到的目标(canvas)添加 useDrop
之后我们绘制了预览的场景,用 FlyControls 来控制自由浏览。

并且给用全景图加了窗外的风景。
最后实现了家具信息的表单编辑:

做完后我们和酷家乐的编辑器对比了下,整体流程是一样的:

总结下这个项目的技术点:
- 用 react、ts、three.js 来开发装修编辑器
- zustand 实现全局 store 存储,实现 3D 场景的数据化保存
- react-dnd 实现拖拽家具到画布,结合 three.js 的 RayCaster 实现拖拖拽家具到 3D 场景的任意位置
- Shape + ExtrudeGeometry + holes 绘制墙,Shape + ShapeGeometry 绘制地板天花板
- LineSegments + SpriteText 绘制尺寸标注
- 向量点积实现面对摄像头的那一面隐藏
- TransformControls 实现家具的位置、旋转角度编辑
- FlyControls 实现全景浏览的飞行视角
- 用 gltf-pipeline 压缩了模型,然后用 DracoLoader 加载
- 实现了模型加载的 loading,之后不再加载模型
做的过程比较曲折,代码也比较多,但这是一个比较综合的实战,把前面学到的基础综合运用了一遍。
以后再做任何 3D 编辑器的项目,都大差不差的,大家应该都有思路了。
做完这个之后,大家可以说自己可以做各种 3D 编辑器项目了。