当前位置: 首页 > news >正文

cesium视锥体

源码: 

class Vertebral {constructor(options: IVertebralOptions) {this.atlas = options.atlasthis.Create(options.value)}public origin!: Cesium.Entitypublic atlas!: PointMappublic 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.positionWCconst directions = spotLightCamera.directionWCconst up = spotLightCamera.upWClet right = spotLightCamera.rightWCright = Cesium.Cartesian3.negate(right, scratchRight)const rotation = scratchRotationCesium.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.droneAltitudeconst farDistance = altitude / Math.sin(value.hfov / 2);spotLightCamera.frustum.far = farDistance;// @ts-ignore// 摄像机视野范围spotLightCamera.frustum.fov = value.vfovconst instanceOutline = new Cesium.GeometryInstance({geometry: new Cesium.FrustumGeometry({// @ts-ignorefrustum: 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-ignorefrustum: 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' + ..., // 唯一IDattributes: {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) {// 尝试从场景中移除并销毁Primitivethis.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 场景中可视化的 “视锥体”,在无人机应用中具体作用:

  • 直观判断拍摄范围:操作人员能实时看到无人机当前参数下能拍摄到的区域,避免漏拍(如测绘时确保覆盖目标区域)。
  • 参数调试辅助:调整焦距(变焦)时,通过视锥体的宽窄变化可直观验证参数是否合适(如长焦时视锥体细长,适合拍摄远处细节)。
  • 动态监控:无人机移动或姿态变化时,视锥体实时更新,帮助判断是否对准目标(如巡检时是否覆盖待检测的设备)。
http://www.dtcms.com/a/308523.html

相关文章:

  • 【C#】基于SharpCompress实现压缩包解压功能
  • TDengine 中 TDgp 中添加算法模型(预测分析)
  • Spring Security之初体验
  • 智慧社区项目开发(四)——前后端登录认证相关功能实现解析
  • QT Word模板 + QuaZIP + LibreOffice,跨平台方案实现导出.docx文件后再转为.pdf文件
  • 安全月报 | 傲盾DDoS攻击防御2025年7月简报
  • 功能强大编辑器
  • [Agent开发平台] 可观测性(追踪与指标) | 依赖注入模式 | Wire声明式配置
  • 量子安全:微算法科技(MLGO)基于比特币的非对称共识链算法引领数字经济未来
  • Linux安装AnythingLLM
  • 【ad-hoc构造】P10033 「Cfz Round 3」Sum of Permutation|普及+
  • langchain--1--prompt、output格式、LCEL示例
  • 2025年7月最新一区SCI-基尔霍夫定律优化算法Kirchhoff’s law algorithm-附Matlab免费代码
  • FastGPT + Kymo AI生态创新平台,搭建企业智能化知识管理
  • XPATH选择器常用语法
  • langchain从入门到精通(四十二)——全面剖析之Memory
  • 机器学习①【机器学习的定义以及核心思想、数据集:机器学习的“燃料”(组成和获取)】
  • 深度学习基础—2
  • [人工智能-综述-17]:AI革命:重塑职业版图,开启文明新篇
  • day066-内容分发网络(CDN)与web应用防火墙(WAF)
  • 大模型+垂直场景:技术纵深、场景适配与合规治理全景图
  • Rust × WebAssembly 项目脚手架详解
  • Linux服务器性能检测与调优指南
  • 深入解析LLM层归一化:稳定训练的关键
  • 【04】大恒相机SDK C++发开——调试千兆网相机心跳超时设备掉线
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | FeedbackUiDesign(评价反馈组件)
  • 工程项目管理软件选型对比:主流平台功能与适用场景深度测评
  • [12月考试] F
  • 用el-table实现的可编辑的动态表格组件
  • 微信小程序中进行参数传递的方法