CAD图纸轻量化中圆的处理

CAD图纸轻量化方案的出现,使得图纸可直接在网页中查看,而不需要用户安装AutoCAD这样专业的软件。

CAD图纸轻量化中圆的处理

CAD图纸在建筑行业及工业设计行业中被广泛使用,为了方便不同职能的人员也能方便的查看CAD图纸,就出现了CAD图纸轻量化方案的出现,轻量化后的图纸可直接在网页中查看,而不需要用户安装AutoCAD这样专业的软件。

进入正题,做过图纸轻量化的小伙伴都知道,通常我们会把dwg文件中的各种图形通过程序解析出来,生成一个中间文件,有的是json,有的是gltt,也有自己定义的,然后在网页端通过加载生成好的中间文件,再通过WebGL技术将这些图形重新绘制出来,就实现了让用户通过网页也能查看dwg图纸了。

当然如果确实不会软件编程的小伙伴,也可以直接使用AutoCAD插件,在网页端进行CAD图纸浏览和格式转换。

方案的核心三步如下图所示:

AutoCAD中常用到的图元类型如下:

解析圆通常有如下几种方案,每种方案的都会对比一下其优缺点。

方案一

解析时将圆形打断成很多条小线段,以32段为例来说,然后保存32个点坐标和这32个点的索引数组数据,在WebGL里再来绘制多段线。

坐标点 position: [p0,p1,p2,p3....p31]

每个点的坐标分别由x,y,z 三个float类型数值来表示,就得到坐标点的数组

[x0,y0,z0,x1,y1,z1,x2,y2,z2....x31,y31,z31]

就得到一个长度为96的一个单精度形数组。

索引数组 indices:[0,1,1,2,2,3,3,4,....30,31,31,0]

就得到一个长度为64的整形数组。

有了这两个数组,那么这个图形的描述数据就有了,现在还缺点啥呢?

没有错,形状有了,还没有颜色呢。由于一个圆形通常只有一种颜色,不会出现一个圆形上出现多种产色的情况,所以这里就用一个RGBA值来表示吧

颜色 color:[255,0,0,255]就有一个长度为4的int形数组表示吧。

细心的小伙伴可能发现,除了颜色还有线宽好像没有。对了这里再加一个线宽

线宽 width:1  就用一个整形来表示即可。

在绘制图形时还必须要有一个法向量,法向量是什么意思呢?简单理解就是告诉计算机,这个圆哪一面是正面哪一面是反面。

尽管此处圆形的正反面看上去都是一样的,但是在绘制三维图形的三角面时,这个法向量就至关重要了,因为一个面可能正面和反面的颜色不同,

要是搞反了可就不是我们要的效果了,好了,法向量概念在这里也不再发散了。

圆形的线型要讲来来就比较复杂,我们这里暂时不关心,统一当作实线来处理好了。

由上面的内容我们总结出来要正常描述这个圆形就得如下数据,我整理成一个json格式

{

"position":[x0,y0,z0,x1,y1,z1,x2,y2,z2,...x31,y31,z31],

"indices":[0,1,1,2,2,3,3,4,....30,31,31,0],

"color":[255,0,0,255],

"width":1

"normal":[] //

}

方案优点

1.中心数据看上清晰易懂,容易扩展。

2.前端页面也方便解析,在绘制方面和多段线一样绘制,无需其他处理。

方案缺点

1.生成的数据比较大,级数据的存储和网络传输带来压力。

2.生成的圆由于被打成了多条小线段,在放大圆时会发现这个圆不够圆。

方案二

上面的方案缺点太明显了,我们这个方案就针对它进行改良。

大家知道描述一个圆,其实只有一个圆心和一个半径即可,为什么要搞得那么麻烦将其打断成小线段呢?

所以这里我们说改变改。

圆心 center:[p0] 一个点即可表示,换成坐标数据得 [x0,y0,z0] 一个长度为3的单精度形数组。

半径radius:5.0 一个单精度数值即可表示。

颜色和线宽同上面的方案

颜色 color:[255,0,0,255]

线宽 width:1

此时我们可以得到图形的描述数据如下:

{

"type":"circle",  //这里必须指定该数据类型为圆,区别椭圆或弧形

"center":[x0,y0,z0],

"radius":5.0,

"color":[255,0,0,255],

"width":1

"normal":[] //

}

数据看上去比前一个小多了。但此时前端将要费点心思用圆心和半径将图形绘制出来,给个参考的Demo代码吧:

将下面代码直接保存为 index.html

<!DOCTYPE html>

<html>

<head>

<title>Draw a Circle with WebGL</title>

</head>

<body>

<canvas id="myCanvas" width="400px" height="400px"></canvas>

<script>

var canvas = document.getElementById('myCanvas');

var gl = canvas.getContext('webgl');

var vertexShaderSource = `

attribute vec2 position;

void main() {

gl_Position = vec4(position, 0.0, 1.0);

}

`;

var fragmentShaderSource = `

precision mediump float;

void main() {

gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

}

`;

var vertexShader = gl.createShader(gl.VERTEX_SHADER);

gl.shaderSource(vertexShader, vertexShaderSource);

gl.compileShader(vertexShader);

var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

gl.shaderSource(fragmentShader, fragmentShaderSource);

gl.compileShader(fragmentShader);

var program = gl.createProgram();

gl.attachShader(program, vertexShader);

gl.attachShader(program, fragmentShader);

gl.linkProgram(program);

gl.useProgram(program);

var vertices = [];

for (var i = 0; i <= 20; i++) {  //为了表现更明显一点,这里将圆切为20个小线段

var angle = i * Math.PI / 180;

var x = Math.cos(angle*360.0/20.0) * 0.5;

var y = Math.sin(angle*360.0/20.0) * 0.5;

vertices.push(x, y);

}

var positionBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

var positionAttributeLocation = gl.getAttribLocation(program, 'position');

gl.enableVertexAttribArray(positionAttributeLocation);

gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

gl.clearColor(0.9, 0.9, 0.9, 1.0);

gl.clear(gl.COLOR_BUFFER_BIT);

gl.drawArrays(gl.LINE_LOOP, 0, vertices.length / 2);

</script>

</body>

</html>

运行效果图:

方案优点

1.中间数据格式看上清晰易懂,容易扩展。

2.生成的中间JSON数据大小明显要小很多。

方案缺点

1.前端通过Javascrip将该圆生成了了多个小线段然后绘制,前端将消耗更多性能。

2.生成的圆任然是被打成了多条小线段,在放大圆时会发现这个圆不够圆。

该方案只解决了生成的中间数据文件过大的问题,看上去并没有完美的解决问题。

方案三

在上面的方案中存在的问题是前端通过javascrip来实现了将圆切分为多条拟合的小线段,然后再交由WebGL来生成圆形,javascrip的执行效率在小数据量时还能掌一下,当图形中有成千上万个圆时,通过javascrip来做这个动作将是一件糟糕的事情。

有没有一种办法可以高效的生成圆,且图形放大后看上去也很平滑呢?

当然有。

我们的思路是,不要让javascript去做消耗性能高的活,因为它消耗的是CPU的性能。那让谁呢?当然是我们的GPU啦!这里就引入GLSL语言,我们可以考虑让javascrip拿到圆的圆心和半径这些数据后,不做任务处理,直接传递给GLSL语言,然后在GLSL语言中来实现画圆,这样将运算转移到GPU.在GLSL中我们使用片源着色器,直接将圆形投影到屏幕上的像素绘制出来,就达到我们目的了。

下面是示例代码:

<!DOCTYPE html>

<html>

<head>

<title>Draw a Hollow Circle with WebGL an GLSL</title>

</head>

<body  onload="main()">

<canvas id="myCanvas" width="400px" height="400px">

abc

</canvas>

<script type="text/javascript">

var vertexShaderSource = `

attribute vec2 position;

void main() {

gl_Position = vec4(position, 0.0, 1.0);

}

`;

var fragmentShaderSource = `

precision mediump float;

void main() {

float radius = 0.5;

vec2 center = vec2(1, 1);

vec2 position = gl_FragCoord.xy / vec2(200.0, 200.0);

float dist = distance(center, position);

if (abs(dist - radius) < 0.01) { // 使用一个很小的阈值来绘制边界

gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置边界颜色

} else {

discard;

}

}

`;

function main(){

// Retrieve <canvas> element

var canvas = document.getElementById('myCanvas');

// Get the rendering context for WebGL

var gl = canvas.getContext('webgl');

if (!gl) {

console.log('Failed to get the rendering context for WebGL');

return;

}

var vertexShader = gl.createShader(gl.VERTEX_SHADER);

gl.shaderSource(vertexShader, vertexShaderSource);

gl.compileShader(vertexShader);

var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

gl.shaderSource(fragmentShader, fragmentShaderSource);

gl.compileShader(fragmentShader);

var program = gl.createProgram();

gl.attachShader(program, vertexShader);

gl.attachShader(program, fragmentShader);

gl.linkProgram(program);

gl.useProgram(program);

var vertices = [-1, -1, -1, 1, 1, 1, 1, -1];

var positionBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

var positionAttributeLocation = gl.getAttribLocation(program, 'position');

gl.enableVertexAttribArray(positionAttributeLocation);

gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

// Specify the color for clearing <canvas>

gl.clearColor(0.9, 0.9, 0.9, 1);

// Clear <canvas>

gl.clear(gl.COLOR_BUFFER_BIT);

// Draw the rectangle

gl.drawArrays(gl.TRIANGLE_FAN, 0, vertices.length / 2);

}

</script>

</body>

</html>

运行效果如下:

嗯。这个圆看上去比上面那个要圆多了。

此时要用到的JSON和方案二一样如下所示:

{

"type":"circle",  //这里必须指定该数据类型为圆,区别椭圆或弧形

"center":[x0,y0,z0],

"radius":5.0,

"color":[255,0,0,255],

"width":1

"normal":[] //

}

我在考虑这个JSON有没有进一步压缩的可能性。

答案是肯定的。我们发现这些数据中,有用的数据都是数字,

如果我们将它转为一个数值类型的数组存储,然后将该数值型数组生成到一个二进制文件,那么相信文件大小会更小。

定义 :

圆的类型为:1      //  椭圆的类型为:2  等等...为我们要处理的图形定义类型

圆心:[x0,y0,z0]    //假如圆心为0点,则有 [0,0,0]

半径:5.0

颜色:[255,0,0,255]  由于在AutoCAD中并没有那么多种颜色,它使用的是索引色,就是将所有的RGB色提炼成我们肉眼容易区分的255种颜色,

分别用1到255数值表示,如纯白RGB为[255,255,255]对应的索引色就是 7,纯红色RGB为[255,0,0] 对应的索引色就是1  ,有人可能会问那RGB为[254,0,0]的红色对应的索引色是多少呢?答案是1,没有错,跟上面的红色一样,因为人的肉眼无法区分RGB[255,0,0]和RGB[254,0,0]两种红色的区别,所以在索引色中都归为了1 红色。

这里颜色就可以用一个整型数值来表示。

宽度:1 //用一位整型数值表示

法向量: 由于二维图纸的渲染,不比三维模型,这里可以固定法向量为由屏幕指向用户的眼睛。也不必在中间数据中保存了。

这时我们得到一个数组:

[1,x0,y0,z0,5,7,1,x1,y1,z1,5,7.....]

第一项的1表示为圆,接下来要读取后面5项的数据来获取该圆的相关参数。

第二项到第四项的 x0,y0,z0就分别表示该圆的圆心坐标点。

第五项表示圆的半径。

第六项表示圆的颜色。

后面同理。

替换为真实数据如下:

[1,0,0,0,5,7,1,1,1,1,5,7]

就表示的是两个半径为5颜色为红色的圆形。

如果该数组是float型数组,那么每一项占用4个字节,表示一个圆要6项,那么就占用6*4=24个字节,

如果再将这个24字节的文件再用zip压缩一下,大小通常会压缩成原来的1/3到1/5,这里取1/4吧。

那么表示一个圆的数据大小就是24*1/4=6字节。

没有错只要6个字节即将这个圆的图形数据传递能WegGL并正确的渲染出来。

我们来总结下该方案的优缺点。

方案优点

1.中间数据被极致压缩,文件更小,占用空间及网络传输更有优势。

2.使用的WegGL的片源着色器绘制,图形更圆滑,放大也不会看到小线段。

3.绘图的动作全部交给GLSL语言来完成,消耗的是GPU资源,性能更好。

方案缺点

1.对前端技术要求较高,使用了GLSL,开发门槛更高。

2.中间数据为二进制,不便查看阅读。

最后我想说的是,技术方案没有最好的,只有最适合我们的,我们应该根据项目的实际需求,自身团队技术结构合理选择技术方案。

如果您们后端dwg解析这块技术强,前端技术实力较弱,则方案一比较适合您。

如果你们前端技术实现强,那么方案三比较适合您。

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号