如何用webgl(three.js)搭建一個3D庫房,3D倉庫,3D碼頭,3D集裝箱可視化孿生系統——第十五課

  又是快兩個月沒寫隨筆了,長時間不總結項目,不鍛煉文筆,一開篇,多少都會有些生疏,不知道如何開篇,如何寫下去。有點江郎才盡,黔驢技窮的感覺。

  寫隨筆,通常三步走,第一步,搭建框架,先把你要寫的內容框架搭建出來;第二步,添磚,在框架基礎上,填寫各部分內容;第三步,加瓦,再寫好的內容上進行修改,潤濕。然後文章的品質,就因人而異了。但不管怎麼說,得寫,得練,得經受的起各路能人志士的批評指教,至於改不改,那也是寫文章的人的事了(通常我是認真接受批評指教的)。

  你看,寫道這裡,我又不知道再序些啥了,索性就這樣吧。

  閑話少序,切入正題

前言

  前面的課程有講解過庫房相關的,但都是密集架庫房,檔案室庫房類的(《如何用webgl(three.js)搭建一個3D庫房,3D密集架,3D檔案室(升級版)》《如何用webgl(three.js)搭建一個3D庫房,3D密集架,3D檔案室,-第二課》《如何用webgl(three.js)搭建一個3D庫房-第一課》)

  該篇主要講解堆放箱體的庫房,以及碼頭集裝箱類似的庫房場地解決方案。

  可視化孿生系統實現起來主要是數據源、業務系統、展示方案這三大部分。

  數據源:就是數據的來源,針對該篇文章,是如何對庫房,庫位的數據進行採集,錄入。物聯網廠家通常叫做前端採集模組。

      該項目,數據源主要通過 rfid+手動錄入 的方式,項目中庫位主要分了室內和室外兩大部分,室內通過門口rfid門禁知道箱子的出入,再通過操作員手動錄入箱子的位置(再庫位上,詳細劃分了位置編號);室外部分直接通過操作員手動錄入入庫出庫資訊。

  業務系統:針對採集上來的數據,如何進行有效的處理與存儲,如何有效符合客戶功能需求,以及要綜合考慮數據源結構,展示端需求數據結構,系統性能等。這些都是業務系統的主要功能要求。

       業務系統,也是我們程式設計師常說的後端服務。

  展示方案:爭對客戶需求,設計符合客戶要求的交互三維可視化方案。

  該篇我們主要詳細講解展示端方案。

  

一、整體效果及功能

1.1、庫房外部及周遭場景

通過對園區進行建模,虛擬模擬周邊道路環境,實現整體場景展示。

1.2、外部庫位集裝箱資訊,以及車輛資訊

滑鼠滑動到集裝箱,或者車輛上,顯示貨物,車輛資訊。

 

 具體實現滑動顯示

ModelBussiness.prototype.mouseInCurrentObj = null;
ModelBussiness.prototype.lastMouseInCurrentObj = null;
//滑鼠滑入事件
ModelBussiness.prototype.mouseOverInCallBack = function (_obj, face, objs) {
  console.log(_obj.name);
  var _this = modelBussiness;
  WT3DObj.controls.autoRotate = false;
  
  var color = 0xbfffea;
  modelBussiness.lastMouseInCurrentObj = _obj;
  modelBussiness.mouseInCurrentObj = _obj;
  if (_obj.name.indexOf("dev_car_") >= 0) {

        var _sobj = _obj;
        if (_obj.name.indexOf("OBJCREN") > 0) {
            _sobj = _obj.parent;
        }
        var id = (_sobj.name.split("_Model_")[1]);

        var name = id;
        modelBussiness.mouseInCurrentObj = _sobj;
        _sobj.visible = true;
        WT3DObj.commonFunc.setSkinColorByObj(_sobj, 0x00ffff);
        $("#MarkMessageHelper").remove();
        $("body").append("<div id='MarkMessageHelper' style='position:absolute;left:" + (window.event.pageX) + "px;top:" + (window.event.pageY - 10) + "px;height:2px;width:2px;z-index:1000;'></div>");
        showCarinfo(name,id);
    }

}

 

//展示貨物資訊
function showGoodInfo(name, id) {
    //顯示結構部分
    var html = ' XXXXX';
    //彈窗
    layer.tips(html, "#MarkMessageHelper", {
        tips: [1, '#003333'],//彈窗類型與顏色
        time: 0,//彈窗自動關閉時長 0表示不自動關閉
        area: ["415px", "230px"],//彈窗大小
        success: function () {//彈窗顯示後回調
            setTimeout(function () {
                //數據介面 根據id獲取貨物詳細資訊
                webapi.GetAllGoodsInfo(id, function (result) {
                    if (result) {
                        modelBussiness.cacheData = {
                            id: id,
                            result: result
                        };
                        for (var item in result) {//填充彈窗內結構的數據
                            $("#devParamValue3D" + id + "_" + item).html(result[item]);
                            if (item == "photo_urls") {
                                var _html = "";
                                $.each(result[item], function (_pindex, _pobj) {

                                    _html += ' <div style="float:left;cursor:pointer;margin-right:10px;" onclick="modelBussiness.showPics(\'' + _pobj.url + '\',\'' + _pobj.doctype + '\')">' + _pobj.doctype + '</div>';
                                })
                                $("#devParamValue3D" + id + "_photos").html(_html);
                            }
                        }
                    } else {
                        $("#devParamValue3D" + id + "_content").html("<font style='color:red;'>獲取數據異常</font>");
                    }
                })
            }, 200);
        }
    });

}

 

1.3、內部倉庫場景

 雙擊進入內部室內倉庫

 

 綁定雙擊事件,實現跳轉即可

 1.4、分區塊資訊

 建模時,已經固定分區,所以直接將分區標題固定即可

 

 分區展示名稱,直接再建模的時候固定即可。

 按區塊展示各部分庫存資訊

 實現方法

ModelBussiness.prototype.showAreaGoods = function (code, callBack) {
    var objs = [];
    var hideobjs = [];
    $.each(WT3DObj.scene.children, function (_index, _obj) {
        //遍歷所有模型,找到對應的模型展示。非對應貨物 隱藏
        if (_obj.name.indexOf("location2_") == 0) {
            _obj.visible = true;
            if (_obj.oldPositionY || _obj.oldPositionY == 0) {
                _obj.position.y = _obj.oldPositionY;
            }
        }
        if (_obj.name.indexOf("g_") == 0) {
            _obj.visible = true;
            if (code == "ALL") {
                _obj.visible = true;
            } else {
                if (_obj.name.indexOf("_Area_" + code) > 0) {
                    _obj.visible = true;
                } else {
                    _obj.visible = false;
                }
            }
        }
    });
  
}

 

 1.5、單獨庫位展示

 單獨庫位展示,採用iframe彈框方式,有效節約資源,降低邏輯複雜度。

 

 

  _this.currentCameraInfo.position ={
            x:WT3DObj.camera.position.x,
            y: WT3DObj.camera.position.y,
            z: WT3DObj.camera.position.z
        } ;
        _this.currentCameraInfo.target = {
            x: WT3DObj.controls.target.x,
            y: WT3DObj.controls.target.y,
            z: WT3DObj.controls.target.z
        };
       
        _this.nearCameraPostion(_sobj, _face, objs);


        var code = _sobj.name.replace("location2_","")
        var index = layer.open({
            type: 2,
            skin: 'myLayer',
            shade: 0.8,
            title: "庫位:" + code,
            area: [($(window).width() - 100) + "px", ($(window).height() - 100) + "px"],
            fixed: false, //不固定
            maxmin: false,
            content: "locationDetail.html?location=" + code,
            cancel: function () {
                WT3DObj.commonFunc.changeCameraPosition(_this.currentCameraInfo.position, _this.currentCameraInfo.target, 500, function () {

                })
            }
        });

View Code

 

 1.6、貨物搜索定位

 實現貨物快速定位與檢索

 

實現方法

//搜索動作
ModelBussiness.prototype.searchActionSate = false;
ModelBussiness.prototype.searchAddObjNames = [];
ModelBussiness.prototype.searchAction = function (result) {
    layer.load();
    var _this = this;
    WT3DObj.commonFunc.changeCameraPosition({ x: 1138.6583366079776, y: 7190.772604284317, z: 9690.731322273507 }, { x: 5051.345919026784, y: 678.7143248996384, z: 2255.8201639552867 }, 500,
         function () {
             modelBussiness.cancelSearchAction(function () {
                 var type="";
                 if (window.location.href.indexOf("index.html") >= 0) {
                     type = "jzx";
                 }
                 _this.searchActionSate = true;
                    var resultobj={};
                    $.each(result, function (_index, _obj) {
                        //areaId: "F5"
                        //id: "cf792a67-bfed-488b-8570-915a73341777"
                        //name: "20006010-2-2"
                        resultobj["g_" + _obj.id] = _obj;
                     
                    });

                    var models = [];
                    var objs = [];
                    modelBussiness.searchAddObjNames = [];
                    $.each(WT3DObj.scene.children, function (_index, _obj) {
                        //areaId: "F5"
                        //id: "cf792a67-bfed-488b-8570-915a73341777"
                        //name: "20006010-2-2"
                        if (!_obj.oldPositionY && _obj.oldPositionY != 0) {
                            _obj.oldPositionY = _obj.position.y;
                        }

                        if (_obj.name.indexOf("location2_") == 0) {
                            _obj.visible = false;
                            _obj.position.y = 1000000;
                        }
                        if (_obj.name.indexOf("g_") == 0) {
                            objs.push(_obj);
                            var cobj = resultobj[_obj.name.split("_Area_")[0]];
                            if (cobj) {
                                modelBussiness.searchAddObjNames.push("gSearch_" + cobj.id + "_name_" + cobj.name);
                               var cacheobj= _this.cacheGoodsResult["c_" + cobj.id];
                               models.push(createGoodCubeModels("gSearch_" + cobj.id + "_name_" + cobj.name, cobj.name, _obj.position, { x: _obj.scale.x * 100, y: _obj.scale.y * 100, z: _obj.scale.z * 100 }, type, cacheobj ? cacheobj.color : 0));
                               _obj.visible = false;
                               _obj.position.y = 1000000;
                            } 
                        }
                    });


                    if (models && models.length > 0) {

                        WT3DObj.commonFunc.loadModelsByJsons(models, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0 }, true, function () {
                            WT3DObj.commonFunc.changeObjsOpacity(objs, 1, 0.1, 500, function () {
                                layer.closeAll();
                            });
                        });
                    } else {
                        WT3DObj.commonFunc.changeObjsOpacity(objs, 1, 0.1, 500, function () {
                            layer.closeAll();
                        });
                    }
               
                 });
         });
}

View Code

 

二、實現邏輯

 2.1、建模

  2.1.1、創建園區整體模型

這裡的道路直接用亮線畫出道路框架即可,然後通過流動的光線模擬車流,這在前面的文章中有詳細講解。

 

 

   2.1.2、創建室內庫房模型

這裡的模型通過程式碼實現,篇幅過長,不便展示。

 

 

   2.1.3、創建箱子模型

 { "show": true, "uuid": "", "name": name, "objType": "ExtrudeGeometry", "position": { "x": position.x, "y": position.y, "z": position.z }, "style": { "skinColor": 16711680, "skin": { "skin_up": { "skinColor": color1, "side": 1, "opacity": 1, "imgurl": imgurl1, "repeatx": true, "width": 0.01, "repeaty": true, "height": 0.01 }, "skin_down": { "skinColor": 16777215, "side": 1, "opacity": 1 }, "skin_side": { "skinColor": color2, "opacity": 1, "imgurl": imgurl2, "repeatx": true, "width": 0.01, "repeaty": true, "height": 0.01 } } }, "scale": { "x": size.x / 100, "y": size.y / 100, "z": size.z / 100 }, "shapeParm": { "points": [{ "x": 0, "y": 0, "type": "nomal" }, { "x": 0, "y": 100, "type": "nomal" }, { "x": 100, "y": 100, "type": "nomal" }, { "x": 100, "y": 0, "type": "nomal" }], "holes": [] }, "extrudeSettings": { "amount": 100, "curveSegments": 1, "steps": 1, "bevelEnabled": false, "bevelThickness": 1, "bevelSize": 1, "bevelSegments": 1, "extrudePathPoints": [] }, "showSortNub": 100, "customType1": "", "customType2": "", "animation": null, "dbclickEvents": null, "rotation": [{ "direction": "x", "degree": 0 }, { "direction": "y", "degree": 0 }, { "direction": "z", "degree": 0 }], "BindDevId": null, "BindDevName": null, "devInfo": null, "BindMeteId": null, "BindMeteName": null }

  2.1.4、創建集裝箱模型

 

 

[{"show":true,"uuid":"","name":"cube2_6","objType":"cube2","length":400,"width":200,"height":200,"x":0,"y":200,"z":0,"style":{"skinColor":16777215,"skin":{"skin_up":{"skinColor":2531071,"side":1,"opacity":1,"imgurl":"../img/3dImg/cbjysfk2.jpg"},"skin_down":{"skinColor":2531071,"side":1,"opacity":1,"imgurl":"../img/3dImg/cbjysfk2.jpg"},"skin_fore":{"skinColor":2531071,"side":1,"opacity":1,"imgurl":"../img/3dImg/cbjysfk2.jpg"},"skin_behind":{"skinColor":2531071,"side":1,"opacity":1,"imgurl":"../img/3dImg/cbjysfk2.jpg"},"skin_left":{"skinColor":2531071,"side":1,"opacity":1,"imgurl":"../img/3dImg/cbjysfk2.jpg"},"skin_right":{"skinColor":2531071,"side":1,"opacity":1,"imgurl":"../img/3dImg/cbjysfk2.jpg"}}},"showSortNub":6,"customType1":"","customType2":"","animation":null,"dbclickEvents":null,"rotation":[{"direction":"x","degree":0},{"direction":"y","degree":0},{"direction":"z","degree":0}],"thick":null,"scale":{"x":1,"y":1,"z":1},"BindDevId":null,"BindDevName":null,"devInfo":null,"BindMeteId":null,"BindMeteName":null}]

  2.1.5、車輛模型

 

 

 { "name": _name, "objType": "objmodel", "position": _position, "scale": _scale, "visible": true, "rotation": [{ "direction": "x", "degree": _rotation.x }, { "direction": "y", "degree": _rotation.y-Math.PI/2 }, { "direction": "z", "degree": _rotation.z }], "filePath": "../js/models/car/", "mtlFileName": "car03.mtl", "objFileName": "car03.obj", "mtlIsPublic": false, "showSortNub": 7, "show": true, "customType1": "", "customType2": "", "animation": null, "dbclickEvents": null, "BindDevId": null, "BindDevName": null, "devInfo": null, "BindMeteId": null, "BindMeteName": null }

2.2、數據載入

  通過數據生成模型,畫出庫位,載入車輛等

 
/* type:
        1://集裝箱
       2://箱子
        3://筒狀
    color://顏色
   id :設備id 唯一    必填
   position :設備位置  必填 格式 { x: 0, y: 0, z: 0} 這裡矢量單位
   size:尺寸 默認值 { x: 1, y: 1, z: 1 };
*/
function createModelJsonByType(type,color, id, position, size) {
    if (!scale) {
        scale = { x: 1, y: 1, z: 1 };
    }
    var modeljson = null;
    switch (type) {
        case 1:
            {

            modeljson = {
             ....
            };
            }
            break;
        case 2:
            modeljson = {
              ....。 };
            break;
        case 3:
            modeljson = {
               ....       };
            break;
       

             
    }

    modeljson.name = "dev_T_" + type + "_ID_" + id;
    if (config && config.name) {
        modeljson.name = config.name;
    }
    if (modeljson.children) {
        $.each(modeljson.children, function (_i, _o) {
            _o.name = "dev_T_" + type + "_ID_" + id + "OBJCREN" + _i;
        });
    }
    if (modeljson.position) {
        modeljson.position.x = position.x;
        if (position.y || position.y == 0) {
            modeljson.position.y = position.y;
        }
        modeljson.position.z = position.z;
    }

    return modeljson;
}
/*
創建車 
*/
function createCarModel(_name, _position, _rotation, _scale, carType) {


    var model = ...model;
    // 1.集卡(帶集裝箱的) 2.集卡(空車) 3.散卡(帶箱的小貨車) 4.正面吊 5.小鏟車 6 板車
    if (carType) {
        switch (carType) {
            case 1: {
                model.filePath = "../js/models/jika/";
                model.mtlFileName = "jika.mtl";
                model.objFileName = "jika.obj";
                model.scale = {
                    x: 4.200,
                    y: 4.200,
                    z: 4.200
                }
            }
                break;
            case 2: {
                model.filePath = "../js/models/jika_nocube/";
                model.mtlFileName = "jika_nocube.mtl";
                model.objFileName = "jika_nocube.obj";
                model.scale = {
                    x: 4.200,
                    y: 4.200,
                    z: 4.200
                }
            }
                break;
            case 3: {
                model.filePath = "../js/models/sanka/";
                model.mtlFileName = "sanka.mtl";
                model.objFileName = "sanka.obj";
                model.scale = {
                    x: 0.080,
                    y: 0.080,
                    z: 0.080
                }
                model.rotation[1].degree -= Math.PI / 2;
            }
                break;
            case 4: {
                model.filePath = "../js/models/diaoche/";
                model.mtlFileName = "dc.mtl";
                model.objFileName = "dc.obj";
                model.scale = {
                    x: 1.150,
                    y: 1.150,
                    z: 1.150
                }
            }
                break;
            case 5: {
                model.filePath = "../js/models/canche/";
                model.mtlFileName = "canche.mtl";
                model.objFileName = "canche.obj";
                model.scale = {
                    x: 0.1,
                    y: 0.1,
                    z: 0.1
                }
            }
                break;
            case 6: {
                model.filePath = "../js/models/banche/";
                model.mtlFileName = "banche.mtl";
                model.objFileName = "banche.obj";
                model.scale = {
                    x: 4.200,
                    y: 4.200,
                    z: 4.200
                }
            }
                break;
        }
    }
    //model.scale.x *= 0.8;
    //model.scale.y *= 0.8;
    //model.scale.z *= 0.8;
    return model;

}

2.3、自動生成貨物模型

 生成模型注意對於批量模型消耗瀏覽器性能,掉幀問題。這裡後面我會用專門的篇幅講解,如何優化載入大量貨物且不掉幀的解決方案。

  //獲取區域庫位劃分數據
        webapi.GetAllArea(1, function (result) {
            var models = [];
            if (result && result.length > 0) {
                $("#room_shelfNub").html(result.length);
                $.each(result, function (_index, _obj) {
                    var _color = _obj.color;
                    if (_color == "") {
                        _color = Math.random() * 16777215 + "";
                    } else {
                        _color = _color.replace("#", "0x")
                    }
                    _color = parseInt(_color)
                    //生成區域畫線
                    var model = createAreaModels(_obj.code, _obj.name, _color, _obj.AreaPoints);
                    models = models.concat(model);
                })
            }
            console.log(models);
            
            WT3DObj.commonFunc.loadModelsByJsons(models, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0 }, true, function () {

            });

        })

 

2.4、主要邏輯

  具體實現邏輯主要分為五個步驟

  1、創建模型

  2、校準坐標系,將模型的坐標系與數據坐標系校準對應。

  3、根據配置載入配置模型,如攝影機等

  4、生成庫位、貨物。根據動態數據,生成庫位、車輛、貨物等模型

  5、業務邏輯。實現滑動,雙擊,搜索等常規業務。

由於篇幅原因,本節先講解到這。

技術交流 [email protected]

交流微信:

    

如果你有什麼要交流的心得 可郵件我

 

其它相關文章:

webgl(three.js)實現室內三維定位,3D定位,3D樓宇bim、實時定位三維可視化解決方案——第十四課(定位升級版)

使用three.js(webgl)搭建智慧樓宇、設備檢測、數字孿生——第十三課

如何用three.js(webgl)搭建3D糧倉、3D倉庫、3D物聯網設備監控-第十二課

如何用webgl(three.js)搭建處理3D隧道、3D橋樑、3D物聯網設備、3D高速公路、三維隧道橋樑設備監控-第十一課

如何用three.js實現數字孿生、3D工廠、3D工業園區、智慧製造、智慧工業、智慧工廠-第十課

使用webgl(three.js)創建3D機房,3D機房微模組詳細介紹(升級版二)

如何用webgl(three.js)搭建一個3D庫房-第一課

如何用webgl(three.js)搭建一個3D庫房,3D密集架,3D檔案室,-第二課

使用webgl(three.js)搭建一個3D建築,3D消防模擬——第三課

使用webgl(three.js)搭建一個3D智慧園區、3D建築,3D消防模擬,web版3D,bim管理系統——第四課

如何用webgl(three.js)搭建不規則建築模型,客流量熱力圖模擬

 使用webgl(three.js)搭建一個3D智慧園區、3D建築,3D消防模擬,web版3D,bim管理系統——第四課(炫酷版一)

使用webgl(three.js)搭建3D智慧園區、3D大屏,3D樓宇,智慧燈桿三維展示,3D燈桿,web版3D,bim管理系統——第六課

如何用webgl(three.js)搭建處理3D園區、3D樓層、3D機房管線問題(機房升級版)-第九課(一)