新版本Cesium中如何通过矩阵变换实现绕自身中心点旋转,附源码

448 阅读3分钟

最近有一个需求是要实现primitive图层绕自身中心点旋转,如下图:

旋转.gif

我本来以为是一个上网搜一搜就能解决的简单问题,没想到搜到的答案全是不可用的,包括gpt给出的答案也是不可用,通常是转一下图层就跑到外太空去了。

后来仔细调研了一下,发现是因为Cesium新版本中旋转是绕地球球心去转的,所以网上那些适用于老版本的方法全部不可用。本人用的是cesium的1.107版本,没办法,只能自己实现一个新版本中可用的旋转方法了。

首先普及一个数学知识:三维空间中绕任意向量轴的旋转都可以分解为绕x,y,z轴的旋转

可以看到,上图中的正方形是绕着地球球心到正方形中心点这条连线来旋转的,所以我们要把绕这条轴线的旋转拆解为绕cesium笛卡尔三维坐标系中x,y,z三轴的旋转。

具体数学原理请参考csdn某大佬的文章绕任意向量旋转分解到坐标系旋转_例.,求受换av,使过原点的向童v=(a,b,c)与z轴的正向一致。y yvx x现步骤:-CSDN博客

清楚了数学原理,那么如何在cesium中实现上述动图中的旋转动画呢?下面直接上源码(强烈建议先看看上面那篇csdn文章里介绍的数学原理,讲得很清晰易懂):

// 绘制一个红色正方形primitive
  const rectangle = new Cesium.RectangleGeometry({
    ellipsoid: Cesium.Ellipsoid.WGS84,
    rectangle: Cesium.Rectangle.fromDegrees(116, 16, 118, 18),
    height: 10000.0,
  });
  const primitive = new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: rectangle,
    }),
    appearance: new Cesium.EllipsoidSurfaceAppearance({
      material: Cesium.Material.fromType("Color", {
        color: new Cesium.Color(1.0, 0.0, 0.0, 1.0),
      }),
    }),
  });
  viewer.scene.primitives.add(primitive);
  // 动画帧中持续旋转正方形
  let angle = 0;
  rotate();
  function rotate() {
    // 以地球中心到正方形中心点的连线为旋转轴,旋转正方形。(117,17)为上述正方形中心经纬度
    const rotateAxis = Cesium.Cartesian3.fromDegrees(117, 17);
    // modelMatrix为正方形primitive的模型矩阵,通过修改模型矩阵实现正方形的旋转
    primitive.modelMatrix = getRotateMatrix(rotateAxis, ++angle);
    requestAnimationFrame(rotate);
  }
});

上面代码中的关键是getRotateMatrix函数,下面来看看这个函数的具体实现:

/**
 * @param rotateAxis 旋转轴向量
 * @param angle 旋转角度的角度值
 * @returns 四维矩阵变换
 */
function getRotateMatrix(rotateAxis: Cesium.Cartesian3, angle: number) {
  const { x, y, z } = rotateAxis;
  // Alpha为将旋转轴向量绕x轴旋转到xoz平面所需的角度
  // Beta为将已经旋转到xoz平面的旋转轴向量再次绕y轴旋转到yoz平面所需的角度
  const sinAlpha = y / Math.sqrt(y * y + z * z);
  const sinBeta = -x / Math.sqrt(x * x + y * y + z * z);
  const alpha = Math.asin(sinAlpha);
  const beta = Math.asin(sinBeta);
  // step1 构造绕x轴旋转到xoz平面的旋转矩阵
  const rotationX = Cesium.Matrix4.fromRotationTranslation(
    Cesium.Matrix3.fromRotationX(alpha)
  );
  // step2 构造从xoz平面绕y轴旋转到yoz平面的旋转矩阵
  const rotationY = Cesium.Matrix4.fromRotationTranslation(
    Cesium.Matrix3.fromRotationY(beta)
  );
  // step3 经过上两步旋转后,旋转轴已经与z轴重合,此处继续构造绕z轴旋转angle角度的旋转矩阵
  const rotationZ = Cesium.Matrix4.fromRotationTranslation(
    Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(angle))
  );
  // 构造step2中的逆向旋转矩阵,恢复旋转轴绕y轴的旋转
  const reverseRotationY = Cesium.Matrix4.fromRotationTranslation(
    Cesium.Matrix3.fromRotationY(-beta)
  );
  // 构造step1中的逆向旋转矩阵,恢复旋转轴绕x轴的旋转
  const reverseRotationX = Cesium.Matrix4.fromRotationTranslation(
    Cesium.Matrix3.fromRotationX(-alpha)
  );
  // 初始单位矩阵依次左乘上述的旋转矩阵
  const matrix4 = Cesium.Matrix4.multiply(
    Cesium.Matrix4.IDENTITY,
    Cesium.Matrix4.IDENTITY,
    new Cesium.Matrix4()
  );
  Cesium.Matrix4.multiply(rotationX, matrix4, matrix4);
  Cesium.Matrix4.multiply(rotationY, matrix4, matrix4);
  Cesium.Matrix4.multiply(rotationZ, matrix4, matrix4);
  Cesium.Matrix4.multiply(reverseRotationY, matrix4, matrix4);
  Cesium.Matrix4.multiply(reverseRotationX, matrix4, matrix4);
  return matrix4;
}

如果帮到你了,点一个赞再copy代码吧。有问题欢迎留言