SampledPositionProperty模拟模型运动

574 阅读7分钟

# SampledPositionProperty

**数据结构**:

  • SampledPositionProperty:这是一种CesiumJS中的属性类型,用于表示物体在三维空间中随时间变化的位置。它基于采样点的位置数据,通过插值计算可以获取任意时间点上的位置信息。

  •插值算法可以是线性的,也可以是其他更复杂的算法(如Hermite多项式逼近)。

**使用方式**:

  •通常在CesiumJS的编程环境中使用,通过JavaScript代码来创建和操作。它适用于需要动态计算和更新位置属性的场景
 
  •适用于需要高精度和高动态性的场景,如实时模拟、动画和游戏开发等。

  •可以用于控制飞行器的轨迹、模拟车辆的运动、实现角色的路径跟随等功能

**效果呈现**:

   •由于属性值是通过插值计算得到的,因此效果呈现上会更加平滑和自然。

   •这使得它非常适合用于表示位置随时间连续变化的情况。

SampledPositionProperty模拟模型运动

  1. 设置系统动画开始时间和结束时间

    Cesium.JulianDate 表示高精度天文日期和时间的一个类

    • addSeconds:向日期添加指定秒数,返回一个新的 Cesium.JulianDate 实例。
    • addDays:向日期添加指定天数,返回一个新的 Cesium.JulianDate 实例。
    • addHours:向日期添加指定小时数,返回一个新的 Cesium.JulianDate 实例。
    • addMinutes:向日期添加指定分钟数,返回一个新的 Cesium.JulianDate 实例。
    • clone:复制一个 Cesium.JulianDate 实例。
    • compare:比较两个 Cesium.JulianDate 实例,返回负值、正值或零,表示第一个实例小于、大于或等于第二个实例。
    • computeTaiMinusUtc:计算提供的实例先于UTC的秒数。
    • daysDifference:计算两个 Cesium.JulianDate 实例之间的天数差。
    • equals:比较两个 Cesium.JulianDate 实例是否相等。
    • equalsEpsilon:比较两个 Cesium.JulianDate 实例是否在指定的epsilon秒内相等。
    • fromDate:从一个 JavaScript Date 对象创建一个新的 Cesium.JulianDate 实例。
    • fromGregorianDate:从一个 GregorianDate(如果 Cesium 支持此类)创建一个新的 Cesium.JulianDate 实例。
    • fromIso8601:从一个 ISO 8601 日期字符串创建一个新的 Cesium.JulianDate 实例。
    // 系统开始时间
      const startTime = Cesium.JulianDate.fromDate(new Date(2023, 11, 4, 8));
      // 系统总时间秒数
      const totalSeconds = 30;
      // 系统结束时间
      const stopTime = Cesium.JulianDate.addSeconds(
        startTime,
        totalSeconds,
        new Cesium.JulianDate()
      );
      // 设置系统画面开始时间
      viewer.clock.startTime = startTime.clone();
      // 设置系统画面结束时间
      viewer.clock.stopTime = stopTime.clone();
      // 设置系统画面当前时间
      viewer.clock.currentTime = startTime.clone();
      // 表示时钟将在指定的时间段内循环播放,但在到达结束时间时停止,而不是重新开始
      viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
      // 使Cesium的时间线(timeline)缩放到指定的开始时间(startTime)和结束时间(stopTime)
      viewer.timeline.zoomTo(startTime, stopTime);
    
  2. 设置模型运动开始位置与结束位置

    1).SampledProperty通过给定多个不同时间点的样本,并在每两个时间点之间进行插值来计算中间时刻的属性值(通用),需要指定数据类型(如Cesium.Cartesian3、Number等)

    • addSample(time, value):添加一个时间-值对样本。
    • getValue(time, result):根据给定时间获取插值后的属性值。

    2).SampledPositionProperty是Cesium中专门用于处理位置数据随时间变化的属性,允许通过添加多个时间点的位置样本来定义一条路径,Cesium会在这些时间点之间进行插值来计算中间位置

    • 主要方法

      • addSample(time, position, derivatives):添加一个时间-位置对样本,还可以包括导数(如速度、加速度等)。
      • getValue(time, result):根据给定时间获取插值后的位置。
      • getValueInReferenceFrame(time, referenceFrame, result):在指定的参考系中,根据给定时间获取插值后的位置。
    • 属性

      • referenceFrame:定义位置的参考系。
      • numberOfDerivatives:伴随每个位置的导数数量(如速度、加速度等)。
      • interpolationAlgorithm:插值算法。
      • interpolationDegree:插值程度。
    // 存储模型随时间变化的运动位置
      const position = new Cesium.SampledPositionProperty();
      // 存储模型随时间变化的运动间隔距离
      const distance = new Cesium.SampledProperty(Number);
      // 计算模型的运动速度
      // 1)根据提供的位置属性(PositionProperty)计算速度,并返回一个基于该速度的 Cartesian3 向量。这个类在模拟动态实体的运动方向时非常有用,比如让模型的朝向或布告牌的旋转方向与其运动方向相匹配。
      const velocityVectorProperty = new Cesium.VelocityVectorProperty(
        position,
        false
      );
      // 2.设置模型运动开始位置与结束位置
      // 模型开始运动位置
      const startPosition = new Cesium.Cartesian3(
        -2379556.799372864,
        -4665528.205030263,
        3628013.106599678
      );
      //  模型结束运动位置
      const endPosition = new Cesium.Cartesian3(
        -2379603.7074103747,
        -4665623.48990283,
        3627860.82704567
      );
    
  3. 根据初始和结束的时间和位置插值生成运动的时间和位置信息

    Cesium.Cartesian3.lerp 是一个用于在两个 Cesium.Cartesian3 对象(代表三维空间中的点)之间进行线性插值的静态方法。

    lerp 是 "linear interpolation" 的缩写,意味着它会根据一个介于0和1之间的参数(通常称为tfactor),在两个点之间计算出一个新的点。

    • 用法

      • Cesium.Cartesian3.lerp(start, end, t, result)
      • start:起始点,一个 Cesium.Cartesian3 对象。
      • end:结束点,一个 Cesium.Cartesian3 对象。
      • t:插值参数,一个介于0和1之间的数值。当t=0时,返回起始点;当t=1时,返回结束点;当t在0和1之间时,返回起始点和结束点之间的一个点。
      • result:(可选)用于存储结果的 Cesium.Cartesian3 对象。如果不提供,将创建一个新的 Cesium.Cartesian3 对象来存储结果。
    // 均匀地往position中加入随时间变化的位置信息
      const numberOfSamples = 100;
      let prevLocation = startPosition;
      let totalDistance = 0;
      for (let i = 0; i <= numberOfSamples; ++i) {
        const factor = i / numberOfSamples;
        const time = Cesium.JulianDate.addSeconds(
          startTime,
          factor * totalSeconds,
          new Cesium.JulianDate()
        );
    
        // 使用非线性因子进行插值,以便模型加速
        const locationFactor = Math.pow(factor, 2);
        const location = Cesium.Cartesian3.lerp(
          startPosition,
          endPosition,
          locationFactor,
          new Cesium.Cartesian3()
        );
        position.addSample(time, location);
        distance.addSample(
          time,
          (totalDistance += Cesium.Cartesian3.distance(location, prevLocation))
        );
        prevLocation = location;
      }
    
  4. 加载模型

    const modelPrimitive = viewer.scene.primitives.add(
        await Cesium.Model.fromGltfAsync({
          url: "../../../../src/assets/models/Cesium_Man.glb",
          scale: 4,
        })
      );
    
  5. 设置模型的动画参数

    modelPrimitive.readyEvent.addEventListener(() => {
        modelPrimitive.activeAnimations.addAll({// addAll 方法来添加所有可用的动画
          loop: Cesium.ModelAnimationLoop.REPEAT,// 动画将在播放完毕后重复
          // 用于根据当前时间计算动画的播放时间,动画的播放速度将取决于 distance 属性随时间的变化率。如果 distance 增加得很快,动画将播放得更快;如果 distance 增加得慢或保持不变,动画的播放速度将相应减慢或暂停
          // animationTime 的返回值并不是动画的“持续时间”,而是动画在给定时间点上的“当前播放时间”
          animationTime: function (duration: any) {
            return distance.getValue(viewer.clock.currentTime) / duration;
          },
          multiplier: 0.25,// 这是一个缩放因子,用于减慢动画的播放速度。乘以 0.25 意味着动画将以原始速度的 1/4 播放
        });
      });
    
  6. 动态更新模型的位置和方向

    viewer.scene.preUpdate.addEventListener(function () {
        const time = viewer.clock.currentTime;
        const pos = position.getValue(time);
        const vel = velocityVectorProperty.getValue(time);
        // 归一化后的速度向量 vel 将保持原始向量的方向,但其长度变为1。确保旋转矩阵仅由速度向量的方向决定,而不受其长度的影响
        Cesium.Cartesian3.normalize(vel, vel);
        // 根据一个位置(position)和一个速度向量(velocity)生成一个旋转矩阵。这个旋转矩阵将速度向量对齐到某个参考方向(通常是Z轴正方向)上
        const rotation = new Cesium.Matrix3();
        Cesium.Transforms.rotationMatrixFromPositionVelocity(
          pos!,
          vel,
          viewer.scene.globe.ellipsoid,
          rotation
        );
        // 根据一个旋转矩阵(或四元数)和一个平移向量生成一个4x4的变换矩阵。这个变换矩阵可以用于将物体从一个位置和方向变换到另一个位置和方向。
        Cesium.Matrix4.fromRotationTranslation(
          rotation,
          pos,
          modelPrimitive.modelMatrix
        );
      });
    
  7. 添加模型运动速度标签

    const modelLabel = viewer.entities.add({
        position: position,
        orientation: new Cesium.VelocityOrientationProperty(position), // 用于根据实体的速度自动设置其方向
        label: {
          // 将VelocityVectorProperty转换为速度
          text: new Cesium.CallbackProperty(function (time: Cesium.JulianDate) {
            const velocityVector = new Cesium.Cartesian3();
            velocityVectorProperty.getValue(time, velocityVector);
            // 返回一个表示向量长度的标量值(即一个数字)
            const meterPerSecond = Cesium.Cartesian3.magnitude(velocityVector);
            const kmPerHour = Math.round(meterPerSecond * 3.6);
            return `${kmPerHour} km/hr`;
          }, false),
          font: "20px sans-serif",
          showBackground: true,//标签文本有背景
          distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 100.0),// 标签在距离观察者多近和多远时可见
          eyeOffset: new Cesium.Cartesian3(0, 7.2, 0),//标签相对于实体位置的偏移量,以便在视觉上更好地定位
        },
      });
    
  8. 设置视角追踪实体以及相对于实体位置的固定视图点

viewer.trackedEntity = modelLabel;//追踪实体,尝试保持这个实体在视野中
  // 定义了一个相对于实体位置的固定视图点
  modelLabel.viewFrom = new Cesium.ConstantProperty(
    new Cesium.Cartesian3(-30.0, -10.0, 20.0)
  );

完整代码

/**
 * 模拟模型运动例子,可以直接传入参数调用
 */
import * as Cesium from "cesium";
export async function sampledPropertyTest(viewer: Cesium.Viewer) {
  // 1.设置系统动画开始时间和结束时间
  // 系统开始时间
  const startTime = Cesium.JulianDate.fromDate(new Date(2023, 11, 4, 8));
  // 系统总时间秒数
  const totalSeconds = 30;
  // 系统结束时间
  const stopTime = Cesium.JulianDate.addSeconds(
    startTime,
    totalSeconds,
    new Cesium.JulianDate()
  );
  // 设置系统画面开始时间
  viewer.clock.startTime = startTime.clone();
  // 设置系统画面结束时间
  viewer.clock.stopTime = stopTime.clone();
  // 设置系统画面当前时间
  viewer.clock.currentTime = startTime.clone();
  // 表示时钟将在指定的时间段内循环播放,但在到达结束时间时停止,而不是重新开始
  viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
  // 使Cesium的时间线(timeline)缩放到指定的开始时间(startTime)和结束时间(stopTime)
  viewer.timeline.zoomTo(startTime, stopTime);
  /*
    1).SampledProperty通过给定多个不同时间点的样本,并在每两个时间点之间进行插值来计算中间时刻的属性值(通用),需要指定数据类型(如Cesium.Cartesian3、Number等)
    2).SampledPositionProperty是Cesium中专门用于处理位置数据随时间变化的属性,允许通过添加多个时间点的位置样本来定义一条路径,Cesium会在这些时间点之间进行插值来计算中间位置
  */
  // 存储模型随时间变化的运动位置
  const position = new Cesium.SampledPositionProperty();
  // 存储模型随时间变化的运动间隔距离
  const distance = new Cesium.SampledProperty(Number);
  // 计算模型的运动速度
  // 1)根据提供的位置属性(PositionProperty)计算速度,并返回一个基于该速度的 Cartesian3 向量。这个类在模拟动态实体的运动方向时非常有用,比如让模型的朝向或布告牌的旋转方向与其运动方向相匹配。
  const velocityVectorProperty = new Cesium.VelocityVectorProperty(
    position,
    false
  );
  // 2.设置模型运动开始位置与结束位置
  // 模型开始运动位置
  const startPosition = new Cesium.Cartesian3(
    -2379556.799372864,
    -4665528.205030263,
    3628013.106599678
  );
  //  模型结束运动位置
  const endPosition = new Cesium.Cartesian3(
    -2379603.7074103747,
    -4665623.48990283,
    3627860.82704567
  );
  // 3.根据初始和结束的时间和位置插值生成运动的时间和位置信息
  // 均匀地往position中加入随时间变化的位置信息
  const numberOfSamples = 100;
  let prevLocation = startPosition;
  let totalDistance = 0;
  for (let i = 0; i <= numberOfSamples; ++i) {
    const factor = i / numberOfSamples;
    const time = Cesium.JulianDate.addSeconds(
      startTime,
      factor * totalSeconds,
      new Cesium.JulianDate()
    );

    // 使用非线性因子进行插值,以便模型加速
    const locationFactor = Math.pow(factor, 2);
    const location = Cesium.Cartesian3.lerp(
      startPosition,
      endPosition,
      locationFactor,
      new Cesium.Cartesian3()
    );
    position.addSample(time, location);
    distance.addSample(
      time,
      (totalDistance += Cesium.Cartesian3.distance(location, prevLocation))
    );
    prevLocation = location;
  }
  // 4.加载模型
  const modelPrimitive = viewer.scene.primitives.add(
    await Cesium.Model.fromGltfAsync({
      url: "../../../../src/assets/models/Cesium_Man.glb",
      scale: 4,
    })
  );
  // 5.设置模型的动画参数
  modelPrimitive.readyEvent.addEventListener(() => {
    modelPrimitive.activeAnimations.addAll({// addAll 方法来添加所有可用的动画
      loop: Cesium.ModelAnimationLoop.REPEAT,// 动画将在播放完毕后重复
      // 用于根据当前时间计算动画的播放时间,动画的播放速度将取决于 distance 属性随时间的变化率。如果 distance 增加得很快,动画将播放得更快;如果 distance 增加得慢或保持不变,动画的播放速度将相应减慢或暂停
      // animationTime 的返回值并不是动画的“持续时间”,而是动画在给定时间点上的“当前播放时间”
      animationTime: function (duration: any) {
        return distance.getValue(viewer.clock.currentTime) / duration;
      },
      multiplier: 0.25,// 这是一个缩放因子,用于减慢动画的播放速度。乘以 0.25 意味着动画将以原始速度的 1/4 播放
    });
  });
  // 6.动态更新模型的位置和方向
  viewer.scene.preUpdate.addEventListener(function () {
    const time = viewer.clock.currentTime;
    const pos = position.getValue(time);
    const vel = velocityVectorProperty.getValue(time);
    // 归一化后的速度向量 vel 将保持原始向量的方向,但其长度变为1。确保旋转矩阵仅由速度向量的方向决定,而不受其长度的影响
    Cesium.Cartesian3.normalize(vel, vel);
    // 根据一个位置(position)和一个速度向量(velocity)生成一个旋转矩阵。这个旋转矩阵将速度向量对齐到某个参考方向(通常是Z轴正方向)上
    const rotation = new Cesium.Matrix3();
    Cesium.Transforms.rotationMatrixFromPositionVelocity(
      pos!,
      vel,
      viewer.scene.globe.ellipsoid,
      rotation
    );
    // 根据一个旋转矩阵(或四元数)和一个平移向量生成一个4x4的变换矩阵。这个变换矩阵可以用于将物体从一个位置和方向变换到另一个位置和方向。
    Cesium.Matrix4.fromRotationTranslation(
      rotation,
      pos,
      modelPrimitive.modelMatrix
    );
  });
  // 7.添加模型运动速度标签
  const modelLabel = viewer.entities.add({
    position: position,
    orientation: new Cesium.VelocityOrientationProperty(position), // 用于根据实体的速度自动设置其方向
    label: {
      // 将VelocityVectorProperty转换为速度
      text: new Cesium.CallbackProperty(function (time: Cesium.JulianDate) {
        const velocityVector = new Cesium.Cartesian3();
        velocityVectorProperty.getValue(time, velocityVector);
        // 返回一个表示向量长度的标量值(即一个数字)
        const meterPerSecond = Cesium.Cartesian3.magnitude(velocityVector);
        const kmPerHour = Math.round(meterPerSecond * 3.6);
        return `${kmPerHour} km/hr`;
      }, false),
      font: "20px sans-serif",
      showBackground: true,//标签文本有背景
      distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 100.0),// 标签在距离观察者多近和多远时可见
      eyeOffset: new Cesium.Cartesian3(0, 7.2, 0),//标签相对于实体位置的偏移量,以便在视觉上更好地定位
    },
  });
  // 8.设置视角追踪实体以及相对于实体位置的固定视图点
  viewer.trackedEntity = modelLabel;//追踪实体,尝试保持这个实体在视野中
  // 定义了一个相对于实体位置的固定视图点
  modelLabel.viewFrom = new Cesium.ConstantProperty(
    new Cesium.Cartesian3(-30.0, -10.0, 20.0)
  );
}