AFSIM-雷达波束视效可视化

AFSIM-雷达波束视效可视化 问博主博主Wizard 里能用天线可视化插件看到方向图Warlock 里也想看有办法吗答有的兄弟必须安排。说明AFSIM 自带雷达探测模型并未细化到波束实时指向层面内置的 SensorVolumes 可视化组件仅能展示雷达极限探测距离只能体现最远探测能力无法实现天线形态、波束实时朝向等精细化效果。本次实现的波束可视化仅依据雷达当前姿态与天线参数完成渲染但实际仿真探测会结合方位俯仰扫描区间、波束宽度、雷达工作模式综合判定并不局限于雷达当前瞬时指向。这也就出现了目标不在可视化波束内依旧能被探测到的情况同时界面也无法展示实际完成探测的精准角度数据。成果展示思路参考 Wizard 中PatternVisualization插件实现三维天线方向图绘制算法同时借鉴 Warlock 里SensorVolumes插件探测范围渲染逻辑整体采用前者核心计算逻辑沿用后者从数据读取到 OSG 场景模型生成的完整绘制流程完成开发。不少朋友会疑惑为何不将该功能封装为独立插件。该方案技术上可行但整体开发流程繁琐。看似简单的功能需经过开发编写、反复测试、多轮迭代调试才能产出稳定可用版本。为减少开发工作量并兼顾使用稳定性本次选择直接在原生源码中拓展实现该方式存在规范风险不建议大家效仿使用。代码修改汇总本次实现直接修改了以下三个核心类SensorVolumesPlatform、SensorVolumesPlugin、SensorVolumesSimEvents。同时在tools/wkf/common/source/sensor_volume目录下新增 WkfAttachmentAntennaPattern 类用于实现天线方向图的三维可视化逻辑。需要完整源码的小伙伴直接私信博主即可本次所有相关代码免费公开、无保留分享欢迎大家交流学习这里更建议大家跟着思路借助各类 AI 开发工具做低代码、无代码式开发这也是当下主流的开发方式。并非传统手写代码的方式不好新手入门阶段亲手敲代码打牢基础依旧是最优选择。而对于经验丰富的开发者自然是能简化流程就尽量省事。大家应该也深有体会心里没有清晰思路、没有确定实现方案时AI 大概率给出的代码往往冗余繁杂、贴合度极低到头来还是得自己动手梳理逻辑、逐行改写落地。往后程序员的身份也会慢慢偏向代码审查员不再是纯粹的代码搬运工。其实在 AI 时代受益最多的向来是头部大厂对中小型团队而言反而算是一场冲击。有完善架构与开发规范才能依托 AI 实现高效量产缺少体系化架构与统一标准即便借助 AI 产出再多内容也难以落地量产始终难成体系上不得台面。未来也会有越来越多资深技术从业者选择自主接单独立发展既吃透业务逻辑、精通代码底层又能熟练玩转各类 AI 开发工具既能大幅拉高开发效率也能有效压缩项目成本。核心代码一、wizard/plugins/PatternVisualization 关键代码1.1 PatternData 数据采样// PatternData.cpp:187-209 PatternData::PatternData(WsfAntennaPattern* aWsfPattern, double aFrequency) : mAzimuthStep(2.0 * PI / NumAzimuthReadings), mElevationStep(PI / (NumElevationReadings - 1)), mMinDB(DB_MAX), mMaxDB(DB_MIN) { mGainReadings.reserve(NumAzimuthReadings * NumElevationReadings); double elevation PI_OVER_TWO; for (unsigned int el 0; el NumElevationReadings; el) { double azimuth -PI; for (unsigned int az 0; az NumAzimuthReadings; az) { // 从 WSF 获取增益 double gain aWsfPattern-GetGain(aFrequency, azimuth, elevation, 0, 0); // 转换为 dB float db static_castfloat(SafeLinearToDB(gain)); // 更新最小值/最大值 mMinDB std::min(mMinDB, db); mMaxDB std::max(mMaxDB, db); mGainReadings.push_back(db); azimuth mAzimuthStep; } elevation - mElevationStep; } }1.2 PatternMesh 三维网格构建// PatternMesh.cpp:78-131 void PatternMesh::CreateVertices(PatternData* aPatternData, double aMinRadius, double aMaxRadius) { auto vertices new osg::Vec3Array(); auto colors new osg::Vec4Array(); vertices-reserve(PatternData::NumAzimuthReadings * PatternData::NumElevationReadings); colors-reserve(PatternData::NumAzimuthReadings * PatternData::NumElevationReadings); PatternDataAllIterator it(aPatternData); while (it) { // 归一化 dB 到 [0,1] float normalizedDB (it.DB() - aPatternData-GetMinDB()) / (aPatternData-GetMaxDB() - aPatternData-GetMinDB()); if (normalizedDB 0.0) normalizedDB 0.0; if (normalizedDB 1.0) normalizedDB 1.0; // dB → 半径: minRadius ~ maxRadius float radius static_castfloat(aMinRadius normalizedDB * (aMaxRadius - aMinRadius)); // 球坐标 → 笛卡尔坐标 double elRad it.Elevation().ToRadians(); double azRad it.Azimuth().ToRadians(); float cosEl static_castfloat(cos(elRad)); float sinEl static_castfloat(sin(elRad)); float cosAz static_castfloat(cos(azRad)); float sinAz static_castfloat(sin(azRad)); float x radius * cosEl * cosAz; float y radius * cosEl * sinAz; float z radius * sinEl; vertices-push_back(osg::Vec3(x, y, z)); colors-push_back(DBToColor(it.DB(), aPatternData-GetMinDB(), aPatternData-GetMaxDB())); it; } mGeometry-setVertexArray(vertices); mGeometry-setColorArray(colors); mGeometry-setColorBinding(osg::Geometry::BIND_PER_VERTEX); }二、warlock/plugins/SensorVolumes 新增关键代码2.1 AntennaPatternSample 数据采样// SensorVolumesPlatform.cpp:37-77 WkSensorVolumes::Platform::AntennaPatternSample::AntennaPatternSample(WsfAntennaPattern* aAntennaPattern, float aFrequency, float aMaxRange) : mFrequency(aFrequency), mMaxRange(aMaxRange) { if (!aAntennaPattern) { return; } uint32_t numSamples NumAzimuthSamples * NumElevationSamples; mGainDB.reserve(numSamples); mMinDB std::numeric_limitsfloat::max(); mMaxDB -std::numeric_limitsfloat::max(); for (uint32_t elIdx 0; elIdx NumElevationSamples; elIdx) { double elRad UtMath::cPI_OVER_TWO - elIdx * (UtMath::cPI / (NumElevationSamples - 1)); for (uint32_t azIdx 0; azIdx NumAzimuthSamples; azIdx) { double azRad -UtMath::cPI azIdx * (2.0 * UtMath::cPI / NumAzimuthSamples); double gain aAntennaPattern-GetGain(aFrequency, azRad, elRad, 0, 0); float db static_castfloat(UtMath::SafeLinearToDB(gain)); mMinDB std::min(mMinDB, db); mMaxDB std::max(mMaxDB, db); mGainDB.push_back(db); } } }2.2 AttachmentAntennaPattern 三维网格渲染// WkfAttachmentAntennaPattern.cpp:88-127 void wkf::AttachmentAntennaPattern::RebuildMesh(const std::vectorfloat aGainDB, float aDbMin, float aDbMax, uint32_t aNumAzimuth, uint32_t aNumElevation, float aMaxRange) { if (aGainDB.empty() || aNumAzimuth 3 || aNumElevation 2) { return; } float maxR aMaxRange; if (std::isinf(maxR) || maxR 0.0f) { maxR 63710000.0f; } const float minRadius 0.1f * maxR; auto vertices new osg::Vec3Array(); auto colors new osg::Vec4Array(); vertices-reserve(aNumAzimuth * aNumElevation); colors-reserve(aNumAzimuth * aNumElevation); for (uint32_t elIdx 0u; elIdx aNumElevation; elIdx) { double elRad UtMath::cPI_OVER_TWO - elIdx * (UtMath::cPI / (aNumElevation - 1)); float cosEl static_castfloat(cos(elRad)); float sinEl static_castfloat(sin(elRad)); for (uint32_t azIdx 0u; azIdx aNumAzimuth; azIdx) { double azRad -UtMath::cPI azIdx * (2.0 * UtMath::cPI / aNumAzimuth); float db aGainDB[elIdx * aNumAzimuth azIdx]; // dB → 半径归一化 float normalizedDB (db - aDbMin) / (aDbMax - aDbMin); if (normalizedDB 0.0f) normalizedDB 0.0f; if (normalizedDB 1.0f) normalizedDB 1.0f; float radius minRadius normalizedDB * (maxR - minRadius); // 球坐标 → 笛卡尔坐标 float cosAz static_castfloat(cos(azRad)); float sinAz static_castfloat(sin(azRad)); float x radius * cosEl * cosAz; float y radius * cosEl * sinAz; float z radius * sinEl; vertices-push_back(osg::Vec3(x, y, z)); colors-push_back(DBToColor(db, aDbMin, aDbMax)); } } auto geometry new osg::Geometry(); geometry-setVertexArray(vertices); geometry-setColorArray(colors); geometry-setColorBinding(osg::Geometry::BIND_PER_VERTEX); // 构建三角形带索引 for (uint32_t elIdx 1u; elIdx aNumElevation; elIdx) { auto strip new osg::DrawElementsUInt(GL_TRIANGLE_STRIP); strip-reserve(aNumAzimuth * 2 1); for (uint32_t azIdx 0u; azIdx aNumAzimuth; azIdx) { uint32_t wrappedAz azIdx % aNumAzimuth; strip-push_back(elIdx * aNumAzimuth wrappedAz); strip-push_back((elIdx - 1) * aNumAzimuth wrappedAz); } geometry-addPrimitiveSet(strip); } mGeode-removeDrawables(0, mGeode-getNumDrawables()); mGeode-addDrawable(geometry); }2.3 朝向更新跟随传感器// WkfAttachmentAntennaPattern.cpp:173-206 void wkf::AttachmentAntennaPattern::Articulate(const double aSlew[3], const double aCue[3], const double aTranslation[3], bool aNonCueing) { osg::Matrix partMat; double azimuth aSlew[0]; double elevation aSlew[1]; double roll aSlew[2]; if (!aNonCueing) { azimuth aCue[0]; elevation aCue[1]; roll aCue[2]; } partMat.makeRotate(azimuth, osg::Vec3(0.0, 0.0, 1.0), // Z: 方位角 elevation, osg::Vec3(0.0, 1.0, 0.0), // Y: 俯仰角 roll, osg::Vec3(1.0, 0.0, 0.0)); // X: 横滚角 partMat.preMultTranslate(osg::Vec3(aTranslation[0], aTranslation[1], aTranslation[2])); mPAT-setMatrix(partMat); }欢迎各位大佬评论区交流探讨觉得内容实用的小伙伴顺手点个赞支持一下