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生态库

相关推荐
zqx_711 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己28 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2341 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河1 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端