Cesium设置模型朝向速度矢量方向

1. 需求场景

现有一段飞机起飞、爬升的轨迹数据,需要在Cesium中模拟出飞行过程动画,要求飞机模型的姿态随着速度矢量方向变化,而不是一直保持飞机模型的原始状态。

2. 技术路线

Cesium.Entity类中有属性orientation 可以用来控制实体模型model的朝向,当不设置该属性时,模型就保持原始状态。如下图所示:

原始状态

根据需求,飞机模型应该向上仰起来,有两种方式可以达到目标。

2.1 VelocityOrientationProperty

Cesium提供了VelocityOrientationProperty类,通过该类可以直接设置实体的orientation属性,其内部会自动计算速度矢量,设置后飞机模型就会沿着速度矢量的方向,官方文档示例代码:

// Create an entity with position and orientation.
var position = new Cesium.SampledProperty();
position.addSamples(...);
var entity = viewer.entities.add({
  position : position,
  orientation : new Cesium.VelocityOrientationProperty(position)
}));

实际应用时示例代码:

entity.orientation = new Cesium.VelocityOrientationProperty(entity.position);

效果如下图:

朝向调整后

2.2 VelocityVectorProperty

第一种方式基本就可以解决问题,但是有一种情况:三维模型本身有问题,有些三维模型从其他格式转换过来,在导入到Cesium后会发现有翻转、角度偏移等现象,需要在上一步的基础上(先将模型变换到速度矢量方向),再进行一些模型旋转变换。

通过VelocityVectorProperty可以计算出速度矢量,通过速度矢量、要沿参考轴旋转的角度(heading、pitch、rool)就可以计算出最终的朝向四元数(quaternion),将该四元数设置给实体的orientation属性即可。

核心代码如下:

    /**
     * 计算朝向四元数
     * X轴正向指向运动方向;Y轴在水平面内垂直于X轴,正向指向右侧;Z轴通过右手法则确定
     * @param {Cartesian3} position 位置
     * @param {Cartesian3} velocity 速度向量
     * @param {*} rotateX 绕X轴旋转的角度(roll)
     * @param {*} rotateY 绕Y轴旋转的角度(pitch)
     * @param {*} rotateZ 绕Z轴旋转的角度(heading)
     * @returns 
     */
    function getQuaternion(position, velocity, rotateX, rotateY, rotateZ) {
      // 1、计算站心到模型坐标系的旋转平移矩阵
      // 速度归一化
      let normal = Cesium.Cartesian3.normalize(velocity, new Cesium.Cartesian3());
      // 计算模型坐标系的旋转矩阵
      let satRotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(position, normal, Cesium.Ellipsoid.WGS84);
      // 模型坐标系到地固坐标系旋转平移矩阵
      let m = Cesium.Matrix4.fromRotationTranslation(satRotationMatrix, position);
      // 站心坐标系(东北天坐标系)到地固坐标系旋转平移矩阵
      var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(position, Cesium.Ellipsoid.WGS84, new Cesium.Matrix4());
      // 站心到模型坐标系的旋转平移矩阵
      let m3 = Cesium.Matrix4.multiply(Cesium.Matrix4.inverse(m1, new Cesium.Matrix4()), m, new Cesium.Matrix4());

      // 2、模型姿态旋转矩阵
      rotateX = rotateX || 0;
      rotateY = rotateY || 0;
      rotateZ = rotateZ || 0;
      let heading = rotateZ, pitch = rotateY, roll = rotateX;
      let postureHpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), Cesium.Math.toRadians(roll));
      let postureMatrix = Cesium.Matrix3.fromHeadingPitchRoll(postureHpr);

      // 3、最终的旋转矩阵
      let mat3 = Cesium.Matrix4.getMatrix3(m3, new Cesium.Matrix3());
      let finalMatrix = Cesium.Matrix3.multiply(mat3, postureMatrix, new Cesium.Matrix3());
      let quaternion1 = Cesium.Quaternion.fromRotationMatrix(finalMatrix);
      let hpr = Cesium.HeadingPitchRoll.fromQuaternion(quaternion1);
      let q2 = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
      return q2;
    }

控制台测试代码:

        // 当前时刻速度向量、位置
        let curVelocityVector = entity.velocityVector.getValue(viewer.clock.currentTime, new Cesium.Cartesian3());
        let curPosition = entity.position.getValue(viewer.clock.currentTime, new Cesium.Cartesian3());
        // 计算朝向四元数
        var quaternion = getQuaternion(curPosition, curVelocityVector);
        // 设置实体朝向,验证是否指向速度矢量方向
        entity.orientation = quaternion;

实际应用代码:

var viewer, entity;
    function startup(Cesium) {
      "use strict";
      //Sandcastle_Begin
      viewer = new Cesium.Viewer("cesiumContainer");
      var scene = viewer.scene;

      // Cesium查看器
      viewer.extend(Cesium.viewerCesiumInspectorMixin);

      // CZML中的orientation并不考虑速度矢量方向
      let dataSourcePromise = Cesium.CzmlDataSource.load("../../SampleData/CZML/Aircraft2.czml");
      dataSourcePromise.then(function (dataSource) {
        viewer.dataSources.add(dataSource);

        // 获取实体
        entity = viewer.dataSources.getByName("1610994859816914946")[0].entities.getById("1610994859816914946");

        // 添加属性:速度向量
        entity.velocityVector = new Cesium.VelocityVectorProperty(entity.position, true);

        /* // 当前时刻速度向量、位置
        let curVelocityVector = entity.velocityVector.getValue(viewer.clock.currentTime, new Cesium.Cartesian3());
        let curPosition = entity.position.getValue(viewer.clock.currentTime, new Cesium.Cartesian3());
        // 计算朝向四元数
        var quaternion = getQuaternion(curPosition, curVelocityVector);
        // 设置实体朝向,验证是否指向速度矢量方向
        entity.orientation = quaternion; */


        let rotateX = 0;
        let rotateY = 0;
        let rotateZ = 0;
        var property = new Cesium.SampledProperty(Cesium.Quaternion);
        if (entity.position instanceof Cesium.CompositePositionProperty) {
          let intervals = entity.position.intervals;
          for (let i = 0; i < intervals.length; i++) {
            const interval = intervals.get(i);
            let positions = interval.data._property._values;
            interval.data._property._times.forEach((time, index) => {

              let curVelocityVector = entity.velocityVector.getValue(time, new Cesium.Cartesian3());
              let curPosition = entity.position.getValue(time, new Cesium.Cartesian3());
              // 计算朝向四元数
              var quaternion = getQuaternion(curPosition, curVelocityVector, rotateX, rotateY, rotateZ);
              // 添加采样值
              property.addSample(time, quaternion);
            });
          }
        }

        // 将转换后的四元数设置给实体
        entity.orientation = property;
      })


      Sandcastle.finishedLoading();
    }
    if (typeof Cesium !== "undefined") {
      window.startupCalled = true;
      startup(Cesium);
    }

    /**
     * 计算朝向四元数
     * X轴正向指向运动方向;Y轴在水平面内垂直于X轴,正向指向右侧;Z轴通过右手法则确定
     * @param {Cartesian3} position 位置
     * @param {Cartesian3} velocity 速度向量
     * @param {*} rotateX 绕X轴旋转的角度(roll)
     * @param {*} rotateY 绕Y轴旋转的角度(pitch)
     * @param {*} rotateZ 绕Z轴旋转的角度(heading)
     * @returns 
     */
    function getQuaternion(position, velocity, rotateX, rotateY, rotateZ) {
      // 1、计算站心到模型坐标系的旋转平移矩阵
      // 速度归一化
      let normal = Cesium.Cartesian3.normalize(velocity, new Cesium.Cartesian3());
      // 计算模型坐标系的旋转矩阵
      let satRotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(position, normal, Cesium.Ellipsoid.WGS84);
      // 模型坐标系到地固坐标系旋转平移矩阵
      let m = Cesium.Matrix4.fromRotationTranslation(satRotationMatrix, position);
      // 站心坐标系(东北天坐标系)到地固坐标系旋转平移矩阵
      var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(position, Cesium.Ellipsoid.WGS84, new Cesium.Matrix4());
      // 站心到模型坐标系的旋转平移矩阵
      let m3 = Cesium.Matrix4.multiply(Cesium.Matrix4.inverse(m1, new Cesium.Matrix4()), m, new Cesium.Matrix4());

      // 2、模型姿态旋转矩阵
      rotateX = rotateX || 0;
      rotateY = rotateY || 0;
      rotateZ = rotateZ || 0;
      let heading = rotateZ, pitch = rotateY, roll = rotateX;
      let postureHpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), Cesium.Math.toRadians(roll));
      let postureMatrix = Cesium.Matrix3.fromHeadingPitchRoll(postureHpr);

      // 3、最终的旋转矩阵
      let mat3 = Cesium.Matrix4.getMatrix3(m3, new Cesium.Matrix3());
      let finalMatrix = Cesium.Matrix3.multiply(mat3, postureMatrix, new Cesium.Matrix3());
      let quaternion1 = Cesium.Quaternion.fromRotationMatrix(finalMatrix);
      let hpr = Cesium.HeadingPitchRoll.fromQuaternion(quaternion1);
      let q2 = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
      return q2;
    }

3. 参考链接

[1]. 【Cesium】计算模型的朝向四元数,实现模型运动中调整朝向

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐