CAD图纸轻量化中圆的处理
CAD图纸轻量化方案的出现,使得图纸可直接在网页中查看,而不需要用户安装AutoCAD这样专业的软件。
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解析这块技术强,前端技术实力较弱,则方案一比较适合您。
如果你们前端技术实现强,那么方案三比较适合您。