【BIM】BIMFACE中实现电梯实时动效

靠山

在运维场景中,电梯作为运维环节主要的一部门是不可获缺的,若是能够在三维场景中,将真切的电梯效果,包罗外观、运行状态等表现出来,无疑是产物的一大亮点。本文将从无到有先容若何在bimface中实现真切的电梯运行效果,主要包罗电梯模子的建立、电梯上下行和停靠楼层动画的实现以及若何对接实时物联网数据来驱动电梯模子运行。

实践

建立电梯模子

首先建立一个立方体模子作为电梯,由于该电梯是外部构件,临时称之为外部电梯,运维场景中已经包罗了电梯模子,这个电梯是建模时代就已经完成的,暂时称之为内部电梯,用来为外部电梯提供起始位置信息。基于以上条件来说说也许的思绪方式,简化的电梯实际上就是一个有宽高深的立方体,然后为立方体的每一个面贴上响应的材质,以便于区分电梯的顶部、正面和其他侧面,然后把建立好的电梯作为外部构件加入到场景中准确的位置,那若何获取准确的位置呢?可以获取模子中的内部电梯的笼罩盒数据,通过笼罩盒数据盘算出外部电梯的位置即可。

let width = 1200, height = 2600, depth = 1000;
let elevatorGeometry = new THREE.BoxBufferGeometry(width, height, depth);
let group = new THREE.Group();
// 电梯侧面材质
let othersMaterial = new THREE.MeshPhongMaterial();
// 电梯顶部材质
let topMaterial = new THREE.MeshPhongMaterial();
// 电梯正面材质
let frontMaterial = new THREE.MeshPhongMaterial();

let loader = new THREE.TextureLoader();
loader.setCrossOrigin("Anonymous");
let others = loader.load('images/basic.png', function (map) {
    othersMaterial.map = map;
    othersMaterial.wireframe = false;
    othersMaterial.needsUpdate = true;
});

let top = loader.load('images/top.png', function (map) {
    topMaterial.map = map;
    topMaterial.wireframe = false;
    topMaterial.needsUpdate = true;
});

let front = loader.load('images/front.png', function (map) {
    frontMaterial.map = map;
    frontMaterial.wireframe = false;
    frontMaterial.needsUpdate = true;
});

let elevatorMaterials = [othersMaterial, othersMaterial, topMaterial, othersMaterial, frontMaterial, othersMaterial];
let elevatorMesh = new THREE.Mesh(elevatorGeometry, elevatorMaterials);

// 调整位置,使模子中电梯构件包罗外部电梯Mesh
group.add(elevatorMesh);
group.rotation.x = Math.PI / 2;
// _position是凭据模子中的电梯盘算得出,从外部传入
group.position.set(_position);
group.updateMatrixWorld();
_viewer_.addExternalObject(_name_, group);
_viewer_.render();

经由上述代码的处置,就可以在场景中看见新建立的电梯的也许样子了,效果如下:

【BIM】BIMFACE中实现电梯实时动效

现在电梯模子有了,然则为了能够实时显示电梯数据,我把电梯轿厢内的楼层指示牌放在电梯的外外面,以便于考察当前电梯的状态,如现在所在楼层、上下行等信息。

let panelWidth = 200, panelHeight = 200, segments = 100;
// 指示上下行的箭头
let panel = new THREE.PlaneBufferGeometry(panelWidth, panelHeight, segments, segments);
// 指示楼层
let panelFloor = new THREE.PlaneBufferGeometry(panelWidth, panelHeight, segments, segments);
// 界说各个楼层的材质
let belowOneFloorMaterial = new THREE.MeshBasicMaterial();
let OneFloorMaterial = new THREE.MeshBasicMaterial();
let TwoFloorMaterial = new THREE.MeshBasicMaterial();
let ThreeFloorMaterial = new THREE.MeshBasicMaterial();
let FourFloorMaterial = new THREE.MeshBasicMaterial();
let FiveFloorMaterial = new THREE.MeshBasicMaterial();
let SixFloorMaterial = new THREE.MeshBasicMaterial();
// 加载材质
let up = loader.load('images/ele_up.png', function (map) {
    upMaterial.map = map;
    upMaterial.wireframe = false;
    upMaterial.needsUpdate = true;
});
up.wrapS = THREE.RepeatWrapping;
up.wrapT = THREE.RepeatWrapping;
up.repeat.y = 1;
window[_name_] = up;

let down = loader.load('images/ele_down.png', function (map) {
    downMaterial.map = map;
    downMaterial.wireframe = false;
    downMaterial.needsUpdate = true;
});
down.wrapS = THREE.RepeatWrapping;
down.wrapT = THREE.RepeatWrapping;
down.repeat.y = 1;

let pathList = [];
pathList.push({ role: belowOneFloorMaterial, path: 'images/Digit/-1F.png' });
pathList.push({ role: OneFloorMaterial, path: 'images/Digit/1F.png' });
pathList.push({ role: TwoFloorMaterial, path: 'images/Digit/2F.png' });
pathList.push({ role: ThreeFloorMaterial, path: 'images/Digit/3F.png' });
pathList.push({ role: FourFloorMaterial, path: 'images/Digit/4F.png' });
pathList.push({ role: FiveFloorMaterial, path: 'images/Digit/5F.png' });
pathList.push({ role: SixFloorMaterial, path: 'images/Digit/6F.png' });

const buildMaterials = (item) => {
    return new Promise((resolve, reject) => {
        loader.load(item.path, function (map) {
            item.role.map = map;
            item.role.wireframe = false;
            item.role.needsUpdate = true;
        });
    });
}

for (let i = 0; i < pathList.length; i++) {
    buildMaterials(pathList[i]);
}
// 建立楼层信息面板(上下行指示箭头以及楼层)
let planeUpDownMesh = new THREE.Mesh(panel, upMaterial);
planeUpDownMesh.position.z = 505;
planeUpDownMesh.position.x = 210;

let planeFloorMesh = new THREE.Mesh(panelFloor, OneFloorMaterial);
planeFloorMesh.position.z = 505;
planeFloorMesh.position.x = 210;
planeFloorMesh.position.y = planeUpDownMesh.position.y - 200;
group.add(planeUpDownMesh);
group.add(planeFloorMesh);
_viewer_.addExternalObject(_name_, group);
_viewer_.render();

电梯指示牌由两个尺寸相同的PlaneBufferGeometry作为基底,一个用于指示上下行,接纳了两个箭头图片作为材质;另一个指示楼层信息,一共有七个楼层,接纳七个数字图片作为材质,以便于切换楼层。

【BIM】BIMFACE中实现电梯实时动效

至此,组成电梯模子的各个部门均已经加入到场景中,下一步让电梯、上下行指示箭头和楼层信息动起来!

建立电梯动画

首先先从指示箭头入手,指示箭头指示电梯的上下行状态,默认是向上移动,它是由PlaneBufferGeometry贴上材质获得的,若是想获取动画效果,就要不停地改变材质的offset参数并同时渲染。在上一部门有这样一行代码window[name] = up;作用是将箭头的材质存储到全局变量中,以便于外部修改它的offset参数来实现动画。

// 界说移动速率
const SPEED = 0.04;

let mgr = viewer.getExternalComponentManager();
function animation() {
    if (!window[_name_]) {
        window[_name_] = up;
    }
    window[_name_].offset.y += SPEED * _direction_;    
    mgr.setTransform(_name_, _position_);
    requestAnimationFrame(animation.bind(this));
    viewer.render();
}
animation();

【BIM】BIMFACE中实现电梯实时动效

你知道Spring是怎么解析配置类的吗?

现在指示箭头可以向上移动了,然则电梯不是单向运行,下行时就要改变箭头的指向以及移动偏向,这里就涉及到材质的动态替换了。为了实现更真切的物理效果,这里引入了Tween.js组件举行动画过渡。

import TWEEN from '../Tween.js'

let tween = new TWEEN.Tween(_position_)
        .to({ z: height / 2 }, 10)
        .onUpdate(onUpdate)
        .onStart(onStart)
        .onComplete(onComplete)
        .start();

function onStart(object) {
    console.log("start");
    if (_target_floor_ - _current_floor_ < 0) {
        // 下行时替换为向下的箭头并改变材质移动偏向
        _direction_ = GO_DOWN;        
        window[_name_] = downMaterial.map;
        planeUpDownMesh.material = downMaterial;
    } else {
        _direction_ = GO_UP;
        window[_name_] = upMaterial.map;
        planeUpDownMesh.material = upMaterial;
    }
};

电梯上下行动画已经解决,下一步让电梯的轿厢动起来,首先获取电梯的起始位置和到达位置,再通过Tween.js实现过渡动画,模拟电梯平稳升降的历程。起始和到达位置可以通过按钮来模拟,以下代码是用于模拟电梯运动的数据,其中data-level示意目的楼层,data-high示意楼层高度:

<div id="levels" style="position: absolute;left:125px;top:25px;width: 60%;height: 30px;">
    <button class="fl" data-level="-1" data-high=-5200>B01</button>
    <button class="fl" data-level="1" data-high=0>F01</button>
    <button class="fl" data-level="2" data-high=4500>F02</button>
    <button class="fl" data-level="3" data-high=8300>F03</button>
    <button class="fl" data-level="4" data-high=12100>F04</button>
    <button class="fl" data-level="5" data-high=15900>F05</button>
    <button class="fl" data-level="6" data-high=19700>F06</button>
</div>
let INTERVAL = 2000;
let list = document.getElementsByClassName(_domClass_);
for (let b = 0, len = list.length; b < len; b++) {
    list[b].addEventListener("click", (e) => {
        let val = list[b].getAttribute('data-high');
        _target_floor_ = list[b].getAttribute('data-level');
        // 凭据电梯跨越的层数盘算运行时间
        _time_ = Math.abs(_target_floor_ - _current_floor_) * INTERVAL;
        let _height = Number(val) + (height / 2);

        tween = null;
        tween = new TWEEN.Tween(_position_)
            .to({ z: _height }, _time_)
            .easing(TWEEN.Easing.Cubic.Out)
            .onUpdate(onUpdate)
            .onStart(onStart)
            .onComplete(onComplete)
            .start();
    });
}

完成上述代码后,我们就可以通过按钮模拟电梯上下行的动画,同时箭头会凭据电梯上下行自行调整到准确的指示和移动偏向,然则还缺少切换楼层的步骤,当电梯从起始位置出发后,到达目的位置时,应该讲楼层展示为目的楼层,这一步和切换指示箭头偏向的逻辑是一致的,通过动态修改材质实现,我们将这一步写在Tween.js完成动画后的complete事宜回调函数中,当电梯住手后将材质修改为目的楼层的材质。

function onComplete(object) {
    // 完成动画后,切换楼层文本
    if (_direction_ < 0) {
        _direction_ = -1;
        window[_name_] = downMaterial.map;
        planeUpDownMesh.material = downMaterial;
    } else {
        _direction_ = 1;
        window[_name_] = upMaterial.map;
        planeUpDownMesh.material = upMaterial;
    }
    _current_floor_ = _target_floor_;
    //切换当前坐标
    _position_.z = object.z;
    
    //切换楼层
    switch (_current_floor_) {
        case 1:
            planeFloorMesh.material = OneFloorMaterial;
            break;
        case 2:
            planeFloorMesh.material = TwoFloorMaterial;
            break;
        case 3:
            planeFloorMesh.material = ThreeFloorMaterial;
            break;
        case 4:
            planeFloorMesh.material = FourFloorMaterial;
            break;
        case 5:
            planeFloorMesh.material = FiveFloorMaterial;
            break;
        case 6:
            planeFloorMesh.material = SixFloorMaterial;
            break;
        case -1:
            planeFloorMesh.material = belowOneFloorMaterial;
            break;
    }
};

到这一步,关于电梯模子的建立以及动画的建立就完成了,然则驱动电梯运行的方式照样通过按钮来模拟的,下一步接纳接入电梯物联网数据来取代按钮的方式,让IoT实时数据驱动电梯运行。

物联网数据驱动电梯运行

这一部门依赖于websocket毗邻实现,也许的思绪就是后端微服务会提供socket毗邻池,通过匹配ServerEndpoint举行毗邻,每当有IoT数据上报时,socket毗邻就会向前端页面推送电梯运行数据,拿到这些数据后,在websocket的吸收新闻的回调中处置数据,从而实现整个的数据驱动电梯的历程。下面调整一下代码,将按钮模拟电梯运行的代码重构下,放在websocket的吸收新闻的回调中。

// 引入websocket取代上面的按钮事宜
var socket;
socket = new WebSocket("ws://localhost:8087/websocket/0004/" + _id_);

socket.onopen = () => {
    console.log("socket opened!");
}

// msg中包罗电梯的IoT运行数据
socket.onmessage = (msg) => {
    let _data = JSON.parse(msg.data);
    let val = 0;
    if (_data.data) {
        let _iot_data = JSON.parse(_data.data);
        if (_iot_data.hight >= 0 && _iot_data.direction >= 0) {
            val = _iot_data.hight;
            _target_floor_ = _iot_data.floor;
            _time_ = Math.abs(_target_floor_ - _current_floor_) * INTERVAL;
            let _height = Number(val) + (height / 2);

            tween = null;
            tween = new TWEEN.Tween(_position_)
                .to({ z: _height }, _time_)
                .easing(TWEEN.Easing.Cubic.Out)
                .onUpdate(onUpdate)
                .onStart(onStart)
                .onComplete(onComplete)
                .start();
        }
    }
}

socket.onclose = () => {
    console.log("socket closed!");
}

socket.onerror = () => {
    console.error("socket error!");
}

效果

在场景中建立两部电梯,一部位于一层,另一部位于二层,通过向websocket后台微服务发送电梯实时IoT数据实现驱动电梯效果。

【BIM】BIMFACE中实现电梯实时动效

总结

整个模拟真实电梯场景的历程主要由三个部门组成,首先通过形状BoxBufferGeometryPlaneBufferGeometry和材质MeshPhongMaterialMeshBasicMaterial建立出电梯并初始化在准确的位置;其次将动画应用于电梯的各个组成部门,主要是应用了Tween.js以及requestAnimationFrame;最后将电梯的物联网数据通过websocket方式接入进来以便于驱动电梯运行。

作者:
悠扬的牧笛


地址:
https://www.cnblogs.com/xhb-bky-blog/p/12819796.html


声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面显著位置给出原文毗邻。

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/8053.html