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

相关推荐
程序员小寒10 分钟前
前端高频面试题之Vue(初、中级篇)
前端·javascript·vue.js
陈辛chenxin17 分钟前
软件测试大赛Web测试赛道工程化ai提示词大全
前端·可用性测试·测试覆盖率
沿着路走到底18 分钟前
python 判断与循环
java·前端·python
Code知行合壹21 分钟前
AJAX和Promise
前端·ajax
大菠萝学姐31 分钟前
基于springboot的旅游攻略网站设计与实现
前端·javascript·vue.js·spring boot·后端·spring·旅游
心随雨下43 分钟前
TypeScript中extends与implements的区别
前端·javascript·typescript
摇滚侠1 小时前
Vue 项目实战《尚医通》,底部组件拆分与静态搭建,笔记05
前端·vue.js·笔记·vue
双向331 小时前
CANN训练营实战指南:从算子分析到核函数定义的完整开发流程
前端
caleb_5201 小时前
vue cli的介绍
前端·javascript·vue.js
Swift社区1 小时前
如何监测 Vue + GeoScene 项目中浏览器内存变化并优化性能
前端·javascript·vue.js