三维组态部件动画解决方案

简介: 三维组态部件动画解决方案

三维组态部件动画解决方案


前言


最近做了一个制冷系统的三维组态监控系统。大致的效果如下图所示:


微信图片_20220425134302.png


其中涉及到的设备有冷却塔、水泵、螺杆机、离心机 、分水器(集水器)、阀门,以及管路。


其中冷却塔,水泵,螺杆机,离心机都有停机/开机状态,开机状态下要有叶轮转动效果。


因此我们需要给这几种模型,在开机状态下做叶轮转动的动画。


分离模型发方案


我们知道一般模型的动画主要是模型的位置,旋转角度等发生变化。位置旋转角度的变化是比较容易实现的一种动画。直接调用模型的setPosition方法和setRotation方法即可。


因此要做叶轮的旋转动画,一种比较简便的模式就是把叶轮的模型,从主体模型里面分离出来,做成一个独立的模型。然后针对独立模型不断的调用setRotation方法即可。示意代码如下:


1function animate(obj){
 2  new mono.Animate({
 3      dur:1000,
 4      from:0,
 5      to:1,
 6      onUpdate(v){
 7        obj.setRotationX(v * Math.PI *2);
 8     }
 9   }).play();           
10}


分离模型的好处是动画的实现方式比较简单。但是也有缺点,增加了模型的数量。比如一个设备有多少个部件要进行动画,那就得独立出多少个模型。另外一个缺点就是独立的出来的部件的模型,需要和建模人员沟通部件在整个模型中的位置,开发人员需要进行位置的摆放,增加了开发的复杂度。


为了解决上面的缺点,我们的想法是使用整体模型。


整体模型方案


不分离模型,把所有的部件都做到一个设备模型上面,但是对部件进行分组命名。这样我们在导入整个模型之后,可以通过分组命名获取到部件子模型。然后对部件子模型进行动画操作。


但是这个里面有一个问题是此时部件子模型的建模中心点不在子模型本身的中心,而是整体设备的中心。因此我们不能通过简单的setRotation的方法来进行模型的旋转动画。而是首先需要把旋转的中心点移动到子模型真实的中心,然后再进行旋转操作。

那么问题来了如何获取子模型本身的中心点呢?


包围盒(BoundingBox)


首先想到的是通过计算模型的包围盒来计算部件的中心点。但是由于通过OBJ格式导入的模型,它的每一个部件的包围盒都是整个模型的大小。所以我们需要修改包外盒的计算逻辑。之所以部件包围盒的大小和整个模型的大小一样,是因为所有部件的顶点都是共享了一个顶点数组,该顶点数组包括了所有的部件顶点的集合。而计算部件的包围盒的时候,是通过所有顶点来进行计算的。而部件的实际的包围盒应该是只能包括自己本身的顶点,因此我们重新构建一个包围盒的计算方法。代码如下:


1mono.Node.prototype.computeRealBoundingBox = function(){
 2    if (this.realBoundingBox == null) {
 3        this.realBoundingBox = new TGL.BoundingBox();
 4    }
 5    const vertices = this.vertices,
 6    faces = this.faces,
 7    realVertices = [];
 8
 9    let indices = [],set = new Set();
10    for(let i =0; i < faces.length;i ++){
11        let {a,b,c,d} = faces[i];
12        set.add(a);
13        set.add(b);
14        set.add(c);
15        if(d != null){
16        set.add(d);
17        }
18    }
19    indices = [...set];
20
21    indices.forEach((i) =>{
22        realVertices.push(vertices[i]);
23    });
24    this.realBoundingBox.setFromPoints(realVertices);
25    return this.realBoundingBox;
26}


通过包围盒得到了部件的实际中心点之后,可以使用先平移再旋转的方法来进行旋转操作。大致代码如下:


1  function rotate(obj,axisDir){
2      let bbox = (obj._realBoundingBox = obj._realBoundingBox ||  obj.computeRealBoundingBox());
3      let center = bbox .center();
4      obj.rotateFromAxis(axisDir,center,0.2);
5  }


首先获取部件真实的包围盒。然后调用对象的rotateFromAxis方法进行旋转,该方法内部的第一个参数指定旋转的轴,第二个参数指定旋转的中心点,第三个参数指定了旋转的角度;该方法内部实际上是先进行平移操作,在进行旋转操作,具体实现上通过矩阵的变换叠加来实现。看看效果:


微信图片_20220425134310.gif微信图片_20220425134314.gif


看起来好像完美,直到遇到这个模型:


微信图片_20220425134317.gif


感觉不对了,好像没有绕着中心点旋转。这是因为通过包围盒计算模型中心点的时候,模型需要是一个基于中心点可以任意均分的形状。上面第一个张图和第二张图就是这样的(限于x轴方向),而第三张图中的模型明显不是。


那应该如何计算第三种模型的中心点呢?答案是使用包围球。


包围球(BoundingSphere)


首先,构造一个方法,计算模型的包围球体。


1        mono.Node.prototype.computeRealBoundingSphere = function() {
 2            if (this.realBoundingSphere == null) {
 3                this.realBoundingSphere = new TGL.BoundingSphere();
 4            }
 5            const vertices = this.vertices,
 6            faces = this.faces,
 7            realVertices = []
 8
 9            let indices = [],set = new Set();
10            for(let i =0; i < faces.length;i ++){
11                let {a,b,c,d} = faces[i];
12                set.add(a);
13                set.add(b);
14                set.add(c);
15                if(d != null){
16                set.add(d);
17                }
18            }
19            indices = [...set];
20
21            indices.forEach((i) =>{
22                realVertices.push(vertices[i]);
23            });
24
25            var center = new mono.Vec3(),
26            vertexNum  = realVertices.length;
27            realVertices.forEach((v) =>{
28                center.x += v.x / vertexNum;
29                center.y += v.y / vertexNum;
30                center.z += v.z / vertexNum;
31            }) 
32
33            this.realBoundingSphere.setFromCenterAndPoints(center, realVertices);
34
35            return this.realBoundingSphere;
36        }


计算出部件的包围球之后,获取中心点,后面就是旋转模型,旋转的方法和前面说的一样。


1       function rotate(obj,axisDir){
2            let bSphere = (obj._realBoundingSphere = obj._realBoundingSphere ||obj.computeRealBoundingSphere());
3            let center = bSphere.center;
4            obj.rotateFromAxis(axisDir,center,0.2);
5        }


使用包围球后的效果:

image.gif微信图片_20220425134322.gif

可以看到中心旋转的效果出来了。


整体效果


最后,上一张整体的三维组态运行效果图:

微信图片_20220425134325.gif



总结


这种整体模型下部件进行动画的技巧,减少了需要建模师的工作量,同时也提高了开发人员的开发效率和开发的整体复杂度。


同时,这种方式有利于我们在三维组态编辑器中进行模型的整体导入和动画配置。因为如果是分离的模型,我们还需要在编辑器中导入多个模型再进行模型的拼装,位置对齐等难度很大。

相关文章
|
3天前
|
SQL 算法
LabVIEW开发机械手圆周插补轨迹控制
LabVIEW开发机械手圆周插补轨迹控制
|
10月前
|
定位技术 内存技术
GIS空间分析 三维分析4 制作飞行动画
本文中,我们利用ArcScene软件用3种方法制作了飞行动画
145 0
HMI-30-【运动模式】仪表右侧模块开始编写
今天喝了点小茶水,来了点写代码的感觉,那么今天就尝试吧运动模式仪表右侧的模块写了,我的资源文件中,只有音乐,所以右侧模块还是先把框架搭好,仅实现音乐模块。
HMI-30-【运动模式】仪表右侧模块开始编写
|
算法
三维手臂动态matlab仿真,可以灵活控制手指运动
三维手臂动态matlab仿真,可以灵活控制手指运动
93 0
三维手臂动态matlab仿真,可以灵活控制手指运动
从0开发游戏引擎之三维几何体数据类
Shape类只是单纯的形状数据,并不能用具真正的绘制,真正的绘制工作是Gizmo类去做的。该类只是作为Gizmo的一个成员去使用的。Shape的函数只是提供了加工数据的,然后把加工后的数据存下来。不多哔哔了,直接贴代码。原理有空了再详细写。
从0开发游戏引擎之在3D空间中渲染出三维几何体
这个类里面会使用第9章里的三维体数据来调用OpenGL的接口绘制出来对应的形状。几何体绘制类主要是调试使用的,比如想要更直观的看到一个对象身上的碰撞框。绘制的形状非常多,大家直接看代码吧。
西门子S7-200 SMART运动控制功能,如何使用向导组态运动轴?
从今天开始我们来介绍西门子S7-200 SMART运动控制功能,本篇我们先来学习如何使用向导组态运动轴。西门子S7-200 SMART CPU内置运动轴,可以实现速度和位置的开环运动控制。
西门子S7-200 SMART运动控制功能,如何使用向导组态运动轴?
西门子S7-200 SMART运动控制功能,使用向导组态运动轴
从今天开始我们来介绍西门子S7-200 SMART运动控制功能,本篇我们先来学习如何使用向导组态运动轴。
西门子S7-200 SMART运动控制功能,使用向导组态运动轴
西门子S7-200 SMART运动控制功能,运动轴组态,运动控制程序编写测试
今天我们来介绍一下西门子S7-200 SMART运动控制功能,下面我们先来学习如何使用向导组态运动轴。
西门子S7-200 SMART运动控制功能,运动轴组态,运动控制程序编写测试
|
移动开发 JSON 前端开发
拓扑/大屏/三维编辑器的设计与思考
拓扑/大屏/三维编辑器的设计与思考
拓扑/大屏/三维编辑器的设计与思考