成功的含義不在於要獲得什麼,而在於你從那個奮鬥的起點走了多遠。 ---《心靈捕手》
複製代碼
原本計劃是最近好好研究下 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();
複製代碼