HarmonyOS鸿蒙Next中在线CAD如何配合three.js绘制带线宽的线段
HarmonyOS鸿蒙Next中在线CAD如何配合three.js绘制带线宽的线段 前言
-
在线CAD的产品经常会被集成到很多用户的网页系统内,前端开发人员只要会Java Script,就可以对在线CAD进行集成和二次开发,今天这篇文章我们讲一下梦想CAD控件云图(H5方式)如何配合three.js绘制带线宽的线段。
-
在这之前,如果还没有安装梦想CAD控件的朋友,可以查看快速入门,链接如下:
http://help.mxdraw.com/?pid=32
函数配置
首先mxdraw的图形是有线宽属性的,但是在连续线段可能会存在一些问题,或者你希望用three.js来实现一些自定义的图形,那么我们就可以使用mxdraw提供的MxDbEntity来实现这样一个带线宽的线段,我们先把最基本需要重写的函数写出来:
import { McGiWorldDraw, MxDbEntity } from "mxdraw"
class MxDbLine extends MxDbEntity {
getTypeName(): string {
return "MxDbLine"
}
worldDraw(pWorldDraw: McGiWorldDraw): void {
}
getGripPoints(): THREE.Vector3[] {
return []
}
moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
return true
}
dwgIn(obj: any): boolean {
this.onDwgIn(obj)
return true
}
dwgOut(obj: any): object {
this.onDwgOut(obj)
return obj
}
}
定义线段数据
现在我们就有了一个MxDbLine类,用来表示它是一条线段,但是它没有任何与线段有关的数据,我们要先定义一些线段数据,代码如下:
class MxDbLine extends MxDbEntity {
points: THREE.Vector3[] = []
dwgIn(obj: any): boolean {
this.onDwgIn(obj)
this.dwgInHelp(obj, ["points"])
return true
}
dwgOut(obj: any): object {
this.onDwgOut(obj)
this.dwgOutHelp(obj, ["points"])
return obj
}
}
现在我们有了points数据了 这些点可以构成一段段的线段,但是它现在还不能在画布中渲染,这时还需要用three.js来实现一个带线宽的线段结合体,这个如何实现呢?
首先,可以在three.js示例中找到Line2 这样相关的类,它就可以实现带线宽的线段,我们先安装:
npm i three@0.113.2
现在只需要Line2、LineGeometry、LineMaterial这三个类,你可以不用安装three@113.2的依赖,只需要找到它对应示例的文件,引入到项目中就可以了,以下是具体的实现代码:
import { Line2 } from 'three/examples/jsm/lines/Line2'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
export class MxDbLine extends MxDbEntity {
material: LineMaterial
worldDraw(pWorldDraw: McGiWorldDraw): void {
const material = new LineMaterial()
this.material = material
const geometry = new LineGeometry()
const THREE = MxFun.getMxFunTHREE()
material.color = new THREE.Color(this.color)
const canvas = MxFun.getCurrentDraw().getCanvas()
material.resolution.set( canvas.width, canvas.height)
material.transparent = true
const positions: number[] = []
for(let i = 0; i < this.points.length; i++) {
positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)?.z || 0)
}
geometry.setPositions(positions)
const line2 = new Line2(geometry, material)
pWorldDraw.drawEntity(line2)
}
}
宽度的配置
现在我们基本上用three.js示例中提供的Line2类绘制线段,MxDbLine也可以完整的显示一条线段了,但是它还没有宽度。
在MxDbEntity中提供了dLinewidth属性用于表示线宽,用lineWidthByPixels属性表示线宽是否始终跟随屏幕宽度,也就是画布缩放,线的宽度始终不变,当lineWidthByPixels为false时就是另一种three.js中的坐标系宽度,这种宽度是固定的了,不会随着画布缩放而变化。
要实现这两种宽度还需要了解到MxDbEntity重写方法onViewChange 当画布缩放时onViewChange 就会执行,还需要了解的是要将当前的屏幕坐标长度转成three.js坐标系长度,在mxdraw中提供了MxFun.screenCoordLong2World来转换。
默认dLinewidth都是跟随屏幕的宽度,我们需要先记录当前绘制这条线段时,1屏幕像素转成three.js坐标系长度的值是多少,然后后面需要根据lineWidthByPixels属性判断是用跟随屏幕像素的宽度还是three.js坐标系一样的固定宽度。
如果lineWidthByPixels = false 那么我们就可以通过当时绘制时记录的three.js坐标系长度的值去比上现在这个时候的1屏幕像素下的three.js坐标系长度,这样就得到了一个线宽比,用线宽比去乘以目前设置的dLinewidth宽度,就算实现需要的宽度了。
如果lineWidthByPixels = true 就不用这么麻烦了,dLinewidth就算我们需要的宽度,具体的代码如下:
export class MxDbLine extends MxDbEntity {
_lineWidthRatio: number
updateLineWidth() {
if(!this._lineWidthRatio) {
this._lineWidthRatio = MxFun.screenCoordLong2World(1)
}
this.material.linewidth =
this.lineWidthByPixels ? this.dLineWidth :
this.dLineWidth *
this._lineWidthRatio / MxFun.screenCoordLong2World(1)
}
worldDraw(pWorldDraw: McGiWorldDraw): void {
this.updateLineWidth()
const material = new LineMaterial()
this.material = material
const geometry = new LineGeometry()
const THREE = MxFun.getMxFunTHREE()
material.color = new THREE.Color(this.color)
const canvas = MxFun.getCurrentDraw().getCanvas()
material.resolution.set( canvas.width, canvas.height)
material.transparent = true
const positions: number[] = []
for(let i = 0; i < this.points.length; i++) {
positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)?.z || 0)
}
geometry.setPositions(positions)
const line2 = new Line2(geometry, material)
pWorldDraw.drawEntity(line2)
}
onViewChange() {
if(!this.lineWidthByPixels) {
this.setNeedUpdateDisplay()
MxFun.updateDisplay()
}
return true
}
getGripPoints(): THREE.Vector3[] {
return this.points
}
moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
this.points[index] = this.points[index].clone().add(offset)
return true
}
dwgIn(obj: any): boolean {
this.onDwgIn(obj)
this.dwgInHelp(obj, ["points", "_lineWidthRatio"])
return true
}
dwgOut(obj: any): object {
this.onDwgOut(obj)
this.dwgOutHelp(obj, ["points", "_lineWidthRatio"])
return obj
}
}
最后就是使用我们写好的MxDbLine类,代码如下:
import { MxFun, MrxDbgUiPrPoint } from "mxdraw"
const drawLine = () => {
const line = new MxDbLine()
line.dLineWidth = 10
const getPoint = new MrxDbgUiPrPoint()
getPoint.setUserDraw((currentPoint, pWorldDraw) => {
if(line.points.length === 0) return
if(line.points.length >= 2) {
pWorldDraw.drawCustomEntity(line)
}
pWorldDraw.drawLine(currentPoint, line.points[line.points.length - 1])
})
getPoint.goWhile(() => {
line.points.push(getPoint.value())
}, () => {
MxFun.getCurrentDraw().addMxEntity(line)
})
}
绘制带宽度的线段过程,如下图:
这时绘制完成后的效果:
把line.lineWidthByPixels 设置成false 当缩放画布时,线段就不会始终是屏幕宽度了,而是当时绘制时的three.js实际宽度。
带宽度的线段当画布缩放时宽度不随屏幕一起变大,如下图:
下面给MxDbLine的完整代码:
import { McGiWorldDraw, McGiWorldDrawType, MxDbEntity, MxFun } from "mxdraw"
import { Line2 } from 'three/examples/jsm/lines/Line2'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
export class MxDbLine extends MxDbEntity {
points: THREE.Vector3[] = []
material: LineMaterial
_lineWidthRatio: number
worldDraw(pWorldDraw: McGiWorldDraw): void {
const material = new LineMaterial()
this.material = material
const geometry = new LineGeometry()
const THREE = MxFun.getMxFunTHREE()
material.color = new THREE.Color(this.color)
const canvas = MxFun.getCurrentDraw().getCanvas()
material.resolution.set( canvas.width, canvas.height)
material.transparent = true
this.updateLineWidth()
const positions: number[] = []
for(let i = 0; i < this.points.length; i++) {
positions.push(this.points[i].x, this.points[i].y, (this.points[i] as any)?.z || 0)
}
geometry.setPositions(positions)
const line2 = new Line2(geometry, material)
pWorldDraw.drawEntity(line2)
}
updateLineWidth() {
if(!this._lineWidthRatio) {
this._lineWidthRatio = MxFun.screenCoordLong2World(1)
}
this.material.linewidth =
this.lineWidthByPixels ? this.dLineWidth :
this.dLineWidth *
this._lineWidthRatio / MxFun.screenCoordLong2World(1)
}
onViewChange() {
this.setNeedUpdateDisplay()
MxFun.updateDisplay()
return true
}
getGripPoints(): THREE.Vector3[] {
return this.points
}
moveGripPointsAt(index: number, offset: THREE.Vector3): boolean {
this.points[index] = this.points[index].clone().add(offset)
return true
}
dwgIn(obj: any): boolean {
this.onDwgIn(obj)
this.dwgInHelp(obj, ["points", "_lineWidthRatio"])
return true
}
dwgOut(obj: any): object {
this.onDwgOut(obj)
this.dwgOutHelp(obj, ["points", "_lineWidthRatio"])
return obj
}
}
Demo源码链接:
以上,在线CAD如何配合three.js绘制带线宽的线段功能就完成了,有不清楚的请移步梦想CAD控件官网。
更多关于HarmonyOS鸿蒙Next中在线CAD如何配合three.js绘制带线宽的线段的实战教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next中,使用在线CAD配合three.js绘制带线宽的线段,可以通过以下步骤实现:
-
初始化three.js场景:首先,创建一个three.js场景,包括场景、相机、渲染器等基本元素。
-
加载CAD模型:使用在线CAD的API加载CAD模型,并将其转换为three.js可识别的格式(如JSON或OBJ)。
-
创建带线宽的线段:在three.js中,使用
THREE.Line
或THREE.LineSegments
创建线段。为了设置线宽,可以使用THREE.LineBasicMaterial
的linewidth
属性,但需要注意的是,linewidth
在某些设备和浏览器上可能不受支持。 -
处理线宽限制:由于WebGL的限制,
linewidth
在某些情况下可能无法生效。可以通过将线段转换为带有厚度的几何体(如THREE.Mesh
)来模拟线宽效果。具体方法是将线段扩展为一个矩形或圆柱体,并设置其厚度。 -
渲染场景:将处理后的线段添加到场景中,并使用渲染器进行渲染。
-
交互与更新:如果需要动态更新线段或线宽,可以通过监听CAD模型的变化,并实时更新three.js中的几何体和材质。
通过以上步骤,可以在HarmonyOS鸿蒙Next中实现带线宽的线段绘制。
更多关于HarmonyOS鸿蒙Next中在线CAD如何配合three.js绘制带线宽的线段的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next中,使用three.js绘制带线宽的线段可以通过以下步骤实现:
- 创建几何体:使用
THREE.BufferGeometry
创建线段的基础几何体。 - 设置顶点和颜色:为几何体添加顶点和颜色属性。
- 使用LineMaterial:采用
THREE.LineMaterial
并设置linewidth
属性来定义线宽。 - 创建线条:使用
THREE.Line2
将几何体和材质组合成线条对象。 - 添加到场景:将线条对象添加到three.js场景中进行渲染。
这种方法可以有效地在HarmonyOS鸿蒙Next中实现带线宽的线段绘制。