ThingJS,注意不是 Three.js,是比Three.js 封装度更高得3D框架,但不是开源的,使用得付费。也不是特别成熟,仅调研一波。
1、场景导入
- 控制台 新建项目保存
- 下载 CampusBuilder ,创建一个场景并导出为 Thingjs 场景包B,如 hmf.tjs 格式
- 控制台 "我的资源-我的场景-上传场景"上传导出的场景包B
- 打开在线开发平台,通过 "资源-场景资源" 引入场景包B
2、模型制作
提供三种方式制作模型
- 打开在线开发平台,通过 "资源-模型资源" 搜索需要的模型
- 在资源中心,可找到很多付费和免费的模型,然后通过url创建
-
个人 obj 模型,将 obj 资源按照以下格式打成zip包
然后在 CampusBuilder 里的DIY模型库 tab 里点击上传资源
3、常用操作
- 创建对象
var truck = app.create({
type: "Thing",
name: "truck",
position: [-5, 0, 0],
url: "https://www.thingjs.com/static/models/truck/",
complete: function() {
console.log("truck created!");
}
});
创建对象后,设置其世界坐标即可展示在场景中。注意要设置其父元素,否则会直接挂到app下
- 获取对象
主要通过 query 方式以及层级关系查询对象,query支持 id,类,属性,正则查询等。查询结果返回的是一个 Selector 对象,查询结果可以相加、排除,也可以直接绑定事件,或一些批量操作
// 查询id是100的对象
app.query("#100")[0];
// 查询名称(name)是 car01 的对象
app.query("car01");
// 查询物体类是Thing的对象
app.query(".Thing");
//有物体类型属性的,无论值是什么
app.query("[alarm]");
//查询物体类型属性是粮仓的对象
app.query("[报警=normal]");
app.query('["userData/物体类型"="粮仓"]');
// 查询levelNum属性大于2的对象,目前支持 <= , < , = , > , >=
app.query("[levelNum>2]");
// 正则表达式(RegExp)对象,目前只是对名称(name)属性值进行正则匹配
app.query(/car/);
// 上例等同于
var reg=new RegExp('car');
app.query(reg);
- 控制对象
可控制的有:
1、物体显示与隐藏 visible;
2、物体移动 position,localPosition,translate;
3、物体旋转 angles,localAngles,rotateX,rotateY,rotate Y
4、缩放 scale
5、位移、旋转、缩放动画 moveTo,rotateTo,scaleTo
6、CSS属性控制,如透明度 opacity、描边outline、颜色 color、进出场动画 fadeIn fadeOut
7、连接操作:通过 add 接口可以添加子元素,添加子元素时,子物体的世界位置不发生变化,并保持那一刻与父物体的相对位置关系进行移动 - 鼠标交互
1、picker,结合 mouseenter 和 mouseleave 事件实现鼠标悬浮选中效果
// 鼠标拾取物体显示边框
app.on(THING.EventType.MouseEnter, '.Thing', function(ev) {
ev.object.style.outlineColor = '#FF0000';
});
// 鼠标离开物体边框取消
app.on(THING.EventType.MouseLeave, '.Thing', function(ev) {
ev.object.style.outlineColor = null;
});
// 每一帧判断拾取的物体是否发生变化
app.on('update', function () {
if (app.picker.isChanged()) {
console.clear();
// 打印当前被pick的物体
if (app.picker.objects[0]) {
console.log('当前拾取的物体 ' + app.picker.objects[0].name);
}
// 打印之前被pick的物体
if (app.picker.previousObjects[0]) {
console.log('之前拾取的物体 ' + app.picker.previousObjects[0].name);
}
}
});
2、selection,鼠标点击选中效果,这个跟click事件不一样,selection 相当于在内部给你保存了选中的所有物体
- 摄像机
摄像机包含两个重要的位置参数:镜头位置 position 和被拍摄物体的位置 target (又叫目标点)
1、设置摄像机位置 position,target 和 object 二选一
// 直接设置
app.camera.position = [0, 20, 20]; // 镜头位置
app.camera.target = [-30, 10, 0]; // 目标点位置
// fit 方法设置
app.camera.fit({
position: [100, 100, 100],
target: [0, 0, 0]
});
//设置摄像机到物体的“最佳看点”
app.camera.fit(obj);
//当不传参数时,设置摄像机到当前整个场景下的“最佳看点”
app.camera.fit();
// 自定义设置
app.camera.fit({
'object': obj,
'xAngle': 60, //绕物体自身X轴旋转角度
'yAngle': 30, //绕物体自身Y轴旋转角度
'radiusFactor':3, //物体包围球半径的倍数
});
2、lookAt 设置相机观察的物体
//摄像机一直“盯着”[0,0,0]点看
app.camera.lookAt([0, 0, 0]); //
//摄像机一直“盯着”某物体看
var obj = app.query("car01")[0];
app.camera.lookAt(obj);
//取消摄影机一直盯着物体看
app.camera.lookAt(null);
3、flyTo 让摄像机从当前位置,飞行到将要设置的位置
//以Quartic.In的插值方式 让飞行速度渐增
app.camera.flyTo({
position: [0, 20, 20],
target: [-30, 10, 0],
time: 3 * 1000,
lerpType: THING.LerpType.Quartic.In
});
//自定义飞到物体的摄像机位置参数(同fit)
app.camera.flyTo({
object: obj,
xAngle: 30, //绕物体自身X轴旋转角度
yAngle: 60, //绕物体自身Y轴旋转角度
radiusFactor: 3, //物体包围盒半径的倍数
time: 5 * 1000,
complete: function() {
console.log("飞行结束");
}
});
4、routateAround 设置相机环绕某点飞行
//环绕[0,0,0]点旋转 180 度,5s 转完
app.camera.rotateAround({
target: [0,0,0],//环绕的坐标点
time: 5*1000,//环绕飞行的时间
yRotateAngle : 180,//环绕y轴飞行的旋转角度
complete:function(){
console.log('结束环绕飞行');
}
});
5、followObject 设置相机跟随物体
app.camera.followObject(obj);
6、move(),zoom(),rotateY(),rotateX()来控制摄像机的移动、缩放、旋转
//摄像机水平移动 10m
app.camera.move(10, 0);
//摄像机垂直移动 10m
app.camera.move(0, 10);
//摄像机向前推进 10m
app.camera.zoom(10);
//设置摄像机target为圆心转在水平方向上旋转的夹角增量
app.camera.rotateY(20);
// 设置摄像机target为圆心转在竖直方向上旋转的夹角增量
app.camera.rotateX(20);
- 界面元素
1、3D元素 Marker,Webview,会随着缩放进大远小
app.create({
type: "Marker",
offset: [0, 2, 0],
size: [4, 4],
url: "https://thingjs.com/static/images/warning1.png",
parent: app.query("car01")[0]
});
2、2D元素UIAnchor,需要自己写 html
var uiAnchor = app.create({
type: "UIAnchor",
parent: app.query("car02")[0],
element: document.getElementById("XXXX"),
localPosition: [0, 2, 0],
pivot: [0.5, 1]
});
uiAnchor.destroy();
uiAnchor.visible = true / false;
3、快捷界面库,一些 panel 控件,也可以绑定到模型上
// 创建UIAnchor面板
var ui = app.create({
// 类型
type: 'UIAnchor',
// 父节点设置
parent: obj,
// 要绑定的页面的 element 对象
element: panel.domElement,
// 设置 localPosition 为[0, 0, 0]
localPosition: [0, 0, 0],
// 指定页面的哪个点放到 localPosition 位置上
pivot: [-0.15, 1.8]
});
- 数据对接
1、ajax,在 ThingJS 在线开发环境中,内置了 JQuery 库,可以直接使用 JQurey 封装的 Ajax 方法进行数据对接
$.ajax({
'url': "http://3dmmd.cn:83/getMonitorDataById", //Ajax请求服务的地址
'type': "GET", //请求方式 "POST" 或 "GET",默认为 "GET"
'dataType': "json", //服务返回的数据类型,推荐使用标准JSON数据格式
//发送到服务器的数据
'data': { 'id': 89757 },
//请求成功后的回调函数
'success': function (data) {
console.log(data);
// 处理返回的数据
},
//请求失败时调用的函数 有以下三个参数:XMLHttpRequest 对象、错误信息、(可选)捕获的异常对象
'error': function (xhr, status, error) {
console.log(xhr);
},
});
2、websocket
// 创建一个WebSocket连接
var webSocket = new WebSocket('ws://3dmmd.cn:82');
// 建立 websocket 连接成功 触发open事件
webSocket.onopen = function () {
console.log("websocket服务器连接成功...");
};
// 接收服务端数据 触发message事件
webSocket.onmessage = function (ev) {
console.log("websocket接收到的数据:" + ev.data);
};
// 关闭连接后 触发close事件
webSocket.onclose = function (evt) {
console.log("websocket关闭...");
};
// 通信发生错误时 触发error事件
webSocket.onerror = function () {
console.log('发生错误')
}
var dataObj = { 'id': 89785 };
// send 数据类型可以是 字符串 或 二进制对象(Blob 对象、ArrayBuffer 对象)
webSocket.send(JSON.stringify(dataObj));
一些概念
资源:场景资源(场景+模型) + 页面资源 + 全景图资源
-
层级
1、层级可以让我们方便管理和查询到场景中物体 && 批量操作物体
2、可以通过 app.level.change(obj) 切换到对应层级,app.level.back() 返回
3、有两套层级体系:父子树和分类对象属性树
父子树,通过 children 连接层级:root => campus => building,ground,thing
分类对象属性树:每个对象都内置了一些属性,如 root.campuses,campus.ground,campus.buildings,campus.things,building.facade,building.floors,building.things,floor.rooms,floor.things
4、当进入层级时会触发 EnterLevel 事件。
当退出层级时会触发 LeaveLevel 事件。
当默认的层级切换飞行结束后,会触发 THING.EventType.LevelFlyEnd 事件 坐标系
1、世界坐标系
2、父级坐标系
3、自身坐标系
基本套路
- 加载场景
- 创建面板
- load 事件中处理逻辑
// 加载场景
var app = new THING.App({
// 场景地址
"url": "models/silohouse",
// 天空盒
"skyBox": "Universal"
});
// load 处理
app.on('load', function (ev) {
// 获取粮仓
siloHouse = app.query("[物体类型=粮仓]");
// 添加粮仓自定义属性monitorData,用来存储监控信息
siloHouse.forEach(function (obj) {
obj.monitorData = {};
});
// 创建开关控件
createSwitchControl();
});