Files
qt-migration-legatus/qt6-after/qt6-migrated-legatus/Lts/Services/CentralEffectSimulation/Plugins/Explosions/handler_explosions.cpp
T
berrux-dev 2df80f90a7 Migrates CentralEffectSimulation to generated rtiddsgen API
Converts accessor calls to plain member access across ces service.
2026-09-21 02:45:54 +02:00

1823 lines
83 KiB
C++
Executable File

/**
* Handler for player data
* $Id$
* $HeadURL$
* $Author$
* Created by hjl, SLSE, 2017-02-20
*/
#include "handler_explosions.h"
#include "expand_polygon.h"
#include <LtsUtilsCore/dds_bitarray.h>
#include <LtsUtilsCore/qpoint3d.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <LtsElevation/lts_elevation_manager.h>
#include <CentralEffectSimulation/handler_participant.h>
#include <LtsCoordinate/lts_coordinate.h>
#include <QPointF>
#include <QVector>
#include <QSet>
#include <QDateTime>
#include <QTimer>
#include <QElapsedTimer>
using namespace CentralEffectSimulation;
using namespace AreaEffectSimulation;
using namespace LtsCoordTrans;
namespace CesExplosions
{
HandlerExplosions::HandlerExplosions(const std::shared_ptr<CentralEffectSimulation::DdsHandler> &ddsHandler,
const std::shared_ptr<CentralEffectSimulation::HandlerParticipant> &participantHandler,
const std::shared_ptr<CentralEffectSimulation::ProxyBuilding> &proxyBuilding,
QObject *parent)
: QObject(parent)
, m_ddsHandler(ddsHandler)
, m_handlerParticipant(participantHandler)
, m_proxyBuilding(proxyBuilding)
, m_logger(CentralEffectSimulation::getLogger("Plugin.Explosions.HandlerExplosions"))
{
} // end of ctor()
//-----------------------------------------------------------------------------
HandlerExplosions::~HandlerExplosions()
{
} // end of dtor()
//-----------------------------------------------------------------------------
bool HandlerExplosions::init()
{
DEBUG_LOG(m_logger) << "HandlerExplosions::init()";
auto dtcAes = m_ddsHandler->dtcAes();
auto ftcTab = m_ddsHandler->ftcTab();
auto dtcCes = m_ddsHandler->dtsCes();
ftcTab->enableSendCesHit();
dtcCes->enableParticipantEvent();
dtcAes->enableAesItemView();
connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::aesItemViewChanged,
this, &HandlerExplosions::onAesItemViewChanged);
dtcAes->enableTriggeredImpacts();
connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::triggeredImpactsChanged,
this, &HandlerExplosions::onTriggeredImpactsChanged);
dtcAes->enableExplosionSingle();
connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::explosionSingleChanged,
this, &HandlerExplosions::onExplosionSingleChanged);
dtcAes->enableEffects();
connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::effectsChanged,
this, &HandlerExplosions::onEffectsChanged, Qt::UniqueConnection);
connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::newOutdoorPosition,
this, &HandlerExplosions::onNewParticipantCesPosition);
connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::newIndoorPosition,
this, &HandlerExplosions::onNewParticipantCesPosition);
connect(m_handlerParticipant.get(), &CentralEffectSimulation::HandlerParticipant::restoreRadioReadinessChanged,
this, &HandlerExplosions::onRestoreRadioReadinessChanged);
dtcAes->enableChargeEffect();
connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::chargeEffectChanged,
this, &HandlerExplosions::onChargeEffectChanged);
return true;
} // end of init()
//-----------------------------------------------------------------------------
void HandlerExplosions::onSendCesHitReply(const Core::FunctionReplyObject &replyObject)
{
if (replyObject.error())
WARNING_LOG(m_logger) << "Can not send CesHitRequest: " << replyObject();
else
DEBUG_LOG(m_logger) << "onSendCesHitReply for id " << replyObject.id() << " success\n"
<< " replyObject.str() = " << replyObject.str();
}
// ///////////////////////////////////
// A E S (AreaEffectSimulation)
// ///////////////////////////////////
void HandlerExplosions::onAesItemViewChanged(const AreaEffectSimulation::AesItemView &data, DataModification modType)
{
if (modType == DataModification::eRemoved)
return;
// NASAT, MIDAS, AirSupport
// Der explizite HandlerAirSupport wird gestrichen.
// Der HandlerExplosions übernimmt die Aufgaben mit, da quasi identisch!!
// if (m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(data.chargeTypeId()) == ProjectDataManagement::GearClass_def::AirSupport)
// return;
static const QVector<AreaEffectSimulation::AesType> validAesTypes = {AesType_def::AtEffectCluster, AesType_def::AtExplosionAreaSingleExplosions, AesType_def::AtExplosionSingle, AesType_def::AtExplosionCluster};
if (!validAesTypes.contains(data.aesTypeId()))
return;
const AreaEffectSimulation::AesType aesTypeId = data.aesTypeId();
const int aesItemId = data.aesItemId();
const int chargeTypeId = data.chargeStruct().chargeTypeId();
const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(chargeTypeId);
const double impactRadius = m_ddsHandler->chargeTypeRadius(data.chargeStruct());
bool isCentral = false;
DEBUG_LOG(m_logger) << "onAesItemViewChanged 1 "
<< ", aesItemId=" << aesItemId
<< ", aesTypeId=" << aesTypeId
<< ", chargeTypeId=" << chargeTypeId
<< ", gearClass=" << gearClass
<< ", impactRadius=" << impactRadius
<< ", effectStateId=" << data.effectStateId()
<< ", chargeStruct=" << data.chargeStruct();
switch (aesTypeId.underlying())
{
case AreaEffectSimulation::AesType_def::AtExplosionSingle:
{
const AreaEffectSimulation::ExplosionSingle &explosionSingle = m_ddsHandler->dtcAes()->explosionSingle(aesItemId);
isCentral = explosionSingle.isCentral();
DEBUG_LOG(m_logger) << "ExplosionSingle";
break;
}
case AreaEffectSimulation::AesType::AtExplosionArea:
case AreaEffectSimulation::AesType::AtExplosionAreaSingleExplosions:
{
const AreaEffectSimulation::ExplosionArea &explosionArea = m_ddsHandler->dtcAes()->explosionArea(aesItemId);
isCentral = explosionArea.isCentral();
DEBUG_LOG(m_logger) << "explosionArea";
break;
}
case AreaEffectSimulation::AesType::AtExplosionCluster:
{
const AreaEffectSimulation::ExplosionCluster &explosionCluster = m_ddsHandler->dtcAes()->explosionCluster(aesItemId);
isCentral = explosionCluster.isCentral() || m_ddsHandler->isLaserGuidable(explosionCluster.chargeStruct());
DEBUG_LOG(m_logger) << "explosionCluster";
break;
}
case AreaEffectSimulation::AesType::AtEffectCluster:
{
isCentral = true;
DEBUG_LOG(m_logger) << "effectCluster";
break;
}
default:
DEBUG_LOG(m_logger) << "default, data.aesTypeId=" << aesTypeId;
} // switch
// skip SMART
if (m_ddsHandler->isSmartChargeType(data.chargeStruct()))
{
DEBUG_LOG(m_logger) << " S M A R T return onAesItemViewChanged";
return;
} // if
if (!isCentral)
return; // <-------------------------- return
//
// just for isCentral == true
//
DEBUG_LOG(m_logger) << "onAesItemViewChanged 2 aesItemId=" << aesItemId
<< " isCentral=" << isCentral;
const bool isSupplyTruck = false;
static CentralEffectSimulation::AesItemViewUtm prevData_empty_justForSignatur;
// hazardDistance and buffer will be done later (prepared, activated)
CentralEffectSimulation::AesItemViewUtm aesItemViewUtm = CentralEffectSimulation::AesItemViewUtm::create(data,
prevData_empty_justForSignatur,
impactRadius,
isSupplyTruck);
// default for useHazardDistance, hazardDistance
DEBUG_LOG(m_logger) << "onAesItemViewChanged 3"
<< ", aesItemId=" << aesItemId
<< ", aesTypeId=" << aesTypeId
<< ", chargeTypeId=" << chargeTypeId
<< ", gearClass=" << gearClass
<< ", isCentral=" << isCentral
<< ", impactRadius=" << impactRadius
<< ", effectStateId=" << data.effectStateId();
switch (data.effectStateId().underlying())
{
case AreaEffectSimulation::AesEffectState_def::EsUnknown:
case AreaEffectSimulation::AesEffectState_def::EsError:
case AreaEffectSimulation::AesEffectState_def::EsExpired:
case AreaEffectSimulation::AesEffectState_def::EsRemoved:
{
DEBUG_LOG(m_logger) << " remove aesItemId=" << data.aesItemId();
// do some cleanup:
m_handledImpactsStorage.removeAesItem(aesItemId);
Q_FALLTHROUGH();
}
case AreaEffectSimulation::AesEffectState_def::EsPaused:
{
// Man kann den m_enteredHazardDistanceStore nutzen.
// Und allen darin für dieses area/aesItemView gespeicherten participants
// ein sendLeaveHazardDistance senden...
const QVector<int> &allParticipantIdsInArea = m_enteredHazardDistanceStore.allParticipantIdsOfArea(aesItemId);
if (m_logger.isDebugEnabled())
qCritical() << "allParticipantIdsInArea = " << allParticipantIdsInArea;
const int remaining = 1; // ICD GUEZSB60_ICD-0052_Zentrale_Tne.doc Wirkfeldtyp Gefahrenbereich Steilfeuer
const auto &participantPositions = m_handlerParticipant->positions();
for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions))
{
if (!allParticipantIdsInArea.contains(pPos.participantId()))
continue;
sendLeaveHazardDistance(pPos.participantId(), aesItemId, remaining);
} // for
// jetzt sollte m_enteredHazardDistanceStore eigentlich bereinigt sein:
const int remaingPartIdsInArea = m_enteredHazardDistanceStore.allParticipantIdsOfArea(aesItemId).size();
DEBUG_LOG(m_logger) << " remaingParticipantIdsInArea=" << remaingPartIdsInArea;
Q_ASSERT(remaingPartIdsInArea == 0);
m_enteredHazardDistanceStore.removeArea(aesItemId);
// cleanup:
if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsPaused)
{
DEBUG_LOG(m_logger) << "Expired, remove aesItemId=" << aesItemId
<< ", m_aesItemViewsUtm.remove(" << aesItemId << ")";
m_aesItemViewsUtm.remove(aesItemId);
m_aesItemViewsImpactsUtm.remove(aesItemId);
#ifdef ORIGINATOR_AT_SENDCESHITREQUEST
m_aesItemId2partIdMap.remove(aesItemId);
#endif // ORIGINATOR_AT_SENDCESHITREQUEST
}
break;
}
case AreaEffectSimulation::AesEffectState_def::EsPrepared:
case AreaEffectSimulation::AesEffectState_def::EsFired:
case AreaEffectSimulation::AesEffectState_def::EsActivated:
{
DEBUG_LOG(m_logger) << "Got AesItemView: " << data.effectStateId()
<< " aesItemId=" << aesItemId
<< " isCentral=" << isCentral;
DEBUG_LOG(m_logger) << "insert AesItemView: " << data.effectStateId()
<< " aesItemViewUtm.effectStateId=" << aesItemViewUtm.effectStateId
<< " chargeTypeId" << aesItemViewUtm.chargeStruct.chargeTypeId();
// pre-calculate 'hazardDistanceForm' only in case of -> EsPrepared and EsActivated
// race condition: what comes first?
// a) Aiv changes to EsActivated or
// b) Effects ?
// So be prepared and calculate hazardDistanceForm already at EsPrepared
if (aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionCluster || aesTypeId == AreaEffectSimulation::AesType_def::AtEffectCluster)
{
// useHazardDistance = (aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionCluster);
const double hazardDistance = m_ddsHandler->dtcPdm()->hazardDistance(data.chargeStruct().chargeTypeId(),
data.chargeStruct().igniterTypeId(),
data.chargeStruct().igniterFunctionId());
DEBUG_LOG(m_logger) << "+++++++++++ ExplosionCluster: hazardDistance=" << hazardDistance
<< ", impactRadius =" << impactRadius;
aesItemViewUtm.hazardDistance_meter = hazardDistance;
aesItemViewUtm.hazardDistanceForm = ExpandPolygon::bufferUtmPolygon(aesItemViewUtm.form, aesItemViewUtm.zone, hazardDistance);
}
if ((aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions) ||
(aesTypeId == AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions))
m_aesItemViewsImpactsUtm.insert(aesItemId, aesItemViewUtm);
else
{
m_aesItemViewsUtm.insert(aesItemId, aesItemViewUtm);
DEBUG_LOG(m_logger) << "m_aesItemViewsUtm.insert aesItemId=" << aesItemId;
}
//
// checkPartEnterLeaveArea
//
if ((aesItemViewUtm.effectStateId == AreaEffectSimulation::AesEffectState::EsActivated) &&
aesItemViewUtm.hasHazardDistance())
{
// const reference (but if required we can benefit from implicit data sharing)
// make m_aesItemViewsUtm const to be able to initialze CONST iterator
const QHash<int, CentralEffectSimulation::AesItemViewUtm> &m_aesItemViewsUtmConstRef = m_aesItemViewsUtm;
const AES_ITEM_VIEW_UTM_CONST_ITER &curAivIt = m_aesItemViewsUtmConstRef.find(aesItemId);
if (curAivIt != m_aesItemViewsUtm.constEnd())
{
const auto &participantPositions = m_handlerParticipant->positions();
const int numberOfParametrizedObjects = checkPartEnterLeaveArea(m_aesItemViewsUtm.constBegin(), m_aesItemViewsUtm.constEnd(), // parametrize a single item
participantPositions.constBegin(), participantPositions.constEnd());
DEBUG_LOG(m_logger) << "++++++++ checkPartEnterLeaveArea checkPartEnterLeaveAreaNumber=" << numberOfParametrizedObjects;
}
} // if (aesItemViewUtm.effectStart == AreaEffectSimulation::AesEffectState::EsActivated)
break;
}
case AreaEffectSimulation::AesEffectState_def::EsCreated:
case AreaEffectSimulation::AesEffectState_def::EsReceived:
break;
} // switch
} // END onAesItemViewChanged
void HandlerExplosions::onTriggeredImpactsChanged(const AreaEffectSimulation::TriggeredImpacts &data, DataModification modType)
{
DEBUG_LOG(m_logger) << "onTriggeredImpactsChanged";
if (modType == DataModification::eRemoved)
return;
DEBUG_LOG(m_logger) << "Got TriggeredImpacts: " << data
<< " bitfield().size=" << data.bitfield().size();
switch (data.aesTypeId().underlying())
{
case AreaEffectSimulation::AesType_def::AtExplosionArea:
case AreaEffectSimulation::AesType_def::AtExplosionSingle:
case AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions:
case AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions:
// explosion at the position of participant
calculateImpacts(data);
break;
default:
//nothing
break;
} // switch
} // END onTriggeredImpactsChanged
void HandlerExplosions::calculateImpact(const AreaEffectSimulation::ChargeEffect &data)
{
int zone = -1;
// Now call calculateImpacts() (createEvent if in circle...)
const auto &participantPositions = m_handlerParticipant->positions();
// GeoPosition to UTM
const QPointF impactPositionUtm = CentralEffectSimulation::geographic2Utm(data.position(), zone);
AreaEffectSimulation::ChargeStruct chargeStruct(data.chargeTypeId(),data.igniterTypeId(), data.igniterFunctionId());
const double impactRadius = m_ddsHandler->chargeTypeRadius(chargeStruct);
for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions))
{
if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number
continue;
DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId();
if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill)
continue;
DEBUG_LOG(m_logger) << "pPos.vulnerabilityState=" << pPos.vulnerabilityState;
if (pPos.insideBuilding())
continue;
const QPointF participantPosUtm = CentralEffectSimulation::geographic2Utm(pPos.pos(), zone);
int equipNo = m_ddsHandler->dtcPpm()->equipmentNumberByParticipantId(pPos.participantId());
ParticipantManagement::DynamicParticipantData dynPart = m_ddsHandler->dtcPpm()->dynamicParticipantData(pPos.participantId());
ParticipantManagement::Participant originator = m_ddsHandler->dtcPpm()->participant(pPos.participantId());
int remainEffectPercent = 100; // TODO hier ist keine Gebäudewand dazwischen der Effekt wirkt komplett auf den Teilnehmer. Ist 100 richtig oder 0?
if (inCircle(impactPositionUtm, impactRadius, participantPosUtm))
{
DEBUG_LOG(m_logger) << "Teilnehmer getroffen => Send CesHitRequest ";
TrainingAreaBase::TabPosition tabPosition;
TrainingAreaBase::SendCesHitRequest requestTab;
tabPosition.position().lat(data.position().lat());
tabPosition.position().lon(data.position().lon());
tabPosition.position().z(data.position().z());
requestTab.equipmentNumber(equipNo);
requestTab.participantId(pPos.participantId());
requestTab.chargeTypeId(data.chargeTypeId());
//default
requestTab.aesId(0);
requestTab.eventType(ProjectDataManagement::EventType_def::FireEvent);
//
if (data.originatorTypeId() == ProjectDataManagement::StateChangeOriginator::Participant)
{
// if we get the equipmentNumber from the participant we will send this (needed by tab)
// if not fall back to participantId, not negated yet as it's not tested and probalbly handled by the TNE
const ParticipantManagement::Participant &part = m_ddsHandler->dtcPpm()->participant(data.originatorId());
if (part.equipmentNumber.is_set())
requestTab.originatorEquipmentNumber(part.equipmentNumber.get());
else
{
WARNING_LOG(m_logger) << "Sending CesHitRequest without EquipNo, using negative ParticipantID : " << data.originatorId();
requestTab.originatorEquipmentNumber(-data.originatorId());
}
}
else if (data.originatorTypeId() == ProjectDataManagement::StateChangeOriginator::Party)
{
requestTab.originatorEquipmentNumber(data.originatorId());
}
else if (data.originatorTypeId() == ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation)
{
requestTab.aesId(data.originatorId());
requestTab.eventType(ProjectDataManagement::EventType_def::ExplosionCluster);
}
requestTab.remainEffectPercent(remainEffectPercent);
requestTab.position(tabPosition);
requestTab.effectTime(data.effectStart() / 1000);
ParticipantManagement::Participant originator = m_ddsHandler->dtcPpm()->participant(data.originatorId());
requestTab.partyId(originator.partyId());
requestTab.mineId(0);
requestTab.partyId(originator.partyId());
DEBUG_LOG(m_logger) << " participantId=" << pPos.participantId()
<< ", chargeTypeId=" << data.chargeTypeId()
<< ", m_sendCesHitRequestQueue.size()=" << m_sendCesHitRequestQueue.size()
<< ", duration=" << data.effectDuration()
<< " \n" << requestTab;
DEBUG_LOG(m_logger) << "ftcTab->SendCesHitRequest: " << requestTab;
m_ddsHandler->ftcTab()->sendCesHitRequest(requestTab, this);
}
} // for
}
void HandlerExplosions::startTimer(int id, int msec) {
auto *timer = new QTimer(this);
timer->setProperty("chargeEffectId", id);
// Timeout-Slot mit echtem SLOT
connect(timer, &QTimer::timeout, this, &HandlerExplosions::onStartTimeReached);
timer->start(msec);
DEBUG_LOG(m_logger) << "Timer " << id << " gestartet fuer " << msec << " ms.";
}
void HandlerExplosions::onStartTimeReached()
{
if (!sender())
return;
bool bOk;
int chargeEffectId = sender()->property("chargeEffectId").toInt(&bOk);
if (!bOk)
return;
DEBUG_LOG(m_logger) << "Timer " << chargeEffectId << " ausgeloest!";
// Timer stoppen und loeschen
AreaEffectSimulation::ChargeEffect data = m_chargeEffects.value(chargeEffectId);
calculateImpact(data);
sender()->deleteLater();
m_chargeEffects.remove(chargeEffectId);
}
void HandlerExplosions::onChargeEffectChanged(const AreaEffectSimulation::ChargeEffect &data, DataModification modType)
{
DEBUG_LOG(m_logger) << "HandlerExplosions::onChargeEffectChanged";
if (modType == DataModification::eRemoved)
return;
const long long effectStart = data.effectStart();
DEBUG_LOG(m_logger) << "HandlerExplosions::onChargeEffectChanged"
<< ", effectStart=" << effectStart
<< ", currentPartitionTime=" << m_ddsHandler->currentPartitionTime();
// Startzeit in der Vergangenheit: aktuelle Zeit - 3 Sekunden
if ( effectStart < m_ddsHandler->currentPartitionTime() - 3000 )
{
DEBUG_LOG(m_logger) << "Startzeit in der Vergangenheit: aktuelle Zeit - 3 Sekunden";
return;
}
// Startzeit entspricht der aktuellen Zeit
if ( effectStart > m_ddsHandler->currentPartitionTime() - 3000
&& effectStart < m_ddsHandler->currentPartitionTime() + data.effectDuration() )
{
DEBUG_LOG(m_logger) << "Startzeit entspricht der aktuellen Zeit";
calculateImpact(data);
}
// Startzeit in der Zukunft:
else if (effectStart > m_ddsHandler->currentPartitionTime() + data.effectDuration())
{
// Start mit Timer
DEBUG_LOG(m_logger) << "Startzeit in der Zukunft"
<< ", id :" << data.id()
<< ", duration=" << effectStart - m_ddsHandler->currentPartitionTime();
m_chargeEffects.insert(data.id(), data);
startTimer(data.id(), effectStart - m_ddsHandler->currentPartitionTime());
}
DEBUG_LOG(m_logger) << "END onChargeEffectChanged";
}
void HandlerExplosions::onExplosionSingleChanged(const AreaEffectSimulation::ExplosionSingle &data, DataModification modType)
{
DEBUG_LOG(m_logger) << "\n onExplosionSingleChanged id=" << data.id()
<< " AesEffectState=" << data.effectStateId();
if (modType == DataModification::eRemoved)
return;
DEBUG_LOG(m_logger) << "onExplosionSingleChanged 1";
if (data.effectStateId() != AreaEffectSimulation::AesEffectState_def::EsActivated)
return;
DEBUG_LOG(m_logger) << "onExplosionSingleChanged"
<< " isCentral=" << data.isCentral();
if (!data.isCentral())
return;
DEBUG_LOG(m_logger) << "onExplosionSingle Activated isCentral=" << data.isCentral()
<< " id=" << data.id();
// get access to the aesItemViewUtm storing the data for bbox check:
CentralEffectSimulation::AesItemViewUtm aesItemViewUtm;
if (m_aesItemViewsUtm.contains(data.id()))
aesItemViewUtm = m_aesItemViewsUtm.value(data.id());
else if (m_aesItemViewsImpactsUtm.contains(data.id()))
aesItemViewUtm = m_aesItemViewsImpactsUtm.value(data.id());
else
ERROR_LOG(m_logger) << "Kein AesItemViewUtm ";
// Now call calculateImpacts() (createEvent if in circle...)
const auto &participantPositions = m_handlerParticipant->positions();
// GeoPosition to UTM
QVector<QPointF> triggeredEffectsUtmPos;
triggeredEffectsUtmPos.reserve(1);
const QPointF &posUtm = CentralEffectSimulation::geographic2Utm(data.impactPosition(), aesItemViewUtm.zone);
triggeredEffectsUtmPos.push_back(posUtm);
if (m_ddsHandler->isSmartChargeType(data.chargeStruct()))
{
DEBUG_LOG(m_logger) << " S M A R T "
<< " aesItemId=" << aesItemViewUtm.aesItemId
<< " chargeTypeId=" << data.chargeStruct().chargeTypeId()
<< " partyId=" << aesItemViewUtm.partyId
<< " sampleTime=" << data.sampleTime();
calculateSmartExplosion(aesItemViewUtm.aesItemId, data.chargeStruct(), aesItemViewUtm.partyId, data.impactPosition(), data.sampleTime());
return;
}
for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions))
{
if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number
continue;
DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId();
if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill)
continue;
DEBUG_LOG(m_logger) << "pPos.vulnerabilityState=" << pPos.vulnerabilityState;
if (pPos.insideBuilding())
continue;
DEBUG_LOG(m_logger) << "pPos.insideBuilding=" << pPos.insideBuilding();
if (!inBoundingBoxCheck(aesItemViewUtm, pPos))
continue;
DEBUG_LOG(m_logger) << "calculateImpacts zone=" << aesItemViewUtm.zone;
const Core::GeoPosition impactPosition3D(data.impactPosition().lon(), data.impactPosition().lat(), 0.0);
Options options;
calculateImpactsEffect_outdated(aesItemViewUtm,
{impactPosition3D}, triggeredEffectsUtmPos,
pPos, options);
} // for
}
void HandlerExplosions::onEffectsChanged(const AreaEffectSimulation::Effects &effects, DataModification modType)
{
QElapsedTimer timer;
if (m_logger.isInfoEnabled())
timer.start();
// The topic Effects is the successor of TriggeredImpacts & AesItemView (to avoid race condition)
Q_UNUSED(modType);
// Until now (04/2024) AreaEffectSimulation::Effects is used at ExplosionCluster only:
if (effects.aesTypeId().underlying() != AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions)
return;
// older then 10sec? ignore
if (effects.sampleTime() < m_ddsHandler->currentPartitionTime() - 10 * 1000)
return;
const int aesItemId = effects.aesItemId();
AreaEffectSimulation::AesItem aesItem;
bool itemAvailable = m_ddsHandler->dtcAes()->aesItemFromId(effects.aesItemId(), aesItem);
if ( itemAvailable)
{
if (m_ddsHandler->isSmartChargeType(aesItem.chargeStruct()))
{
DEBUG_LOG(m_logger) << "Calculate SMART";
calculateSmartEffect(effects);
return;
}
}
else
{
DEBUG_LOG(m_logger) << "No aesItem found for effect aesItemId=" << effects.aesItemId();
}
// get access to the aesItemViewUtm storing the data for bbox check:
const CentralEffectSimulation::AesItemViewUtm &aesItemViewUtm = m_aesItemViewsUtm.value(aesItemId);
// Is the m_aesItemViewsUtm filled with data _BEFORE_ onEffectsChanged is called?
// AesItem will be created _BEOFRE_ effects (Topic Effects) could be published:
if (aesItemViewUtm.aesItemId != aesItemId)
{
DEBUG_LOG(m_logger) << "aesItemViewUtm (with extent) not found for aesItemId =" << aesItemId
<< " perhaps the field is not central, expired or SMART return onEffectsChanged";
return;
}
//
// assume that areas with (isCentral = false) are not stored at m_aesItemViewsUtm
//
const int chargeTypeId = aesItemViewUtm.chargeStruct.chargeTypeId();
const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(chargeTypeId);
DEBUG_LOG(m_logger) << "onEffectsChanged, aesItemId=" << aesItemId
<< ", chargeTypeId = " << chargeTypeId
<< ", gearClass = " << gearClass;
// determine relevant positions:
const std::vector<AreaEffectSimulation::SingleEffect> &singleEffectsRef = effects.singleEffects();
QVector<LtsCoordinate> triggeredEffectsLtsCoord;
triggeredEffectsLtsCoord.reserve(singleEffectsRef.size());
int curSeIndex = -1;
for (const AreaEffectSimulation::SingleEffect &se : qAsConst(singleEffectsRef))
{
++curSeIndex;
if (se.singleEffectState() != AreaEffectSimulation::AesSingleEffectState_def::SesTriggered)
continue;
// compare current triggered idx with triggered idx of the past
if (m_handledImpactsStorage.isIndexProcessed(aesItemId, curSeIndex))
continue;
// okay, the current idx is triggered now and was not triggered before -> changing
triggeredEffectsLtsCoord.push_back(LtsCoordinate(se.position())); // ZpDraped and ZpAbsolute should have a valid zValue
// update storage ('remember')
m_handledImpactsStorage.setIndexProcessed(aesItemId, curSeIndex);
} // for const AreaEffectSimulation::SingleEffect &se
// now triggeredEffectsLtsCoord stores the (new) GeoPositions to be calculated:
DEBUG_LOG(m_logger) << "Effects of aesItemId =" << aesItemId
<< ", number of new SingleEffects (in this timeslot) = " << triggeredEffectsLtsCoord.size();
// Now call calculateImpacts() (createEvent if in circle...)
const auto &participantPositions = m_handlerParticipant->positions();
for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions))
{
if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill)
continue;
if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number
continue;
DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId()
<< ", vulnerabilityState=" << pPos.vulnerabilityState;
if (pPos.insideBuilding())
continue;
DEBUG_LOG(m_logger) << "pPos.insideBuilding=" << pPos.insideBuilding();
// skip inBoundingBoxCheck for ExplosionCluster since impacts could be OUTSIDE of bbox (intersection with bldg)
// if (!inBoundingBoxCheck(aesItemViewUtm, pPos))
// continue;
DEBUG_LOG(m_logger) << "calculateImpactsEffect for participantId " << pPos.participantId()
<< " Pos=" << pPos.ltsCoordinate().toString() << " ....";
// both: BLDG_LOS, ELEVATION_LOS
Options options = EXECUTE_BLDG_LOS_CALC |
EXECUTE_ELEVATION_LOS_CALC;
calculateImpactsEffect(aesItemViewUtm, triggeredEffectsLtsCoord, pPos, options);
DEBUG_LOG(m_logger) << "calculateImpactsEffect for participantId " << pPos.participantId() << " DONE";
} // for const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos
INFO_LOG(m_logger) << " onEffectsChanged took [ms] " << timer.elapsed();
}
// [GUEZE-2257]3D Wirksim - Gefaehrdungsabstand Zentral
void HandlerExplosions::onNewParticipantCesPosition(const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantData)
{
if (m_aesItemViewsUtm.empty())
{
TRACE_LOG(m_logger) << "++++++++ onNewParticipantOutdoorPosition participantId=" << participantData.participantId()
<< ", m_aesItemViewsUtm.size=" << m_aesItemViewsUtm.size();
return;
}
//
// checkPartEnterLeaveArea
//
// const reference (but if required we can benefit from implicit data sharing)
const auto &participantPositions = m_handlerParticipant->positions();
const HandlerParticipant::POSITIONS_CONST_ITER curPartIt = participantPositions.find(participantData.participantId());
if (curPartIt == participantPositions.constEnd())
return;
const int numberOfParametrizedObjects = checkPartEnterLeaveArea(m_aesItemViewsUtm.constBegin(), m_aesItemViewsUtm.constEnd(),
curPartIt, curPartIt + 1); // parametrize a single item
DEBUG_LOG(m_logger) << "++++++++ checkPartEnterLeaveArea checkPartEnterLeaveAreaNumber=" << numberOfParametrizedObjects;
}
int HandlerExplosions::checkPartEnterLeaveArea(AES_ITEM_VIEW_UTM_CONST_ITER currentAreaIt,
const AES_ITEM_VIEW_UTM_CONST_ITER &endingAreaIt,
HandlerParticipant::POSITIONS_CONST_ITER currentParticipantIt,
const HandlerParticipant::POSITIONS_CONST_ITER &endingParticipantIt)
{
DEBUG_LOG(m_logger) << "checkPartEnterLeaveArea...";
int numberOfParametrizedObjects = 0;
// outer loop (aesitemViewsUtm)
for (; currentAreaIt != endingAreaIt; ++currentAreaIt)
{
const CentralEffectSimulation::AesItemViewUtm &curAivUtmArea = *currentAreaIt;
// check aiv:
if (curAivUtmArea.effectStateId != AreaEffectSimulation::AesEffectState_def::EsActivated)
continue;
if (!curAivUtmArea.hasHazardDistance())
continue;
int zoneInOut = curAivUtmArea.zone;
// inner loop (participants)
for (; currentParticipantIt != endingParticipantIt; ++currentParticipantIt)
{
++numberOfParametrizedObjects;
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantData = *currentParticipantIt;
const int participantId = participantData.participantId();
const Core::GeoPosition &participantGeoPos = participantData.pos();
if (LtsCoordTrans::LtsCoordinate::valueIsNullOrNan(participantGeoPos.lon()) ||
LtsCoordTrans::LtsCoordinate::valueIsNullOrNan(participantGeoPos.lat()))
continue;
if (m_handlerParticipant->isOutdated(participantData)) // Participant Position is not valid Todo Magic Number
continue;
// participant insideBuilding should also be 'effected' regarding hazardDistance
// process following type of positions:
// - outdoor positions (based on GPS or IndoorPositioningSystem)
// - indoor positions (basedIndoorPositioningSystem) - Not implemented yet
// see also : https://devhub.defence-elec.de/confluence/display/ORGSL/Wirkung+CES
//
// if (participantData.insideBuilding())
// {
// DEBUG_LOG(m_logger) << "Participant:" << participantData.participantId() << " inside Building";
// continue;
// }
const QPointF &posUtm = participantData.ltsCoordinate().toUtm<QPointF>(zoneInOut);
DEBUG_LOG(m_logger) << "Check, if participant with id = " << participantId
<< " is inside hazardDistanceForm: "
<< ", hasHazardDistance=" << curAivUtmArea.hasHazardDistance()
<< ", hazardDistance=" << curAivUtmArea.hazardDistance_meter
<< ", posUtm=" << posUtm
<< ", zone=" << zoneInOut;
if (m_logger.isDebugEnabled())
{
for (int i = 0; i < curAivUtmArea.form.size(); ++i)
DEBUG_LOG(m_logger) << "form[" << i << "]=" << curAivUtmArea.form[i].x() << "/" << curAivUtmArea.form[i].y();
if (m_logger.isTraceEnabled())
for (int i = 0; i < curAivUtmArea.hazardDistanceForm.size(); ++i)
TRACE_LOG(m_logger) << "hazardDistanceForm[" << i << "]=" << curAivUtmArea.hazardDistanceForm[i].x() << "/" << curAivUtmArea.hazardDistanceForm[i].y();
}
// Laut ICD GUEZSB60_ICD-0052_Zentrale_Tne.doc Wirkfeldtyp Gefahrenbereich Steilfeuer
// ist der Gefahrenbereich/Gefährdungsabstand
// der a) INNERHALB des hazardDistance, aber b) AUSSERHALB des eigentlichen Wirkfeldes.
// also: bRealInHazardDistance = participantIsInsideHazardDistance && !participantIsInsideForm
// assume hazardDistanceForm is bigger than aiv :-)
const bool participantIsInsideHazardDistance = curAivUtmArea.hazardDistanceForm.containsPoint(posUtm, Qt::FillRule::OddEvenFill);
const bool participantIsInsideForm = participantIsInsideHazardDistance &&
curAivUtmArea.form.containsPoint(posUtm, Qt::FillRule::OddEvenFill);
const bool bRealInHazardDistance = participantIsInsideHazardDistance && !participantIsInsideForm;
const bool bWasRealInHazardDistance = m_enteredHazardDistanceStore.hasParticipantAlreadyEnteredArea(participantId, curAivUtmArea.aesItemId);
DEBUG_LOG(m_logger) << "participantId = " << participantId
<< ", participantIsInsideHazardDistance = " << participantIsInsideHazardDistance
<< ", participantIsInsideForm = " << participantIsInsideForm
<< ", bRealInHazardDistance = " << bRealInHazardDistance
<< ", bWasRealInHazardDistanc = " << bWasRealInHazardDistance;
// N. Thoden, 19.11.2024
// Ein Übergang von ganz innen nach ganz aussen oder umgekehrt, scheint nicht definiert zu sein.
// Mit Hilfe der Verwendung von b___Real___InHazardDistance würde keine Modifikation erkannt werden...
// does state changes?
if (bWasRealInHazardDistance != bRealInHazardDistance)
{
// now IN?
if (bRealInHazardDistance)
{
// part is ENTERING
DEBUG_LOG(m_logger) << "Send HazardDistanceMessage to participant (ENTERING)...";
sendEnterHazardDistance(participantId, curAivUtmArea.aesItemId);
}
else
{
// logical consequence: part is LEAVING
int remaining = 2; // ICD GUEZSB60_ICD-0052_Zentrale_Tne.doc Wirkfeldtyp Gefahrenbereich Steilfeuer
// 1 = verlassen in Richtung Wirkfeld (nach innen)
// 2 = verlassen nach aussen
// 3 = Info
// 0 = betreten
// Die Meldung wird einmalig bei Betreten des Gefahrbereichs und bei Verlassen gesendet.
// Die Meldung erfolgt fuer jeden Gefahrbereich, in dem sich der TN zu einem Zeitpunkt befindet
// (sich ueberlappende Gefahrbereiche sind moeglich). Die Warnmeldung auf dem CAN-Bus erfolgt im 30 Sekunden-Takt,
// solange sich der TN in einem oder mehreren Gefahrbereich(en) befindet. Betritt ein TN den Wirkbereich selber,
// kommt eine Verlassensmeldung fuer das betreffende Wirkfeld ("Verlassen nach innen").
// Erst wenn der TN alle Gefahrbereiche - entweder nach innen in Richtung Wirkfeld selber oder nach aussen - verlassen hat,
// unterbleiben die zyklischen Warnmeldungen auf dem CAN-Bus.
// Bei Wirkung = 3 (Info) wird nur einmalig eine Warnmeldung ausgegeben.
// Wird eine TNE stromlos (Restart) oder verliert zwischenzeitlich die Funkbereitschaft,
// kommen fuer alle betretenen Gefahrbereich neue Betreten-Meldungen von der Zentrale (inkl. Wirkbeginn + Wirkdauer).
if (participantIsInsideForm)
remaining = 1;
DEBUG_LOG(m_logger) << "Send HazardDistanceMessage to participant (LEAVING)... remaining =" << remaining;
sendLeaveHazardDistance(participantData.participantId(), curAivUtmArea.aesItemId, remaining);
}
} // if (bWasRealInHazardDistance != bRealInHazardDistance)
} // for
} // for
return numberOfParametrizedObjects;
}
void HandlerExplosions::onRestoreRadioReadinessChanged(const int participantId)
{
DEBUG_LOG(m_logger) << "HandlerExplosions::onRestoreRadioReadinessChanged participantId=" << participantId;
m_enteredHazardDistanceStore.removeParticipant(participantId);
}
// return true, if participant is inside of the bounding Box
bool HandlerExplosions::inBoundingBoxCheck(const CentralEffectSimulation::AesItemViewUtm &itemViewUtm,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition)
{
DEBUG_LOG(m_logger) << "inBoundingBoxCheck 1"
<< ", aesItemId=" << itemViewUtm.aesItemId
<< ", effectStateId=" << itemViewUtm.effectStateId;
if ((itemViewUtm.effectStateId != AreaEffectSimulation::AesEffectState_def::EsActivated) &&
(itemViewUtm.effectStateId != AreaEffectSimulation::AesEffectState_def::EsPrepared) &&
(itemViewUtm.effectStateId != AreaEffectSimulation::AesEffectState_def::EsFired))
return false;
DEBUG_LOG(m_logger) << "inBoundingBoxCheck 2";
// QRectF(topLeft, bottomRight) -> no need to normalize, QRectF:contains() handles it correct
const QRectF boundingBoxImpact(itemViewUtm.boundingBoxBottomLeft,
itemViewUtm.boundingBoxTopRight);
const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemViewUtm.zone);
if (boundingBoxImpact.contains(participantPosUtmPoint))
{
DEBUG_LOG(m_logger) << "inBoundingBoxCheck Paricipant " << participantPosition.participantId()
<< " inside AesItemView:" << itemViewUtm.aesItemId;
return true;
}
DEBUG_LOG(m_logger) << "inBoundingBoxCheck Paricipant " << participantPosition.participantId()
<< " outside AesItemView:" << itemViewUtm.aesItemId;
return false;
} // END inBoundingBoxCheck
// [GUEZE-3526] SMART Munition ueberpruefen
// Nach der Umstellung auf 3D-WS wird der Einschlag von SMART aus dem DDS-Topic AES::Effects genommen.
void HandlerExplosions::calculateSmartEffect(const AreaEffectSimulation::Effects &effects)
{
DEBUG_LOG(m_logger) << "calculateSmartEffect";
AreaEffectSimulation::AesItem aesItem;
bool itemAvailable = m_ddsHandler->dtcAes()->aesItemFromId(effects.aesItemId(), aesItem);
if ( itemAvailable)
{
for (const AreaEffectSimulation::SingleEffect &se : qAsConst(effects.singleEffects()))
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " ---> singleEffect=" << se
<< ", aesItem= " << aesItem;
calculateSmartExplosion(effects.aesItemId(),
aesItem.chargeStruct(),
aesItem.partyId.is_set() ? aesItem.partyId.get() : 0,
Core::GeoPosition(se.position().lon(), se.position().lat(), 0),
effects.sampleTime());
} // for
} // if
else
{
WARNING_LOG(m_logger) << "No aesItem found for effect aesItemId=" << effects.aesItemId();
} // else
} // END calculateSmartEffect
void HandlerExplosions::calculateSmartExplosion(const int32_t aesItemId,
const AreaEffectSimulation::ChargeStruct &chargeStruct,
int32_t partyId,
const Core::GeoPosition2D &impactPosition,
qint64 impactTime)
{
DEBUG_LOG(m_logger) << "calculateSmartExplosion "
<< " partyId=" << partyId;
if (partyId == 0)
return;
int impactPosZone = -1;
const QPointF impactPos(CentralEffectSimulation::geographic2Utm(impactPosition, &impactPosZone));
QVector <ParticipantDistance> vehiclesInSmartExplosion;
DEBUG_LOG(m_logger) << "calculateSmartExplosion chargeTypeId=" << chargeStruct.chargeTypeId();
// Insert all Vehicles inside of explosion-Radius in list vehiclesInSmartExplosion
const double searchRadius = m_ddsHandler->searchRadius(chargeStruct); // where the smart munition should search for targets
const double impactRadius = m_ddsHandler->chargeTypeRadius(chargeStruct); // the real explosion area radius
DEBUG_LOG(m_logger) << "Handle ExplosionSingle (EsActivated)";
bool bHumanFound = false;
const auto &participantPositions = m_handlerParticipant->positions();
for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition : qAsConst(participantPositions))
{
if (m_handlerParticipant->isOutdated(participantPosition)) // Participant Position is not valid Todo Magic Number
continue;
const QPointF participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), impactPosZone);
ParticipantManagement::TypeClass typeClass = m_handlerParticipant->typeClassFromTypeId(participantPosition.participantTypeId());
DEBUG_LOG(m_logger) << "participant id=" << participantPosition.participantId()
<< " participantTypeId=" << participantPosition.participantTypeId()
<< " partyType =" << participantPosition.partyId()
<< " typeClass=" << typeClass;
// Only send, if Any Participant is in Smart-Range (save Tetra load)
if (!bHumanFound && typeClass == ParticipantManagement::TypeClass_def::Human)
bHumanFound = inCircle(impactPos, impactRadius, participantPosUtmPoint);
if (typeClass != ParticipantManagement::TypeClass_def::GroundVehicle &&
typeClass != ParticipantManagement::TypeClass_def::VehicleWithGear &&
typeClass != ParticipantManagement::TypeClass_def::WeaponSystem)
continue;
DEBUG_LOG(m_logger) << "participantPosition.timestamp =" << participantPosition.sampleTime()
<< " effectTime = " << m_ddsHandler->currentPartitionTime();
DEBUG_LOG(m_logger) << "Vehicle => calculate SMART participantId=" << participantPosition.participantId()
<< " ImpactPos=" << CentralEffectSimulation::debug(impactPos)
<< " searchRadius=" << searchRadius
<< " participantPosition=" << CentralEffectSimulation::debug(participantPosUtmPoint);
if (inCircle(impactPos, searchRadius, participantPosUtmPoint))
{
ParticipantDistance oneEntry;
oneEntry.squareDistance = CentralEffectSimulation::getSquareDistance(impactPos, participantPosUtmPoint);
oneEntry.participantPosition = participantPosition;
DEBUG_LOG(m_logger) << "squareDistance = " << oneEntry.squareDistance
<< " append participantId=" << participantPosition.participantId();
vehiclesInSmartExplosion.append(std::move(oneEntry));
}
}
CentralEffectSimulation::AesItemViewUtm itemViewUtm = m_aesItemViewsImpactsUtm.value(aesItemId);
itemViewUtm.chargeStruct = chargeStruct; // SingleExplosion: no Entry
itemViewUtm.aesItemId = aesItemId;
DEBUG_LOG(m_logger) << "aesItemViewUtm itemView.chargeTypeId =" << itemViewUtm.chargeStruct.chargeTypeId();
if (vehiclesInSmartExplosion.size() == 0)
{
DEBUG_LOG(m_logger) << "size=0";
if (bHumanFound)
{
CentralEffectSimulation::HandlerParticipant::CesParticipantData pos;
pos.pos(Core::GeoPosition(impactPosition.lon(), impactPosition.lat(), 0));
// send single fire to all if at least one participant is inside circle
// REGG-4160 no SMART-reachtion in GUEZ
// sendCesHitRequest(itemView, pos, impactTime);
}
}
else if (vehiclesInSmartExplosion.size() == 1)
{
DEBUG_LOG(m_logger) << "size=1";
// send two single fire to the same vehicle
sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, impactTime , 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent
sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, impactTime + 1000, 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent
sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation);
sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.first().participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation);
}
else
{
DEBUG_LOG(m_logger) << "size=" << vehiclesInSmartExplosion.size();
// send single fire to the two nearest vehicles
// sort the list by the distance
const auto mapSortLessThan = [](const ParticipantDistance & s1, const ParticipantDistance & s2) -> bool
{
return s1.squareDistance < s2.squareDistance;
};
std::sort(vehiclesInSmartExplosion.begin(), vehiclesInSmartExplosion.end(), mapSortLessThan);
sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.at(0).participantPosition, impactTime, 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent
sendCesHitRequest( itemViewUtm, vehiclesInSmartExplosion.at(1).participantPosition, impactTime, 100, ProjectDataManagement::EventType_def::HitEffectSimulation); // 100 Percent
sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.at(0).participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation);
sendParticipantEvent(itemViewUtm, vehiclesInSmartExplosion.at(1).participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation);
}
} // calculateSmartExplosion
// Check for all participants wheter he is in the explosion area
void HandlerExplosions::calculateImpacts(const AreaEffectSimulation::TriggeredImpacts &data)
{
DEBUG_LOG(m_logger) << "calculateImpacts m_aesItemViewsImpactsUtm.size " << m_aesItemViewsImpactsUtm.size();
// older then 10sec? ignore
if (data.sampleTime() < m_ddsHandler->currentPartitionTime() - 10 * 1000)
return;
if (!m_aesItemViewsImpactsUtm.contains(data.aesItemId()))
{
DEBUG_LOG(m_logger) << "calculateImpacts no aesItemId " << data.aesItemId();
return;
}
//CentralEffectSimulation::AesItemViewUtm aesItemViewUtm = m_aesItemViewsImpactsUtm.value(static_cast<int>(data.aesItemId()));
CentralEffectSimulation::AesItemViewUtm aesItemViewUtm;
if ((data.aesTypeId().underlying() == AreaEffectSimulation::AesType_def::AtExplosionClusterSingleExplosions)
|| (data.aesTypeId().underlying() == AreaEffectSimulation::AesType_def::AtExplosionAreaSingleExplosions))
aesItemViewUtm = m_aesItemViewsImpactsUtm.value(static_cast<int>(data.aesItemId()));
else
aesItemViewUtm = m_aesItemViewsUtm.value(static_cast<int>(data.aesItemId()));
DEBUG_LOG(m_logger) << "m_aesItemViewsUtm.insert aesItemId=" << aesItemViewUtm.aesItemId;
const bool bIsSmartChargeType = m_ddsHandler->isSmartChargeType(aesItemViewUtm.chargeStruct);
DEBUG_LOG(m_logger) << "calculateImpacts, isSMart: " << bIsSmartChargeType;
DEBUG_LOG(m_logger) << "XXXXXXXXXXXX calculateImpacts1 " << data.aesItemId();
QVector<int> bitsToCheck;
for (int bitFieldIdx = 0; bitFieldIdx < data.impactCount(); ++bitFieldIdx)
{
// not yet triggered (bit is still 1)
if (LtsUtils::BitArray::testBit(data.bitfield(), bitFieldIdx))
continue;
if (m_handledImpactsStorage.isIndexProcessed(data.aesItemId(), bitFieldIdx))
continue;
bitsToCheck += bitFieldIdx;
DEBUG_LOG(m_logger) << "bitsToCheck+=" << bitFieldIdx;
// update storage ('remember')
m_handledImpactsStorage.setIndexProcessed(data.aesItemId(), bitFieldIdx);
if (bIsSmartChargeType)
{
DEBUG_LOG(m_logger) << "XXXXXXXXXXXX SMART";
calculateSmartExplosion(aesItemViewUtm.aesItemId, aesItemViewUtm.chargeStruct, aesItemViewUtm.partyId, aesItemViewUtm.formGeo[bitFieldIdx], m_ddsHandler->currentPartitionTime());
}
}
if (bIsSmartChargeType)
return;
const auto &participantPositions = m_handlerParticipant->positions();
for (const CentralEffectSimulation::HandlerParticipant::CesParticipantData &pPos : qAsConst(participantPositions))
{
if (m_handlerParticipant->isOutdated(pPos)) // Participant Position is not valid Todo Magic Number
continue;
DEBUG_LOG(m_logger) << "pPos.participantId=" << pPos.participantId();
if (pPos.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill)
continue;
DEBUG_LOG(m_logger) << "pPos.vulnerabilityState=" << pPos.vulnerabilityState;
if (pPos.insideBuilding())
continue;
DEBUG_LOG(m_logger) << "pPos.insideBuilding=" << pPos.insideBuilding();
DEBUG_LOG(m_logger) << "calculateImpacts";
calculateImpacts(aesItemViewUtm, bitsToCheck, pPos);
}
} // END calculateImpacts
// calculate all impacts of the field for a participant
void HandlerExplosions::calculateImpacts(const CentralEffectSimulation::AesItemViewUtm &itemView,
const QVector<int> &impactsToCheck,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition)
{
DEBUG_LOG(m_logger) << "calculateImpacts";
// get radius of munition
const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct);
const QPointF &participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPosition.pos(), itemView.zone);
DEBUG_LOG(m_logger) << "XXXXXXXXXXXX calculateImpacts2a impactsToCheck.size " << impactsToCheck.size();
for (int bitFieldIdx : qAsConst(impactsToCheck))
{
if (itemView.form.size() <= bitFieldIdx)
continue;
// get position of impact to corresponding bit
const QPointF &impactPos = itemView.form.value(bitFieldIdx);
DEBUG_LOG(m_logger) << "XXXXXXXXXXXX calculateImpacts2b check impactPos: x= " << impactPos.x() << " y=" << impactPos.y();
if (inCircle(impactPos, impactRadius, participantPosUtmPoint))
sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, m_ddsHandler->currentPartitionTime());
} // foreach Impact
}
void HandlerExplosions::calculateImpactsEffect(const CentralEffectSimulation::AesItemViewUtm &itemView,
const QVector<LtsCoordTrans::LtsCoordinate> &triggeredEffectsLtsCoord,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition,
Options lineOfSightOptions)
{
const LtsCoordinate &partLtsCoordinate = participantPosition.ltsCoordinate(); // WITH zValue
const int participantId = participantPosition.participantId();
INFO_LOG(m_logger) << "calculateImpactsEffect" << " START"
<< ", participantId = " << participantId
<< ", participantPosition.ltsCoordinate = '" << participantPosition.ltsCoordinate().toString() << "'"
<< ", triggeredEffectsLtsCoord.size() = " << triggeredEffectsLtsCoord.size()
<< ", -> partLtsCoordinate " << partLtsCoordinate
<< ", lineOfSightOptions " << lineOfSightOptions
<< ", chargeStruct= " << itemView.chargeStruct;
if (triggeredEffectsLtsCoord.empty())
return;
// get radius of munition
const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(itemView.chargeStruct.chargeTypeId());
const bool bIsAirSupport = (gearClass.underlying() == ProjectDataManagement::GearClass_def::AirSupport);
const QVector<double> &rangesInMeter = chargeRanges(itemView.chargeStruct);
DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " chargeRanges:"
<< ", chargeTypeId = " << itemView.chargeStruct.chargeTypeId()
<< ", gearClass = " << gearClass
<< ", bIsAirSupport = " << bIsAirSupport
<< ", chargeTypeId = " << itemView.chargeStruct.chargeTypeId()
<< ", ranges.size = " << rangesInMeter.size();
Q_ASSERT(!rangesInMeter.isEmpty());
// Der z-Wert duerfte der Unterkante des Participants entsprechen.
// Wir heben pauschal um 1.0m an, um leichte Bodenunebenheiten zu kompensieren:
// Muss dies evtl. Typ-anhaengig geschehen?
LtsCoordinate shiftedParticipantLtsCoord = partLtsCoordinate; // WITH zValue
shiftedParticipantLtsCoord.rz() += 1.0; // shift 1.0m
DEBUG_LOG(m_logger) << "shiftedParticipantLtsCoord = " << shiftedParticipantLtsCoord.toString(true);
for (const LtsCoordinate &newTriggeredImpactLtsCoord : qAsConst(triggeredEffectsLtsCoord))
{
// const int zoneToUse = itemView.zone;
// 2D or 3D??
const double planarDistancePartToImpact = newTriggeredImpactLtsCoord.distanceUtm2D(partLtsCoordinate);
// is distance > max range?
if (planarDistancePartToImpact > rangesInMeter.back())
continue;
DEBUG_LOG(m_logger) << " planarDistancePartToImpact = " << planarDistancePartToImpact
<< ", bIsAirSupport = " << bIsAirSupport;
// Wir heben pauschal um 1.0m an, um leichte Bodenunebenheiten zu kompensieren:
LtsCoordinate shiftedNewTriggeredImpact = newTriggeredImpactLtsCoord; // WITH zValue
shiftedNewTriggeredImpact.rz() += 1.0; // shift 1.0m
//
// 1 range -> typical explosionCluster
// == impactRadius
// 3 ranges -> typical explosionCluster with chargeTypeId of GearClass_def::AirSupport
// == lethalRadius, nonLethalRadius, hazardDistance
//
// GUEZE-3136 Prüfauftrag - Gefährdungsabstand - Meldung
if (bIsAirSupport)
{
int effect = 3;
const RANGE mostInnerRange = getMostInnerRange(planarDistancePartToImpact, itemView.chargeStruct, effect);
DEBUG_LOG(m_logger) << "getMostInnerRange, participantId=" << participantId
<< ", planarDistancePartToImpact=" << planarDistancePartToImpact
<< " -> RANGE=" << toString(mostInnerRange);
switch (mostInnerRange)
{
case RANGE::HAZARD_DISTANCE:
{
effect = 3; // hazardDistance
DEBUG_LOG(m_logger) << "Sende CES Hit Request: participantId=" << participantId << ", RANGE=" << toString(mostInnerRange) << ", effect=" << effect;
sendCesHitRequest(itemView, participantPosition, QDateTime::currentMSecsSinceEpoch(),
effect, ProjectDataManagement::EventType_def::EnterHazardDistance);
}
break;
case RANGE::NON_LETHAL:
case RANGE::LETHAL:
{
// Wenn verdeckt, dann sendCesHitRequest mit EnterHazardDistance:
bool bLOS = true;
const bool success = lineOfSight_elevationAndBuilding(m_ddsHandler->getExerciseId(),
shiftedNewTriggeredImpact,
shiftedParticipantLtsCoord,
lineOfSightOptions,
bLOS,
m_proxyBuilding);
INFO_LOG(m_logger) << " lineOfSight_elevationAndBuilding "
<< ", success=" << success
<< ", shiftedNewTriggeredImpact = " << shiftedNewTriggeredImpact.toString(true);
// check LOS / LineOfSight
DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " check LOS / LineOfSight bLOS=" << bLOS
<< " participantId=" << participantId;
if (bLOS)
{
DEBUG_LOG(m_logger) << "Sende CES Hit Request: participantId=" << participantId << ", RANGE=" << toString(mostInnerRange) << ", effect=" << effect;
sendCesHitRequest(itemView, participantPosition, QDateTime::currentMSecsSinceEpoch(),
effect, ProjectDataManagement::EventType_def::AirSupport); // AirSupport for both: LETHAL_RADIUS, NONLETHAL_RADIUS
}
#ifdef GUEZE_3136_IMPLEMENTED
else
{
DEBUG_LOG(m_logger) << "Sende CES Hit Request: participantId=" << participantId << ", RANGE=" << toString(mostInnerRange) << " (Verdeckung -> EnterHazardDistance)";
sendCesHitRequest(itemView, participantPosition, QDateTime::currentMSecsSinceEpoch(),
effect, ProjectDataManagement::EventType_def::EnterHazardDistance);
}
#endif // GUEZE_3136_IMPLEMENTED
} // case
case RANGE::OUTSIDE:
// empty
;
} // switch
}
else // if (bIsAirSupport)
{
QElapsedTimer timer;
DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " vor lineOfSightOptions start";
if (m_logger.isInfoEnabled())
timer.start();
bool bLOS = true;
const bool success = lineOfSight_elevationAndBuilding(m_ddsHandler->getExerciseId(),
shiftedNewTriggeredImpact,
shiftedParticipantLtsCoord,
lineOfSightOptions,
bLOS,
m_proxyBuilding);
INFO_LOG(m_logger) << " lineOfSight_elevationAndBuilding took [ms] " << timer.elapsed()
<< ", success=" << success;
// check LOS / LineOfSight
DEBUG_LOG(m_logger) << "calculateImpactsEffect" << " check LOS / LineOfSight bLOS=" << bLOS
<< " participantId=" << participantId;
if (bLOS)
{
INFO_LOG(m_logger) << "calculateImpactsEffect 3 participantId = " << participantId
<< " -> bInCircle and given LOS -> sendEffectTypeEvent ...";
sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, m_ddsHandler->currentPartitionTime());
}
} // if (bIsAirSupport)
} // for newTriggeredImpactLtsCoord
}
void HandlerExplosions::calculateImpactsEffect_outdated(const CentralEffectSimulation::AesItemViewUtm &itemView,
const std::vector<Core::GeoPosition> &triggeredEffectsGeoPos,
const QVector<QPointF> &triggeredEffectsUtmPos,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition,
Options lineOfSightOptions)
{
const LtsCoordTrans::LtsCoordinate &partLtsCoordinate = participantPosition.ltsCoordinate(); // WITH zValue
INFO_LOG(m_logger) << LOGFUNCTION << " START"
<< " participantId = " << participantPosition.participantId()
<< " participantPosition.ltsCoordinate = '" << participantPosition.ltsCoordinate().toString() << "'"
<< ", triggeredEffectsGeoPos.size() = " << triggeredEffectsGeoPos.size()
<< ", triggeredEffectsUtmPos.size() = " << triggeredEffectsUtmPos.size()
<< ", participantPosition = " << participantPosition
<< ", -> partLtsCoordinate " << partLtsCoordinate
<< ", lineOfSightOptions " << lineOfSightOptions;
INFO_LOG(m_logger) << LOGFUNCTION << participantPosition;
if (triggeredEffectsGeoPos.empty())
return;
if (static_cast<int>(triggeredEffectsGeoPos.size()) != triggeredEffectsUtmPos.size())
return;
DEBUG_LOG(m_logger) << "calculateImpactsEffect, triggeredEffectsUtmPos.size: " << triggeredEffectsGeoPos.size()
<< " participantPosition = " << participantPosition;
// get radius of munition
const double impactRadius = m_ddsHandler->chargeTypeRadius(itemView.chargeStruct);
// Der z-Wert duerfte der Unterkante des Participants entsprechen.
// Wir heben pauschal um 0.5m an.
// Muss dies evtl. Typ-anhaengig geschehen?
Core::GeoPosition participantPositionGeoPos = partLtsCoordinate.geoPosition(); // WITH zValue
participantPositionGeoPos.z() += 0.5; // shift 0.5m
const QPointF &participantPosUtmPoint = CentralEffectSimulation::geographic2Utm(participantPositionGeoPos, itemView.zone);
DEBUG_LOG(m_logger) << " participantPositionGeoPos=" << participantPositionGeoPos.lon() << "/" << participantPositionGeoPos.lat()
<< " itemView.zone=" << itemView.zone
<< " participantPosUtmPoint=" << participantPosUtmPoint.x() << "/" << participantPosUtmPoint.y();
DEBUG_LOG(m_logger) << "calculateImpactsEffect 1 triggeredEffectsUtmPos.size " << triggeredEffectsUtmPos.size();
int idx = -1;
for (const QPointF &newTriggeredImpactUtm : qAsConst(triggeredEffectsUtmPos))
{
++idx;
const bool bInCircle = inCircle(newTriggeredImpactUtm, impactRadius, participantPosUtmPoint);
DEBUG_LOG(m_logger) << "calculateImpactsEffect 2a check impactPos: x= " << newTriggeredImpactUtm.x() << " y=" << newTriggeredImpactUtm.y()
<< " participantPosUtmPoint=" << participantPosUtmPoint.x() << "/" << participantPosUtmPoint.y()
<< " impactRadius=" << impactRadius
<< ", inCircle = " << bInCircle
<< " participantId=" << participantPosition.participantId();
if (!bInCircle)
{
DEBUG_LOG(m_logger) << "Not in Circle => continue";
continue;
}
const Core::GeoPosition &newTriggeredImpactGeo = triggeredEffectsGeoPos[idx];
const int exerciseId = m_ddsHandler->getExerciseId();
QElapsedTimer timer;
DEBUG_LOG(m_logger) << LOGFUNCTION << " vor lineOfSightOptions start";
if (m_logger.isInfoEnabled())
timer.start();
bool bLOS = true;
const bool success = lineOfSight_elevationAndBuilding(exerciseId,
LtsCoordTrans::LtsCoordinate(newTriggeredImpactGeo),
LtsCoordTrans::LtsCoordinate(participantPositionGeoPos),
lineOfSightOptions,
bLOS,
m_proxyBuilding);
INFO_LOG(m_logger) << " lineOfSight_elevationAndBuilding took [ms] " << timer.elapsed()
<< " success=" << success;
// check LOS / LineOfSight
DEBUG_LOG(m_logger) << LOGFUNCTION << " check LOS / LineOfSight bLOS=" << bLOS
<< " participantId=" << participantPosition.participantId();
if (bLOS)
{
INFO_LOG(m_logger) << "calculateImpactsEffect 3 participantId = " << participantPosition.participantId()
<< " -> bInCircle and given LOS -> sendEffectTypeEvent ...";
sendEffectTypeEvent(itemView, participantPosition, EffectType::ExplosionArea, m_ddsHandler->currentPartitionTime());
}
} // foreach newTriggeredImpactUtm
}
// Anmerkung: Im SFM werden Quadrate und keine Kreise genommen. ( Wird hier nach Absprache mit Manfred nicht gemacht)
bool HandlerExplosions::inCircle(const QPointF &centerUtm, const double radius, const QPointF &pointUtm)
{
double squareDist = getSquareDistance(centerUtm, pointUtm);
double squareRadius = radius * radius;
DEBUG_LOG(m_logger) << "squareDist: " << squareDist << ", squareRadius: " << squareRadius
<< " radius=" << radius
<< " inCircle=" << (squareDist < squareRadius)
<< " centerUtm=" << centerUtm.x() << "/" << centerUtm.y()
<< " pointUtm=" << pointUtm.x() << "/" << pointUtm.y();
return (squareDist < squareRadius);
}
QVector<double> HandlerExplosions::chargeRanges(const AreaEffectSimulation::ChargeStruct &chargeStruct) const
{
const ProjectDataManagement::GearClass &gearClass = m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(chargeStruct.chargeTypeId());
DEBUG_LOG(m_logger) << "chargeTypeId = " << chargeStruct.chargeTypeId() << ", gearClass = " << gearClass;
if (gearClass.underlying() == ProjectDataManagement::GearClass_def::AirSupport)
{
const double lethalRadius = m_ddsHandler->getLethalRadius(chargeStruct);
const double nonLethalRadius = m_ddsHandler->getNonLethalRadius(chargeStruct);
const double hazardDistance = m_ddsHandler->getHazardDistance(chargeStruct);
return {lethalRadius, nonLethalRadius, hazardDistance};
}
const double impactRadius = m_ddsHandler->chargeTypeRadius(chargeStruct);
return {impactRadius};
}
int HandlerExplosions::iedClassToEffect(int iedClass) const
{
switch (iedClass)
{
case 1:
return 48;
case 2:
return 49;
case 3:
return 68;
case 4:
return 69;
case 5:
return 70;
case 255:
return 255;
default:
return 0;
}
}
RANGE HandlerExplosions::getMostInnerRange(const QVector<double> &ranges, double currentDistance)
{
static_assert((int)RANGE::LETHAL == 1, "RANGE mismatch");
static_assert((int)RANGE::NON_LETHAL == 2, "RANGE mismatch");
static_assert((int)RANGE::HAZARD_DISTANCE == 3, "RANGE mismatch");
static_assert((int)RANGE::OUTSIDE == 4, "RANGE mismatch");
int cnt = 4; // OUTSIDE
QVector<double>::const_reverse_iterator rit = ranges.crbegin();
while (rit != ranges.crend())
{
double currentGearRange = *rit;
if (currentGearRange == 0)
{
--cnt;
++rit;
}
if (currentDistance < currentGearRange )
--cnt;
else
break;
++rit;
}
return static_cast<RANGE>(cnt);
}
RANGE HandlerExplosions::getMostInnerRange(double currentDistance, const AreaEffectSimulation::ChargeStruct &chargeStruct, int &retEffect)
{
int hazard_Distance = m_ddsHandler->dtcPdm()->hazardDistance(chargeStruct.chargeTypeId(), chargeStruct.igniterTypeId(), chargeStruct.igniterFunctionId());
if (currentDistance > hazard_Distance)
return RANGE::OUTSIDE;
int lastRange = 9999;
ProjectDataManagement::SeverityType lastST = ProjectDataManagement::SeverityType::noEffect;
for (const auto &casEffect : m_ddsHandler->getCasEffects(chargeStruct))
{
if (currentDistance < casEffect.impactRadius() && casEffect.impactRadius() < lastRange)
{
lastRange = casEffect.impactRadius();
lastST = casEffect.severityType();
retEffect = iedClassToEffect(casEffect.iedClass());
}
}
switch (lastST.underlying())
{
case ProjectDataManagement::SeverityType::noEffect:
return RANGE::HAZARD_DISTANCE;
case ProjectDataManagement::SeverityType::lethal:
return RANGE::LETHAL;
case ProjectDataManagement::SeverityType::light:
case ProjectDataManagement::SeverityType::medium:
case ProjectDataManagement::SeverityType::heavy:
return RANGE::NON_LETHAL;
default:
break;
}
return RANGE::OUTSIDE;
}
void HandlerExplosions::sendEffectTypeEvent(const CentralEffectSimulation::AesItemViewUtm &itemView,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition,
EffectType effectType,
qint64 effectTime)
{
Q_UNUSED(effectTime);
// Trefferereignis Sekundaerwirkung
const int ctasEffectType = static_cast<int>(effectType) & 0xffff;
const int participantId = participantPosition.participantId();
DEBUG_LOG(m_logger) << "Sending EffectType event " << ctasEffectType
<< ", participant: " << participantId
<< ", aesItemId: " << itemView.aesItemId
<< ", ctasEffectType: " << ctasEffectType;
sendCesHitRequest(itemView,
participantPosition,
//effectTime >= 0 ? effectTime : participantPosition.sampleTime(),
QDateTime::currentMSecsSinceEpoch(),
ctasEffectType,
ProjectDataManagement::EventType_def::HitEffectSimulation);
}
// Send via TAB-Proxy to battlefield
void HandlerExplosions::sendCesHitRequest(const CentralEffectSimulation::AesItemViewUtm &itemView,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition,
const qint64 effectTime,
const int remaining,
const ProjectDataManagement::EventType eventType)
{
int32_t legacyChargeTypeId = 0;
legacyChargeTypeId = m_ddsHandler->getLegacyIdFromChargeTypeId(itemView.chargeStruct);
// [E01911-49] This is just a band-aid.
if (m_ddsHandler->isSmartChargeType(itemView.chargeStruct))
legacyChargeTypeId = 0;
if (remaining < 0)
return;
DEBUG_LOG(m_logger) << "sendCesHitRequest"
<< " participantId=" << participantPosition.participantId()
<< " remaining=" << remaining
<< " eventType=" << eventType
<< " chargeTypeId=" << itemView.chargeStruct.chargeTypeId();
// Bei Nebel soll die Weste nur einmal piepen und nicht regelmaessig. Das wird mit Wirkung = 3 uebertragen
int effect = remaining;
if ((m_ddsHandler->isSmoke(itemView.chargeStruct)) && (remaining == 0))
effect = 3;
const int equipNo = m_ddsHandler->participantId2EquipNo(participantPosition.participantId());
DEBUG_LOG(m_logger) << " ftc sendCESHit to Participant participantId = " << participantPosition.participantId()
<< " equipNo = " << equipNo
<< " aesItemId =" << itemView.aesItemId;
// participantPosition.participantPosition == 0 -> send to all
if ((participantPosition.participantId() != 0) && (equipNo <= 0))
{
DEBUG_LOG(m_logger) << "participantPosition.participantId != 0 && equipNo <= 0" ;
return;
}
if (participantPosition.participantId() != 0 &&
participantPosition.vulnerabilityState == ProjectDataManagement::VulnerabilityState_def::CatastrophicKill)
{
DEBUG_LOG(m_logger) << "participantPosition.participantId != 0 CatastrophicKill)";
return;
}
#ifdef ORIGINATOR_AT_SENDCESHITREQUEST
const int32_t originatorParticipantId = m_aesItemId2partIdMap.value(itemView.aesItemId, 0);
const int originatorEquipNo = m_ddsHandler->participantId2EquipNo(originatorParticipantId);
#else
const int originatorEquipNo = 0;
#endif // ORIGINATOR_AT_SENDCESHITREQUEST
const TrainingAreaBase::TabPosition pos(participantPosition.pos(),
Core::PositionReportingSource_def::Gps, // irrelevant
participantPosition.buildingId(),
participantPosition.roomId());
// mineFieldBitIndex wird als reserve1 uegertragen und hier wird die Wirkdauer uebertragen.
const int duration = m_ddsHandler->dtcAes().get()->explosionCluster(itemView.aesItemId).effectDuration() / 1000;
// mineFieldBitIndex wird als reserve2 uebertragen und hier wird die Wirkbeginn uebertragen.
int effectTime2 = 0;
if (eventType == ProjectDataManagement::EventType::EnterHazardDistance)
{
const auto &ec = m_ddsHandler->dtcAes().get()->explosionCluster(itemView.aesItemId);
if (ec.effectStart.is_set())
effectTime2 = ec.effectStart.get() / 1000; // msec to sec
}
TrainingAreaBase::SendCesHitRequest requestTab(
equipNo, // equipmentNumber (injured/damaged)
participantPosition.participantId(), // participantId
legacyChargeTypeId > 0 ? legacyChargeTypeId : itemView.chargeStruct.chargeTypeId(), // chargeTypeId
itemView.aesItemId, // aesId
originatorEquipNo, // originatorEquipmentNumber
effect, // Wirkung
pos,
effectTime2 > 0 ? effectTime2 : effectTime, // effectTime
eventType,
itemView.partyId, // originPartyId
duration
);
DEBUG_LOG(m_logger) << " participantId=" << participantPosition.participantId()
<< ", chargeTypeId=" << itemView.chargeStruct.chargeTypeId()
<< ", legacyChargeTypeId=" << legacyChargeTypeId
<< ", m_sendCesHitRequestQueue.size()=" << m_sendCesHitRequestQueue.size()
<< ", duration=" << duration;
DEBUG_LOG(m_logger) << "ftcTab->SendCesHitRequest: " << requestTab;
m_ddsHandler->ftcTab()->sendCesHitRequest(requestTab, this);
if (!m_ddsHandler->isSmartChargeType(itemView.chargeStruct))
sendParticipantEvent(m_aesItemViewsUtm.value(itemView.aesItemId), participantPosition, ProjectDataManagement::EventType_def::HitEffectSimulation);
}
double HandlerExplosions::getSquareDistance(const QPointF &centerUtm, const QPointF &pointUtm)
{
QPointF p(pointUtm - centerUtm);
return p.x() * p.x() + p.y() * p.y();
}
void HandlerExplosions::on_newHazardInfo(int participantId, int aesId)
{
DEBUG_LOG(m_logger) << "HandlerFunctionalAreas::on_HazardFinished participantId=" << participantId
<< " aesId =" << aesId
<< " TODO: Nach Start des Wirkfeldes müssen Teilnehmer alle 30 Sekunden bis Ablauf des Wirkfelds darüber informiert werden, dass sie sich im Gefährdungsabstand befinden";
}
void HandlerExplosions::sendEnterHazardDistance(const int participantId, const int aesItemId)
{
DEBUG_LOG(m_logger) << "sendEnterHazardDistance: participantId=" << participantId
<< " aesItemId=" << aesItemId;
DEBUG_LOG(m_logger) << "sendCesHitRequest";
const int ctasEffectType = 0;
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &cesParticipantData = m_handlerParticipant->position(participantId);
m_enteredHazardDistanceStore.participantEnteringArea(participantId, aesItemId);
ProjectDataManagement::EventType eventType = ProjectDataManagement::EventType_def::EnterHazardDistance;
sendCesHitRequest(m_aesItemViewsUtm.value(aesItemId),
cesParticipantData,
QDateTime::currentMSecsSinceEpoch(),
ctasEffectType,
eventType);
sendParticipantEvent(m_aesItemViewsUtm.value(aesItemId), cesParticipantData, ProjectDataManagement::EventType_def::EnterHazardDistance);
}
void HandlerExplosions::sendLeaveHazardDistance(const int participantId, const int aesItemId, const int remaining)
{
DEBUG_LOG(m_logger) << "sendLeaveHazardDistance: participantId=" << participantId
<< " aesItemId=" << aesItemId;
DEBUG_LOG(m_logger) << "sendCesHitRequest"
<< " aesItemId=" << aesItemId;
const int ctasEffectType = remaining;
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &cesParticipantData = m_handlerParticipant->position(participantId);
const CentralEffectSimulation::AesItemViewUtm itemView = m_aesItemViewsUtm.value(aesItemId);
m_enteredHazardDistanceStore.participantLeavingArea(participantId, aesItemId);
sendCesHitRequest(itemView,
cesParticipantData,
QDateTime::currentMSecsSinceEpoch(),
ctasEffectType,
ProjectDataManagement::EventType_def::LeaveHazardDistance);
sendParticipantEvent(itemView, cesParticipantData, ProjectDataManagement::EventType_def::LeaveHazardDistance);
}
void HandlerExplosions::sendParticipantEvent(const CentralEffectSimulation::AesItemViewUtm &item,
const CentralEffectSimulation::HandlerParticipant::CesParticipantData &participantPosition,
const ProjectDataManagement::EventType eventType)
{
DEBUG_LOG(m_logger) << "sendParticipantEvent participantId=" << participantPosition.participantId();
ParticipantManagement::TypeClass typeClass = m_handlerParticipant->typeClassFromTypeId(participantPosition.participantTypeId());
if (typeClass == ParticipantManagement::TypeClass_def::Unit ||
typeClass == ParticipantManagement::TypeClass_def::SupportUnit ||
typeClass == ParticipantManagement::TypeClass_def::SupportEntity ||
typeClass == ParticipantManagement::TypeClass_def::Unknown)
return;
DEBUG_LOG(m_logger) << " sendParticipantEvent participantId=" << participantPosition.participantId();
int equipmentNumber = m_ddsHandler->participantId2EquipNo(participantPosition.participantId());
if (equipmentNumber <= 0)
return;
DEBUG_LOG(m_logger) << " sendParticipantEvent participantId=" << participantPosition.participantId();
//const AreaEffectSimulation::OffRouteMine &of = m_ddsHandler->dtcAes()->offRouteMine(offRouteItem.aesItemId);
const AreaEffectSimulation::ExplosionCluster &explosionCluster = m_ddsHandler->dtcAes()->explosionCluster(item.aesItemId);
CentralEffectSimulation::ParticipantEvent pe;
DEBUG_LOG(m_logger) << " sendParticipantEvent participantId=" << participantPosition.participantId()
<< " lat= " << participantPosition.pos().lat()
<< " lon= " << participantPosition.pos().lon()
<< " equipmentNumber=" << equipmentNumber
<< " eventType=" << eventType
<< " Name=" << explosionCluster.name()
<< " position=" << participantPosition.pos().lat() << "/" << participantPosition.pos().lon();
pe.id(m_ddsHandler->getParticipantEventId());
pe.eventTypeId(eventType);
pe.participantId(participantPosition.participantId());
pe.eventTime(participantPosition.sampleTime());
pe.aesItemId(item.aesItemId);
pe.partyId(participantPosition.partyId());
pe.aesName(explosionCluster.name());
pe.equipmentNumber(equipmentNumber);
pe.friendlyFire(item.partyId == participantPosition.partyId());
pe.position(participantPosition.pos());
m_ddsHandler->dtsCes()->publish(pe);
qint64 id = pe.id();
QTimer::singleShot(1 * 60 * 1000, this, [id, this]() { m_ddsHandler->dtsCes()->removeParticipantEventByKey(id); });
}
//
// class HandledImpactsStorage::Private
//
class EnteredHarzardDistanceStorage::Private
{
public:
Private() = default;
~Private() = default;
// data:
QHash<int, QSet<int>> m_participantId2aesItemId; // Key = participantId ; entered areas
};
//
// class HandledImpactsStorage
//
EnteredHarzardDistanceStorage::EnteredHarzardDistanceStorage()
: d(new Private)
{}
EnteredHarzardDistanceStorage::~EnteredHarzardDistanceStorage()
{
delete d;
}
void EnteredHarzardDistanceStorage::participantEnteringArea(int participantId, int areaId)
{
// Key = participantId ; entered areas
d->m_participantId2aesItemId[participantId].insert(areaId); // QSet: no duplicate
}
void EnteredHarzardDistanceStorage::participantLeavingArea(int participantId, int areaId)
{
// Key = participantId ; entered areas
const auto &areaIt = d->m_participantId2aesItemId.find(participantId);
if (areaIt != d->m_participantId2aesItemId.end())
areaIt->remove(areaId);
}
bool EnteredHarzardDistanceStorage::hasParticipantAlreadyEnteredArea(int participantId, int areaId) const
{
// Key = participantId ; entered areas
const auto &areaIt = d->m_participantId2aesItemId.find(participantId);
if (areaIt != d->m_participantId2aesItemId.end())
return areaIt->contains(areaId);
return false;
}
void EnteredHarzardDistanceStorage::removeParticipant(int participantId)
{
// Key = participantId ; entered areas
d->m_participantId2aesItemId.remove(participantId); // QSet:
}
void EnteredHarzardDistanceStorage::removeArea(int aesItemId)
{
// Key = participantId ; entered areas
QMutableHashIterator<int, QSet<int>> it(d->m_participantId2aesItemId);
while (it.hasNext())
{
it.next();
QSet<int> &aesItemsRef = it.value();
aesItemsRef.remove(aesItemId); // is based on indxOf ...
} // while
}
QVector<int> EnteredHarzardDistanceStorage::allParticipantIdsOfArea(int areaId) const
{
// Key = participantId ; entered areas
QVector<int> allIds;
allIds.reserve(d->m_participantId2aesItemId.size());
QHashIterator<int, QSet<int>> it(d->m_participantId2aesItemId);
while (it.hasNext())
{
it.next();
const QSet<int> &aesItemsRef = it.value();
if (aesItemsRef.contains(areaId))
allIds.push_back(it.key());
} // while
return allIds;
}
QString toString(RANGE range)
{
switch (range)
{
case RANGE::LETHAL: return QStringLiteral("LETHAL");
case RANGE::NON_LETHAL: return QStringLiteral("NON_LETHAL");
case RANGE::HAZARD_DISTANCE: return QStringLiteral("HAZARD_DISTANCE");
case RANGE::OUTSIDE: return QStringLiteral("OUTSIDE");
}
return QStringLiteral("RANGE_Error");
}
} // namespace CesExplosions