控制对象

控制显示/隐藏

控制显隐

ThingJS 中通过设置物体的 visible 属性来直接控制物体的显示/隐藏,例如:

var car=app.query('car01')[0];
car.visible=false; 

当然也可以设置对象集合(Selector)的 visible 属性控制多个物体的显示/隐藏,例如:

// 获取场景内所有的建筑 并 隐藏
var buildings=app.query('.Building');
buildings.visible=false; 

如果对象有相应的父子关系,那么,当隐藏父亲时,他的子子孙孙也会跟随隐藏。

比如当隐藏建筑时,建筑的外立面、楼层、楼层里的物体等子孙会同时隐藏。

系统层级默认显隐规则

当开启系统内置层级时,系统会有一套默认的显示/隐藏规则:

  • 进入园区级别,显示该园区下的地面(Ground)、建筑的外立面(Facade)以及其他直属物体(Thing)
  • 进入建筑级别,隐藏该建筑的外立面,显示该建筑的楼层(包括子孙)
    help
    注意事项

    此时当前建筑并没隐藏,只是隐藏了该建筑的外立面

  • 进入楼层级别,隐藏其他楼层,显示当前楼层的子孙
  • 依此类推……

控制位置

要想控制一个对象物体的空间位置,首先需要理解空间坐标系。

描述或控制一个物体的位置,我们在不同情况下会分别使用 3 套坐标系统:

  • 世界坐标系
  • 父物体坐标系
  • 自身坐标系
help
注意事项

ThingJS获取的[x,y,z]位置坐标的单位均为米。

世界坐标系

世界坐标系是系统的绝对坐标系,当场景(注意不是指园区)创建后,在整个场景空间中标绘一个位置,此时场景空间的坐标系就是世界坐标系。

一个物体在世界坐标系下控制物体位置直接使用 position 属性,如:

obj.position = [10,0,10]  

获取世界坐标系下的物体位置,也直接使用 position 属性,如:

console.log(obj.position)  

父物体坐标系:

当在场景中创建了园区,在园区下我们放置一个飞机,飞机是园区的子物体,我们想在园区的坐标系下,设置飞机的位置,这时使用的园区的坐标系,就是飞机的父物体坐标系。

再举个例子,我们想给一个人物添加一顶帽子,因为人在场景里位置和方向是不确定的,在世界坐标系下设置帽子的位置,要经过非常复杂的计算。但不管人在哪,面向哪,帽子和人的相对位置是一定的,所以我们在以人为坐标系的情况下就很容易把帽子放到正确的位置。前提是帽子是人的子物体,人的坐标系就是帽子的父物体坐标系,如下图所示:

一个物体要在父物体坐标系下设置或获取位置我们使用 localPosition 属性。

自身坐标系:

聪明的你,一定能想到,有时候我们也希望在以自身作为坐标系统下控制,比如,叉车向前走2米,就是在自身坐标系下设置坐标[0,0,2](物体自身的z轴正向,可以理解为是物体正面的方向,比如我们面部的朝向)。

一个物体在自身坐标系下控制位置使用如下接口:

obj.translate([0,0,2]);  

正常情况下,子物体会随着父物体移动而一起移动,如果想控制子物体不随父物体移动,可通过设置子物体的 inheritPosition 属性为 false 而实现。

控制旋转

旋转

ThingJS 使用角度控制物体旋转。

通常使用如下属性和接口控制物体旋转:

  • 在世界坐标系下,使用 angles 属性来设置或访问旋转信息。

    obj.angles = [0,45,0]   //设置世界坐标系Y轴向旋转45角度 
  • 在父物体坐标系下,使用 localAngles 属性来设置或访问旋转信息。

    obj.localAngles = [0,45,0]   //设置父物体坐标系Y轴向旋转45角度 
  • 在自身坐标系下,使用如下接口方法:

    //使用rotate,可输入角度和轴向。设置沿给定轴向转一定角度,传入的旋转轴是自身坐标系下的轴方向       
    obj.rotate( 45, [0,1,0])      
    
    //沿自身x轴向旋转,等同于 obj.rotate( 30, [1,0,0])       
    obj.rotateX(30)  
    
    //沿自身y轴向旋转,等同于 obj.rotate( 90, [0,1,0]) 
    obj.rotateY(90)  
                
    //沿自身z轴向旋转,等同于 obj.rotate( -45, [0,0,1])         
    obj.rotateZ(-45)     
  • 我们还可以使用 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 而实现。

控制缩放

对于缩放,是个 3D 里面比较复杂的概念,我们这里只提供自身坐标系下的缩放控制。

使用 scale 属性访问或设置:

 obj.scale = [1,2,1]   //让物体在高度方向上放大两倍 

正常情况下,子物体会随着父物体缩放而一起缩放,如果想控制子物体不随父物体缩放,可通过设置子物体的 inheritScale 属性为 false 而实现。

物体轴心点

轴心点

物体自身坐标系的原点,就是这个物体的轴心点(pivot)。

一个物体在世界坐标系和父坐标系下的位置,其实就是物体轴心点在世界坐标系下和父坐标系下的位置,物体在自身坐标系下的旋转和放缩,也是基于轴心点。

一个人的轴心点最好在脚下,如下图所示,以方便设定位置,如果轴心点在腰部,是不是不太好设定他在地上位置?

不是所有的物体的轴心点都是在正中心,比如一个卫星天线的轴心点在卫星天线的旋转轴中心,以方便进行旋转的控制。

树的轴心点和人一样最好放在地面根部,一个方便控制在地面上的位置,另一个也方便控制放缩。当放缩时,如果轴心点在地上,整棵树可以向上放缩,根部也不会因为放大扎到地里。

当轴心点在中心,就会不方便控制,如下图:

物体在世界坐标系下的旋转的正确理解

一个物体在世界坐标系下的坐标位置是 [10,0,0],旋转值是 [0,0,45]。我们该如何理解沿世界坐标系 Z 轴旋转的 45 度呢?

  1. 每帧将物体的位移和旋转置空,等于先把物体放到世界坐标原点下
  2. 将物体沿世界坐标的 Z 轴旋转 45 度
  3. 将物体放置到世界坐标系下的 [10,0,0] 的位置上

每帧按照上面 3 个步骤,进行设置

这样的操作,下面的理解是错误的。

  • 每帧将物体的位移和旋转置空,等于先把物体放到世界坐标原点下
  • 将物体放置到世界坐标系下的 [10,0,0] 的位置上
  • 将物体沿世界坐标的 Z 轴旋转 45 度

位移,旋转,缩放动画

ThingJS提供了 moveTo 设置一个移动动画,rotateTo 设置一个旋转动画,scaleTo 设置一个缩放动画,用 movePath 可设置让物体沿一条路径移动等方法。

moveTo

moveTo 可实现将物体移动到某个目标位置。

示例:

 obj.moveTo({
position: [10, 0, 10],
orientToPath: true,
orientToPathDegree: 90,
time: 12000,
complete: function() {
    console.log("moveto completed");
}
}); 

相关参数如下:

  • position :在世界坐标系下设置目标位置,关于获取坐标点可使用坐标拾取工具
  • offsetPosition :在自身坐标系下设置目标位置,和 position 任选其一设置
  • orientToPath :物体是否朝向移动的方向
  • orientToPathDegree :沿向路径方向偏移一定角度
  • time :移动总时间
  • speed :速度,和time任选其一设置
  • lerpType :插值类型,参见本页 下方 ,默认是THING.LerpType.Linear.None(线性插值),如果填 null 则不插值
  • loopType :循环类型,参见本页 下方 ,默认是 null,等同于 'no',或者是THING.LoopType.No
  • complete :完成时的回调, repeatpingpong 模式下没有回调

可以使用 stopMoving 接口来终止移动。

rotateTo

rotateTo 可将物体旋转至某角度。

示例:

obj.rotateTo({
'angles': [0, 90, 0],   
'time': 12000,                   
'complete': function () {
    console.log('rotateto completed');  
}
}); 

相关参数如下:

  • angles:设置旋转角度
  • 其它参数详见 API

可以使用 stopRotating 接口来停止旋转。

scaleTo

scaleTo 可以将物体缩放至某比例大小。

示例:

obj1.scaleTo({
    scale: [1, Math.randomFloat(2.0, 5.0), 1],
    time: 5000,
}); 

相关参数如下:

  • scale :在自身坐标系下三个轴向目标缩放值
  • 其它参数详见 API

我们使用 stopScaling 接口来终止缩放

movePath

还有一个神奇的方法 movePath,可以设置一条路径,让物体沿路径移动。

示例:

// 路径坐标点数组
var path = [[0, 0, 0], [20, 0, 0], [20, 0, 10], [0, 0, 10], [0, 0, 0]];
car.movePath({
orientToPath: true, // 物体移动时沿向路径方向
path: path, // 路径坐标点数组
time: 5 * 1000, // 路径总时间 毫秒
lerpType: null, // 插值类型(默认为线性插值)此处设置为不插值
// 仅当无循环时 有回调函数
complete: function (ev) {
    console.log(ev.object.name + "移动结束")
}
}); 

相关参数如下:

  • path :由世界坐标系下的坐标点组成的路径,关于获取坐标点可使用坐标拾取工具
  • 其它参数和 moveTo 类似。 API

可以使用 stopMoving 接口来终止移动。可以使用 stopMoving 接口来终止移动。

lerpType 类型说明

上述的示例中我们都有提到一个参数 lerpType ,是控制运动的插值方法的,见下图,找到你需要的插值类型。

loopType 类型说明

上述的示例中我们都有提到一个参数 loopType ,是控制运动的重复方法的,见下图,找到你需要的重复类型。

模型动画

对于 Thing 类的对象,是通过 url 加载的模型,很多模型在制作阶段就内置了动画。

如果模型有内置动画,则可以在 ThingJS 中利用 API 调用播放这些动画。

help
注意事项

如果是动态创建的物体,由于模型加载是异步的,则必须在模型加载完成后才能调用动画。

获取模型动画

首先我们通过 animationNames 属性获取上图中的模型都有什么动画。

示例:

console.log(obj1.animationNames); 

输出:

播放模型动画

我们使用 playAnimation 接口进行动画播放。

  • 简单播放动画
     obj.playAnimation({
        name: "animation",
        reverse: true//反转数组
    }); 
  • 可以反向播放动画
    obj.playAnimation("animation"); 
  • 可以循环播放动画,并且可以同 loopType ,来控制循环类型
    obj.playAnimation({
        name: "open1",
        loopType: THING.LoopType.Repeat
    }); 
  • 还可以同时播放多个动画;
    obj.playAnimation({
        name: ["open1", "open2"],
        loopType: THING.LoopType.PingPong,
        speed: 0.4
    }); 

停止播放模型动画

我们使用 stopAnimation 接口来停止动画播放

//当物体带有多个动画时,`stopAnimation`接口将会停止所有动画播放
obj.stopAnimation();
//指定停止哪个动画
obj.stopAnimation("open1"); 

官方模型动画介绍

我们有提供各个行业的模型供用户选择,这些模型有部分在制作阶段就内置了动画。例如:

      以下面这个门为例:

      • 在CamBuilder中我们可以通过选中该模型,查看模型是否带有动画,例如这个门动画名为‘OpenDoor’和‘CloseDoor’,分别控制开门和关门动画。

      • 在ThingJS中,我们可以通过console.log(obj1.animationNames)来判断模型是否带有动画

      机柜

      以下面的机柜为例:

      在CamBuilder中我们可以通过选中该模型,查看模型是否带有动画,例如这个机柜动画名为‘close_all’,‘open_all’,‘open1’,‘close1’,‘open2’和‘close2’,控制机柜开关柜门动画。

      仓储

      以下面的粮仓为例:

      在CamBuilder中我们可以通过选中该模型,查看模型是否带有动画,例如这个粮仓动画名为‘CloseRoof’和‘OpenRoof’,分别控制粮仓开盖关盖动画。

      动物

      以下面这个蝴蝶为例:

      在CamBuilder中我们可以通过选中该模型,查看模型是否带有动画,例如这个蝴蝶动画名为‘_defaultAnim_’,控制蝴蝶飞舞动画。

坐标转换

对一个物体我们已经了解针对它的3种坐标系统,可以在不同情况下使用对应方式,很方便地控制或获取物体位置。

但在有些时候,我们需要三套坐标体系坐标的转换,我们使用如下接口:

// 将输入的物体自身坐标系下坐标转换成世界坐标
obj.selfToWorld(pos) 
// 将输入的世界坐标标转换成物体自身坐标系下坐标
obj.worldToSelf(pos) 
// 将输入的世界坐标转换成父物体坐标
obj.worldToLocal(pos)
// 将输入的父物体坐标转换成世界坐标
obj.localToWorld(pos)  

效果

style属性

  • 设置颜色和透明度
    //设置不透明度是0.5,如果设置成1.0即为完全不透明
    car.style.opacity = 0.5;
    //设置物体附加颜色
    car.style.color = "#00ff00";
    //取消物体颜色可直接设置:
    app.style.color = null; 
    help
    注意事项

    ThingJS 中的颜色支持使用颜色对应的单词,使用十六进制表示(#00-FF),使用 rgb 表示等几种方法。

    查看示例

  • wireframe
    //给物体开启线框模式
    car.style.wireframe = true;
    //给物体关闭线框模式
    car.style.wireframe = false; 
  • outlinecolor
    //设置物体为红色沟边
    car1.style.outlineColor = "#ff0000";
    //设置物体为绿色沟边
    car2.style.outlineColor = "#00ff00"; 
  • alwaysOnTop

    平常场景中我们看到的如下:

    但是当用户想让某物体显示在最上层时,可以选择 alwaysOnTop

    var car = app.query('car01')[0];
    car.style.alwaysOnTop  =true; 

对象方法

目前可以使用 fadeInfadeOut设置物体淡入淡出效果。

效果如下:

连接

在讲解园区层级的章节时,提到了父子树的概念,除了在创建物体对象时(app.create)可以指定一个对象的父物体外,还可以使用 add 接口让一个物体 B 作为孩子添加到另一个物体 A 的子物体集合中,物体 A 即为物体 B 的父物体。

因为子物体会跟随父物体一同移动、旋转和缩放,所以我们把绑定父物体的操作定义为 “连接操作” 。

可以直接使用 add(object) 方法进行连接操作:

car.add(box); 
help
注意事项

此时 “连接” 上的一刻,子物体的世界位置不发生变化,并保持那一刻与父物体的相对位置关系进行移动。

如果我们要删除 “连接” 关系,那么就需要将该物体指定另一个父物体进行 “连接”。

// 创建箱子
var box = app.create({ type: 'Box', center: 'Bottom', position: [5, 0, 2] });

// 创建Thing 叉车
var car = app.create({
    type: 'Thing',
    name: '叉车',
    url: 'https://model.3dmomoda.cn/models/7fb3a14c34cc42bd81a39bdf075d5d85/0/gltf/',// 模型地址 
    position: [-12, 0, 0],// 世界坐标下的位置 
    complete: function (ev) {

        new THING.widget.Button('连接到叉车', function () {
            // 将物 box 作为孩子添加到 car 上
            car.add(box);
        });

        new THING.widget.Button('从叉车移除', function () {
            // 将 box 从叉车移除 ,重新连接到园区上
            var campus = app.query('.Campus')[0];
            campus.add(box);
        });

        // 设置物体沿路径移动 
        car.moveTo({
            'position': [27, 0, 0], // 路径点数组 
            'time': 10 * 1000, // 路径总时间,2秒 
            'orientToPath': true, // 物体移动时沿向路径方向 
            'loopType': THING.LoopType.PingPong,
            'lerpType': null
        });
    }
}); 

如果我们 “连接” 时,想设置子物体与父物体的相对位置关系,示例如下:

car.add({
    object: box, // 作为孩子的对象
    localPosition: [0, 2, 0] // 相对于父物体的坐标
}); 

此时,“连接” 的一刻,子物体将以设置的相对坐标作为与父物体的相对位置关系,并保持此相对位置关系。

// 创建箱子
var box = app.create({ type: 'Box', center: 'Bottom', position: [5, 0, 2] });

// 创建Thing 叉车
var car = app.create({
type: 'Thing',
name: '叉车',
url: 'https://model.3dmomoda.cn/models/7fb3a14c34cc42bd81a39bdf075d5d85/0/gltf/',// 模型地址 
position: [-12, 0, 0],// 世界坐标下的位置 
complete: function (ev) {

    new THING.widget.Button('连接到叉车', function () {
        // 将物 box 作为孩子添加到 car 上
        car.add({
            object: box, // 作为孩子的对象
            localPosition: [0, 2, 0] // 相对于父物体的坐标
        });

    });

    new THING.widget.Button('从叉车移除', function () {
        // 将 box 从叉车移除 ,重新连接到园区上
        var campus = app.query('Campus')[0];
        campus.add(box);
    });

    // 设置物体沿路径移动 
    car.moveTo({
        'position': [27, 0, 0], // 路径点数组 
        'time': 10 * 1000, // 路径总时间,2秒 
        'orientToPath': true, // 物体移动时沿向路径方向 
        'loopType': THING.LoopType.PingPong,
        'lerpType': null
    });
}
}); 

以子节点作为基准连接

如果一个物体的模型是由多个“子节点”组合而成的

效果如下:

那么我们也可以基于某个“子节点”设置该模型与所连接的父物体的相对位置关系,如下:

car.add({
    object: box,// 作为孩子的对象
    basePoint: "chazi", // 作为“基准”的“子节点”名称
    offset: [0, 0.2,0] // 相对于参考点位的自身偏移量
}); 
// 创建箱子
var box = app.create({ type: 'Box', center: 'Bottom', position: [5, 0, 5] });
box.style.color = 'rgb(255,0,0)';

// 创建Thing 
var car = app.create({
    type: 'Thing',
    name: '叉车',
    url: 'https://model.3dmomoda.cn/models/7fb3a14c34cc42bd81a39bdf075d5d85/0/gltf/',
    // 模型地址 
    position: [10, 0, 5],
    complete: function (ev) {

        var radio = createUI();

        radio.on('change', function (ev) {
            console.clear();

            var subNodeName = ev;

            console.log('将 ' + subNodeName + ' 节点 作为基准');
            car.add({
                object: box,
                basePoint: subNodeName,
                offset: [0, 0.1, 0]
            });
        })
    }
});

function createUI() {
    // 界面组件
    var panel = new THING.widget.Panel({
        titleText: '各个子节点',
        width: '200px',
        hasTitle: true, // 是否有标题
    });
    // 创建数据对象 
    var dataObj = {
        'subNodes': 'chazi',
    };
    // 界面绑定对象 
    var radio = panel.addRadio(dataObj, 'subNodes', ['chazi', 'qianlun', 'houlun', 'SubModelNode001']);
    return radio;
} 

如何知道一个模型中的所有子节点的名称呢?

我们可以通过物体对象的 subNodes 属性得到所有的子节点,如下:

var subNodes = obj.subNodes;
//console.log(obj.subNodes);
for (var i = 0; i < subNodes.length; i++) {
    var subnode = subNodes[i];
    var name = subnode.name;
    console.log(name);
} 
在线咨询
QQ交流群
微信公众号