cesium视锥体

源码: 

class Vertebral {
  constructor(options: IVertebralOptions) {
    this.atlas = options.atlas
    this.Create(options.value)
  }

  public origin!: Cesium.Entity
  public atlas!: PointMap
  public spotLightCamera!: any;
  public primitivesone!: any;
  public primitivestwo!: any;

  public Create(value: valueType): void {
    const positions = Cesium.Cartesian3.fromDegrees(value.droneLon, value.droneLat, value.droneAltitude)
    const spotLightCamera = this.spotLightCamera = new Cesium.Camera(this.atlas.viewer.scene)
    spotLightCamera.setView({
      destination: positions,
      orientation: {
        heading: Cesium.Math.toRadians(value.aircraftHeading),
        pitch: Cesium.Math.toRadians(value.gimbalPitchRotateAngle),
        roll: Cesium.Math.toRadians(0)
      }
    })
    const scratchRight = new Cesium.Cartesian3()
    const scratchRotation = new Cesium.Matrix3()
    const scratchOrientation = new Cesium.Quaternion()
    const positionWC = spotLightCamera.positionWC
    const directions = spotLightCamera.directionWC
    const up = spotLightCamera.upWC
    let right = spotLightCamera.rightWC
    right = Cesium.Cartesian3.negate(right, scratchRight)

    const rotation = scratchRotation
    Cesium.Matrix3.setColumn(rotation, 0, right, rotation)
    Cesium.Matrix3.setColumn(rotation, 1, up, rotation)
    Cesium.Matrix3.setColumn(rotation, 2, directions, rotation)

    // 计算视锥姿态
    const orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation)
    // 摄像机近距离
    spotLightCamera.frustum.near = 0.1
    // 动态设置摄像机远距离
    const altitude = value.droneAltitude
    // const altitude = value.altitude + value.droneAltitude
    const farDistance = altitude / Math.sin(value.hfov / 2);
    spotLightCamera.frustum.far = farDistance;
    // @ts-ignore
    // 摄像机视野范围
    spotLightCamera.frustum.fov = value.vfov


    const instanceOutline = new Cesium.GeometryInstance({
      geometry: new Cesium.FrustumGeometry({
        // @ts-ignore
        frustum: spotLightCamera.frustum,
        origin: positionWC,
        orientation: orientation
      }),
      // material: Cesium.Color.RED.withAlpha(1),
      id: 'pri' + this.atlas.viewer.scene.primitives.length + 1,
      attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(3 / 255, 215 / 255, 145 / 255, 0.5)),
        show: new Cesium.ShowGeometryInstanceAttribute(true)
      }
    })


    const instance = new Cesium.GeometryInstance({
      geometry: new Cesium.FrustumOutlineGeometry({
        // @ts-ignore
        frustum: spotLightCamera.frustum,
        origin: positionWC,
        orientation: orientation
      }),
      // material: Cesium.Color.RED.withAlpha(0.1),
      id: 'pri0' + this.atlas.viewer.scene.primitives.length + 1,
      attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(3 / 255, 215 / 255, 145 / 255, 1)),
        show: new Cesium.ShowGeometryInstanceAttribute(true)
      }
    })

    this.primitivesone = this.atlas.viewer.scene.primitives.add(new Cesium.Primitive({
      geometryInstances: instance,
      appearance: new Cesium.PerInstanceColorAppearance({
        translucent: true,
        flat: true,
      }),
      asynchronous: false
    }))

    this.primitivestwo = this.atlas.viewer.scene.primitives.add(new Cesium.Primitive({
      geometryInstances: instanceOutline,
      appearance: new Cesium.PerInstanceColorAppearance({
        translucent: true,
        flat: true
      }),
      asynchronous: false
    }))
    this.primitivesone.type = 'vertebral'
    this.primitivestwo.type = 'vertebral'
  }

  public Destroy(): void {
    new Promise(resolve => {
      if (this.primitivesone) {
        this.atlas.viewer.entities.remove(this.primitivesone)
        this.atlas.viewer.entities.remove(this.primitivestwo)
        this.primitivesone.destroy()
        this.primitivestwo.destroy()
        this.spotLightCamera = null
      }
      resolve(true)
    })
  }

  public async UpDateVertebral(value: valueType) {
    await this.Destroy()
    this.Create(value)
  }

}

使用

在别的cesium实例类里面

	const cameraVertebralParams = {
			aircraftHeading, //偏航角
			gimbalPitchRotateAngle: pointInfo.payload[0].gimbalPitch,//俯仰角
			focalLength, //变焦倍数
			droneAltitude,//海拔高
			droneLat,
			droneLon,
			altitude: 1700,//没有用到
		}
		let hfov = 2 * Math.atan(SENSOR_WIDTH / (2 * cameraVertebralParams.focalLength));
		let vfov = 2 * Math.atan(SENSOR_HEIGHT / (2 * cameraVertebralParams.focalLength));
		this.pointCamera && this.pointCamera.Destroy()
		this.pointCamera = new Vertebral({
			atlas: this,(cesium)
			value: {
				...cameraVertebralParams,
				vfov,
				hfov
			},
		})

这些代码的核心作用是在 Cesium 3D 场景中实时创建和更新无人机相机的 “视锥体”(视野范围可视化),通过Vertebral类实现视锥体的创建、销毁,结合相机参数计算视场角,最终在场景中渲染出无人机当前姿态下的视野范围(类似一个 “可视锥”)。以下是详细解析:

一、Vertebral类:视锥体的创建与管理

Vertebral类是核心,负责视锥体的初始化(Create方法)和销毁(Destroy方法),本质是对 Cesium 几何体 API 的封装,将无人机参数转换为 3D 可视化的 “锥体”。

1. 类属性说明

typescript

class Vertebral {
  public origin!: Cesium.Entity; // 预留的原点实体(未实际使用)
  public atlas!: any; // 外部传入的场景上下文(通常是CesiumMap实例,包含viewer)
  public spotLightCamera!: any; // 视锥体对应的相机实例(用于计算姿态和视野)
  public primitivesone!: any; // 视锥体轮廓线的Primitive(Cesium的3D渲染对象)
  public primitivestwo!: any; // 视锥体填充区域的Primitive
}

  • atlas:关键依赖,需要包含 Cesium 的viewer实例(场景核心),用于添加 / 移除 3D 对象。
  • spotLightCamera:模拟无人机相机的虚拟相机,用于计算视锥体的姿态(方向)和视野范围(视场角)。
  • primitivesone/primitivestwo:Cesium 中用于渲染几何体的Primitive对象,分别对应视锥体的 “轮廓线” 和 “填充区域”。
2. constructor构造函数

typescript

constructor(options: IVertebralOptions) {
  this.atlas = options.atlas; // 接收外部传入的场景上下文(含viewer)
  this.Create(options.value); // 初始化时立即调用Create方法,创建视锥体
}

  • 作用:接收初始化参数(options),保存场景上下文(atlas),并触发视锥体创建。
  • IVertebralOptions类型:应包含atlas(场景上下文)和value(无人机 / 相机参数)。
3. Create方法:核心逻辑(创建视锥体)

该方法是视锥体可视化的核心,通过 6 个步骤将无人机参数转换为 3D 视锥体:

步骤 1:计算无人机位置(世界坐标)

typescript

// 将无人机的经纬度、海拔转换为Cesium的世界坐标(Cartesian3)
const positions = Cesium.Cartesian3.fromDegrees(
  value.droneLon,  // 经度
  value.droneLat,  // 纬度
  value.droneAltitude  // 海拔高度(米)
);

  • 作用:确定视锥体的 “顶点” 位置(无人机当前位置)。
步骤 2:初始化虚拟相机(spotLightCamera

typescript

// 创建与场景绑定的虚拟相机(用于模拟无人机相机姿态)
const spotLightCamera = this.spotLightCamera = new Cesium.Camera(this.atlas.viewer.scene);

// 设置相机位置和姿态(与无人机一致)
spotLightCamera.setView({
  destination: positions,  // 相机位置 = 无人机位置
  orientation: {
    heading: Cesium.Math.toRadians(value.aircraftHeading), // 偏航角(转为弧度)
    pitch: Cesium.Math.toRadians(value.gimbalPitchRotateAngle), // 俯仰角(转为弧度)
    roll: Cesium.Math.toRadians(0) // 横滚角(默认水平,无倾斜)
  }
});

  • 关键:虚拟相机的姿态完全匹配无人机的姿态(偏航角控制水平方向,俯仰角控制上下倾斜),确保视锥体的朝向与无人机实际拍摄方向一致。
  • 单位转换:Cesium 的角度参数需用弧度,因此通过Cesium.Math.toRadians将传入的角度(如aircraftHeading)转换为弧度。
步骤 3:计算视锥体的姿态(方向矩阵→四元数)

视锥体的姿态由相机的三个方向向量(右、上、前)决定,通过矩阵和四元数表示:

typescript

// 初始化临时变量(优化性能,避免频繁创建对象)
const scratchRight = new Cesium.Cartesian3();
const scratchRotation = new Cesium.Matrix3();
const scratchOrientation = new Cesium.Quaternion();

// 获取相机的三个基础方向向量(Cesium相机自带)
const positionWC = spotLightCamera.positionWC; // 相机世界坐标
const directions = spotLightCamera.directionWC; // 前向向量(拍摄方向)
const up = spotLightCamera.upWC; // 上向向量
let right = spotLightCamera.rightWC; // 右向向量

// 右向向量取反(修正坐标系方向,确保视锥体与Cesium场景坐标系匹配)
right = Cesium.Cartesian3.negate(right, scratchRight);

// 用三个方向向量构建旋转矩阵(描述视锥体在空间中的姿态)
const rotation = scratchRotation;
Cesium.Matrix3.setColumn(rotation, 0, right, rotation); // X轴:右向(取反后)
Cesium.Matrix3.setColumn(rotation, 1, up, rotation);    // Y轴:上向
Cesium.Matrix3.setColumn(rotation, 2, directions, rotation); // Z轴:前向

// 旋转矩阵转换为四元数(Cesium中更高效的姿态表示方式,用于几何体定位)
const orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation);
  • 作用:通过相机的方向向量计算视锥体的空间姿态,确保视锥体的朝向与无人机相机完全一致(比如无人机朝东偏航,视锥体也朝东)。
  • 为什么右向向量取反?Cesium 相机的rightWC默认方向可能与视锥体几何体的坐标系相反,取反后可确保视锥体左右方向正确。
步骤 4:配置视锥体的核心参数(近平面、远平面、视场角)

视锥体的 “形状” 由近平面(near)、远平面(far)和视场角(fov)决定:

// 1. 近平面:相机到最近可见平面的距离(过滤过近的物体,避免遮挡)
spotLightCamera.frustum.near = 0.1; // 单位:米

// 2. 远平面:相机到最远可见平面的距离(视锥体的“长度”)
const altitude = value.droneAltitude; // 无人机海拔高度(米)
// 计算公式:远距 = 海拔高度 / sin(水平视场角/2)(基于三角函数,确保视锥体覆盖地面)
const farDistance = altitude / Math.sin(value.hfov / 2); 
spotLightCamera.frustum.far = farDistance;

// 3. 垂直视场角:控制视锥体的“高度”范围(与水平视场角hfov共同决定视野宽高比)
spotLightCamera.frustum.fov = value.vfov; // vfov为垂直视场角(弧度)

  • 远平面计算逻辑:假设无人机在高空altitude处,水平视场角为hfov,则视锥体底部边缘到无人机正下方地面的距离为altitude / sin(hfov/2),确保视锥体刚好覆盖相机能拍摄到的最远距离。
  • 视场角关系:hfov(水平)和vfov(垂直)的比例与相机传感器的宽高比一致(你的传感器宽 9.6mm、高 7.2mm,比例 4:3,因此 hfov:vfov≈4:3)。
步骤 5:创建视锥体的几何体(填充区域 + 轮廓线)

通过 Cesium 的FrustumGeometry(视锥体几何体)和FrustumOutlineGeometry(视锥体轮廓几何体)创建 3D 模型:

// 1. 视锥体填充区域(半透明实体)
const instanceOutline = new Cesium.GeometryInstance({
  geometry: new Cesium.FrustumGeometry({
    frustum: spotLightCamera.frustum, // 关联相机的视锥体参数(near/far/fov)
    origin: positionWC, // 视锥体原点(无人机位置)
    orientation: orientation // 视锥体姿态(四元数,之前计算的方向)
  }),
  id: 'pri' + ..., // 唯一ID(用于后续销毁)
  attributes: {
    // 颜色:青绿色,透明度0.5(半透明,避免遮挡场景其他元素)
    color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(3/255, 215/255, 145/255, 0.5)),
    show: new Cesium.ShowGeometryInstanceAttribute(true) // 初始显示
  }
});

// 2. 视锥体轮廓线(边框,不透明,增强可视性)
const instance = new Cesium.GeometryInstance({
  geometry: new Cesium.FrustumOutlineGeometry({ // 轮廓几何体
    frustum: spotLightCamera.frustum,
    origin: positionWC,
    orientation: orientation
  }),
  id: 'pri0' + ..., // 唯一ID
  attributes: {
    color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(3/255, 215/255, 145/255, 1)), // 不透明
    show: new Cesium.ShowGeometryInstanceAttribute(true)
  }
});

  • GeometryInstance:Cesium 中几何体的 “实例”,包含几何体数据和样式属性(颜色、是否显示)。
  • 填充区域 vs 轮廓线:填充区域用半透明青绿色展示视野范围,轮廓线用同色不透明线条勾勒边缘,确保 3D 场景中清晰可见。
步骤 6:将几何体添加到场景中(渲染视锥体)

通过Cesium.Primitive将几何体添加到场景的primitives集合中,完成渲染:

// 1. 添加轮廓线(线)
this.primitivesone = this.atlas.viewer.scene.primitives.add(new Cesium.Primitive({
  geometryInstances: instance, // 轮廓线几何体实例
  appearance: new Cesium.PerInstanceColorAppearance({ // 样式:按实例定义的颜色
    translucent: true, // 半透明(线条轻微透明,避免过于刺眼)
    flat: true // 无光照效果(平面着色,性能更好)
  }),
  asynchronous: false // 同步渲染(参数变化时立即更新,适合动态场景)
}));

// 2. 添加填充区域(体)
this.primitivestwo = this.atlas.viewer.scene.primitives.add(new Cesium.Primitive({
  geometryInstances: instanceOutline, // 填充区域几何体实例
  appearance: new Cesium.PerInstanceColorAppearance({
    translucent: true, // 半透明(核心:能看到视锥体内的场景元素)
    flat: true
  }),
  asynchronous: false
}));

// 标记类型,方便后续批量管理
this.primitivesone.type = 'vertebral';
this.primitivestwo.type = 'vertebral';

  • scene.primitives.add:将Primitive添加到场景中,Cesium 会自动渲染。
  • asynchronous: false:关闭异步加载,确保视锥体参数变化时立即重新渲染(无人机移动或姿态变化时实时更新)。
3. Destroy方法:销毁视锥体(释放资源)
public Destroy(): void {
  new Promise(resolve => {
    if (this.primitivesone) {
      // 尝试从场景中移除并销毁Primitive
      this.atlas.viewer.entities.remove(this.primitivesone);
      this.atlas.viewer.entities.remove(this.primitivestwo);
      this.primitivesone.destroy();
      this.primitivestwo.destroy();
      this.spotLightCamera = null; // 清空相机实例
    }
    resolve(true);
  });
}
  • 作用:移除场景中的视锥体渲染对象,释放内存,避免重复渲染导致的性能问题。
  • 潜在问题:Primitive是添加到scene.primitives中的,而entities.remove用于移除Entity对象,这里应该用scene.primitives.remove

    typescript

    // 正确的移除方式:从scene.primitives中移除Primitive
    this.atlas.viewer.scene.primitives.remove(this.primitivesone);
    this.atlas.viewer.scene.primitives.remove(this.primitivestwo);
    

二、使用逻辑:创建 / 更新视锥体

// 1. 收集无人机和相机参数
const cameraVertebralParams = {
  aircraftHeading, // 偏航角(无人机水平方向,如朝向正东为90°)
  gimbalPitchRotateAngle: pointInfo.payload[0].gimbalPitch, // 俯仰角(相机上下倾斜角度,如俯视30°为-30°)
  focalLength, // 相机焦距(mm,如7mm)
  droneAltitude, // 无人机海拔高度(米)
  droneLat, // 无人机纬度
  droneLon, // 无人机经度
  altitude: 1700, // 预留参数(未使用)
};

// 2. 计算水平和垂直视场角(关键:决定视锥体的宽窄)
let hfov = 2 * Math.atan(SENSOR_WIDTH / (2 * cameraVertebralParams.focalLength)); // 水平视场角
let vfov = 2 * Math.atan(SENSOR_HEIGHT / (2 * cameraVertebralParams.focalLength)); // 垂直视场角

// 3. 先销毁旧的视锥体,再创建新的(更新逻辑)
this.pointCamera && this.pointCamera.Destroy(); 
this.pointCamera = new Vertebral({
  atlas: this, // 传入场景上下文(含viewer)
  value: {
    ...cameraVertebralParams, // 扩展无人机参数
    vfov, // 传入计算好的垂直视场角
    hfov // 传入计算好的水平视场角
  },
});
关键步骤解析
  1. 参数收集cameraVertebralParams包含无人机的位置(经纬度、海拔)、姿态(偏航角、俯仰角)和相机参数(焦距),是视锥体计算的基础。

  2. 视场角计算

    • 公式:hfov = 2 * arctan(传感器宽度/(2*焦距))(水平),vfov = 2 * arctan(传感器高度/(2*焦距))(垂直)。
    • 你的传感器参数:宽 9.6mm、高 7.2mm,焦距 7mm 时,hfov≈68°vfov≈53.6°(宽高比 4:3,与传感器一致)。
    • 意义:视场角决定视锥体的 “宽窄”—— 焦距越小(广角),视场角越大,视锥体越 “胖”;焦距越大(长焦),视场角越小,视锥体越 “瘦”。
  3. 更新逻辑this.pointCamera && this.pointCamera.Destroy()确保先销毁旧的视锥体,再创建新实例,避免场景中存在多个视锥体导致混乱。

三、整体作用与业务价值

这套代码的核心价值是将无人机相机的抽象参数(位置、姿态、焦距)转换为 3D 场景中可视化的 “视锥体”,在无人机应用中具体作用:

  • 直观判断拍摄范围:操作人员能实时看到无人机当前参数下能拍摄到的区域,避免漏拍(如测绘时确保覆盖目标区域)。
  • 参数调试辅助:调整焦距(变焦)时,通过视锥体的宽窄变化可直观验证参数是否合适(如长焦时视锥体细长,适合拍摄远处细节)。
  • 动态监控:无人机移动或姿态变化时,视锥体实时更新,帮助判断是否对准目标(如巡检时是否覆盖待检测的设备)。
基于C2000 DSP的电力电子、电机驱动和数字滤波器的仿真模型构建及其C代码实现方法。首先,在MATLAB/Simulink环境中创建电力电子系统的仿真模型,如三相逆变器,重点讨论了PWM生成模块中死区时间的设置及其对输出波形的影响。接着,深入探讨了C2000 DSP内部各关键模块(如ADC、DAC、PWM定时器)的具体配置步骤,特别是EPWM模块采用上下计数模式以确保对称波形的生成。此外,还讲解了数字滤波器的设计流程,从MATLAB中的参数设定到最终转换为适用于嵌入式系统的高效C代码。文中强调了硬件在环(HIL)和支持快速原型设计(RCP)的重要性,并分享了一些实际项目中常见的陷阱及解决方案,如PCB布局不当导致的ADC采样异常等问题。最后,针对中断服务程序(ISR)提出了优化建议,避免因ISR执行时间过长而引起的系统不稳定现象。 适合人群:从事电力电子、电机控制系统开发的技术人员,尤其是那些希望深入了解C2000 DSP应用细节的研发工程师。 使用场景及目标:①掌握利用MATLAB/Simulink进行电力电子设备仿真的技巧;②学会正确配置C2000 DSP的各项外设资源;③能够独立完成从理论设计到实际产品落地全过程中的各个环节,包括但不限于数字滤波器设计、PWM信号生成、ADC采样同步等。 其他说明:文中提供了大量实用的代码片段和技术提示,帮助读者更好地理解和实践相关知识点。同时,也提到了一些常见错误案例,有助于开发者规避潜在风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值