此文章为本人方便查找使用ThingJsAPI的个人总结,原创API请查看ThingJs官网http://www.thingjs.com
目录
-
App对象
- 创建App
-
环境和效果控制
- 改变场景背景色
- 改变园区、建筑、楼层各层级的背景色
- 设置带有随时间变化的天空盒
- 灯光效果
- 特效模拟
- 屏幕后期处理
-
获取对象--query查询
- 全局查询
- 局部查询
- 对查询结果的操作
-
对象创建
- 创建物体及参数介绍
- 对象属性
-
界面
- 2Dhtml界面
- 3D界面
-
控制对象
- 控制显隐
- 控制位置
- 控制旋转
- 控制缩放
- 控制移动
- 物体移动
- 物体连接
- 沿路径移动
- 控制效果
- 坐标转换
- 位移旋转缩放动画
- 模型动画
- 物体编辑
拾取和选择
事件
摄像机
园区与层级
APP对象
-
创建App
html文件
<div id="div3d"></div>
js文件var app = new THING.App({ container: 'div3d', url:'场景的url地址', complete: () => { } });
App作为ThingJs的入口,提供了很多功能,详细说明请往下阅读
一、环境和效果控制
-
改变场景背景色
//设置背景颜色 app.background = 0Xffffff; //设置背景图片 app.background = 'imageUrl'; //设置天空盒 app.skyBox = 'Night'; //ThingJs提供的内置天空盒属性 SunCloud、BlueSky、MilkyWay、Night、CloudySky、Universal、White、Dark //取消天空盒效果 app.skyBox = null;
-
改变园区、建筑、楼层各层级的背景色
//设置园区的天空盒 var app = new THING.App({ skyBox: 'blueSky' //也可以加载本地图片../../images/blueSky }); //设置建筑层级的背景颜色 app.on(THING.EventType.EnterLevel, '.Building', function () { app.skyBox = null; app.background = '#BDE2FF'; }) //设置楼层层级的背景颜色 app.on(THING.EventType.EnterLevel, 'Floor', function () { app.skyBox = null; app.background = 0xffaaee; })
-
设置带有随时间变化的天空盒
app.skyEffect = { showHelper: false, //显示光源位置 turbidity: 10, //光源扩散大小 rayleigh: 2, //大气散射 time: 14, //时间 [0~24] beta: 30, //水平角度 mieCoefficient: 0.028, //该数越大,天空越暗 mieDirectionalG: 0.9999 //该数越大,太阳越小 } //取消效果 app.skyEffect = null;
<font color="#DC143C">*注意:app.skyBox与app.skyEffect不可同时使用,否则会出现问题</font>
-
灯光效果
灯光效果对于场景效果有决定性作用,ThingJS提供了一套通用方案来设置灯光效果
-
特效模拟(下雨、下雪、着火、爆炸等等)
1、着火
//创建火焰粒子 var car = app.query('car01')[0]; var fire = app.query('#fire01')[0]; if (!fire) { app.create({ id: 'fire01', //设定火焰唯一标识 type: 'ParticleSystem', //设置此物体类实现粒子效果 url: 'https://thingjs.com/static/particles/fire1',//设置火焰图片 parent: car, //设置粒子的父物体 localPosition: [0, 0, 0] //设置粒子相对于父物体的位置 }) } //销毁火焰 if (fire) { fire.destroy(); }
2、下雪
var particle = app.create({ type: 'ParticleSystem', url: 'http://model.3dmomoda.com/models/18112014q3t8aunaabahzxbxcochavap/0/particles', position: [0, 40, 0] //设置雪花下落的位置 });
3、下雨
var particle = app.create({
type: 'ParticleSystem',
url: 'http://model.3dmomoda.com/models/18112113d4jcj4xcoyxecxehf3zodmvp/0/particles',
position: [0,30, 0]
});
4、喷水
var particle = app.query('#water01')[0];
if (!particle) {
// 创建喷水效果
particle = app.create({
id: 'water01',
type: 'ParticleSystem',
url: 'https://thingjs.com/static/particles/water1',
position: [0, 0, 5]
});
// 每一帧旋转 1 度
particle.on('update', function (ev) {
ev.object.rotateY(1.0);
}, '喷水旋转')
}
//销毁喷水效果
if (particle) {
// 先卸载掉事件
particle.off('update', null, '喷水旋转');
// 再删除粒子
particle.destroy();
}
-
屏幕后期处理
对场景模型进行后期处理,调整色彩效果,使用app.postEffect接口
二、获取对象——query查询
-
定义说明
ThingJs的query方法包括 全局 和 局部
如何快速找到对象呢?
1.在父子树上,通过parent,children属性找到要控制的对象
2.在分类对象属性树上,通过类身上分类属性找到要控制的对象(例如:floor.style.color = 0xffffff)
3.使用query方法 -
全局查询
let buildings = app.query('.Building'); let things = app.query('.Thing'); let floors = app.query('.Floor'); let rooms = app.query('.Room); let markers = app.query('Marker);
-
局部查询
//根据对象名称name查询 app.query('car01'); //根据物体类查询 app.query('.Thing); //根据对象ID查询 app.query('#0001'); //根据物体类型属性查询 app.query('[Deploy]'); //根据具体物体类型属性查询 app.query('[视频=video]'); app.query('["userData/物体类型" = "摄像头"]'); //查询levelNums属性大于等于2的对象 app.query('[levelNum >= 2]');
-
对查询结果的操作
查询结果返回的是selector对象,查询结果可以相加、排除、绑定事件批量操作
//在查询结果中进行再查询,可实现多个条件的'与操作' let result = app.query('.Thing').query('["userData" = "Door"]');
//实现多个条件的'或操作' let result = app.query('["userData" = "Door"]'); app.query('["userData" = "Desk"]').add(result);
//实现'非操作',not操作支持标准的条件 let building = app.query('.Building'); building.query('.Floor').not('1层');
//获取第一个对象 let obj = app.query('.Thing')[0];
//循环选择器对象,数组方式 var objs = app.query('.Thing'); for (let i =0; i < objs.length; i++) { console.log(objs[i]); }
//循环选择器对象,对象方式 app.query('.Thing').forEach(thing => { console.log(thing); })
//批量操作对象 app.query('.Thing').visible = false; //对查询到的物体绑定事件 app.query('.Thing').on('click', function () { // do something })
三、对象创建
-
创建物体
var truck = app.create({ type: "Thing", name: 'truck', position: [-5, 0, 0], url: '模型的地址', complete: () => { console.log('物体创建成功’); } }) //删除物体 truck.destroy();
<font color="#D2691E">*注意:如果该物体被销毁后,则会连同该物体的后代一同销毁</font>
-
创建物体的通用相关参数
参数 说明 type 该物体用什么物体类来创建 id 该物体的编号 name 物体的名字 position 设置世界位置 localPosition 设置在父物体下的相对位置,和position只能选择一个 angles 设置世界下的旋转角度 localAngles 设置在父物体下的相对角度,和angles只能选择一个 scale 设置相对自身坐标下的缩放比例 parent 设置父物体是谁 -
常见的物体种类
类型 参数 模型物体 Thing 基本形体 Box;Sphere;Plane;Cylinder;Tetrahedron; 建筑相关 Campus;Building;Floor;Room; 界面相关 UIAnchor;Marker;WebView; 粒子相关 ParticleSystem 线相关 Line;RouteLine; 其他 Heatmap <font color="#D2691E">*注意:创建物体,没有显示填写该物体的父物体时</font>
1、如果没有开启系统层级,则该物体的默认父级是root(*不会是园区campus)
2、如果开启了层级系统,但是创建物体时没有写parent,则默认当前层级为父级例如:j进入楼层层级后再创建Thing,如果没有指定parent,则默认当前层为parent
var app = new THING.App({ url: 'models/storehouse' // 场景地址 }); app.on('load', function (ev) { var campus = ev.campus; var floor = campus.buildings[0].floors[0]; // 将层级切换到楼层级别 app.level.change(floor); // 第一次进入该楼层后 创建物体 floor.one(THING.EventType.EnterLevel, function () { // 进入楼层层级后再创建 Thing // 如果没有指定 parent ,则 parent 默认为该楼层 var obj = app.create({ type: 'Thing', name: 'truck', url: 'http://model.3dmomoda.com/models/66b7f5979ff043afa4e79f7299853a4b/0/gltf/', // 模型地址 complete: function (ev) { // 打印结果:创建的物体父亲为该楼层 console.log('thing created: ' + ev.object.parent.id); } }); }) });
*注意:为了方便对物体对象进行管理,建议遵守如下规则
1、显示指明该物体对象的parent
2、针对type参数,创建指定类型的物体
3、url:物体模型资源路径,是Thing物体需要的参数
4、complete:初始化完成函数回调 -
区分对象物体类
每个物体类都有它自己的类识别属性
if (b instanceof THING.Building) { console.log('This is a building'); }
-
对象属性
一个对象的属性分为4个类别:通用类型、类专属属性、CampusBuilder 中用户添加的属性、用户在程序运行中自行添加的属性
1、通用属性
参数 说明 id 作为物体的唯一编号 name 用户设置的物体名字,用户可自行定义 position 世界坐标系下的位置 localPosition 父物体坐标下的位置 angles 世界坐标系下三轴旋转角度 localAngles 世界父物体下三轴旋转角度 scale 自身坐标系下三轴缩放量 visible 是否可见(true,false) style 效果控制(color,outlineColor...) children 获取所有子物体 brothers 获取所有兄弟排除自己 parent 获取父物体 parents 获取所有父物体(第0位为直属父物体,最后一位为世界根物体) 2、CampusBuilder 中用户添加的属性
//从 CampusBuilder 导入的用户自定义的属性可通过 userData 属性访问到 obj.userData["物体类型"];
3、用户在程序运行中自行添加的属性
//比如,我们从后台接收到的监控数据,可以直接给对象添加自定义属性 monitorData 来进行存储: obj.monitorData = { 温度:10, 单位:“摄氏度” }
4、类专属属性
详细参数请查看上面的常见物体种类
四、界面
-
2Dhtml界面
UIAnchor
还有一个神奇的功能,虽然是 2D html 界面,我们可以把它连接到 3D 物体上,跟随 3D 物体移动,我们使用 UIAnchor 物体来实现这个功能html文件
<div id="UI">界面内容</div>
js文件
// 生成一个新面板 function create_element() { var srcElem = document.getElementById('UI'); var newElem = srcElem.cloneNode(true); newElem.style.display = "block"; app.domElement.insertBefore(newElem, srcElem); return newElem; } //物体顶界面 var uiAnchor = app.create({ type: "UIAnchor", parent: app.query("car02")[0], element: create_element(), localPosition: [0, 2, 0], pivot: [0.5, 1] }); //根绝位置创建界面 var uiAnchor0 = app.create({ type: 'UIAnchor', element: create_element(), position: [0, 1, 0] }); //删除UIAnchor uiAnchor.destroy();
参数 说明 element 要绑定的html页面元素 pivot 指定页面的哪个点放到 localPosition 位置上,0.5 相当于 50% -
3D界面
ThingJS 主要提供 Marker 物体和 WebView 物体以支持 3D 空间界面
1.Marker
- 创建图片url类型的marker
app.create({ type: "Marker", offset: [0, 2, 0], size: [4, 4], url: "https://thingjs.com/static/images/warning1.png", parent: app.query("car01")[0] });
- 创建canvas类型的marker
function createTextCanvas(text, canvas) { if (!canvas) { canvas = document.createElement("canvas"); canvas.width = 64; canvas.height = 64; } const ctx = canvas.getContext("2d"); ctx.fillStyle = "rgb(32, 32, 256)"; ctx.beginPath(); ctx.arc(32, 32, 30, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = "rgb(255, 255, 255)"; ctx.lineWidth = 4; ctx.beginPath(); ctx.arc(32, 32, 30, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = "rgb(255, 255, 255)"; ctx.font = "32px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(text, 32, 32); return canvas; } var marker = app.create({ type: "Marker", offset: [0, 2, 0], size: 3, canvas: createTextCanvas('100'), parent: app.query('car02')[0] }).on('click', function (ev) { var txt = Math.floor(Math.random() * 100); ev.object.canvas = createTextCanvas(txt, ev.object.canvas) })
- 创建html界面类型的marker
html文件
<div id="buildMarker" class="loadview"> <img src="img/normalLocation.png" alt="定位图标"> <span class="buildName"></span> </div>
js文件
var buildings = app.buildings; buildings.forEach(function (building) { $('#buildMarker .buildName').text('建筑ID:' + building.id); // 创建Marker var buildingMarker = app.create({ type: "Marker", offset: [0, 5, 0], size: 4, parent: building, keepSize: true, element: document.getElementById('buildMarker'), }); })
参数 说明 type 设定创建类型marker offset 设置自身坐标系下偏移量 size 设置marker物体大小 url 图片的url canvas 接收 canvas 作为贴图显示,创建的canvas函数 element 要绑定的html页面元素 parent 指定marker的父物体 keepSize 控制是否受距离远近影响,true:表示保持大小,false:表示呈现近大远小的效果 - WebView物体
我们可以使用 WebView 物体,将其他网站或者页面的内容嵌到 3D 中
var webView = app.create({ type: "WebView", url: "http://www.thingjs.com", position: [0, 10, 0], size:16, width: 10, height: 8, isClose: true, });
- 其他界面
快捷界面库、iframey引用界面等详细内容这里不做展示,详情请看ThingJs官网--在线开发--界面
五、控制对象
-
控制显示\隐藏
通过设置visible属性直接控制单个物体或对象集合的显隐
//单个物体 var car = app.query('car01')[0]; car.visible = false; //对象集合 var buildings = app.query('.Building'); buildings.visible = false;
<font color="#D2691E">*注意:如果对象有相应的父子关系,那么,当隐藏父级时,相对应的其后代也会隐藏</font>
系统层级默认显隐规则- 进入园区级别,显示该园区下的地面(Ground)、建筑的外立面(Facade)以及其他直属物体(Thing)
- 进入建筑级别,隐藏该建筑的外立面,显示该建筑的楼层(包括子孙)
<font color="#D2691E">*注意:此时当前建筑并没隐藏,只是隐藏了该建筑的外立面</font> - 进入楼层级别,隐藏其他楼层,显示当前楼层的子孙
-
控制位置
描述或控制一个物体的位置,我们在不同情况下会分别使用 3 套坐标系统:
1.世界坐标系:position
2.父物体坐标系:ocalPosition
3.自身坐标系:translate([0,0,2])
注意:正常情况下,子物体会随着父物体移动而一起移动,如果想控制子物体不随父物体移动,可通过设置子物体的inheritPosition*属性为 false 而实现 -
控制旋转
thingJs使用角度控制物体的旋转
1.世界坐标系下
obj.angles = [0, 45, 0];//设置世界坐标系下Y轴方向旋转45度
2.父物体坐标系下
obj.localAngles = [0, 45, 0];//设置父物体坐标系下Y轴向旋转45度
3.自身坐标系下
//使用rotate,可输入角度和轴向。设置沿给定轴向转一定角度,传入的旋转轴是自身坐标系下的轴方向 obj.rotate(45, [0,1,0]); //沿自身x轴向旋转,等同于 obj.rotate( 30, [1,0,0]) obj.rotateX(30); //沿自身y轴向旋转,等同于 obj.rotate( 90, [1,0,0]) obj.rotateY(90); //沿自身z轴向旋转,等同于 obj.rotate( -45, [1,0,0]) obj.rotateZ(-45);
4.使用lookAt接口方法,使得物体的观察方向一直对准一个位置
//让物体面向[0,1,0],该坐标是世界坐标系下 obj.lookAt([0,1,0]) //一直看向摄像机 obj.lookAt(app.camera); //一直看向一个物体 obj.lookAt(obj); //让物体一直面向一个物体,同时物体沿自身Y轴向再旋转90度 obj.lookAt(obj, [0,90,0]); //取消lookAt的功能 obj.lookAt(null);
正常情况下,子物体会随着父物体旋转而一起旋转,如果想控制子物体不随父物体旋转,可通过设置子物体的 inheritAngles 属性为 false 而实现
-
控制缩放
只提供自身坐标系下的缩放控制
obj.scale = [1,2,1];//让物体在Y轴方向上放大两倍 //默认原始状态[1,1,1]
//鼠标触摸控制楼层缩放 app.query('.Floor').forEach(floor => { app.on('mouseEnter', function () { floor.scale = [2,2,2]; }); app.on('mouseLeave', function () { floor.scale = [1,1,1]; }) })
> 正常情况下,子物体会随着父物体缩放而一起缩放,如果想控制子物体不随父物体缩放,可通过设置子物体的 inheritScale 属性为 false 而实现 -
控制移动
- 物体连接
var car = app.query('car01')[0]; // 旋转180度,并保存下叉车当前位置 car.rotateY(180); car.initPos = car.position; // 创建箱子 var box = app.create({ type: 'Box', position: [22, 0, 1], center: 'Bottom' }); // 叉车移动到箱子位置 car.moveTo({ position: box.position, time: 2000, // 移动到目的地之后把箱子放在叉车的叉臂上 complete: function () { // 可通过 subNodes 获取所有的 子节点 //console.log(car.subNodes); // 叉车拿箱子 car.add({ object: box, basePoint: 'Box001_0', offset: [0, 0.2, -1] }); // 旋转180度 car.rotateY(180); // 返回原地 car.moveTo({ position: car.initPos, time: 2000, complete: function () { // 将箱子从叉车上移除 // 即重新将箱子 add 到另一父物体下 campus.add(box); car.rotateY(180); car.moveTo({ position: [22, 0, 0], time: 2000 }) } }); } });
- 沿路径移动
// 创建一个路径 var path = [ [35,1,10], [-10,1,10], [-30,1,10], ]; //创建一条线 var line = app.create({ type: 'RouteLine', points: path, style: { color: '#58DEDE' } }); // 让车沿路线运动 var car = app.query('car01')[0]; // 延迟500毫秒后进行 setTimeout(function () { car.movePath({ 'orientToPath': true, // 物体移动时沿向路径方向 'orientToPathDegree': 90, // 沿向路径方向偏移一定角度 'path': path, // 路径点数组 'time': 12000, // 路径总时间 'loopType': THING.LoopType.Repeat // 是否循环 }); }, 500);
取消移动
obj.stopMoving();
-
控制效果
style属性大部分物体类型,都可以通过style来设置效果
设置透明度(虚化)
obj.style.opacity = 0.5;(0-1)
设置颜色效果及勾边效果
obj.style.color = "#00ff00";//可使用十六进制或者rgb obj.style.wireframe = true;//给物体开启线框模式 obj.style.wireframe = false;//给物体关闭线框模式 obj.style.outlineColor = "#00ff00";//可使用十六进制或者rgb obj.style.boundingBox = true;//打开包围盒 obj.style.boundingBoxColor = '#00ff00';//设置包围盒颜色 obj.plan.style.color = '#00ff00';//设置楼层地板颜色 obj.wall.style.color = '#00ff00';//设置楼层墙体颜色
设置动画效果
obj.style.alwaysOnTop = true;//是否让物体显示在最上层 obj.fadeIn();//淡入效果 obj.fadeOut();//淡出效果
设置楼层展开合并
// 水平展开或垂直展开(可尝试修改后运行看效果) var isHorzMode = false; // 是否显示天花板 var isHideRoof = true; // 展开建筑楼层 function test_expand() { building.floors.visible = true; building.facades.visible = false; building.expandFloors({ 'time': 1000, 'length': isHorzMode ? -30 : 10, 'horzMode': isHorzMode, 'hideRoof': isHideRoof, 'complete': function () { console.log('expand complete '); } }); } // 恢复建筑楼层 function test_unexpand() { building.unexpandFloors({ 'time': 500, 'complete': function () { building.floors.visible = false; building.facades.visible = true; console.log('unexpand complete '); } }); }
-
坐标转换
三套坐标体系坐标相互之间的转换,方便控制或获取物体位置
// 将输入的物体自身坐标系下坐标转换成世界坐标 obj.selfToWorld(pos) // 将输入的世界坐标转换成物体自身坐标系下坐标 obj.worldToSelf(pos) // 将输入的世界坐标转换成父物体坐标 obj.worldToLocal(pos) // 将输入的父物体坐标转换成世界坐标 obj.localToWorld(pos)
-
位移旋转缩放动画
我们可以使用 moveTo 设置一个移动动画,rotateTo 设置一个旋转动画,scaleTo 设置一个缩放动画,用 movePath 可设置让物体沿一条路径移动
moveTo
obj.moveTo({ position: [10, 0, 10], orientToPath: true, orientToPathDegree: 90, time: 12000, delayTime: 500, complete: function() { console.log("moveto completed"); } });
参数 说明 position 在世界坐标系下设置目标位置 offsetPosition 在自身坐标系下设置目标位置 orientToPath 物体是否朝向移动的方向 orientToPathDegree 沿向路径方向偏移一定角度 time 移动总时间 speed 速度,和time任选其一设置 lerpType 插值类型,参见本页 下方 ,默认是null,等同于 THING.LerpType.Linear.None delayTime 延迟移动 loopType 循环类型,参见本页 下方 ,默认是 null,等同于 'no',或者是THING.LoopType.No complete 完成时的回调, * repeat 和 pingpong模式下没有回调 rotateTo
obj.rotateTo({ 'angles': [0, 90, 0], 'time': 12000, 'delayTime': 500, 'complete': function () { console.log('scale completed'); } }); //angles:设置旋转角度 //除了 orientToPath 和 orientToPathDegree 这两个参数不需要,其它参数和 moveTo 是一样的
scaleTo
obj1.scaleTo({ scale: [1, Math.randomFloat(2.0, 5.0), 1], time: 5000, }); //scale :在自身坐标系下三个轴向目标缩放值 //其它参数和 rotateTo 是一样的
-
模型动画
var cabinet = app.query('.Thing')[0]; //查看 模型有哪些动画 console.log(cabinet.animationNames); //开始播放动画 cabinet.playAnimation('open1'); cabinet.playAnimation({ name: 'open1', reverse: true,//倒播动画 loopType: THING.LoopType.Repeat,//循环播放动画 }); //同时播放多个动画 cabinet.playAnimation({ name: ['open1', 'open2'], // 同时播放多个动画 loopType: THING.LoopType.PingPong, // 先正向播放后,再反向播放,如此循环 speed: 0.4 //设置播放速率 });
-
物体编辑
显示旋转控制轴
referenceObj = app.query('car03')[0]; //单击选中物体 referenceObj.on('singleclick', function (ev) { var object = ev.object; app.selection.select(object); }); //添加、显示旋转控制轴 referenceObj.on(THING.EventType.Select, function (ev) { var object = ev.object; // 判断物体是否有控制轴 if (!(object.hasControl('axisControl'))) { //添加控制轴 object.addControl(new THING.AxisTransformControl(object), 'axisControl'); // 改变控制轴 Mode 为旋转模式 object.getControl('axisControl').mode = 'rotate'; } else { // 将控制轴设置为显示 object.getControl('axisControl').visible = true; } }); // 隐藏旋转控制轴 referenceObj.on(THING.EventType.Deselect, function (ev) { var object = ev.object; // 物体如果有控制轴 if (object.hasControl('axisControl')) { // 控制轴设置为隐藏 object.getControl('axisControl').visible = false; } });
拖拽物体,改变位置
var testCar = app.query('car01')[0]; // 拖拽物体 改变物体位置 testCar.on(THING.EventType.Drag, function (ev) { if (ev.picked) ev.object.position = ev.pickedPosition; }, '物体拖拽'); // 拖拽结束 testCar.on(THING.EventType.DragEnd, function (ev) { console.log('拖拽结束'); // do something }, '结束拖拽')
单击清空选择集
//单击清空选择集 app.on('singleclick', function (ev) { var object = ev.object; // 当 Pick 的物体不在 Selection集 中 if (!ev.picked || !app.selection.has(object)) { // 清空 Selection集 app.selection.clear(); } }, '单击清空选择集');
拖拽模型到场景并创建、编辑
/** * 说明:物体场景编辑功能 * 操作:拖动左上方按钮来创建场景中的物体,并且进行编辑。 * 鼠标右键: 取消移动轴 */ THING.Utils.dynamicLoad(['examples/css/sample_ThingEdit.css'], function () { var panel = `<div class="model"> <ul> <li title="卡车" id="truck" data-url="https://www.thingjs.com/static/models/truck"> <img src="/guide/image/model/mox_01.jpg" alt=""> </li> <li title="铲车" id="forklift" data-url="https://www.thingjs.com/static/models/forklift2"> <img src="/guide/image/model/mox_02.jpg" alt=""> </li> </ul> </div>` $('#div3d').append($(panel)); }); var app = new THING.App({ url: 'https://www.thingjs.com/static/models/simplebuilding' }); // 加载场景后执行 var thing = null; app.on('load', function () { // 按钮拖拽事件(dragIcon为教程页面提供的工具) document.getElementsByClassName('model')[0].style.display = 'block'; dragIcon('truck', dragStart); dragIcon('forklift', dragStart); }); // 物体跟随鼠标移动 app.on('mousemove', function (ev) { if (thing) { var worldPosition = app.picker.pickWorldPosition(ev.clientX, ev.clientY); if (!worldPosition) { worldPosition = app.camera.screenToWorld(ev.clientX, ev.clientY); } thing.position = worldPosition; } }); // 鼠标右键点击取消所有控制轴 app.on('mousedown', function (ev) { if (ev.button == 2) { app.query('.Thing').forEach(function (object) { object.removeControl('axisControl'); }); } }); // 鼠标抬起取消物体移动 app.on('mouseup', function (ev) { if (thing) { // 重新让物体开启拾取 thing.pickable = true; // 点击物体可以开启/关闭控制轴 thing.on('singleclick', function (ev) { var object = ev.object; if (object.hasControl('axisControl')) { object.removeControl('axisControl'); } else { object.addControl(new THING.AxisTransformControl(object), 'axisControl'); } app.query('.Thing').not(object).forEach(function (object) { object.removeControl('axisControl'); }); }); // 出发点击事件来给物体添加控制轴 thing.trigger('singleclick'); thing = null; } // 开启摄像机转动 app.camera.enableRotate = true; }) // 创建物体并且开始拖动 function dragStart(url, x, y) { // 创建物体 thing = app.create({ type: 'Thing', url, position: app.camera.screenToWorld(x, y), }); // 防止拖动过程中获取时间坐标的时候,对位置进行重复拾取 thing.pickable = false; // 关闭摄像机转动 app.camera.enableRotate = false; } // 关联拖动图标 function dragIcon(id, callback1) { var dom = document.getElementById(id); var url = dom.getAttribute('data-url'); dom.onmousedown = function (e) { callback1(url, e.pageX, e.pageY); } }
六、拾取和选择
1.通过属性和接口获取鼠标拾取(Pick)的物体
if (app.picker.isChanged()) {
//通过app.picker.objects 得到当前拾取的物体
console.log(app.picker.objects);
//通过app.picker.previousObjects 得到之前拾取的物体
console.log(app.picker.previousObjects);
}
2.通过事件获取鼠标拾取(Pick)的物体
// 鼠标拾取的物体勾边
app.on(THING.EventType.Pick, '.Thing' ,function(ev) {
ev.object.style.outlineColor = '#FF0000';
});
// 鼠标离开物体取消勾边
app.on(THING.EventType.Unpick,'.Thing', function(ev) {
ev.object.style.outlineColor = null;
});
//也可以通过 mouseover 和 mouseleave 来实现
// 改变颜色
app.on(THING.EventType.MouseOver,'.Thing', function () {
app.picker.objects.style.color = '#ff0000';
});
// 还原颜色
app.on(THING.EventType.MouseLeave, '.Thing',function () {
app.picker.previousObjects.style.color = null;
});
//当 Pick 发生变化时会触发 PickChange 事件,也可以通过事件的回调参数获取当前和之前的拾取物体
app.on(THING.EventType.PickChange,function (ev) {
ev.objects.style.color = '#ff0000';
ev.previousObjects.style.color = null;
});
3.区域 Pick 物体
//由于框选比较消耗性能,因此预先设置框的“候选集”,只在候选集中框选
var things = app.query('.Thing');
app.picker.areaCandidates = things;
//启动框选 传入 鼠标按下时开始框选的屏幕坐标
app.picker.startAreaPicking({
x: x,
y: y
});
//结束框选
app.picker.endAreaPicking();
//通过 pickedResultFunc 设置拾取对象回调函数
app.on(THING.EventType.EnterLevel, '.Building', function () {
app.picker.pickedResultFunc = function () {
//do something
}
})
4.选择物体
//将物体加入到选择集中
app.selection.select(obj);
//将物体从选择集中删除
app.selection.deselect(obj);
//清空选择集
app.selection.clear();
七、事件
八、摄像机
九、园区与层级
文章后续会继续更新