成功的含义不在于要获得什么,而在于你从那个奋斗的起点走了多远。 ---《心灵捕手》
复制代码
原本计划是最近好好研究下 Vue ,但是项目一个接一个,需求开发有点吃力效率还通常。做为一个又高又帅的全栈特别不爽啊,挤时间把关于 ThingJs 资料所有过了一遍,满满的干货拉回家啊。其实 ThingJs 文档作的挺全,之前没仔细看过,还吐槽了,🤦🤦,我错了。html
早上6:30起床,忙活到 16:22,终于搞定。顺利写完内心仍是挺开心的。😬前端
下一阶段封装一套采集工具出来。又是一个执念啊。☹️jquery
ThingJs 文档中心es6
ThingJs Apiajax
面向过程的编码散落在业务逻辑中很差维护。尝试了一下 OOP ,还满意。后端
摄影机飞向物体,达不到满意的效果。 基于物体自身坐标系换算世界坐标。api
先后端分离的状况下,须要依赖后端服务才能进行操做。Mock 数据,拦截 ajax 请求。app
蒙多,想去哪就去哪。😄😄前后端分离
飞以前,先了解下坐标系,否则迷失茫茫宇宙没法自拔啊。
每一个物体都会有对应的世界坐标系。摄影机控制主要基于世界坐标。
![]()
在 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});不符合安培定则,强迫症感受特别不适啊。😄
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);
复制代码
研究半天,仍是没有通,早上六点半到如今,牙都没刷,饭也没吃,执念啊。😭😭🙄🙄 幸亏,最后写这个,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
});
复制代码
当须要部署的时候,直接将 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);
}
});
复制代码
$.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
})
});
复制代码
面向对象三大特征,封装,继承,多态。es6 以后,基于 class 实现的继承能够知足需求了。想实现多重继承也能够,本身再封装一下。 多态呢就比较坑了,父类引用指向子类对象,运行时期动态执行代码。js 呢作为弱类型语言,想实现这个只能本身封装了,不过之前看文章好像有个开源库能帮忙作到了,ts 好像能够(猜想,对 ts 不了解,只是有印象)。 可是 js 基于原型实现继承,多态的特性也能够用。
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
复制代码
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();
复制代码
// 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。🤦🤦
复制代码
const videos=app.query(/.*video.*/);
videos.forEach(item=>THING.Utils.convertObjectClass(item, 'Video'));
// 根据物体类型进行查找物体,而后调用原型方法
app.query('.Video')[0].changOutLineColor();
复制代码