基于 ThingJs OOP 编程尝试

成功的含义不在于要得到什么,而在于你从那个奋斗的起点走了多远。 ---《心灵捕手》
复制代码

Demo GitHub

前言

本来计划是最近好好研究下 Vue ,可是项目一个接一个,需求开发有点吃力效率还一般。作为一个又高又帅的全栈特别不爽啊,挤时间把关于 ThingJs 资料全部过了一遍,满满的干货拉回家啊。其实 ThingJs 文档做的挺全,以前没仔细看过,还吐槽了,??,我错了。

早上6:30起床,忙活到 16:22,终于搞定。顺利写完心里还是挺开心的。?

下一阶段封装一套采集工具出来。又是一个执念啊。☹️

ThingJs 文档中心

ThingJs Api

Thingjs 在线开发 Demo

面临的问题

面向过程的编码散落在业务逻辑中不好维护。尝试了一下 OOP ,还满意。

摄影机飞向物体,达不到满意的效果。 基于物体自身坐标系换算世界坐标。

前后端分离的情况下,需要依赖后端服务才能进行操作。Mock 数据,拦截 ajax 请求。

方案尝试

摄影机,想飞哪,就飞那

蒙多,想去哪就去哪。??

坐标系

飞之前,先了解下坐标系,不然迷失茫茫宇宙无法自拔啊。

  • 世界坐标系

每个物体都会有对应的世界坐标系。摄影机控制主要基于世界坐标。

在 ThingjJs 中,坐标系如图所示。参照物为场景中心点(猜测)。 app.query('.Car')[0].position 获取世界坐标。

  • 物体父级坐标系

还没来得及尝试,先跳过,有经验之后来补。

  • 物体自身坐标系

物体自身坐标系在摄影机飞行的时候很有用。基于物体自身坐标系,很容易得到相对于物体的一个位置坐标,然后利用 api 将物体自身坐标系的坐标,转换为世界坐标(摄影机位置 position)。然后 target 为物体的坐标。

终于可以飞了。

坐标转换
// 将输入的物体自身坐标系下坐标转换成世界坐标
obj.selfToWorld(pos) 
// 将输入的世界坐标标转换成物体自身坐标系下坐标
obj.worldToSelf(pos) 
// 将输入的世界坐标转换成父物体坐标
obj.worldToLocal(pos)
// 将输入的父物体坐标转换成世界坐标
obj.localToWorld(pos)  
复制代码
app.camera.flyTo({
    // 物体在世界坐标系坐标
    position: position,
    // 基于物体的相对位置转换的世界坐标。obj.selfToWord([-2,2,2])。这样摄影机就会飞向物体右前方(以自己作为参考系)。
    target: target,
    time: 1000,
});
app.camera.flyTo({
    target: target,
    xAngle: 30, //绕物体自身X轴旋转角度
    yAngle: 60, //绕物体自身Y轴旋转角度
    radiusFactor: 3, //物体包围盒半径的倍数
    //radius: 3, //距离物体包围盒中心的距离 单位米
    time: 1000,
});
复制代码

MMP,本来想写个简单的 demo,写着写着又去看 ThingJs 代码去了。???

坐标系 api 没中不足的就是没有全部可以用 安培定则记忆。右手大拇指指向正方向,四根手指指向旋转的正方向。

camera.rotateX(xAngle);符合安培定则

camera.rotateY(yAngle);符合安培定则

app.camera.flyTo({yAngle: 60});符合安培定则

app.camera.flyTo({xAngle: 60});不符合安培定则,强迫症感觉特别不适啊。?

Demo,可以在 ThingJs 在线开发运行
window.uino=window.uino||{};
// 创建一个地球
const app = new THING.App({
    // 指定 3d 容器 div标签 的id属性值
    container: 'div3d',
    url: 'https://www.thingjs.com/static/models/storehouse',
    // url: `${uino.sceneUrl}public/thingjs/scene/jail`,
    // 模型库 url
    // loaderResourceUrl: `${uino.modelsUrl}public/thingjs/models/jail`,
    // 天空盒
    // skyBox: './images/blueSky',
    // 加载模型库的时候是否加载最高级别的,依照场景文件版本号加载
    enableUseHighestVersionResource: false
});
uino.app = app;
// 获取特定小车
const getCar = function _getCar(name = 'car01') {
    if (uino.car) {
        return uino.car;
    }
    uino.car = uino.app.query(name)[0];
    return uino.car;
};
// 监听场景加载完成
uino.app.on('load', ev => {
    const car = getCar();
    // 改变小车颜色
    car.style.color = 'red';
    // 显示包围盒
    car.style.boundingBox = true;
    // 设置包围盒颜色
    car.style.boundingBoxColor = 'rgb(255,0,0)';
    app.level.change(ev.campus);
});
// 信息面板
const panel = new THING.widget.Panel({
    titleText: '摄影机属性',
    hasTitle: true,
    position: [200, 5],
    width: 230
});
const camera = uino.app.camera;
const panelData = {
    distance: camera.distance,
    position: camera.position,
    target: camera.target,
    xAngle: camera.cameraObject.angleX,
    yAngle: camera.cameraObject.angleY,
    zAngle: camera.cameraObject.angleZ
};
panel.addString(panelData, 'distance').caption('摄影机距离物体距离');
panel.addString(panelData, 'position').caption('摄影机世界坐标');
panel.addString(panelData, 'xAngle').caption('xAngle');
panel.addString(panelData, 'yAngle').caption('yAngle');
panel.addString(panelData, 'zAngle').caption('zAngle');
const updatePanelData = function _updatePanelData(camera) {
    panelData.distance = camera.distance;
    panelData.position = camera.position;
    panelData.target = camera.target;
    panelData.xAngle = camera.cameraObject.angleX;
    panelData.yAngle = camera.cameraObject.angleY;
    panelData.zAngle = camera.cameraObject.angleZ;
};
// 小车自身坐标系,x 每次点击改变
// 小车自身坐标系,y 每次点击改变
// 小车自身坐标系,z 每次点击改变
const [objSelfX, objSelfY, objSelfZ] = [1, 1, 1];
// 绕小车 x 轴,每次旋转
// 绕小车 y 轴,每次旋转
// 绕小车 z 轴,每次旋转
const [xRotate, yRotate] = [10, 10];
// 摄影机位置 基于世界坐标系
const cameraPosition = app.camera.tempComplete;
// 相对于红色小车自身坐标系的偏移
let [x, y, z] = [0, 0, 0];
// 旋转角度的偏移量
let [xAngle, yAngle, zAngle] = [0, 0, 0];
// 摄影机基于小车自身坐标系 x 轴移动
const addCameraPositionX = function _changeCameraPositionX() {
    x = x + objSelfX;
    updateCameraPosition();
};
// 摄影机基于小车自身坐标系 x 轴移动
const reduceCameraPositionX = function _changeCameraPositionX() {
    x = x - objSelfX;

    updateCameraPosition();
};
// 摄影机基于小车自身坐标系 y 轴移动
const addCameraPositionY = function _changeCameraPositionY() {
    y = y + objSelfY;
    updateCameraPosition();
};
// 摄影机基于小车自身坐标系 y 轴移动
const reduceCameraPositionY = function _changeCameraPositionY() {
    y = y - objSelfY;
    updateCameraPosition();
};
// 摄影机基于小车自身坐标系 z 轴移动
const addCameraPositionZ = function _changeCameraPositionZ() {
    z = z + objSelfZ;
    updateCameraPosition();
};
// 摄影机基于小车自身坐标系 z 轴移动
const reduceCameraPositionZ = function _changeCameraPositionZ() {
    z = z - objSelfZ;
    updateCameraPosition();
};
// 更新摄影机飞向
const updateCameraPosition = function _updateCameraPosition({
    targetObj = getCar(),
    xAngle = 0,
    yAngle = 0,
    radiusFactor = 1.5,
    radius
} = {}) {
    let obj = null;
    if (arguments.length > 0) {
        obj = { target: targetObj, xAngle, yAngle, radiusFactor };
    } else {
        const nowPosition = targetObj.selfToWorld([x, y, z]);
        obj = {
            // 飞行时间
            time: 1000,
            // 摄影机位置
            position: nowPosition,
            // 小车位置
            target: targetObj.position
        };
    }
    obj.complete = function _complete(ev) {
        // 更新面板数据
        updatePanelData(uino.app.camera);
    };
    uino.app.camera.flyTo(obj);
};
// 预先找好的复原视角位置
const reset = function _reset() {
    const car = getCar();
    const position = [23.416928429425614, 10.920238566451447, 19.87585306710976];
    uino.app.camera.flyTo({
        time: 1000,
        // 摄影机位置
        position: position,
        // 小车位置
        target: car.position,
        complete() {
            updatePanelData(uino.app.camera);
        }
    });
};
// 摄影机绕 x 轴旋转
const addRotateCameraPositionX = function _addRotateCameraPositionX() {
    // uino.app.camera.rotateX(xAngle);
    xAngle = xAngle + xRotate;
    updateCameraPosition({ xAngle, yAngle });
}; // 摄影机绕 x 轴旋转
const reduceRotateCameraPositionX = function _reduceRotateCameraPositionX() {
    // uino.app.camera.rotateX(-xAngle);
    xAngle = xAngle - xRotate;
    updateCameraPosition({ xAngle, yAngle });
};
// 摄影机绕 y 轴旋转
const addRotateCameraPositionY = function addRotateCameraPositionY() {
    // uino.app.camera.rotateY(yAngle);
    yAngle = yAngle + yRotate;
    updateCameraPosition({ xAngle, yAngle });
};
// 摄影机绕 y 轴旋转
const reduceRotateCameraPositionY = function _reduceRotateCameraPositionY() {
    // uino.app.camera.rotateY(-yAngle);
    yAngle = yAngle - yRotate;
    updateCameraPosition({ xAngle, yAngle });
};

new THING.widget.Button(`复位`, reset);
new THING.widget.Button(`红车自身 x 轴 + ${objSelfX}`, addCameraPositionX);
new THING.widget.Button(`红车自身 x 轴 - ${objSelfX}`, reduceCameraPositionX);
new THING.widget.Button(`红车自身 y 轴 + ${objSelfX}`, addCameraPositionY);
new THING.widget.Button(`红车自身 y 轴 - ${objSelfX}`, reduceCameraPositionY);
new THING.widget.Button(`红车自身 z 轴 + ${objSelfX}`, addCameraPositionZ);
new THING.widget.Button(`红车自身 z 轴 - ${objSelfX}`, reduceCameraPositionZ);
new THING.widget.Button(`红车 x 轴旋转 + ${xRotate}`, addRotateCameraPositionX);
new THING.widget.Button(`红车 x 轴旋转 - ${xRotate}`, reduceRotateCameraPositionX);
new THING.widget.Button(`红车 y 轴旋转 + ${yRotate}`, addRotateCameraPositionY);
new THING.widget.Button(`红车 y 轴旋转 - ${yRotate}`, reduceRotateCameraPositionY);
复制代码

Mock 排除依赖,独立部署

mock.js 拦截 ajax (JQuery),排除后端依赖

研究半天,还是没有通,早上六点半到现在,牙都没刷,饭也没吃,执念啊。???? 幸好,最后写这个,MMP,要不晚上都睡不着了。

单独 mock 数据没有问题,但是加入到 thingjs 中就会报错。难道拦截到 thingjs 权限验证的接口了?

setInterval(() => {
    $.ajax({
        url: '/api/alarm',
        type: 'get',
        dataType: 'json',
        success(data) {
            if (data.code == 200) {
                console.log(data);
            }
        },
        error(error) {
            console.error(error);
        }
    });
}, 2000);
Mock.setup({ timeout: 4000 });
Mock.mock('/api/alarm', 'get', {
    'data|1-5': [
        {
            name: '测试创建摄像头',
            'color|1': ['red', 'yellow', 'green']
        }
    ],
    code: 200
});

复制代码
基于 ThingJs mock 方案

当需要部署的时候,直接将 js 文件去掉即可。开发或演示的时候,只需部署前端。

  • jquery.mockjax.js 结合 mock.js 排除后端接口依赖
let interval;
uino.thingjsUtil.createWidgetButton(
    '开启查询告警信息',
    ev => {
        interval = setInterval(() => {
            const video = createVideo();
            $.ajax({
                url: '/fly/alarm',
                type: 'get',
                dataType: 'json',
                success(data) {
                    const i = Math.round(Math.random() * 10);
                    data.data.forEach((item, index) => {
                        if (i != index) {
                            return;
                        }
                        if (video) {
                            video.triggerAlarm(item);
                        }
                    });
                },
                error(error) {
                    console.error(error);
                }
            });
        });
    },
    10000
);
uino.thingjsUtil.createWidgetButton('关闭查询告警信息', ev => {
    if (interval) {
        clearInterval(interval);
    }
});
复制代码
  • jquery.mockjax.js 结合 mock.js 拦截 ajax 并且模拟数据返回。
$.mockjax({
    url: '/fly/alarm',
    contentType: 'application/json',
    responseText: Mock.mock({
        'data|10': [
            {
                name: '测试创建摄像头',
                'color|1': [
                    'red',
                    'yellow',
                    'green',
                    '#FF34B3',
                    '#F0F8FF',
                    '#ADFF2F',
                    '#9400D3',
                    '#1A1A1A',
                    '#008B8B',
                    '#00FF00'
                ]
            }
        ],
        code: 200
    })
});

复制代码

ThingJs OOP 尝试

面向对象三大特征,封装,继承,多态。es6 之后,基于 class 实现的继承可以满足需求了。想实现多重继承也可以,自己再封装一下。 多态呢就比较坑了,父类引用指向子类对象,运行时期动态执行代码。js 呢做为弱类型语言,想实现这个只能自己封装了,不过以前看文章好像有个开源库能帮忙做到了,ts 好像可以(猜测,对 ts 不了解,只是有印象)。 但是 js 基于原型实现继承,多态的特性也可以用。

代码验证 OOP 的可行性

A,B,C,D,E,F

B,C 继承 A

D 继承 B

E 继承 C

F 继承 A

class A {
    run() {
        console.log('A');
    }
}
class B extends A {
    run() {
        console.log('B');
    }
}
class C extends A {
    run() {
        console.log('C');
    }
}
class D extends B {
    run() {
        console.log('D');
    }
}
class E extends C {}
class F extends A {}
const client = function _client(a) {
    if (a instanceof A) {
        a.run();
        return;
    }
    throw new Error('传入变量类型有误');
};
const a = new A();
const d = new D();
const b = new B();
const c = new C();
const e = new E();
const f = new F();
console.log(a instanceof A); // true
console.log(d instanceof A); // true
console.log(d instanceof B); // true
console.log(c instanceof A); // true
console.log(b instanceof A); // true
console.log(e instanceof A); // true
console.log(e instanceof C); // true
console.log(f instanceof A); // true
client(a); // A
client(b); // B
client(c); // C
client(d); // D
client(e); // C
client(f); // A

复制代码
扩展 Campus,Building,Floor,Room

ThingJs 场景中我们经常操作的是 Campus,Building,Floor,Room。这是类呢是 ThingJs 原生的,但是也是可以扩展的。

class Building extends THING.Building {
    /**
     * @author: 张攀钦
     * @description: 给建筑描边
     */
    changOutLineColor() {
        this.style.color = '#00ff00';
    }
    enterBuilding() {
        this.app.level.change(this);
    }
    enterFloorFromBuilding(floorNum) {
        this.app.level.change(this.getFloor(floorNum));
    }
    getFloor(floorNum) {
        return this.floors[floorNum];
    }
}
THING.factory.registerClass('Building', Building);
复制代码
使用的时候呢,就爽歪歪了,obj.enterBuilding();即可进入建筑立面了;
app.query('.Building')[0].enterBuilding();
复制代码
扩展搭建场景时填写自定义属性_TYPE_
// scene.json 场景文件
{
    "id": "1444",
    "userid": "Cabinet_01",
    "type": "Cabinet",
    "name": "Cab",
    "properties": {
        "Group": "Inside01"
    },
    "model": 17,
    "position": [-5.087, 0.01, 9.347],
    "rotation": [0.0, 0.707, 0.0, 0.707],
    "scale": [0.998, 1.0, 0.999]
}
class Cabinet extends THING.Thing {

    changOutLineColor() {
        this.style.color = '#00ff00';
    }
}
THING.factory.registerClass('Cabinet', Cabinet);
// 给对象描边
app.query('.Cabinet')[0].changOutLineColor();
复制代码

id 对应 thingjs 对象 uuid 属性。

userid 对应搭建时候填写的 [自定义 ID]。对应 thingjs 物体对象 id 属性。

name 对应搭建时填写的 [名称]。对应 thingjs 物体对象 name 属性。

model 为查找当前物体所用模型,对应 scene.json 中 models 中的索引。

properties 为自定义的属性

type 为自定义属性添加TYPE(TYPE两边有_)。THING.factory.registerClass('Cabinet', Cabinet); app.query('.Cabinet');可以查到这个物体。

加载场景之后新创建物体及监听自定义事件
class Video extends THING.Thing {
    constructor(app = uino.app) {
        // 不传 app 报错。
        super(app);
        this._init();
    }
    // 调用创建对象需要绑定的事件之类的数据
    _init() {
        this.on('alarm', ev => {
            this.style.color = 'red';
        });
    }
    changOutLineColor() {
        this.style.color = '#00ff00';
    }
     // 触发告警事件
    triggerAlarm(eventData, tag) {
        this.trigger(AlarmManage.eventType, eventData, tag);
    }
}
THING.factory.registerClass('Video', Video);

class VideoFactory {
    static createVideo(obj) {
        return VideoFactory.app.create(obj);
    }
    static getVideos() {
        return VideoFactory.app.query('.Video');
    }
}
VideoFactory.app = uino.app;

 const videoPro = {
        id: THING.Utils.generateUUID(),
        name: '测试创建摄像头',
        type: 'Video',
        url: 'https://model.3dmomoda.com/models/335472acb6bb468ead21d1a8d9a2d24e/1/gltf',
        position: [-5, 0, 7],
        scale: [50, 50, 50],
        complete(ev) {
            console.log('创建物体:', ev.object);
        }
    };
    
    const video = VideoFactory.createVideo(videoPro);
    // 即可对物体进行描边
    video.changOutLineColor();
复制代码

通常呢,创建的物体有业务逻辑。比如摄像头可能会有监测功能,当触发告警规则之后呢,需要在 3D 中进行特殊展示。

当 ajax 请求后台告警数据时,就可以知道那个摄像头告警了。
const video = uino.app.query('#id')[0];
// 对象触发的只会当前对象监听到
video.triggerAlarm({ name: '宁姚', native: '倒悬山' });
// 全局触发的不会传递到对象上
AlarmManage.globalTriggerAlarm({ name: '陈平安', native: '落魄山' }, 'app-global');
});
uino.app.on('Alarm', ev => {
    console.log('全局事件', ev);
});
// 最近小说的男女主不停撒狗粮啊,心痛啊。幸好对象还可以 new。??
复制代码
基于物体类型为 Thing 的对象进行设置原型,从而可以进行 OOP 。
const videos=app.query(/.*video.*/);
videos.forEach(item=>THING.Utils.convertObjectClass(item, 'Video'));

// 根据物体类型进行查找物体,然后调用原型方法
app.query('.Video')[0].changOutLineColor();
复制代码

转载于:https://juejin.im/post/5d0d5de5518825327938acc3

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值