Primitive图元实现模型的平滑运动(entity加强版)

由于entity加载大批量模型并实现平滑运动时,存在性能瓶颈(500-800以上很卡顿),现如今实现通过primitive方式实现(更底层,效率更高)

1、通过接口获取数据(2-8秒一次)

javascript 复制代码
  if (newValue.trucks) {
    newValue.trucks.forEach((value) => {
      if (realStore.truckShowSet[value.deviceId]) {
        if (value.online) {
          setTimeout(() => {
            mapView.updateTruckPrimitive(value)
          }, 10)
        } else {
          realStore.truckShowSet[value.deviceId] = false
          mapView.showHideEntitySingle(value.deviceId, false)
        }
      } else {
        mapView.showHideEntitySingle(value.deviceId, false)
      }
    })
  }
javascript 复制代码
  updateTruckPrimitive(data) {
    let truck = this.trucks.getById(data.deviceId)
    if (truck) {
      truck.addData(data)
    } else {
      truck = new Truck(this, data.deviceId)
      truck.init()
      truck.addData(data)
      this.trucks.add(truck)
    }
  }

2、primitive的初始化和预加载

javascript 复制代码
 constructor(mapView, id, modelUri, options) {
    this.mapView = mapView.map
    this.mapView.clock.shouldAnimate = true
    this.id = id
    this.model = null
    this.modelLabel = null
    this.label = null
    this.speed = 0
    this.lon = 0
    this.lat = 0
    this.heading = 0
    this.modelUri = modelUri
    this.modelScale = 1
    this.isDrill = false
    this.groundHeight = 0
    this.sampledPositionProperty = new Cesium.SampledPositionProperty()
    this.velocityVectorProperty = new Cesium.VelocityVectorProperty(this.sampledPositionProperty, true)
    if (options) {
      this.modelScale = options.scale
      this.isDrill = options.isDrill
      this.is3d = options.is3d
    }
    this.cacheTime = null
    this.cachePosition = null
    this.lastSampledTime = null
    this.lastOrientation = null
    this.empty = null
    this.equalCount = 0
    this.lon = 0
    this.lat = 0
    this.notDeal = false
    // this.init()
  }
  init() {
    var that = this
    var distanceDisplayCondition = null
    if (this.id?.indexOf('.worker') != -1) {
      distanceDisplayCondition = new Cesium.DistanceDisplayCondition(undefined, 2000.0)
    } else {
      distanceDisplayCondition = new Cesium.DistanceDisplayCondition(undefined, 8000.0)
    }
    this.model = Cesium.Model.fromGltf({
      id: this.id,
      url: this.modelUri,
      scale: this.modelScale,
      minimumPixelSize: 32,
      maximumScale: 32,
      allowPicking: true,
      // incrementallyLoadTextures:true,
      // maximumScreenSpaceError: 64, // 更高的值=更低的精度
      // enableDebugWireframe: false,
      distanceDisplayCondition: distanceDisplayCondition,
    })
    let data = this.mapView.scene.primitives.add(this.model)
    data.id = this.id
    data.name = this.id
    Object.assign(this.model, { isFirstEnter: true })

    //worker增加动画
    // if (this.id?.indexOf('.worker') != -1) {
    //   this.model.activeAnimations.addAll({
    //     loop: Cesium.ModelAnimationLoop.REPEAT,
    //     animationTime: function (duration) {
    //       return Date.now() / 1000 / duration;
    //     },
    //     multiplier: 0.25,
    //   });
    // }
  }

3、实时位置改变和平滑运动

javascript 复制代码
 addData(data) {
    if (!data.online) {
      return
    }
    let heading = null
    if (data?.heading) {
      heading = -data.heading
    } else {
      heading = 0
    }

    const isNull = data.longitude == null || data.latitude == null
    if (isNull || (floatsEqual(data.longitude, this.lon) && floatsEqual(data.latitude, this.lat))) {
      this.notDeal = true
      return
    }

    let height = this.mapView.scene.sampleHeight(Cesium.Cartographic.fromDegrees(data.longitude, data.latitude))
    const newPosition = Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude, height)


    const currentTime = Cesium.JulianDate.fromDate(new Date());
    let text = ''
    if (data.deviceId.includes('.mml') || data.deviceId.includes('.truck') || data.deviceId.includes('.cmd')) {
      if (data.speed && data.speed > 0) {
        data.speed = Math.round(data.speed * 100) / 100
        text = data.speed + 'km/h' + '\n' + data.name
      } else {
        text = data.name
      }
    } else {
      text = data.name
    }
    // var eyeSet = null
    // if (this.id?.indexOf('.worker') != -1 || this.id?.indexOf('.cmd') != -1) {
    //   eyeSet = new Cesium.Cartesian3(0, 3, 0)
    // } else {
    //   eyeSet = new Cesium.Cartesian3(0, 6, 0)
    // }
    if (this.model.isFirstEnter) {
      // 位置变化
      // const height = this.mapView.scene.sampleHeight(Cesium.Cartographic.fromDegrees(data.longitude, data.latitude)) || 0
      //设定方向
      let orientation = Cesium.Transforms.headingPitchRollQuaternion(newPosition, new Cesium.HeadingPitchRoll(heading, 0, 0))
      this.lon = data.longitude
      this.lat = data.latitude
      this.heading = heading
      this.cacheTime = currentTime
      this.cachePosition = newPosition
      this.model.isFirstEnter = false
      this.sampledPositionProperty.addSample(this.cacheTime, newPosition)
      this.lastOrientation = orientation
      this.lastSampledTime = null
      // var modelMatrix = Cesium.Matrix4.fromTranslationQuaternionRotationScale(
      //   newPosition,
      //   orientation,
      //   new Cesium.Cartesian3(1.0, 1.0, 1.0) // 缩放
      // )
      let textColor = Cesium.Color.fromCssColorString('#000')
      let pixelOffset = new Cesium.Cartesian2(0, -50)

      if (data.deviceId.includes('.worker')) {
        pixelOffset = new Cesium.Cartesian2(0, -70)
      }
      var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(newPosition)
      this.model.modelMatrix = modelMatrix
      this.modelLabel = this.mapView.labels.add({
        id: this.id + '-label',
        position: Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude, height),
        text: text,
        font: '15px sans-serif',
        showBackground: false,
        // eyeOffset: eyeSet,
        backgroundPadding: new Cesium.Cartesian2(3, 3),
        fillColor: textColor,
        outlineColor: Cesium.Color.WHITE,
        outlineWidth: 3,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        verticalOrigin: Cesium.VerticalOrigin.TOP,
        pixelOffset: pixelOffset,
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(undefined, 2000.0),
        show: true,
      })
      this.modelLabel.isRealShow = true
    } else {
      let duration, startTime
      if (!this.lastSampledTime) {
        duration = 7.5
        startTime = currentTime
      } else {
        const diff = Cesium.JulianDate.secondsDifference(currentTime, this.lastSampledTime)
        if (diff >= 0) {
          // 当前时间比采样时间慢了
          duration = 7.5
          startTime = currentTime
        } else if (diff <= -5) {
          // 当前时间比采样时间快了超过5秒
          duration = 2.5
          startTime = this.lastSampledTime
        } else {
          // 当前时间比采样时间快了不超过5秒
          duration = Cesium.JulianDate.secondsDifference(currentTime, this.cacheTime)
          startTime = this.lastSampledTime
        }
      }
      const samples = 20
      for (let i = 0; i <= samples; ++i) {
        const factor = i / samples
        const sampleTime = Cesium.JulianDate.addSeconds(startTime, duration * factor, new Cesium.JulianDate())
        const samplePosition = Cesium.Cartesian3.lerp(this.cachePosition, newPosition, factor, new Cesium.Cartesian3())
        this.sampledPositionProperty.addSample(sampleTime, samplePosition)
        this.lastSampledTime = sampleTime
      }

      // this.sampledPositionProperty.addSample(currentTime, newPosition)
      this.cachePosition = newPosition.clone()
      this.cacheTime = currentTime
      this.lon = data.longitude
      this.lat = data.latitude
      this.heading = heading

      var that = this
      this.mapView.clock.onTick.addEventListener(function (clock) {
        that.updateModel(clock.currentTime)
      })
    }
  }

  updateModel(time) {
    const position = this.sampledPositionProperty.getValue(time)
    if (!Cesium.defined(position)) return

    const velocity = this.velocityVectorProperty.getValue(time)
    if (Cesium.defined(velocity)) {

      // this.cachePosition == this.newPosition
      // 计算朝向(使箭头指向运动方向)
      // const heading = Math.atan2(velocity.y, velocity.x) + Cesium.Math.PI_OVER_TWO
      const heading = this.calculateHeadingBetweenPoints(this.cachePosition, position)
      let orientation = Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, 0, 0))
      // const orientation = Cesium.Quaternion.fromHeadingPitchRoll(new Cesium.HeadingPitchRoll(heading, 0, 0))
      this.lastOrientation = orientation
      Cesium.Matrix4.fromTranslationQuaternionRotationScale(position, orientation, new Cesium.Cartesian3(1.0, 1.0, 1.0), this.model.modelMatrix)
    } else {
      Cesium.Matrix4.fromTranslationQuaternionRotationScale(position, this.lastOrientation, new Cesium.Cartesian3(1.0, 1.0, 1.0), this.model.modelMatrix)
    }
    this.modelLabel.position = position
  }
  calculateHeadingBetweenPoints(startPosition, endPosition) {
    // 将笛卡尔坐标转换为地理坐标(弧度)
    const startCartographic = Cesium.Cartographic.fromCartesian(startPosition);
    const endCartographic = Cesium.Cartographic.fromCartesian(endPosition);

    const lon1 = startCartographic.longitude;
    const lat1 = startCartographic.latitude;
    const lon2 = endCartographic.longitude;
    const lat2 = endCartographic.latitude;

    // 计算经度差
    const dLon = lon2 - lon1;

    // 使用球面三角公式计算朝向
    const y = Math.sin(dLon) * Math.cos(lat2);
    const x = Math.cos(lat1) * Math.sin(lat2) -
      Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);

    // 计算角度(-π 到 π)
    let heading = Math.atan2(y, x);

    // 转换为0到2π范围
    heading = (heading + Cesium.Math.PI_OVER_TWO + Cesium.Math.TWO_PI) % Cesium.Math.TWO_PI;

    return heading;
  }