Cesium加载3Dtiles模型的平移和旋转

一、基础说明

  • Cesium中点由Cartesian3 表示,类似POINT3D点,其形式为包含 (x, y, z)的一维向量

  • Cesium中默认坐标系以地心为原点,所有未指明的Cartesian3 都基于此坐标系

  • 我们需要进行的平移与旋转基于模型所在点为原点,正东为X,正北为Y的局部坐标系

  • 使用函数Cesium.Transforms.eastNorthUpToFixedFrame(origin) 获取上述局部坐标系中点转换为世界坐标系中的位置的变换矩阵,P世界=M变换矩阵P局部

  • 3Dtiles模型的变换由一个4维矩阵完成,通过不断左乘变换矩阵可以完成一系列的复杂变换

二、模型平移与调整透明度

使用vue2,参考官方调整模型高度的案例。

  1. 假设模型的原点在世界坐标系下坐标为(xorigin, yorigin, zorigin)

  2. 获取坐标系变换矩阵

  3. 假设模型需要平移(x, y, z)量,先计算局部坐标系中点(x, y, z)在世界坐标下的位置(xworld, yworld, zworld)

    const offset = Cesium.Matrix4.multiplyByPoint(m, tempTranslation, new Cesium.Cartesian3(0, 0, 0));

  4. 使用(xworld, yworld, zworld)-(xorigin, yorigin, zorigin)获得世界坐标系中平移向量

  5. 使用Cesium.Matrix4.fromTranslation(translation); 获取3Dtiles平移变换矩阵

  6. 透明度调整tileset.style = new Cesium.Cesium3DTileStyle({color: "color('rgba(255,255,255," + opacity + ")')"});

transferModel(tileset, _tx, _ty, _tz,_opacity) {
      if(!this.checkModelLoad()){
        return
      }
      let tx = _tx ? _tx : 0;
      let ty = _ty ? _ty : 0;
      let tz = _tz ? _tz : 0;
      let opacity = _opacity ? _opacity/100 : 1

      const origin = tileset.boundingSphere.center;

      const m = Cesium.Transforms.eastNorthUpToFixedFrame(origin);//获取到以模型中心为原点,Z轴垂直地表的局部坐标系,以矩阵表示,此矩阵为将局部坐标系变换到世界坐标系的变换矩阵
      //平移
      const tempTranslation = new Cesium.Cartesian3(tx, ty, tz);//平移向量
      const offset = Cesium.Matrix4.multiplyByPoint(m, tempTranslation, new Cesium.Cartesian3(0, 0, 0));//局部坐标中(tx,ty,tz)在世界坐标系中位置
      const translation = Cesium.Cartesian3.subtract(offset, origin, new Cesium.Cartesian3());//终点世界坐标减去原点世界坐标得到世界坐标系下平移向量
      tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
      //透明度
      tileset.style = new Cesium.Cesium3DTileStyle({
        color: "color('rgba(255,255,255," + opacity + ")')",
      });
    },

三、模型旋转

模型旋转时,由于Cesium默认绕轴旋转是绕世界坐标系轴旋转,因此我们需要做如下操作完成绕局部坐标轴旋转

  1. 将模型原点平移回世界坐标系原点(地心),矩阵记为T1

  2. 将局部坐标Z轴调整到与世界坐标Z轴重合,矩阵记为R1

  3. 将模型绕世界坐标系的某个轴旋转(真正进行旋转的矩阵),此处以Z轴为例,矩阵记为R

  4. 将局部坐标Z轴旋转回原来的指向,矩阵记为R2

  5. 平移回初始位置,矩阵记为T2

最终模型旋转的矩阵为T2R2RR1T1

将旋转开始前模型的初始变换矩阵左乘此矩阵即可完成旋转变换

(一)回到地心

	  const origin = tileset.boundingSphere.center;
      console.log("初始世界坐标", origin)
      const localToWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);//获取到以模型中心为原点,Z轴垂直地表的局部坐标系的变换矩阵,左乘此矩阵可以将局部坐标变换为世界坐标
      const originMatrix = tileset.modelMatrix//贴地变换矩阵或者初始变换矩阵M0
      console.log("当前坐标变换矩阵", localToWorldMatrix)
      const backToEarthCenter = new Cesium.Cartesian3(-origin.x, -origin.y, -origin.z)//回到地心位移量
      let backToEarthCenterMatrix = Cesium.Matrix4.fromTranslation(backToEarthCenter);//回到地心变换矩阵
      Cesium.Matrix4.multiply(backToEarthCenterMatrix, originMatrix, backToEarthCenterMatrix)//贴地变换矩阵左乘回到地心矩阵 T1M0
      console.log("回到地心变换矩阵", backToEarthCenterMatrix)

(二)局部Z轴校正

坐标轴校正示意图

先将红线表示的向量(z'轴)绕Z轴顺时针旋转Φ度,再绕Y轴顺时针旋转θ度

1、通过一维向量表示坐标轴,通过左乘变换矩阵完成局部坐标轴在世界坐标系下的表示

//旋转模型使得Z轴与世界坐标Z轴重合
      let arrowX = new Cesium.Cartesian3(1, 0, 0)
      let arrowY = new Cesium.Cartesian3(0, 1, 0)
      let arrowZ = new Cesium.Cartesian3(0, 0, 1)
      let localArrowX = Cesium.Matrix4.multiplyByPoint(localToWorldMatrix, new Cesium.Cartesian3(1, 0, 0), new Cesium.Cartesian3)
      let localArrowY = Cesium.Matrix4.multiplyByPoint(localToWorldMatrix, new Cesium.Cartesian3(0, 1, 0), new Cesium.Cartesian3)
      let localArrowZ = Cesium.Matrix4.multiplyByPoint(localToWorldMatrix, new Cesium.Cartesian3(0, 0, 1), new Cesium.Cartesian3)

2、先绕世界Z轴旋转局部Z轴到世界XOZ面上,再旋转到Z轴上

  • Cesium.Cartesian3.angleBetween 可计算两个向量间的弧度

  • Cesium.Cartesian3.angleBetween(arrowX, new Cesium.Cartesian3(localArrowZ.x, localArrowZ.y, 0)) 即为Φ对应弧度

  • Cesium.Cartesian3.angleBetween(localArrowX, arrowZ)即为θ对应弧度

  • Cesium.Matrix3.fromRotationX、Cesium.Matrix3.fromRotationY、Cesium.Matrix3.fromRotationZ 的旋转正方向为逆时针,传入参数为弧度

3、需要注意由于只对齐了Z轴,如需绕X轴,Y轴旋转需要做类似的处理

let angleToXZ = Cesium.Cartesian3.angleBetween(arrowX, new Cesium.Cartesian3(localArrowZ.x, localArrowZ.y, 0))//局部Z轴在世界坐标系XY平面上投影到X轴角度,即绕Z顺时针旋转这个角度可以到XZ平面上
      let angleToZ = Cesium.Cartesian3.angleBetween(localArrowX, arrowZ)//然后绕Y轴顺时针旋转此角度可使得Z轴与世界坐标系Z轴重合
      const rotationAngleToXZ = Cesium.Matrix3.fromRotationZ(-angleToXZ);//此函数正方向为逆时针
      const rotationAngleToZ = Cesium.Matrix3.fromRotationY(-angleToZ);
      let rotationAngleToZMatrix = Cesium.Matrix3.multiply(rotationAngleToZ, rotationAngleToXZ, new Cesium.Matrix3)
      rotationAngleToZMatrix = Cesium.Matrix4.fromRotationTranslation(rotationAngleToZMatrix)
      Cesium.Matrix4.multiply(rotationAngleToZMatrix, backToEarthCenterMatrix, rotationAngleToZMatrix)//局部轴校正R1T1M0

(三)真实旋转矩阵

此处完成真正需要进行的旋转,此处以绕Z轴旋转为例

      // 绕Z轴旋转
      console.log(rz - this.originRz)
      const rotationZ = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(rz - this.originRz)); // 绕Z轴旋转变换矩阵R
      let rotationMatrix = Cesium.Matrix4.fromRotationTranslation(rotationZ)
      Cesium.Matrix4.multiply(rotationMatrix, rotationAngleToZMatrix, rotationMatrix)//RR1T1M0

(四)局部Z轴回位

将局部Z轴回到原本的指向,即刚刚旋转的角度取负值,此处需要注意变换的顺序,后进行的变换先复位

  • 校正矩阵:Cesium.Matrix3.multiply(rotationAngleToZ, rotationAngleToXZ, new Cesium.Matrix3)

  • 复位矩阵:Cesium.Matrix3.multiply(rotationAngleLeaveXZ, rotationAngleLeaveZ, new Cesium.Matrix3)

      // 旋转模型回到原本朝向
      const rotationAngleLeaveXZ = Cesium.Matrix3.fromRotationZ(angleToXZ);
      const rotationAngleLeaveZ = Cesium.Matrix3.fromRotationY(angleToZ);
      let rotationAngleLeaveZMatrix = Cesium.Matrix3.multiply(rotationAngleLeaveXZ, rotationAngleLeaveZ, new Cesium.Matrix3)
      rotationAngleLeaveZMatrix = Cesium.Matrix4.fromRotationTranslation(rotationAngleLeaveZMatrix)// 局部Z轴回到原本方向
      Cesium.Matrix4.multiply(rotationAngleLeaveZMatrix, rotationMatrix, rotationAngleLeaveZMatrix)//R2RR1T1M0

(五)回到原本位置

位移量即为模型原本的世界坐标,直接可获得变换矩阵

      //回到原来位置
      const backToOriginMatrix = Cesium.Matrix4.fromTranslation(origin);//从地心回归原位T2

(六)绕Z轴旋转完整代码

rotationModel(tileset,rz) {

      if(!this.checkModelLoad()){
        return
      }
      const origin = tileset.boundingSphere.center;
      console.log("初始世界坐标", origin)
      const localToWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);//获取到以模型中心为原点,Z轴垂直地表的局部坐标系的变换矩阵,左乘此矩阵可以将局部坐标变换为世界坐标
      const originMatrix = tileset.modelMatrix//贴地变换矩阵或者初始变换矩阵M0
      console.log("当前坐标变换矩阵", localToWorldMatrix)
      const backToEarthCenter = new Cesium.Cartesian3(-origin.x, -origin.y, -origin.z)//回到地心位移量
      let backToEarthCenterMatrix = Cesium.Matrix4.fromTranslation(backToEarthCenter);//回到地心变换矩阵
      Cesium.Matrix4.multiply(backToEarthCenterMatrix, originMatrix, backToEarthCenterMatrix)//贴地变换矩阵左乘回到地心矩阵 T1M0
      console.log("回到地心变换矩阵", backToEarthCenterMatrix)
      // 旋转
      //旋转模型使得Z轴与世界坐标Z轴重合
      let arrowX = new Cesium.Cartesian3(1, 0, 0)
      let arrowY = new Cesium.Cartesian3(0, 1, 0)
      let arrowZ = new Cesium.Cartesian3(0, 0, 1)
      let localArrowX = Cesium.Matrix4.multiplyByPoint(localToWorldMatrix, new Cesium.Cartesian3(1, 0, 0), new Cesium.Cartesian3)
      let localArrowY = Cesium.Matrix4.multiplyByPoint(localToWorldMatrix, new Cesium.Cartesian3(0, 1, 0), new Cesium.Cartesian3)
      let localArrowZ = Cesium.Matrix4.multiplyByPoint(localToWorldMatrix, new Cesium.Cartesian3(0, 0, 1), new Cesium.Cartesian3)
      let angleToXZ = Cesium.Cartesian3.angleBetween(arrowX, new Cesium.Cartesian3(localArrowZ.x, localArrowZ.y, 0))//局部Z轴在世界坐标系XY平面上投影到X轴角度,即绕Z顺时针旋转这个角度可以到XZ平面上
      let angleToZ = Cesium.Cartesian3.angleBetween(localArrowX, arrowZ)//然后绕Y轴顺时针旋转此角度可使得Z轴与世界坐标系Z轴重合
      const rotationAngleToXZ = Cesium.Matrix3.fromRotationZ(-angleToXZ);//此函数正方向为逆时针
      const rotationAngleToZ = Cesium.Matrix3.fromRotationY(-angleToZ);
      let rotationAngleToZMatrix = Cesium.Matrix3.multiply(rotationAngleToZ, rotationAngleToXZ, new Cesium.Matrix3)
      rotationAngleToZMatrix = Cesium.Matrix4.fromRotationTranslation(rotationAngleToZMatrix)
      Cesium.Matrix4.multiply(rotationAngleToZMatrix, backToEarthCenterMatrix, rotationAngleToZMatrix)//局部轴校正R1T1M0


      // 绕Z轴旋转
      console.log(rz - this.originRz)
      const rotationZ = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(rz - this.originRz)); // 绕Z轴旋转变换矩阵R
      let rotationMatrix = Cesium.Matrix4.fromRotationTranslation(rotationZ)
      Cesium.Matrix4.multiply(rotationMatrix, rotationAngleToZMatrix, rotationMatrix)//RR1T1M0

      // 旋转模型回到原本朝向
      const rotationAngleLeaveXZ = Cesium.Matrix3.fromRotationZ(angleToXZ);
      const rotationAngleLeaveZ = Cesium.Matrix3.fromRotationY(angleToZ);
      let rotationAngleLeaveZMatrix = Cesium.Matrix3.multiply(rotationAngleLeaveXZ, rotationAngleLeaveZ, new Cesium.Matrix3)
      rotationAngleLeaveZMatrix = Cesium.Matrix4.fromRotationTranslation(rotationAngleLeaveZMatrix)// 局部Z轴回到原本方向
      Cesium.Matrix4.multiply(rotationAngleLeaveZMatrix, rotationMatrix, rotationAngleLeaveZMatrix)//R2RR1T1M0

      //回到原来位置
      const backToOriginMatrix = Cesium.Matrix4.fromTranslation(origin);//从地心回归原位T2
      // 应用变换矩阵

      const lastMatrix = Cesium.Matrix4.multiply(backToOriginMatrix, rotationAngleLeaveZMatrix, new Cesium.Matrix4)//最终矩阵T2R2RR1T1M0

      tileset.modelMatrix = lastMatrix
      console.log("结束世界坐标", tileset.boundingSphere.center)
      this.originRz = rz

    },