园区与层级

ThingJS 场景中可以加载园区,加载后系统自动创建了园区、建筑、楼层、房间等物体对象,这些对象也自然把场景分成了不同的层级,这个章节将详细介绍了园区和层级相关概念。

场景搭建概览

通过 CamBuilder 可搭建并输出一个园区,该园区可在 ThingJS 场景中加载。

园区搭建步骤:

下图中带图标的地方均可查看详细描述。

完成场景搭建后,即可导出ThingJS场景包(.tjs),上传至 ThingJS 开发平台中使用。

help
注意事项

在ThingJS中,所有的长度、距离度量单位都是米,因此在进行场景搭建时,请按照实际尺寸搭建园区、建筑、楼层、房间。摆放的物体如果是用户自行建模导入的,也请按照实际尺寸建模。

场景上传

可通过控制台上传至ThingJS平台,过程如下:

  1. 点击 ThingJS 官网导航栏右侧“控制台”,即可进入“我的项目”;

  2. 选择左侧列表中的“我的资源”,点击切换;

  3. 点击“上传场景”,进行上传;

  4. 在弹出层中点击“选择文件”,选择已导出的 ThingJS 资源包,点击“立即上传”:

  5. 上传成功后即可选择“开发”、“复制地址”、“预览”、“删除”等操作;:

场景与园区

场景与园区

当我们使用 App 启动了 ThingJS,ThingJS 就会创建一个三维空间,整个三维空间我们称之为“场景”(scene),在场景内我们可以创建对象,比如园区,建筑,车辆,传感器等等。

通过 CamBuilder 可编辑并输出一个园区,该园区可在 ThingJS 场景中加载。创建 App 时,我们传入的 url,就是被创建园区的地址。

如下所示:

CamBuilder 与 ThingJS 联系

在 CamBuilder 中创建的物体,只有在编辑了 UserID、Name 或者 自定义属性 后,导入到 ThingJS 中才能成为独立的管理对象,被程序读取或修改。并且 CamBuilder 中 UserID 和 Name 与 ThingJS 中的对象有对应关系。

如下所示:

添加多个园区

在场景里,是可以添加多个独立园区的,每一个园区是一个 THING.Campus 类的对象,我们通过“app.create”接口来实现。

var app = new THING.App();
var campus1 = app.create({
    type: "Campus",
    url: "models/storehouse",
    complete: function (ev) {
        console.log("Campus created: " + ev.object.id);
    }
});
var campus2 = app.create({
    type: "Campus",
    url: "models/chinesehouse",
    position: [50, 0, 0],
    complete: function (ev) {
        console.log("Campus created: " + ev.object.id);
    }
}); 
help
注意事项:

有时候,我们经常会说制作场景,上传场景,下载场景,加载场景,这里的场景其实就是园区,我们通常也会简称为"场景"。

场景与地图

通过 CamBuilder 可编辑并输出一个园区,该园区可在 ThingJS 场景中加载。创建 App 时,我们传入的 url,就是被创建园区的地址。

通过 CamBuilder 搭建一个园区后,我们可以用插件设置场景在地图上面的位置。

在地图中选择需要摆放的位置后保存,CamBuilder中的场景就会自动同步到ThingJS平台同一账号下。

场景同步过去之后,我们可以通过代码获取场景在地图中摆放的经纬度数据。

app.on('load', function () {
    let tjsLnglat = app.root.defaultCampus.extraData;
    console.log(tjsLnglat);
}) 

在ThingJS中,可以把园区摆放在地球对应位置上,上文提到的获取到的经纬度数据使用示例如下:

var app = new THING.App({
    url : 'https://www.thingjs.com/./client/ThingJS/13628/20191010182917578932750'
});
var sceneLonlat = null;
app.on('load', function(ev){
    app.background = [0, 0, 0];
    var map;
    let tjsLnt = app.root.defaultCampus.extraData.coordinates;
    tjsLnt = tjsLnt.split(',')
    sceneLonlat = tjsLnt;
    createMap();
})

function createMap(){
    var map;
    THING.Utils.dynamicLoadJS(["https://www.thingjs.com/uearth/uearth.min.js"], function () {
        // 新建一个地图
        map = app.create({
            type: 'Map',
            style: {
                night: false
            },
            attribution: 'Google'
        });

        // 新建一个瓦片图层
        var tileLayer = app.create({
            type: 'TileLayer',
            name: 'tileLayer1',
            url: 'https://mt{0,1,2,3}.google.cn/vt/lyrs=s&hl=zh-CN&gl=cn&x={x}&y={y}&z={z}',
        });
        // 将瓦片图层添加到map中
        map.addLayer(tileLayer);
        app.root.defaultCampus.position = CMAP.Util.convertLonlatToWorld(sceneLonlat, 0);
        app.root.defaultCampus.angles = CMAP.Util.getAnglesFromLonlat(sceneLonlat, 90);
        app.camera.flyToGeoPosition({
            lonlat: sceneLonlat,
            height: 200,
            time: 3000,
            complete: function () {  
            }
        });
    })
} 

效果如下图所示:

当然这里的经纬度数据也可以直接用在CityBuilder中,我们在使用CityBuilder的时候,会将场景放在地球上的某个位置,这个时候就可以根据我们获取到的经纬度进行场景摆放了。

如下图所示:

当场景的开发交互功能完成后,在场景预览时通过选择地图背景,就可以看到场景摆放在对应位置上。

场景层级

ThingJS 场景中加载了园区后,场景中自动创建了 campus,building,floor,room 和一些在 CamBuilder 中添加的物体对象。这些对象不是独立散落在场景中的,他们会相互关联,形成一棵树的结构,从而构建了场景的层级。

ThingJS 提供了两套层级体系:父子树、分类对象属性树。

父子树

在 ThingJS 场景中,每个对象,都可以通过 children 访问到下层子对象物体,通过 parent 访问到对应的父物体。

help
注意事项:

子物体可以是多个,父物体只能有一个,children 属性是数组(Array)类型。

如您所见,场景会有一个根物体,可通过 app.root 访问到,所有对象都是他的子子孙孙。

创建一个物体对象时,可指定该对象的父物体。

一个物体对象也可以通过 add ,添加子物体。

分类对象属性树

每个对象可以有多个孩子,为了方便分类查找物体,ThingJS 又针对每类对象提供了一些内置属性。

如图,campus 提供了三个分类内置属性:

  • buildings:可以访问到该园区下所有的建筑对象。
  • ground:可以访问到园区的地面对象。
  • things:其他所有 Thing 类型的物体。

如果属性的英文拼写是复数,说明该属性管理了多个物体对象,使用的是 Selector 数据结构。

如果是单数,说明管理的只能是一个物体对象,属性返回就是该对象本身。

图中,其他物体提供的分类内置属性,可参考对应的 API 文档 查看具体含义。

help
注意事项:

分类对象属性树一般是无需用户维护,系统会自动管理。比如,通过 add 方法将一个物体(Thing)添加到 campus 下,系统会自动将该物体添加到 campus 的 things 下进行管理。

目前 CamBuilder 中放在房间内的物体,并不属于房间,即便在房间内拖拽的物体,也是放置在相应的楼层下。

上面我们介绍了层级,那么层级结构有什么用呢:

  • 可以让我们方便管理和查询到场景中物体;
  • 可以批量操作物体,比如移动父物体可以带着孩子一起移动等等。

如何切换层级

ThingJS 场景中提供了层级结构,但是如何实现场景层级切换呢?下面的内容将会告诉你答案。

层级切换

场景提供了层级结构,我们可以通过 “父子树” 和 “分类对象属性树” 来批量控制子物体,比如移动、显示或者透明控制等。

借用此能力,系统在园区加载完成后仅显示建筑外立面、隐藏楼层;当双击进入建筑时,再把该建筑的所有楼层都显示出来,以提高场景显示的性能。

我们把从园区进入到建筑内,定义为一次 “层级切换” 。

层级切换

为了方便 “层级切换” 操作, ThingJS 提供了 SceneLevel 模块,通过 app.level 可以访问到。

  • 提供如下接口,方便控制当前物体层级:

    • app.level.change(object):将场景设置到指定物体的层级,具体参数参见 API文档
    • app.level.back():返回当前层级的父物体层级,具体参数参见 API文档
  • 提供如下属性,方便获取当前状态:

    • app.level.current :获取当前的层级对象,具体参数参见 API文档
    • app.level.previous :获取之前的层级对象,具体参数参见 API文档

系统启动后,只要调用了一次 app.level.change(无论是将层级切换到了园区还是切换到了某个Thing),ThingJS 就启动了内置的 园区<—>建筑<—>楼层<—>物体…… 的逐级进入和退出的交互操作流程和对应的响应。

场景层级事件

ThingJS 中设定左键双击可进入到所拾取的物体层级,右键单击可返回到上一层级。

当进入层级时会触发 EnterLevel 事件。

当退出层级时会触发 LeaveLevel 事件。

help
注意事项:

“进入” 和 “退出” 是两个方向的,以进入建筑(Building)为例:

“从园区进入建筑” 和 “从楼层退出,进入到建筑” 都会触发建筑层级的 EnterLevel 事件。

我们可通过暂停系统内置的 LevelEnterOperation 来屏蔽掉默认的左键双击进入层级操作。

暂停系统内置的 LevelBackOperation 来屏蔽掉系统默认的右键单击退出层级的操作。

// 暂停默认的 左键双击 进入层级的操作 
// 如果只想暂停某一层级的 左键双击进入 第二个参数可填写 '.Building' 或 '.Floor' 等
app.pauseEvent(THING.EventType.DBLClick, '*', THING.EventTag.LevelEnterOperation); 
// 暂停默认的 右键单击 退出层级的操作
app.pauseEvent(THING.EventType.Click, '*', THING.EventTag.LevelBackOperation);
 

如果要修改默认的交互操作,详见代码块:

当默认的层级切换飞行结束后,会触发 THING.EventType.LevelFlyEnd 事件。

可在该事件的回调函数中,进行层级切换飞行结束后的行为控制。

app.on(THING.EventType.LevelFlyEnd, '*', function (ev) {
    if(ev.previous ){
        console.log('上一层级:'+ev.previous.name)
    }
    console.log('[' + ev.object.name + '] 物体层级飞行结束');
});
 

切换场景层级响应

当层级发生变化后,会触发进入层级事件(EnterLevel)的四个内置响应和退出层级事件(LeaveLevel)的一个内置响应,他们分别是:

  • 进入层级时的场景控制(THING.EventTag.LevelSceneOperations)

    如进入建筑时显示所有楼层;进入物体时,设置兄弟物体半透明

  • 进入层级时的飞行控制(THING.EventTag.LevelFly)

    如进入各个层级时的飞行控制(飞行时间、视角等)

  • 进入层级时背景控制(THING.EventTag.LevelSetBackground)

    如进入建筑后隐藏天空盒

  • 进入层级时的 Pick 设置(THING.EventTag.LevelPickedResultFunc)

    如进入建筑后是只能 Pick 楼层还是也能 Pick 楼层下的物体

  • 退出层级时的场景控制(THING.EventTag.LevelSceneOperations)

    如从园区进入建筑层级(即退出园区)后,园区隐藏

如果想修改默认设置,可以暂停掉内置响应后再重新注册 EnterLevel 、 LeaveLevel 事件来进行修改。

可使用代码块快捷完成修改:

动态创建组合场景

对于一些大型场景,使用 CamBuilder 直接制作会比较困难,并且直接加载也存在性能、加载时间等问题。下面的内容则针对这种大型场景制作的问题提供了解决的办法。

大型场景上述问题解决办法

  • 在 CamBuilder 中我们可以分成多个工程进行搭建,比如园区和所有建筑的外立面使用一个独立的工程进行搭建,每栋建筑的室内可分别使用其他独立工程进行搭建。在搭建过程中有一条重要的规则需要遵守:

    每个工程里的物体命名需要保证唯一

    为了保证物体对象不重名,每个工程里的命名(工程文件的名称就是园区的名字),和每个工程里建筑的命名都要唯一。因为建筑的外立面和室内是在两个工程里分开搭建的,两个工程里本应有同一个名字的建筑,但为了后期可以加载到一起,就不能用同一个建筑名字了。

    比如,建模需求是一个园区内有一个建筑,我们分成两个工程进行搭建,分别是“XX工业园区”、“XX工业园区-办公楼室内”,工程内物体命名如下:

    XX工业园区(工程文件名,代表园区名),包括如下物体:

    办公楼(建筑)

    办公楼外立面(建筑外立面)

    XX工业园区-办公楼室内(此工程和上个工程文件名不能一样),包括如下物体:

    办公楼楼层一(楼层)

    桌子。。。。。(物体)

    办公楼楼层二(楼层)

    桌子。。。。。(物体)

  • 分别导出各个工程,并上传到 ThingJS 网站;
  • 在 ThingJS 先加载"XX工业园区",该园区中包含建筑,但该建筑只有外立面。
  • 使用事件,可重新注册进入建筑的响应函数,事件回调内使用 app.create ,动态加载“XX工业园区-办公楼室内”这个园区工程。
  • 再使用代码,获取“办公楼TMP”这个园区物体的建筑,将其下的“办公楼楼层一”,“办公楼楼层二”,添加到本来只有外立面的“办公楼”对象身上。再将“XX工业园区-办公楼室内”和“办公楼TMP”这些临时对象删掉。此时,我们就动态加载了一个完整的“办公楼”。

要保证后加载的楼层和之前加载建筑对齐

方案如下:

  • 在 CamBuilder 搭建场景时,保证楼层相对建筑“办公楼”和“办公楼TMP”的位置一致。
  • 在 ThingJS 在线开发中,将“办公楼TMP”下的楼层“挂接”到“办公楼”时,设置楼层的相对坐标。
buildingTmp.floors.forEach(function (floor) {
buildingMain.add({
    object: floor,
    // 设置相对坐标,楼层相对于建筑的位置保持一致
    localPosition: floor.localPosition
    });
}) 

提前知道未加载场景的物体结构

有时我们还需要在未加载场景的时候知道一个物体在场景树里的位置,以便于飞过去。我们提供两套方案:

  • 我们提供 app.getCampusJSON 接口,可以在不加载园区场景的情况下,解析出该园区的场景树结构,做些类似上面动态加载场景的操作,在3D系统中建立完整的场景结构。我们再通过 EnterLevel 相关事件,配合上面自动动态加载的方案,就可以找到并飞到物体。

  • 用户也可以自行建立后台数据结构,后台进行查询,通知前台,前台使用 EnterLevel 相关事件,配合上面自动动态加载的方案,就可以找到并飞到物体。
在线咨询
QQ交流群
微信公众号