3D低代码编辑中线的应用

今年下半年主要负责了一个3d低代码的开发,所以借此机会分享一些此块的东西

系列篇一,共计有三篇来完成,主要来完成下面gif中的示例

编辑器仓库地址

应用

在3D低代码编辑器中线有哪些应用呢?我们接下来的例子为实现以下案例

  1. 绘制墙体
  2. 绘制公里
  3. 种植树木、草坪

知识储备

threejs线扫盲

直线

在threejs中所有的物体都是由几何体和材质组成的,线当然也不例外

线型几何体

threejs核心包中没有内置线几何体,因为本身两个点就能确定一条直线较为简单。所以在threejs中,设置线相关的顶点信息只需要给BufferGeometry借助setFromPoints设置点坐标信息即可

线型材质

threejs内置了两种线型材质

  • LineBasicMaterial
  • LineDashedMaterial

如名称一样,LineDashedMaterial 主要用于描绘虚线

线型网格对象

在threejs中提供了三种线相关的网格对象

  • Line
  • LineLoop
  • LineSegments

它们的核心区别就是内部调用webgl的drawArrays APi mode参数不同

drawArrays 的7中mode

  • gl.POINTS: 绘制一系列点
  • gl.LINE_STRIP:绘制一个线条。即,绘制一系列线段,上一点连接下一点。
  • gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
  • gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
  • gl.TRIANGLE_STRIP:绘制一个三角带
  • gl.TRIANGLE_FAN:绘制一个三角扇
  • gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。

而这里threejs内置的三种线型集合体的核心区别是

  • Line 使用的gl.LINE_STRIP
  • LineLoop 使用的gl.LINE_LOOP
  • LineSegments 使用的gl.LINES

创建一个简单的线

ini 复制代码
const material = new THREE.LineBasicMaterial({
	color: 0x0000ff
});

const points = [];
points.push( new THREE.Vector3( - 10, 0, 0 ) );
points.push( new THREE.Vector3( 0, 10, 0 ) );
points.push( new THREE.Vector3( 10, 0, 0 ) );

const geometry = new THREE.BufferGeometry().setFromPoints( points );

const line = new THREE.Line( geometry, material );

曲线

生成曲线与直线的生成略有不同。直线两点就可确定,但是对于曲线它则需要更多的点,点数越多曲线越完美(但性能也需要的更多)。

故曲线比之直线多了一个步骤,那便是生成曲线点位信息。对于曲线的生成,threejs也内置了很多方式。比如使用率最高的线条曲线和贝塞尔曲线。

贝塞尔曲线

首先来写一个三维二次贝塞尔曲线来看一下

scss 复制代码
const curve = new QuadraticBezierCurve3(
    new Vector3(-10, 0, 0),
    new Vector3(20, 15, 0),
    new Vector3(10, 0, 0),
)


const points = curve.getPoints(50);
const geometry = new BufferGeometry().setFromPoints(points)
const material = new LineBasicMaterial({
    color: 0xff0000
})

const curveObject = new Line(geometry, material)

上面解释了,贝塞尔曲线更加像手绘版本,更加好看。那么我们在绘制一个样条曲线来对比一下,将控制点作为生成样条曲线的一个中间点位

scss 复制代码
const curve2 = new CatmullRomCurve3(
    [
        new Vector3(-10, 0, 0),
        new Vector3(20, 15, 0),
        new Vector3(10, 0, 0),
    ]
)


const points = curve2.getPoints(100);
const geometry = new BufferGeometry().setFromPoints(points)
const material = new LineBasicMaterial({
    color: "#ffffff"
})

const curveObject = new Line(geometry, material)

通过画面对比一下,贝塞尔曲线果真是好看不少

其他

  • 二次贝塞尔与三次贝塞尔的区别,表面是看是三次贝塞尔多了一个控制点。故可知它能实现的曲线效果可以更加高级复杂。更深处可以去了解两者的曲线方程

案例一:线的绘制

首先让我们先来写一个根据鼠标点绘制直线的功能

开始先来绘制一个平面

scss 复制代码
const planeGeometry = new PlaneGeometry(100, 100)
const material = new MeshBasicMaterial({ color: 0xffffff, side: 2 })

const plane = new Mesh(planeGeometry, material)

plane.rotateX(-Math.PI / 2)
scene.add(plane)

然后,我们在鼠标点击拾取到平面时获取当前坐标进行记录,并开始绘制直线

ini 复制代码
const MAX_POINTS = 500;
const positions = new Float32Array(MAX_POINTS * 3);
let count = 0

const lineGeometry = new BufferGeometry()
lineGeometry.setAttribute('position', new BufferAttribute(positions, 3));

const lineMaterial = new LineBasicMaterial({ color: 0x000000 })
const line = new Line(lineGeometry, lineMaterial)

const setPoints = (point: Vector3) => {
    positions[count * 3] = point.x
    positions[count * 3 + 1] = point.y
    positions[count * 3 + 2] = point.z

    count++

    line.geometry.setDrawRange(0, count);
}

点击是进行点的记录

typescript 复制代码
plane.addNatureEventListener('click', (object, intersect) => {
    setPoints(intersect?.point!)
})

其实这里的核心,就是线的重新渲染。有两种方式

  1. 将原line网格从场景中移除在重新走一遍绘制
  2. 更新buffer

一般为了性能考虑,最好是使用方式2。但是使用方式2有几个点需要注意一下

  1. 动态地调整BufferGeometry的buffer大小的操作通常会比较昂贵,因为这实际上涉及到创建一个全新的geometry,然后将旧数据复制到新的buffer中。这个过程可能会涉及内存的重新分配和大量数据的复制,这会导致性能消耗相对较高。因此,在使用BufferGeometry时,最好在创建时就预先分配足够大的buffer来容纳可能创建的任意新顶点数,而不是动态地调整buffer的大小。这样可以避免不必要的性能开销

如上代码示例我先创建了一个500个点的buffer

  1. 由于threejs基于性能的考虑,他一般只是读显存中的缓存。但是此时我们的BufferGeometry的调整还只是在内存中,我们需要在渲染的时候加上line.geometry.attributes.position.needsUpdate = true用于更新缓存

完整代码

scss 复制代码
import { use,PlaneGeometry, Mesh, SceneControl as Scene, MeshBasicMaterial, Vector3, BufferGeometry, Line, LineBasicMaterial, BufferAttribute } from 'thunder-3d'

const scene = new Scene({
    orbitControls: true,
    defCameraOps: {
        position: new Vector3(0, 10, 30)
    }
})

scene.render(document.querySelector('#app')!)



const planeGeometry = new PlaneGeometry(100, 100)
const material = new MeshBasicMaterial({ color: 0xffffff, side: 2 })

const plane = new Mesh(planeGeometry, material)

plane.rotateX(-Math.PI / 2)
scene.add(plane)


const MAX_POINTS = 500;
const positions = new Float32Array(MAX_POINTS * 3);
let count = 0


const lineGeometry = new BufferGeometry()
lineGeometry.setAttribute('position', new BufferAttribute(positions, 3));

const lineMaterial = new LineBasicMaterial({ color: 0x000000, linewidth: 2 })
const line = new Line(lineGeometry, lineMaterial)

scene.add(line)


const setPoints = (point: Vector3) => {
    positions[count * 3] = point.x
    positions[count * 3 + 1] = point.y
    positions[count * 3 + 2] = point.z

    count++

    line.geometry.setDrawRange(0, count);

    console.log(count)
}

plane.addNatureEventListener('click', (object, intersect) => {
    setPoints(intersect?.point!)
})

use.useframe(()=>{
    line.geometry.attributes.position.needsUpdate = true
})

注: thunder-3d是笔者封的一个threejs生态库

相关推荐
超人不会飛22 分钟前
就着HTTP聊聊SSE的前世今生
前端·javascript·http
蓝胖子的多啦A梦25 分钟前
Vue+element 日期时间组件选择器精确到分钟,禁止选秒的配置
前端·javascript·vue.js·elementui·时间选选择器·样式修改
夏天想27 分钟前
vue2+elementui使用compressorjs压缩上传的图片
前端·javascript·elementui
今晚打老虎z36 分钟前
dotnet-env: .NET 开发者的环境变量加载工具
前端·chrome·.net
用户38022585982441 分钟前
vue3源码解析:diff算法之patchChildren函数分析
前端·vue.js
烛阴1 小时前
XPath 进阶:掌握高级选择器与路径表达式
前端·javascript
小鱼小鱼干1 小时前
【JS/Vue3】关于Vue引用透传
前端
JavaDog程序狗1 小时前
【前端】HTML+JS 实现超燃小球分裂全过程
前端
独立开阀者_FwtCoder1 小时前
URL地址末尾加不加 "/" 有什么区别
前端·javascript·github
独立开阀者_FwtCoder1 小时前
Vue3 新特性:原来watch 也能“暂停”和“恢复”了!
前端·javascript·github