Files
qt-migration-legatus/qt6-after/qt6-migrated-legatus/Lts/Services/AreaEffectSimulation/object_explosion_cluster.cpp
T
torsten d17d397fe7 LtsUtilsCore: Alle Include-Pfade korrigiert (LtsUtils/ → LtsUtilsCore/)
- CMakeLists.txt: Include-Pfade von ../ zu . geändert, json.h hinzugefügt
- ~95 Dateien: Include-Pfade systematisch korrigiert
  - LtsUtils/ → LtsUtilsCore/ für alle Core-Header
  - LtsUtilsGui/LtsUtils/LtsUtilsGui/ → LtsUtilsGui/LtsUtilsGuiDds/
- Logger-API: getLogger() mit const char* Parameter
- DataModification: enum class global (nicht im Lts-Namespace)
- Participant-Struct: Member mit m_-Präfix
- Export-Macro: SVCALMCOMMON_EXPORT statt ALMCOMMON_EXPORT
- Signal/Slot-Signaturen korrigiert für Qt6 template matching

Build validiert: LtsUtilsCore, LtsUtilsDds, LtsSessionData erfolgreich
2026-08-21 01:49:35 +02:00

3029 lines
133 KiB
C++
Executable File

/**
* @RDE_HEADER@
* ObjectMap for ExplosionCluster
* $Id$
* $HeadURL$
* $Author$
*/
#include "object_explosion_cluster.h"
#include <utility>
#include "Utils/polygons.h"
#include "handler_charge_administration.h"
#include "handler_trajectory.h"
#include "special_area_provider.h"
#include <LtsCoordTrans/lts_coord_trans.h>
#include <LtsUtils/dds_bitarray.h>
#include <Trajectory/trajectory_generator.h>
#include <Trajectory/trajectory_generator_vulcano.h>
#include <Trajectory/trajectory_helper.h>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QPolygonF>
#include <QTimer>
#include <QVector3D>
/*
* Typen von ExplosionCluster: (Stand 24.05.2024)
*
Typ | participantId Flugkurve(n) Target Streuung
--------------------------------------------------------------------------------
Normal | ja ja ja ja
Vulcano | ja ja ja ja
--------------------------------------------------------------------------------
HLA | nein (-1) nein ja nein == light
--------------------------------------------------------------------------------
NASAT | ja reduziert ja ?ja? == light+
?? Vulcano?? | ? ? ? ?
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
OC |
--------------------------------------------------------------------------------
*/
/*
* possible Annotations: 2024-02-23
*
Incomplete UserInput:
reason = QString(QStringLiteral("firingParticipantId is invalid (%1)")).arg(firingParticipantId);
reason = QString(QStringLiteral("firingParticipantId not found (%1)")).arg(firingParticipantId);
reason = QString(QStringLiteral("firingParticipantId '%1' has invalid position (NaN)")).arg(firingParticipantId);
reason = QString(QStringLiteral("firingParticipantId '%1' has invalid position (0,0)")).arg(firingParticipantId);
*reasonOut = QStringLiteral("originatorOrigin isNaN");
*reasonOut = QStringLiteral("originatorOrigin isNull");
Logical error:
reason = QString(QStringLiteral("Not enough time for estimated flight duration (timeUntilFireMSec=%1).")).arg(timeUntilFireMSec);
Internal Error:
*reasonOut = QStringLiteral("no MaxFlightDuration");
reason = QString(QStringLiteral("GenerateGunToTargetLine (%1)")).arg(reasonGtlOut);
reason = QString(QStringLiteral("(flightDurationSecError (%1;%2))")).arg(tdRef.flightDurationMSec).arg(reasonTraj);
reason = QString(QStringLiteral("(peakHeightAbs outOfRange (%1))")).arg(reasonTraj);
reason += QString(QStringLiteral("(elevation_angle_degree outOfRange (%1))")).arg(elevation_angle_degree);
reason += QStringLiteral("(generateTrajectory failed)");
Trajectory:
*reasonOut = QString(QStringLiteral("HeightElevationError(%1 %2 %3)")).arg(posStart.z()).arg(heightAbs).arg(posEnd.z());
*reasonOut = QString(QStringLiteral("StartEndDistanceError(%1)")).arg(r);
*reasonOut = QString(QStringLiteral("flightDurationSecErrorUnknown(%1)")).arg(result);
*reasonOut = QString(QStringLiteral("45DegreeShot(%1)")).arg(arcSin);
*
*/
// See E01562-191 Anpassung Trajektorien (Von Explosion Cluster zu Effect Cluster)
#define USE_TRAJECTORY_VIA_HANDLER
#define DUMP() dump(__FILE__, __LINE__)
// ToDo: mark the end of aes enum and update following (check Endemarke)
static_assert((int)AreaEffectSimulation::AesType_def::AtExplosionClusterGunToTargetLine < 100, "id may overlap at runtime");
// Aufteilung der ids:
// ExplosionCluster ddsData(m_data.id,
// aiv.id (1000 * m_data.id + AesType::AtExplosionCluster);
// aiv2.id (1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions);
// aiv3.id (1000 * m_data.id + AesType::AtExplosionClusterGunToTargetLine);
// trajectoryTimeAwareRef.id(1000 * m_data.id + 100 + explosionIndex);
// ^^^ um Platz zu lassen fuer AesType::At...
// trajectoryTimeAwareRef.id(1000 * 1 + 100 + 0); == 1100
// trajectoryTimeAwareRef.id(1000 * 1 + 100 + 899); == 1999
// trajectoryTimeAwareRef.id(1000 * 2 + 100 + 0); == 1200
// Note: const int explosionIndex = explosionNumber-1;
// Effects (Impacts)
// m_effects.id (1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions)
/* Ablauf
*
createObject
- setupFiringOrigin
- checkPreConditionsComplete
- firingOriginIsValid
- initialCalculations
- calculate the subAreas / mtps
- estimate max FlightDuration:
- checkPreConditions (configuring preStartTimer)
*/
using DetonationLocationRequest = CentralEffectSimulation::DetonationLocationRequest;
using DetonationLocationReply = CentralEffectSimulation::DetonationLocationReply;
//
// AreaEffectSimulation
//
namespace AreaEffectSimulation
{
Core::FunctionReplyObject createAnnotationObject(int id, const QString &reason)
{
//: "Anmerkung:"
return Core::FunctionReplyObject(id, tr_lts("dds", "Annotation: '%1' (id=%2).")).arg(reason).arg(id);
}
Core::FunctionReplyObject createAnnotationObjectError(int id, const QString &reason)
{
//: "Fehler:"
return Core::FunctionReplyObject(id, tr_lts("dds", "Error: '%1' (id=%2).")).arg(reason).arg(id);
}
Core::FunctionReplyObject createAnnotationObjectIncomplete(int id, const QString &reason)
{
//: "Unvollstaendig:"
return Core::FunctionReplyObject(id, tr_lts("dds", "Incomplete: '%1' (id=%2).")).arg(reason).arg(id);
}
ObjectExplosionCluster::ObjectExplosionCluster(MainHandler *mainHandler, QObject *parent)
: ObjectBaseTmpl(mainHandler, QStringLiteral("ExplosionCluster"), parent)
, m_singleExplosionTriggerTimer(new QTimer(this))
, m_preStartTimer(new QTimer(this))
, m_lastSentTriggeredExplosionNumber(-1)
, m_chargeCountUntilLastStart(0)
, m_explosionIndex(0)
{
// connect(m_singleExplosionTriggerTimer, &QTimer::timeout,
// this, &ObjectExplosionCluster::checkNextSingleExplosion);
connect(m_preStartTimer, &QTimer::timeout, this, &ObjectExplosionCluster::checkNextFire);
m_bSupportsPausing = true;
}
ObjectExplosionCluster::~ObjectExplosionCluster() { removeTopics(); }
bool ObjectExplosionCluster::isOnlyObjectNameModifiable() const
{
const bool bBaseIsOnlyNameModifiable = ObjectBase::isOnlyObjectNameModifiable();
const qint64 nowMsec = partitionTimeMSec();
const qint64 activationMsec = activationTime().toMSecsSinceEpoch();
qint64 preStartMsec = activationMsec - m_temporaryData.estimatedMaxFlightDurationMSec; // estimatedMaxFligh contains a buffer time (10% + 2sec)
const bool bReached = nowMsec >= preStartMsec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", bBaseIsOnlyNameModifiable=" << bBaseIsOnlyNameModifiable
<< ", m_estimatedMaxFlightDurationMSec=" << m_temporaryData.estimatedMaxFlightDurationMSec << ", nowMsec=" << nowMsec
<< ", activationMsec=" << activationMsec << ", prePreStartMsec=" << preStartMsec << ", leftMsec=" << (preStartMsec - nowMsec)
<< ", bReached=" << bReached << ", m_temporaryData.isObjectModifiable=" << m_temporaryData.isObjectModifiable;
return bBaseIsOnlyNameModifiable || !m_temporaryData.isObjectModifiable;
}
bool ObjectExplosionCluster::isObjectModifiable() const
{
return ObjectBase::isObjectModifiable(); // ToDo: check: && m_temporaryData.isObjectModifiable
}
bool ObjectExplosionCluster::isFiringTimeReached() const
{
return isActivationTimeSet() && partitionTime().toMSecsSinceEpoch() >= activationTime().toMSecsSinceEpoch() - flightDurationMsec();
}
Core::FunctionReplyObject ObjectExplosionCluster::createObject(const Database::AesExplosionCluster &data)
{
Core::FunctionReplyObject ret;
// some plausibility checks
ret = checkObjectData(data);
if (ret.error())
return ret;
if (data.impactCount > AreaEffectSimulation::s_maxTrajectoriesPerExplosionCluster)
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error with explosion cluster with id %1, impactCount exceeds limit.")).arg(m_data.id);
const bool bFromOpwUser = data.id == 0;
// No need to create object, if there is not enough charge left.
// HLA Interface is using externalId...
// external (externalId>0) -> no claim required
// external (externalId==0) -> claim required
// NASAT: AirSupport -> no claim required
const bool bDoClaimPlannedCharge
= (data.id == 0) && (data.externalId <= 0) && (m_mainHandler->gearClassOfChargeTypeId(data.chargeTypeId) != ProjectDataManagement::GearClass_def::AirSupport);
if (bDoClaimPlannedCharge) // new, not read from db
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, data.impactCount);
if (ret.error())
return ret;
// GUEZE-2675
// ExplosionClusters created via HLA have to be always central
// so every ExplosionCluster with an external id set (> 0), is handled central
ret = ObjectBaseTmpl::createObject(data, false, data.externalId > 0);
if (ret.error())
{
if (bDoClaimPlannedCharge)
m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, (-1) * data.impactCount);
return ret;
}
// If an explosionCluster is EsPrepared (EffectStart set), maybe the calculations
// to generate trajectories etc are already done.
// And the corresponding topics are most likely already published.
// SvcAes could be restarted _after_ calculations and before effectStart (maybe also during effectDuration)
// The initialCaculation fails due to not enough time for the ammo to fly.
// In that case, the ExplosionCluster is marked invalid.
// But if the user re-enables the ExplosionCluster impacts or trajectories
// from former calculations are _accidentally_ considered and displayed.
// To avoid that situation:
removeTopics();
// keep in mind: if no participantId is provided: no gunToTargetLine will be calculated
if (isMhpRotationProvided())
m_temporaryData.calculatedMhpRotationNorthDeg = m_data.mhpRotationRequest;
// setupFiringOrigin
if (bFromOpwUser && isOriginatorParticipantIdProvided())
{
Core::FunctionReplyObject retSFO = setupFiringOrigin();
m_temporaryData.annotationString = retSFO.translated().toStdString();
if (!isExplosionClusterMarkedValid())
m_data.effectStart = QDateTime();
}
// checkPreConditions :
// if not valid, annotationString and effectStart will be modified AFTER ObjectBaseTmpl::createObject
// But the publishData will be executed via m_publishDataTimer and therefore
// after our checkPreConditions-update :-)
{
Core::FunctionReplyObject retCPC = checkPreConditionsComplete();
m_temporaryData.annotationString = retCPC.translated().toStdString();
if (!isExplosionClusterMarkedValid())
m_data.effectStart = QDateTime();
}
// : checkPreConditions
if (data.id <= 0) // new, not read from db
{
m_data.originalEffectDuration = data.effectDuration;
m_data.originalEffectStart = data.effectStart;
}
// Do not continue running areas on restart of svcAes:
// else
// checkNextSingleExplosion();
DUMP();
return ret;
}
// [GUEZE-2952] Schutzwirkung der Gebaeude spaeter berechnen
//
// First, the DetonationLocationReply is extended by the building ID.
// The building status is subscribed to in AES ObjectExplosionCluster
// and the BuildingId is added to TracjectoryData.
// When a building reaches 80% destruction, the intersection of the trajectory with a building is determined again
// and the trajectory is updated on the DDS bus.
// Folgendes connect an geeigneter Stelle einbauen (Aktivieren und dekativieren.
// connect(m_mainHandler->dds()->bldDtc().get(), &Buildings::Interface::Dtc::buildingStateChanged,
// this, &ObjectExplosionCluster::onBuildingStateChanged);
// void ObjectExplosionCluster::onBuildingStateChanged(const Buildings::BuildingState &data, DataModification modType)
// {
// if (modType != DataModification::eModified)
// return;
// if (data.damagePercent() < 80 )
// return;
// DEBUG_LOG(m_logger) << "data.damagePercent() > 80. Check, if m_trajectoryCont contains building:" << data.id()
// << " aesItemId=" << m_aiv.aesItemId();
// for (const TracjectoryData &tracjectoryData : std::as_const(m_trajectoryCont))
// {
// DEBUG_LOG(m_logger) << "print_m_trajectory"
// << " value: index=" << tracjectoryData.index
// << " buildingId=" << tracjectoryData.buildingId;
// if (tracjectoryData.buildingId == data.id())
// {
// DEBUG_LOG(m_logger) << "Update trajectory id=" << tracjectoryData.index;
// QString reason;
// bool startDetonationLocationRequest = true;
// generateTrajectory(tracjectoryData.index, startDetonationLocationRequest, &reason);
// }
// } // for
// }
Core::FunctionReplyObject ObjectExplosionCluster::updateObject(const Database::AesExplosionCluster &data)
{
DEBUG_LOG(m_logger) << "updateObject properties=" << data.properties;
Core::FunctionReplyObject ret;
// some plausibility checks
ret = checkObjectData(data);
if (ret.error())
return ret;
if (data.impactCount > AreaEffectSimulation::s_maxTrajectoriesPerExplosionCluster)
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error with explosion cluster with id %1, impactCount exceeds limit.")).arg(m_data.id);
ret = ObjectBaseTmpl::updateObject(data);
if (ret.error() || (isOnlyObjectNameModifiable() && (data.id > 0))) // data.id > 0 -> negative values from oc always get a full update
return ret;
// if (data.id == 0) // should not happen, maybe an assert here ?
// return ret;
// only allow some fields
Database::AesExplosionCluster tmpData(m_data);
tmpData.id = std::abs(data.id);
tmpData.name = data.name;
tmpData.element = data.element;
tmpData.effectStart = data.effectStart;
tmpData.effectDuration = data.effectDuration;
tmpData.zog = data.zog;
tmpData.explosionGeometryId = data.explosionGeometryId;
tmpData.impactPosition = data.impactPosition;
tmpData.rotationAngle = data.rotationAngle;
tmpData.impactWidth = data.impactWidth;
tmpData.impactLength = data.impactLength;
tmpData.impactCount = data.impactCount;
tmpData.mhpCountRequest = data.mhpCountRequest;
tmpData.igniterTypeId = data.igniterTypeId;
tmpData.igniterFunctionId = data.igniterFunctionId;
tmpData.peakHeight = data.peakHeight;
tmpData.flightDuration = data.flightDuration;
tmpData.partyId = data.partyId;
tmpData.participantId = data.participantId;
tmpData.firingCenterPosition = data.firingCenterPosition;
tmpData.chargeTypeId = data.chargeTypeId;
tmpData.fireRequestTime = data.fireRequestTime;
tmpData.altitude = data.altitude;
tmpData.firePurpose = data.firePurpose;
// [GUEZE-3145] Automatische Veraenderung bei Steilfeuer Dialog -> Stoeren
tmpData.firePurposeId = data.firePurposeId;
tmpData.effectMarkingInTa = data.effectMarkingInTa;
tmpData.effectStateId = data.effectStateId; // overwrite for oc
tmpData.properties = data.properties;
if (!isOnlyObjectNameModifiable()) // check again, this time also if oc
{
tmpData.originalEffectDuration = data.effectDuration;
tmpData.originalEffectStart = data.effectStart;
tmpData.impactCount = data.impactCount;
tmpData.mhpCountRequest = data.mhpCountRequest;
tmpData.mhpRotationRequest = data.mhpRotationRequest;
}
tmpData.launchVelocity = data.launchVelocity;
tmpData.launchAngleDeg = data.launchAngleDeg;
tmpData.dropAngle = data.dropAngle;
tmpData.tapTimepoint = data.tapTimepoint;
tmpData.lcPrf = data.lcPrf;
tmpData.lcTermination = data.lcTermination;
tmpData.cloudBase = data.cloudBase;
if (!m_mainHandler->db()->update(tmpData))
{
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating explosion cluster with id %1 in database.")).arg(m_data.id);
}
if ((data.externalId <= 0) && m_mainHandler->gearClassOfChargeTypeId(m_data.chargeTypeId) != ProjectDataManagement::GearClass_def::AirSupport) // internal
{
// this is for party change or charge type change
if (data.partyId != m_data.partyId || data.chargeTypeId != m_data.chargeTypeId)
{
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, (-1) * m_data.impactCount);
if (ret.error())
return ret;
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, data.impactCount);
if (ret.error())
return ret;
}
else
// this is the case when cannonCount or cannonRounds changed
{
int diff = data.impactCount - m_data.impactCount;
if (diff != 0)
{
DEBUG_LOG(m_logger) << "Claiming planned charge " << diff;
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, diff);
if (ret.error())
return ret;
}
}
}
m_data = std::move(tmpData);
// m_lastSentTriggeredExplosionNumber = -1;
// reset some data:
m_aiv2.form().clear(); // enforce recalculation
m_effects.singleEffects().clear();
m_aivGunToTargetTrajectory.form().clear(); // enforce recalculation
m_temporaryData.reset(); // estimatedMaxFlightDurationMSec, mhPoints, mhWidth...
// keep in mind: if no participantId is provided: no gunToTargetLine will be calculated
if (isMhpRotationProvided())
m_temporaryData.calculatedMhpRotationNorthDeg = m_data.mhpRotationRequest;
m_data.firingCenterPosition = LtsUtils::QPoint3D(); // Core::GeoPosition();
// setupFiringOrigin
if (isOriginatorParticipantIdProvided())
{
Core::FunctionReplyObject retSFO = setupFiringOrigin();
m_temporaryData.annotationString = retSFO.translated().toStdString();
if (!isExplosionClusterMarkedValid())
m_data.effectStart = QDateTime();
}
// do checkPreConditions
{
Core::FunctionReplyObject retCPC = checkPreConditionsComplete();
if (retCPC.error())
abortPreStartTimer();
m_temporaryData.annotationString = retCPC.translated().toStdString();
if (!isExplosionClusterMarkedValid())
m_data.effectStart = QDateTime();
}
if (!m_data.effectStart.isValid())
abortPreStartTimer();
m_publishDataTimer->start();
checkEnterNewState();
DUMP();
return Core::FunctionReplyObject(data.id); // data not m_data for the possible negative id in the oc-laptop
}
bool ObjectExplosionCluster::initialCalculations(QString *reasonOut)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START" << " id/name= '" << m_data.name << "'/" << m_data.id;
//
// calculate the subAreas / mtps:
//
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START" << " id/name= '" << m_data.name << "'/" << m_data.id << " IS_VULCANO = " << isVulcano();
if (!isVulcano())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << "GEOMETRY ID: " << m_data.explosionGeometryId << " " << "----------------------------------------";
double singleWidthOut, singleHeightOut;
singleWidthOut = singleHeightOut = 0;
// Area
const LtsCoordTrans::UtmCoordinate &impactPositionUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.impactPosition); // impactPosition = cornerBottomLeft
const Polygons::AreaBottomLeft areaBLOrgWithoutTla(impactPositionUtm, m_data.impactWidth, m_data.impactLength, m_data.rotationAngle);
const Polygons::AreaBottomLeft areaBlWithTla = Polygons::explosionClusterAddTla(areaBLOrgWithoutTla, m_data.zog);
// Radial extras
const QPointF center = Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, 0.0);
const LtsCoordTrans::UtmCoordinate &centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center, impactPositionUtm.zone);
const LtsCoordTrans::UtmCoordinate &firingOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.firingCenterPosition.toPointF());
DEBUG_LOG(m_logger) << "impactPositionUtm Coords: X: " << impactPositionUtm.x << " | Y: " << impactPositionUtm.y << " zone: " << impactPositionUtm.zone
<< "firingOriginUtm Coords: X: " << firingOriginUtm.x << " | Y: " << firingOriginUtm.y << " zone: " << firingOriginUtm.zone
<< "centerUtm Coords: X: " << centerUtm.x << " | Y: " << centerUtm.y << " zone: " << centerUtm.zone
<< " m_data.mhpCountRequest: " << m_data.mhpCountRequest;
QVector<QPointF> mtps_asUtm;
if (m_data.explosionGeometryId.underlying() == AreaEffectSimulation::AesExplosionGeometry_def::EgRadial)
mtps_asUtm = explosionClusterRadialToMtps(areaBlWithTla, m_data.mhpCountRequest, firingOriginUtm, centerUtm, &singleWidthOut, &singleHeightOut);
else
mtps_asUtm = explosionClusterAreaToMtps(areaBlWithTla, m_data.mhpCountRequest, &singleWidthOut, &singleHeightOut);
if (mtps_asUtm.size() == m_data.mhpCountRequest)
{
m_temporaryData.mhpLength = singleHeightOut;
m_temporaryData.mhpWidth = singleWidthOut;
// m_temporaryData.mhpRotation = m_data.rotationAngle; // mhpRotation depends on firingOrigin
m_temporaryData.mhPoints.clear();
// GUEZE-2746
// if less impacts are requested than mtps are provided:
if (m_data.impactCount < mtps_asUtm.size())
mtps_asUtm.resize(m_data.impactCount);
m_temporaryData.mhPoints.reserve(mtps_asUtm.size());
for (const QPointF &mtp_asUtm : std::as_const(mtps_asUtm))
{
LtsCoordTrans::UtmCoordinate temp(impactPositionUtm.zone, mtp_asUtm.x(), mtp_asUtm.y());
m_temporaryData.mhPoints.emplace_back(Core::GeoPosition2D(temp.geographic().x(), temp.geographic().y()));
} // for
} // if (mtps_asUtm.size() == m_data.mhpCountRequest)
} // if (!isVulcano())
if (!isOriginatorParticipantIdProvided())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", no originatorParticantId -> no trajectrory";
// it's already reseted, but to make it clear:
m_temporaryData.estimatedMaxFlightDurationMSec = 0;
m_temporaryData.calculatedFiringAngle = 0;
m_temporaryData.calculatedFiringSpeed = 0;
m_temporaryData.calculatedPeakHeightAbs = 0;
m_temporaryData.calculatedFlightDurationMsec = 0;
return true;
}
//
// estimate max FlightDuration: (to start the generation of trajectories)
//
if (isVulcano())
{
// For the flightDuration more or less we have to calculate the whole curve.
// So we calculate the GTL ...
QString reasonGtlVulcanoOut;
bool successGTL = !generateGunToTargetForm(&reasonGtlVulcanoOut).empty();
if (!successGTL || !reasonGtlVulcanoOut.isEmpty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " name= '" << m_data.name << ", generateGunToTargetForm failed"
<< ", reasonGtlVulcanoOut = " << reasonGtlVulcanoOut;
if (reasonOut)
*reasonOut = reasonGtlVulcanoOut;
return false;
}
if (!reasonGtlVulcanoOut.isEmpty())
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " id/name= '" << m_data.name << "'/" << m_data.id << " reasonGtlVulcanoOut = " << reasonGtlVulcanoOut;
// ... and retrieve the duration:
const TracjectoryData &tdRefGTL = m_trajectoryCont[-1];
const TrajectoryTimeAware &trajTimeAwareGTL = tdRefGTL.trajectoryTimeAware;
const Core::GeoPositionRelativeTime &geoPosRelTimeRef = trajTimeAwareGTL.geometry().back();
m_temporaryData.calculatedFlightDurationMsec = geoPosRelTimeRef.relativeTime();
m_temporaryData.estimatedMaxFlightDurationMSec = (geoPosRelTimeRef.relativeTime() * 1.1) + 5 * 1000; // ( *1.1) + (10 *1000)
m_temporaryData.calculatedFiringSpeed = TrajectoryGeneratorVulcano::defaultv0();
m_temporaryData.calculatedPeakHeightAbs = tdRefGTL.peakHeightAbs;
// to avoid re-calculation, store that at m_aivGunToTargetTrajectory.
// At publishAiv the new calculation will be skipped:
m_aivGunToTargetTrajectory.form(tdRefGTL.trajectoryGeometry);
m_aivGunToTargetTrajectoryDirty = true;
}
else
{
m_temporaryData.calculatedFlightDurationMsec = Polygons::explosionClusterCalculatedFlightDuration2DMSec(m_data);
if (m_isAirSupportSource)
m_temporaryData.estimatedMaxFlightDurationMSec = m_temporaryData.calculatedFlightDurationMsec;
else
m_temporaryData.estimatedMaxFlightDurationMSec
= Polygons::explosionClusterEstimatedMaxFlightDuration2DMSec(m_temporaryData.calculatedFlightDurationMsec); // ( *1.1) + (10 *1000)
}
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", calculatedFlightDuration=" << m_temporaryData.calculatedFlightDurationMsec
<< ", m_estimatedMaxFlightDurationMSec=" << m_temporaryData.estimatedMaxFlightDurationMSec;
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END, m_estimatedMaxFlightDurationMSec=" << m_temporaryData.estimatedMaxFlightDurationMSec;
const bool ret = m_temporaryData.calculatedFlightDurationMsec > 0;
if (!ret && reasonOut)
*reasonOut = QStringLiteral("no MaxFlightDuration");
return ret;
}
void ObjectExplosionCluster::abortPreStartTimer()
{
m_preStartTimer->stop();
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", preStartTimer STOPPED";
}
Core::FunctionReplyObject ObjectExplosionCluster::removeObject()
{
Core::FunctionReplyObject ret = ObjectBaseTmpl::removeObject();
if (ret.error())
return ret;
// avoid unneeded calculations:
m_firingTimer.pauseFiring();
m_preStartTimer->stop();
// int chargeLeft = m_data.impactCount;
DEBUG_LOG(m_logger) << "Impact count: " << m_data.impactCount;
// DEBUG_LOG(m_logger) << "Last triggered explosion number: " << m_lastSentTriggeredExplosionNumber;
// if (isActivationTimeReached())
// chargeLeft -= m_lastSentTriggeredExplosionNumber;
int remainingAmmoCount = m_firingTimer.remainingAmmoCount();
DEBUG_LOG(m_logger) << "remainingAmmoCount: " << remainingAmmoCount;
if (remainingAmmoCount < 0)
remainingAmmoCount = 0;
DEBUG_LOG(m_logger) << "Charge left / remainingAmmoCount: " << remainingAmmoCount;
// restore original stuff
m_data.effectDuration = m_data.originalEffectDuration;
m_data.effectStart = m_data.originalEffectStart;
// Negative, as we free the charge
ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, 0 - remainingAmmoCount);
return ret;
}
Core::FunctionReplyObject ObjectExplosionCluster::checkObjectData(const Database::AesExplosionCluster &data) const
{
// To avoid crashed / asserts etc
Core::FunctionReplyObject ret;
using geoType = AreaEffectSimulation::AesExplosionGeometry_def;
const geoType::type curExGeoType = data.explosionGeometryId.underlying();
static const QVector<geoType::type> ecValidGeos = {AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint, // 0 -> default
AreaEffectSimulation::AesExplosionGeometry_def::EgPoint,
AreaEffectSimulation::AesExplosionGeometry_def::EgArea,
AreaEffectSimulation::AesExplosionGeometry_def::EgRadial,
AreaEffectSimulation::AesExplosionGeometry_def::EgLine};
QString reason;
if (data.name.isEmpty())
reason = QStringLiteral("Name is empty");
else if (data.impactPosition.isNull())
reason = QStringLiteral("Position is null");
else if (data.impactLength <= 0)
reason = QString(QStringLiteral("ImpactLength invalid (%1)")).arg(data.impactLength);
else if (data.impactWidth <= 0)
reason = QString(QStringLiteral("ImpactWidth invalid (%1)")).arg(data.impactWidth);
else if (data.impactCount <= 0)
reason = QString(QStringLiteral("ImpactCount invalid (%1)")).arg(data.impactCount);
else if (!ecValidGeos.contains(curExGeoType))
reason = QString(QStringLiteral("ExplosionGeometryType not supported yet (%1)")).arg(static_cast<int>(curExGeoType));
// else if ((curExGeoType == AreaEffectSimulation::AesExplosionGeometry_def::EgArea) &&
// (data.impactCount <= 2))
// reason = QString(QStringLiteral("ImpactCount invalid (%1)")).arg(QStringLiteral("Area requires at least 2 impacts"));
else if (data.zog < 0)
reason = QString(QStringLiteral("TLA invalid (%1)")).arg(data.zog);
else if (data.mhpCountRequest < 0)
reason = QString(QStringLiteral("mhpCountRequest invalid (%1)")).arg(data.mhpCountRequest);
// LaserGuiding:
else if (data.launchVelocity < -1)
reason = QString(QStringLiteral("launchVelocity invalid (%1)")).arg(data.launchVelocity);
else if ((data.launchAngleDeg < -1) || (data.launchAngleDeg > 90))
reason = QString(QStringLiteral("launchAngleDeg invalid (%1)")).arg(data.launchAngleDeg);
else if ((data.dropAngle < -1) || (data.dropAngle > 90))
reason = QString(QStringLiteral("dropAngle invalid (%1)")).arg(data.dropAngle);
// finally
if (!reason.isEmpty())
{
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion cluster with id %1 and name '%2' invalid. (%3)")).arg(m_data.id).arg(m_data.name).arg(reason);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Error: " << ret.translated();
}
return ret;
}
bool ObjectExplosionCluster::removeObjectFromDb() { return m_mainHandler->db()->removeAesExplosionCluster(m_data.id); }
Core::FunctionReplyObject ObjectExplosionCluster::resumePause(const ExplosionClusterPauseResumeRequest &req)
{
Core::FunctionReplyObject ret;
if (req.resumeExecution())
{
// resume
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " RESUME" << " ... ";
if (m_firingTimer.remainingAmmoCount() <= 0)
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion cluster with id %1 and name '%2' could not be resumed (%3)"))
.arg(m_data.id)
.arg(m_data.name)
.arg(tr("no ammo left"));
else if (!m_firingTimer.startFiring())
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion cluster with id %1 and name '%2' could not be resumed (%3)")).arg(m_data.id).arg(m_data.name);
}
else
{
// pause
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " PAUSE" << " ... ";
if (m_firingTimer.remainingAmmoCount() <= 0)
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion cluster with id %1 and name '%2' could not be paused (%3)"))
.arg(m_data.id)
.arg(m_data.name)
.arg(tr("no ammo left"));
else if (!m_firingTimer.pauseFiring())
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion cluster with id %1 and name '%2' could not be paused")).arg(m_data.id).arg(m_data.name);
}
if (ret.error())
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " PAUSE" << " ... resumePause failed"
<< ", errorStr = " << ret.translated();
return ret;
//
// if (req.resumeExecution())
// {
// // resume
// if (req.timeToResume().is_set())
// m_data.effectStart = QDateTime::fromMSecsSinceEpoch(req.timeToResume().get());
// else
// m_data.effectStart = partitionTime();
// if (req.remainingDuration().is_set())
// m_data.effectDuration = req.remainingDuration().get();
// // req.resumeSeed() currently not used, as it is calculated internaly. maybe in the future
// if (!trySwitchToActivated())
// return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion cluster is currently not paused."));
// }
// else
// // pause
// if (!trySwitchToPaused())
// return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Switching explosion cluster to pause is not possible in the current state."));
// //m_lastSentTriggeredExplosionNumber = -1;
// return Core::FunctionReplyObject(m_data.id);
//
}
Core::FunctionReplyObject ObjectExplosionCluster::targetPositionsModify(const TargetPositionsModifyRequest &req)
{
static int s_counter = 0;
const qint64 now = partitionTimeMSec();
const qint64 remainingMsec = m_data.effectStart.toMSecsSinceEpoch() - now;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START"
<< " TargetPositionsModifyRequest = " << req << ", s_counter = " << s_counter++ << ", current state = " << m_data.effectStateId.underlying()
<< ", now = " << now << ", effectStart = " << m_data.effectStart.toMSecsSinceEpoch() << ", remainingMsec = " << remainingMsec;
DUMP();
const std::vector<Core::GeoPosition> &positions = req.positions();
Core::FunctionReplyObject ret;
if (m_data.effectStateId.underlying() == AreaEffectSimulation::AesEffectState_def::EsExpired)
{
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error modifying explosion cluster with id %1 since object is already expired")).arg(m_data.id);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Error: " << ret.str();
return ret;
}
// check size
if (positions.empty() || (m_data.mhpCountRequest != static_cast<int>(positions.size())))
{
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error modifying explosion cluster with id %1 since size does not match")).arg(m_data.id);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Error: " << ret.str();
return ret;
}
// check isVulcano:
if (!isVulcano())
{
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error modifying explosion cluster with id %1 is not supported for this charge type")).arg(m_data.id);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Error: " << ret.str();
return ret;
}
// check Time:
// accept targetPositionsModify only if effectStart is NOT reached:
if (remainingMsec < 0)
{
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error modifying explosion cluster with id %1 since object is already active (it's %2 msec to late) "))
.arg(m_data.id)
.arg(remainingMsec);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Error: " << ret.translated();
return ret;
}
const Core::GeoPosition &newTargetPos = req.positions().front();
const int explosionIndex = 0;
TracjectoryData &tdRef = m_trajectoryCont[explosionIndex];
TrajectoryTimeAware &trajTimeAwareRef = tdRef.trajectoryTimeAware;
std::vector<Core::GeoPositionRelativeTime> &trajectoryTimeAwareGeometry = trajTimeAwareRef.geometry();
if (trajectoryTimeAwareGeometry.empty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " has no trajectoryTimeAwareGeometry";
ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error modifying explosion cluster with id %1 since trajectory is empty")).arg(m_data.id);
return ret;
}
// "adjust" by replacing the last GeoPosition by the new value, but leave the timepoint untouched:
Core::GeoPosition &hitPos = trajectoryTimeAwareGeometry.back(); // implicit downcast
hitPos = newTargetPos;
// // look for youngest item:
// const qint64 now = partitionTimeMSec();
// const qint64 starttime = trajTimeAwareRef.effectStart();
// const Core::GeoPositionRelativeTime lastItemCopy = trajectoryTimeAwareGeometry.back();
// for (unsigned int adjustIndex = trajectoryTimeAwareGeometry.size() - 1; adjustIndex > 0; --adjustIndex)
// {
// const Core::GeoPositionRelativeTime &curItem_geo = trajectoryTimeAwareGeometry[adjustIndex-1];
// if ((starttime + curItem_geo.relativeTime()) > now)
// {
// // remove...
// trajectoryTimeAwareGeometry.pop_back();
// continue;
// }
// // ... and set new end/back:
// trajectoryTimeAwareGeometry.emplace_back(Core::GeoPositionRelativeTime(newTargetPos.lon(),
// newTargetPos.lat(),
// newTargetPos.z(),
// lastItemCopy.relativeTime()) );
// } // for
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " XXXX"
// << " trajectoryTimeAwareGeometry size = " << trajectoryTimeAwareGeometry.size();
// m_aiv2 CHANGES HERE
//
std::vector<Core::GeoPosition> &formRef = m_aiv2.form();
// compare former and updated ImpactPos: context targetModify
if (m_logger.isDebugEnabled())
{
Core::GeoPosition &curImpactPosRef = formRef[explosionIndex];
LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(curImpactPosRef.lon(), curImpactPosRef.lat());
const QVector3D u1Vec(utm1.x, utm1.y, curImpactPosRef.z());
LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(newTargetPos.lon(), newTargetPos.lat(), utm1.zone);
const QVector3D u2Vec(utm2.x, utm2.y, newTargetPos.z());
const QVector3D diff = u2Vec - u1Vec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " for explosionIndex " << explosionIndex << ", curImpactPosRef " << curImpactPosRef << ", utm1 " << utm1
<< ", newTargetPos " << newTargetPos << ", utm2 " << utm2 << " the impactPos moved 2D = " << LtsCoordTrans::distance(utm1, utm2)
<< " the impactPos moved 3D = " << diff.length();
}
Core::GeoPosition &curImpactPosRef = formRef[explosionIndex];
curImpactPosRef = newTargetPos;
// Effects:
std::vector<AreaEffectSimulation::SingleEffect> &singleEffectsRef = m_effects.singleEffects();
SingleEffect &ref = singleEffectsRef[explosionIndex];
ref.position(newTargetPos);
ref.zPlacement(AesZPlacement_def::ZpAbsolute); // intersection: change from draped to absolute
// Trajectory
publishTrajectory(explosionIndex);
if (ret.error())
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " some checks failed";
return ret;
}
Core::FunctionReplyObject ObjectExplosionCluster::checkPreConditionsComplete()
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START";
DUMP();
QString reason;
if (m_data.externalId <= 0) // -> internal
{
if (m_data.participantId == 0)
{
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", m_data.participantId invalid: " << m_data.participantId;
reason = QString(QStringLiteral("firingParticipantId is invalid (%1)")).arg(m_data.participantId);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
DUMP();
return createAnnotationObjectIncomplete(m_data.id, reason);
}
if ((m_data.participantId > 0) && !firingOriginIsValid(m_data.firingCenterPosition, &reason))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " firingOriginIsValid failed";
if (!reason.isEmpty())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
DUMP();
return createAnnotationObjectIncomplete(m_data.id, reason);
}
}
if (!initialCalculations(&reason) || !reason.isEmpty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " initialCalculations failed";
DUMP();
return createAnnotationObjectError(m_data.id, reason);
}
} // if (m_data.externalId <= 0)
// keep start of svc in mind:
Core::FunctionReplyObject ret;
if (m_data.effectStateId.underlying() != AreaEffectSimulation::AesEffectState_def::EsExpired)
ret = checkPreConditions();
DUMP();
if (ret.error())
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " checkPreConditions failed";
return ret;
}
Core::FunctionReplyObject ObjectExplosionCluster::checkPreConditions()
{
// setupParticipantOrigin, initialCalculations should be executed.
QString reason;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START";
//
// check time
//
m_preStartTimer->stop();
if (!activationTime().isNull() && isOriginatorParticipantIdProvided()) // no originatorParticipantId provided, so no trajectory is intended
{
m_preStartTimer->stop();
const qint64 timeUntilFireMSec = activationTime().toMSecsSinceEpoch() - m_temporaryData.estimatedMaxFlightDurationMSec - partitionTimeMSec();
// to support 'late' / 'immediate' requests and avoid 'Error, not enough time for estimated flight duration'
// following may work:
// if (timeUntilFireMSec < 0)
// timeUntilFireMSec = 0;
if (timeUntilFireMSec < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Error, not enough time for estimated flight duration"
<< " estimatedMaxFlightDurationMSec=" << m_temporaryData.estimatedMaxFlightDurationMSec << " (timeUntilFireMSec=" << timeUntilFireMSec
<< " < 0 ";
reason = QString(QStringLiteral("Not enough time for estimated flight duration (timeUntilFireMSec=%1).")).arg(timeUntilFireMSec);
}
else
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " preStartTimer->setInterval, timeUntilFireMSec = " << timeUntilFireMSec;
m_preStartTimer->setInterval(timeUntilFireMSec);
// m_preStartTimer->setInterval(5000);
m_preStartTimer->start();
}
// slot: checkNextFire
}
if (!reason.isEmpty())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
return createAnnotationObject(m_data.id, reason);
}
//
// check GunToTargetForm (generates trajectory to center of area.)
// It will use flightDuration or peakHeight as provided
//
if (isOriginatorParticipantIdProvided())
{
QString reasonGtlOut;
m_temporaryData.gunToTargetLine = ObjectExplosionCluster::generateGunToTargetForm(&reasonGtlOut);
if (m_temporaryData.gunToTargetLine.size() < 2)
reason = QString(QStringLiteral("GenerateGunToTargetLine (%1)")).arg(reasonGtlOut);
if (!reason.isEmpty())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
return createAnnotationObjectError(m_data.id, reason);
}
}
return Core::FunctionReplyObject(m_data.id);
}
void ObjectExplosionCluster::setEffectState(AesEffectState newState)
{
// if (newState == AesEffectState::EsPaused ||
// newState == AesEffectState::EsExpired ||
// newState == AesEffectState::EsRemoved ||
// newState == AesEffectState::EsUnknown)
// stopSingleExplosionHandling(newState);
// more or less a hack for ec of HLA (m_explosionCluster.externalId() > 0)
bool bExecuteFiringImpact = false;
if (noTracjectoryRequired() && (m_data.effectStateId != AesEffectState::EsActivated) && (newState == AesEffectState::EsActivated))
bExecuteFiringImpact = true;
ObjectBaseTmpl::setEffectState(newState);
// if (newState == AesEffectState::EsActivated)
// startSingleExplosionHandling();
if (newState == AesEffectState::EsCreated || newState == AesEffectState::EsError || newState == AesEffectState::EsReceived)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", setEffectState to " << newState;
abortPreStartTimer();
}
if (bExecuteFiringImpact)
{
m_temporaryData.calculatedFlightDurationMsec = 0;
checkNextFire();
}
m_publishDataTimer->start();
}
void ObjectExplosionCluster::onPublishData()
{
ObjectBaseTmpl::onPublishData();
if (!m_mainHandler->db()->update(m_data))
ERROR_LOG(m_logger) << "Error updating ObjectExplosionCluster-data in database";
ExplosionCluster ddsData(m_data.id,
m_data.name.toStdString(),
effectState() == AesEffectState::EsRemoved,
false,
0, // partitionTimeMSec(),
{m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId},
m_data.effectDuration, // pause/resume: remainDuration
toOptional(toMSec(m_data.effectStart)),
m_data.effectStateId,
m_data.effectSourceId,
toOptional(m_data.partyId),
toOptional(m_data.participantId),
m_data.effectMarkingInTa,
m_data.externalId,
m_data.isCentral,
m_data.properties.toStdString(),
m_data.element.toStdString(),
m_temporaryData.annotationString, // relevant for "isExplosionClusterValid()
Area(Core::GeoPosition2D(m_data.impactPosition.x(), m_data.impactPosition.y()), m_data.impactWidth, m_data.impactLength, m_data.rotationAngle),
m_data.explosionGeometryId,
m_data.zog, // aka zog
m_temporaryData.mhPoints,
m_temporaryData.mhpWidth,
m_temporaryData.mhpLength,
m_temporaryData.calculatedMhpRotationNorthDeg,
m_data.impactCount,
// -1, // currentExplosionNumber(), currentExplosionNumber is skipped until stop/resume is implemented
// m_firingTimer.nextAmmoToFire(),
m_data.remainExplosionNumber, // is more _NEXT_ ExplosionNumber
toMSec(m_data.fireRequestTime),
m_data.firePurposeId, // m_data.firePurpose.toStdString(),
toCoreGeoPosition(m_data.firingCenterPosition),
toMSec(m_data.originalEffectStart),
m_data.originalEffectDuration,
m_temporaryData.isObjectModifiable,
dds::core::optional<AreaEffectSimulation::FlightCurve>(),
dds::core::optional<AreaEffectSimulation::LaserGuiding>(),
m_data.esmParticipantIds());
// add FlightCurve only if SingleEffectOnly == (externalId > 0)
if (m_data.externalId == 0)
{
FlightCurve fc(m_data.launchVelocity,
m_data.launchAngleDeg,
m_data.dropAngle,
m_temporaryData.calculatedFiringSpeed,
m_temporaryData.calculatedPeakHeightAbs,
m_temporaryData.calculatedFlightDurationMsec,
m_data.peakHeight,
m_data.flightDuration);
ddsData.flightCurve(toOptional(fc));
}
if (isVulcano())
{
LaserGuiding lg(m_data.lcPrf, m_data.lcTermination, m_data.tapTimepoint, m_data.cloudBase);
ddsData.laserGuiding(toOptional(lg));
}
// publish only on real changes:
if (m_explosionCluster != ddsData)
{
m_explosionCluster = ddsData;
ddsData.sampleTime(partitionTimeMSec()); // publish sampleTime, but do not store
m_mainHandler->dds()->dts()->publish(ddsData);
}
publishAiv();
}
void ObjectExplosionCluster::onDetonationLocationReply(const DetonationLocationReply &reply)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START";
// int32_t aesItemId, // aesItemId
// const std::string& annotationString,
// const std::vector< std::string >& errorParams,
// int32_t explosionIndex, // explosionIndex
// const Core::GeoPosition& position);
if (reply.id() != m_data.id) // aesItemId
{
DEBUG_LOG(m_logger) << "ID mismatch: " << reply.id() << " != " << m_data.id;
return;
}
// do we have the initial geometry?
const int explosionIndex = reply.index();
if (!m_trajectoryCont.contains(explosionIndex))
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << "unknown explosionIndex "
<< ", reply.index() = " << explosionIndex;
return;
}
const Core::GeoPosition &replyPos = reply.position();
if ((replyPos.lon() < 0.00001) || (replyPos.lat() < 0.00001))
{
// no intersection
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " got DetonationLocationReply"
<< " : no intersection - reply.position is: "
<< ", reply.index() = " << explosionIndex << ", reply.position() " << replyPos;
}
else
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " got DetonationLocationReply"
<< " : INTERSECTION - reply.position is: "
<< ", reply.index() = " << explosionIndex << ", reply.position() " << replyPos;
adjustTrajectoryToDetonationLocation(explosionIndex, replyPos, reply.buildingId());
}
// publish new triggered explosions data here
// first new positions...
if (m_aiv2HasChanged)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Publishing AesItemView (AtExplosionClusterSingleExplosions) for TriggeredExplosions, explosionIndex "
<< explosionIndex;
m_aiv2.sampleTime(partitionTimeMSec());
// // m_mainHandler->dds()->dts()->publish(m_aiv2); // does anyone needs this topic???
m_aiv2HasChanged = false;
emit newAiv(m_aiv2);
}
else
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id
<< " Publishing AesItemView (AtExplosionClusterSingleExplosions) for TriggeredExplosions SKIPPED, explosionIndex " << explosionIndex;
}
publishTrajectory(explosionIndex);
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
}
void ObjectExplosionCluster::adjustTrajectoryToDetonationLocation(int explosionIndex, const Core::GeoPosition &intersectionLocation, const int buildingId)
{
TRACE_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << " START";
// do we have the initial geometry?
if (!m_trajectoryCont.contains(explosionIndex))
{
ERROR_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << "unknown explosionIndex "
<< ", reply.index() = " << explosionIndex;
return;
}
TracjectoryData &tdRef = m_trajectoryCont[explosionIndex];
tdRef.buildingId = buildingId;
std::vector<Core::GeoPosition> &trajectoryGeometry = tdRef.trajectoryGeometry;
if (trajectoryGeometry.empty())
{
ERROR_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << " trajectory is empty";
return;
}
DEBUG_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << "VOR : m_trajectory.size()=" << trajectoryGeometry.size()
<< " back: " << trajectoryGeometry.back();
const Core::GeoPosition &trajBack = trajectoryGeometry.back();
if (trajectoryGeometry.back() == intersectionLocation) // no adjustment necessary
{
DEBUG_LOG(m_logger) << "Detonation/Intersection location coincides with last point of trajectory for ID: " << m_data.id;
return;
}
const Core::GeoPosition &trajFront = trajectoryGeometry.front();
const double distToOriginSquared = LtsCoordTrans::squareDistance(trajFront.lon(), trajFront.lat(), intersectionLocation.lon(), intersectionLocation.lat());
ProjectDataManagement::IgniterFunctionBase ignFunctionBase
= m_mainHandler->dds()
->pdmDtc()
->getChargeTypeIgniterTypeIgniterFunction(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId)
.igniterFunctionBaseId();
if ((ignFunctionBase == ProjectDataManagement::IgniterFunctionBase::IfbProximity) || (ignFunctionBase == ProjectDataManagement::IgniterFunctionBase::IfbImpact))
{
const double distTrajectorySquared = LtsCoordTrans::squareDistance(trajFront.lon(), trajFront.lat(), trajBack.lon(), trajBack.lat());
if (distToOriginSquared > distTrajectorySquared)
{
ERROR_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << "Detonation location is outside of trajectory for ID: " << m_data.id
<< ", explosionIndex=" << explosionIndex;
return;
} // if
}
bool bAdjusted = false;
// reverse iteration;
const Core::GeoPosition &trajectoryOrigin_geo = trajFront;
for (unsigned int i = trajectoryGeometry.size() - 1; i > 0; --i)
{
const Core::GeoPosition &curItem_geo = trajectoryGeometry[i - 1];
const double distStartSegmentSquared = LtsCoordTrans::squareDistance(curItem_geo.lon(), curItem_geo.lat(), trajectoryOrigin_geo.lon(), trajectoryOrigin_geo.lat());
// remove...
if (distStartSegmentSquared > distToOriginSquared)
{
trajectoryGeometry.pop_back();
continue;
} // if
// ... and set new end/back:
trajectoryGeometry.pop_back();
trajectoryGeometry.push_back(intersectionLocation);
bAdjusted = true;
} // for
if (bAdjusted)
{
// 'adjust' the according SingleExlosions position!?
// initially form has no zValue, but we replace the ideal impact with the intersectionLocation (with zValue)
//
// m_aiv2 CHANGES HERE
//
std::vector<Core::GeoPosition> &formRef = m_aiv2.form();
Core::GeoPosition &curImpactPosRef = formRef[explosionIndex];
// compare former and updated ImpactPos: context intersection
if (m_logger.isDebugEnabled())
{
LtsCoordTrans::UtmCoordinate utm1 = LtsCoordTrans::UtmCoordinate::fromGeographic(curImpactPosRef.lon(), curImpactPosRef.lat());
const QVector3D u1Vec(utm1.x, utm1.y, curImpactPosRef.z());
LtsCoordTrans::UtmCoordinate utm2 = LtsCoordTrans::UtmCoordinate::fromGeographic(intersectionLocation.lon(), intersectionLocation.lat(), utm1.zone);
const QVector3D u2Vec(utm2.x, utm2.y, intersectionLocation.z());
const QVector3D diff = u2Vec - u1Vec;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " for explosionIndex " << explosionIndex << ", curImpactPosRef " << curImpactPosRef << ", utm1 " << utm1
<< ", intersectionLocation " << intersectionLocation << ", utm2 " << utm2 << " the impactPos moved 2D = " << LtsCoordTrans::distance(utm1, utm2)
<< " the impactPos moved 3D = " << diff.length();
}
curImpactPosRef = intersectionLocation;
m_aiv2HasChanged = true;
// Effects:
std::vector<AreaEffectSimulation::SingleEffect> &singleEffectsRef = m_effects.singleEffects();
SingleEffect &ref = singleEffectsRef[explosionIndex];
// ref.effectState(AesSingleEffectState_def::SesScheduled);
ref.position(intersectionLocation);
ref.zPlacement(AesZPlacement_def::ZpAbsolute); // intersection: change from draped to relative
// m_effectsHaveChanged = true;
if (explosionIndex == m_data.impactCount - 1)
DEBUG_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << ", update SingleExlosion: "
<< ", explosionIndex=" << explosionIndex << ", intersectionLocation=" << intersectionLocation;
else
DEBUG_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << ", update SingleExlosion: "
<< ", explosionIndex=" << explosionIndex << ", intersectionLocation=" << intersectionLocation;
}
DEBUG_LOG(m_logger) << "adjustTrajectoryToDetonationLocation " << m_data.id << ", Trajectory bAdjusted: " << bAdjusted
<< ", m_trajectory.size()=" << trajectoryGeometry.size() << " back: " << trajectoryGeometry.back();
TRACE_LOG(m_logger) << "adjustTrajectoryToDetonationLocation, " << m_data.id << " END";
}
// void ObjectExplosionCluster::setTrajectoryStartTime(int explosionIndex)
//{
// TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START, explosionIndex = " << explosionIndex;
// // Pause/Resume has to be considered.
// // use simple approach until then:
// // startime = impactTime - flightDuration
// const qint64 effectDurationMSec = m_data.effectDuration;
// // smooth:
// // Erst Schuss direct am Anfang, letzter Schuss am Ende
// // Bsp: dur = 10sec ; impactCount = 2;
// // intervalMSec = 10 / 1 = 10;
// // ^ !
// // Also bei effectStart und bei effectStart+effectDuration
// //
// qint64 intervalMSec;
// if (m_data.impactCount > 1)
// intervalMSec = effectDurationMSec / (m_data.impactCount);
// else
// intervalMSec = effectDurationMSec;
// const qint64 effectStartMSec = m_data.effectStart.toMSecsSinceEpoch();
// const qint64 impactTimeMSec = effectStartMSec + (explosionIndex*intervalMSec);
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " explosionIndex = " << explosionIndex
// << ", impactTimeMSec= " << QDateTime::fromMSecsSinceEpoch(impactTimeMSec).toString();
// // save to m_trajectoryCont:
// TracjectoryData &tdRef = m_trajectoryCont[explosionIndex];
// tdRef.trajectoryTimeAware.trajectoryStartTime(impactTimeMSec - tdRef.flightDurationMSec);
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " explosionIndex = " << explosionIndex
// << ", trajectoryStartTime = " << QDateTime::fromMSecsSinceEpoch(tdRef.trajectoryTimeAware.trajectoryStartTime()).toString()
// << ", trajectoryStartTime [msec] = " << tdRef.trajectoryTimeAware.trajectoryStartTime()
// << ", tdRef.flightDurationMSec [msec] = " << tdRef.flightDurationMSec
// << ", impactTimeMSec= " << QDateTime::fromMSecsSinceEpoch(impactTimeMSec).toString();
// TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END, explosionIndex = " << explosionIndex;
//}
Core::FunctionReplyObject ObjectExplosionCluster::setupFiringOrigin()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START";
QString reason;
LtsUtils::QPoint3D geoPosOut;
if (!getFiringUnitOrigin(m_data.participantId, geoPosOut, &reason))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " getFiringOrigin failed for id " << m_data.participantId << ", reason: '" << reason << "'";
if (!reason.isEmpty())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
DUMP();
return createAnnotationObjectIncomplete(m_data.id, reason);
}
}
m_data.firingCenterPosition = geoPosOut;
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
return Core::FunctionReplyObject();
}
void ObjectExplosionCluster::publishAiv()
{
const bool isEcValid = isExplosionClusterMarkedValid();
QPolygonF poly;
// [GUEZE-2487] PointGeometry:
// Move circle / form to real impact:
const bool bIsPoint = isPointSimilar(m_data.explosionGeometryId);
if (bIsPoint && (m_lastSentTriggeredExplosionNumber == 1)) // 'move' only AFTER impact
{
// on svcRestart, m_aiv2 may not been set. To avoid crash:
const std::vector<Core::GeoPosition> &formRef = m_aiv2.form();
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " since isPointSimilar==true form has to be updated"
<< ", m_data.effectStateId = " << m_data.effectStateId << ", m_data.impactWidth = " << m_data.impactWidth
<< ", m_lastSentTriggeredExplosionNumber = " << m_lastSentTriggeredExplosionNumber << ", formRef.size() = " << formRef.size();
QPointF centerGeo;
// on svcRestart, m_aiv2 may not been set. To avoid crash:
if (formRef.empty())
{
centerGeo = AreaEffectSimulation::Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, m_data.rotationAngle);
}
else
{
const Core::GeoPosition &impactPosition_geo3D = formRef[0];
centerGeo = QPointF(impactPosition_geo3D.lon(), impactPosition_geo3D.lat());
}
// ... and now take zog into account:
double radius = m_data.impactWidth / 2.0;
if (radius < 1)
radius = 1;
poly = Polygons::circle(centerGeo, radius + m_data.zog);
}
else
{
poly = Polygons::explosionClusterArea(m_data);
}
const QRectF bounds(poly.boundingRect());
//
// Major
//
AesItemView aiv(m_aiv);
aiv.aesItemId(m_data.id);
aiv.chargeStruct().chargeTypeId(m_data.chargeTypeId);
aiv.effectStart(toMSec(m_data.effectStart));
ddsVecFromPoly(poly, aiv.form());
aiv.boundingBoxBottomLeft(Core::GeoPosition2D(bounds.topLeft().x(), bounds.topLeft().y())); // QRectF's bigger values are at bottom
aiv.boundingBoxTopRight(Core::GeoPosition2D(bounds.bottomRight().x(), bounds.bottomRight().y()));
aiv.aesTypeId(AesType::AtExplosionCluster);
aiv.effectStateId(effectState());
aiv.id(1000 * m_data.id + AesType::AtExplosionCluster);
if (m_data.partyId != 0)
aiv.partyId(m_data.partyId);
// ObjectBaseTmpl::checkIsCentral honors pdm->isLaserGuidable
// and therefore isCentral is set to true; )
checkIsCentralAndPublish(aiv.form(), m_aiv.form(), m_data.isCentral);
//
// SingleExplosions
//
if (isEcValid)
{
AesItemView aiv2(m_aiv2);
aiv2.aesItemId(m_data.id);
aiv2.chargeStruct().chargeTypeId(m_data.chargeTypeId);
aiv2.effectStart(toMSec(m_data.effectStart));
// calculate just one time.
// maybe intersections of trajectories with building will 'adjust' some impact positions later.
if (aiv2.form().empty())
{
if (isVulcano() || (m_data.externalId > 0)) // m_data.externalId > 0 -> HLA / SingleFire / PointGeometry
{
const QPointF &impactPointGeo
= AreaEffectSimulation::Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, m_data.rotationAngle);
ddsVecFromPoly(QVector<QPointF>() << impactPointGeo, aiv2.form());
}
else
{
const QVector<QPointF> &singleExplosions = Polygons::explosionClusterSingleExplosionsGAUSS_Mtp(m_data, m_temporaryData.calculatedMhpRotationNorthDeg);
ddsVecFromPoly(singleExplosions, aiv2.form());
}
}
aiv2.boundingBoxBottomLeft(aiv.boundingBoxBottomLeft());
aiv2.boundingBoxTopRight(aiv.boundingBoxTopRight());
aiv2.aesTypeId(AesType::AtExplosionClusterSingleExplosions);
aiv2.id(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions);
aiv2.effectStateId(effectState());
if (m_data.partyId != 0)
aiv2.partyId(m_data.partyId);
//
// m_aiv2 CHANGES HERE
//
m_aiv2 = aiv2;
// if (aiv2 != m_aiv2)
// {
// m_aiv2 = aiv2;
// aiv2.sampleTime(partitionTimeMSec()); // publish sampleTime, but do not store
// m_mainHandler->dds()->dts()->publish(aiv2);
// emit newAiv(aiv2);
// }
m_effects.id(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions);
m_effects.aesItemId(m_data.id);
m_effects.aesTypeId(AesType::AtExplosionClusterSingleExplosions);
std::vector<AreaEffectSimulation::SingleEffect> &singleEffectsRef = m_effects.singleEffects();
if (singleEffectsRef.empty())
{
if (isVulcano() || (m_data.externalId > 0)) // m_data.externalId > 0 -> HLA / SingleFire / PointGeometry
{
singleEffectsRef.resize(1);
SingleEffect &seRef = singleEffectsRef.front();
seRef.singleEffectState(AesSingleEffectState_def::SesScheduled);
const QPointF &impactPointGeo
= AreaEffectSimulation::Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, m_data.rotationAngle);
// seRef.position(Core::GeoPosition(impactPointGeo.x(), impactPointGeo.y(), 0.0));
const double elevation = getElevationFor(impactPointGeo, QStringLiteral("getElevationForSingleImpactVulcano"));
seRef.position(Core::GeoPosition(impactPointGeo.x(), impactPointGeo.y(), elevation));
seRef.zPlacement(AesZPlacement_def::ZpDraped);
}
else
{
const QVector<QPointF> &singleExplosions = Polygons::explosionClusterSingleExplosionsGAUSS_Mtp(m_data, m_temporaryData.calculatedMhpRotationNorthDeg);
int explosionIndex = 0;
singleEffectsRef.reserve(singleExplosions.size());
for (const QPointF &singleExplosion : std::as_const(singleExplosions))
{
SingleEffect se;
se.singleEffectState(AesSingleEffectState_def::SesScheduled);
// se.position(Core::GeoPosition(singleExplosion.x(), singleExplosion.y(), 0.0));
const double elevation = getElevationFor(singleExplosion, QStringLiteral("getElevationForSingleEffect"));
se.position(Core::GeoPosition(singleExplosion.x(), singleExplosion.y(), elevation));
se.zPlacement(AesZPlacement_def::ZpDraped);
// // se.effectTime();
singleEffectsRef.push_back(se);
++explosionIndex;
} // for
} // if (isVulcano())
}
}
else
{
auto dts = m_mainHandler->dds()->dts();
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions);
// up to s_maxTrajectoriesPerExplosionCluster are supported. One sample per trajectory.
const bool errorMsgIfSampleDoesNotExists = false;
dts->removeEffectsByKey(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
}
//
// GunToTargetTrajectory
//
// EsUnknown = 0,
// EsError = 255,
// EsExpired = 1,
// EsRemoved = 2,
// EsCreated = 4,
// EsPrepared = 5,
// EsActivated = 6,
// EsPaused = 7
// AtExplosionClusterGunToTargetLine is suppressed at map display, when main object changes to EsActivated
// So it does not have to be changed/published anymore.
// Especially helpful while restarting scvAes!
static QVector<AesEffectState_def::type> relevantEffStates = {
AesEffectState_def::EsCreated,
AesEffectState_def::EsPrepared,
AesEffectState_def::EsActivated, // 2026-05-27, N.Thoden: E01562-393: GTL auch bei EsFired anzeigen, daher auch bei EsActivated
AesEffectState_def::EsFired, // 2026-05-27, N.Thoden: E01562-393: GTL auch bei EsFired anzeigen
};
DEBUG_LOG(m_logger) << " AtExplosionClusterGunToTargetLine RELEVANT " << relevantEffStates.contains(effectState().underlying()) << ", id " << m_data.id;
if (isEcValid && isOriginatorParticipantIdProvided() && // no Originator -> no curve
relevantEffStates.contains(effectState().underlying()))
{
AesItemView aiv3(m_aivGunToTargetTrajectory);
aiv3.aesItemId(m_data.id);
aiv3.chargeStruct().chargeTypeId(m_data.chargeTypeId);
// aiv3.effectStart(0);
if (aiv3.form().empty())
{
const std::vector<Core::GeoPosition> &form = ObjectExplosionCluster::generateGunToTargetForm();
aiv3.form(form);
if (form.size() > 2)
{
QPolygonF polyGTL;
polyGTL.reserve(2);
polyGTL.push_back(QPointF(form.front().lon(), form.front().lat()));
polyGTL.push_back(QPointF(form.back().lon(), form.back().lat()));
const QRectF boundsGTL(polyGTL.boundingRect());
aiv3.boundingBoxBottomLeft(Core::GeoPosition2D(boundsGTL.topLeft().x(), boundsGTL.topLeft().y())); // QRectF's bigger values are at bottom
aiv3.boundingBoxTopRight(Core::GeoPosition2D(boundsGTL.bottomRight().x(), boundsGTL.bottomRight().y()));
}
}
aiv3.aesTypeId(AesType::AtExplosionClusterGunToTargetLine);
aiv3.id(1000 * m_data.id + AesType::AtExplosionClusterGunToTargetLine);
// aiv3.effectStateId(effectState());
if (m_data.partyId != 0)
aiv3.partyId(m_data.partyId);
if (m_aivGunToTargetTrajectoryDirty || (aiv3 != m_aivGunToTargetTrajectory))
{
m_aivGunToTargetTrajectoryDirty = false;
m_aivGunToTargetTrajectory = aiv3;
aiv3.sampleTime(partitionTimeMSec()); // publish sampleTime, but do not store
m_mainHandler->dds()->dts()->publish(aiv3);
emit newAiv(aiv3);
}
}
else
{
auto dts = m_mainHandler->dds()->dts();
const bool errorMsgIfSampleDoesNotExists = false;
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionClusterGunToTargetLine, errorMsgIfSampleDoesNotExists);
}
// if (!setupParticipantOrigin())
// ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " setupParticipantOrigin failed";
//--------------------------------
if (aiv != m_aiv)
{
m_aiv = aiv;
aiv.sampleTime(partitionTimeMSec()); // publish sampleTime, but do not store
m_mainHandler->dds()->dts()->publish(aiv);
emit newAiv(aiv);
}
// Don not publish ideal positions, wait for DetonationLocationReply
// Evtl. von m_mainHandler->dds()->cesFtc() abhaengig machen!?
// Trajectory will be generated later at checkNextSingleExplosion()
}
void ObjectExplosionCluster::removeTopics()
{
// Unfortunately removeTopics() will generate about 1000 ERROR_...
// Who knows a better way?
const auto &dts = m_mainHandler->dds()->dts();
const bool errorMsgIfSampleDoesNotExists = false;
dts->removeExplosionClusterByKey(m_data.id, errorMsgIfSampleDoesNotExists);
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionCluster, errorMsgIfSampleDoesNotExists);
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionClusterGunToTargetLine, errorMsgIfSampleDoesNotExists);
// ti.id(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions);
dts->removeTriggeredImpactsByKey(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
#ifdef USE_TRAJECTORY_VIA_HANDLER
// remove TrajectoryTimeAware created via Handler:
// topicIds are stored at m_trajectoryHandlerExplosionIndexToTopicId
for (int topicId : std::as_const(m_trajectoryHandlerExplosionIndexToTopicId))
{
dts->removeTrajectoryTimeAwareByKey(topicId);
}
#else
// up to s_maxTrajectoriesPerExplosionCluster are supported. One sample per trajectory.
for (int explosionIndex = 0; explosionIndex < AreaEffectSimulation::s_maxTrajectoriesPerExplosionCluster; ++explosionIndex)
{
dts->removeAesItemViewByKey(1000 * m_data.id + 100 + explosionIndex, errorMsgIfSampleDoesNotExists);
}
#endif // USE_TRAJECTORY_VIA_HANDLER
// Effects / Impacts
// m_effects.id (1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions)
dts->removeEffectsByKey(1000 * m_data.id + AesType::AtExplosionClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
emit removeAiv(m_data.id);
}
// void ObjectExplosionCluster::startSingleExplosionHandling()
//{
// const qint64 impactDeltaMSecValue = impactDeltaMSec();
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Starting explosion handling"
// << ", impactDeltaMSec = " << impactDeltaMSecValue;
// DUMP();
// if (!m_data.effectStart.isValid()) // 0 -> paused
// {
// // trim to second (really? yes since we can only enter seconds)
// m_data.effectStart = partitionTime();
// m_publishDataTimer->start();
// }
// m_singleExplosionTriggerTimer->setInterval(impactDeltaMSecValue);
// m_singleExplosionTriggerTimer->start();
// checkNextSingleExplosion(); // for second 0
//}
// void ObjectExplosionCluster::stopSingleExplosionHandling(AesEffectState newState)
//{
// m_singleExplosionTriggerTimer->stop();
// if (m_data.effectStateId != AesEffectState_def::EsActivated)
// return;
// // int chargeCountUntilLastStart = lrint((m_data.originalEffectDuration - m_data.effectDuration) / impactDeltaMSec());
// int chargeCountUntilLastStart = m_chargeCountUntilLastStart;
// qint64 msecsSinceLastStart = m_data.effectStart.msecsTo(partitionTime());
// int impactCount = m_data.impactCount;
// int curExplosionNumber = m_lastSentTriggeredExplosionNumber; // keep before modification !? Here -1 because we want to consume the proper amount of charge!
// if (curExplosionNumber > impactCount)
// curExplosionNumber = impactCount;
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " impactCount: " << impactCount;
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " chargeCountUntilLastStart: " << chargeCountUntilLastStart;
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " curExplosionNumber: " << curExplosionNumber;
// m_data.effectDuration -= msecsSinceLastStart;
// DEBUG_LOG(m_logger) << "stopSingleExplosionHandling: effectDuration = " << m_data.effectDuration;
// // ToDo
// if (newState == AesEffectState::EsExpired)
// {
// m_data.effectDuration = m_data.originalEffectDuration;
// m_data.effectStart = m_data.originalEffectStart;
// }
// Core::FunctionReplyObject ret = m_mainHandler->chargeAdministrationHandler()->consumeCharge(m_data.chargeTypeId,
// m_data.partyId,
// curExplosionNumber - chargeCountUntilLastStart);
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Charge marked used in last lap: " << curExplosionNumber - chargeCountUntilLastStart;
// if (ret.error())
// ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Could not consume Charge! " << ret();
// return; // avoid crash with sql_janitor ToDo: implement pause / resume
// // ToDo
// m_chargeCountUntilLastStart = curExplosionNumber;
// if (m_data.effectDuration < 0 || newState == AesEffectState::EsExpired)
// {
// DEBUG_LOG(m_logger) << "Expired!";
// m_data.effectDuration = m_data.originalEffectDuration;
// m_data.effectStart = m_data.originalEffectStart;
// }
// else // Paused
// m_data.effectStart = QDateTime(); // Invalid QDateTime to mark it paused
// m_publishDataTimer->start();
//}
void ObjectExplosionCluster::checkNextFire()
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " XXXXXXXXXXXXXXXXXXXX checkNextFire";
// For special purpose (db guez_241121)
// if (m_data.name.startsWith(QStringLiteral("SAAB")))
// return;
if (m_data.effectStateId == AreaEffectSimulation::AesEffectState_def::EsCreated)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " OBJECT IS EsCreated, RETURN;";
return;
}
m_preStartTimer->stop();
// object should NOT be modified anymore:
if (m_temporaryData.isObjectModifiable)
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " OBJECT IS NOT Modifiable ANYMORE";
m_temporaryData.isObjectModifiable = false;
onPublishData();
if (isVulcano())
{
// copy data from GTL:
// const TracjectoryData tdRefGTL = m_trajectoryCont[-1];
// TracjectoryData &tdRefDest = m_trajectoryCont[0];
// // // tdRefDest.index
// tdRefDest.peakHeightAbs = tdRefGTL.peakHeightAbs;
// tdRefDest.trajectoryGeometry = tdRefGTL.trajectoryGeometry;
// tdRefDest.trajectoryTimeAware = tdRefGTL.trajectoryTimeAware;
const TrajectoryGeneratorVulcano::Options opts = TrajectoryGeneratorVulcano::OptRasterApoTap | TrajectoryGeneratorVulcano::OptIncludeIdealHit
// | TrajectoryGeneratorVulcano::OptRasterTapHit
;
QString reasonOutCheckNextFire;
generateTrajectoryVulcanoSpecific(1, opts, &reasonOutCheckNextFire); // 1 == explosionNumber
if (!reasonOutCheckNextFire.isEmpty())
ERROR_LOG(m_logger) << " " << m_data.id << " reasonOutCheckNextFire=" << reasonOutCheckNextFire;
publishTrajectory(0);
}
else
{
// real work:
QElapsedTimer elapsedTimer;
elapsedTimer.start();
// const int impactCount = m_data.impactCount;
// for (int explosionNumber=1; explosionNumber<=impactCount; ++explosionNumber) // explosionNumber start with '1'
// generateTrajectory(explosionNumber);
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " generateTrajectory for all (" << impactCount << ")"
// << " took " << elapsedTimer.elapsed() << " [ms]";
}
DUMP();
{
const bool initSuccess = m_firingTimer.init(m_data.effectDuration, m_data.impactCount, m_temporaryData.calculatedFlightDurationMsec);
if (!initSuccess)
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " firingTimer.init() failed";
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " firingTimer.init() initSuccess " << initSuccess;
const qint64 delay = activationTime().toMSecsSinceEpoch() - m_temporaryData.calculatedFlightDurationMsec - partitionTimeMSec();
if (noTracjectoryRequired())
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " startFiring (no delay)";
// // connect(&m_firingTimer, &FiringTimer::firing, this, &ObjectExplosionCluster::onFiringTimerFiring, Qt::UniqueConnection);
connect(&m_firingTimer, &FiringTimer::impact, this, &ObjectExplosionCluster::onFiringTimerImpact, Qt::UniqueConnection);
// // connect(&m_firingTimer, &FiringTimer::stateChangeDelayedAsInt,
// // this, &ObjectExplosionCluster::onFiringTimerStateChangeDelayedCommandAsInt, Qt::UniqueConnection);
m_firingTimer.startFiring();
}
else
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " trigger startFiringDelay, delay " << delay;
connect(&m_firingTimer, &FiringTimer::firing, this, &ObjectExplosionCluster::onFiringTimerFiring, Qt::UniqueConnection);
connect(&m_firingTimer, &FiringTimer::impact, this, &ObjectExplosionCluster::onFiringTimerImpact, Qt::UniqueConnection);
connect(&m_firingTimer, &FiringTimer::stateChangeDelayedAsInt, this, &ObjectExplosionCluster::onFiringTimerStateChangeDelayedCommandAsInt, Qt::UniqueConnection);
m_firingTimer.startFiringDelay(delay);
}
}
}
//// called every second
// void ObjectExplosionCluster::checkNextSingleExplosion()
//{
// return;
// const int curExplosionNumber = currentExplosionNumber();
// const int impactCount = m_data.impactCount;
// if (curExplosionNumber > impactCount)
// {
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " Remaining effect duration is zero - no more single explosions"
// << ", curExplosionNumber = " << curExplosionNumber
// << ", impactCount = " << impactCount
// << ", m_lastSentTriggeredExplosionNumber = " << m_lastSentTriggeredExplosionNumber;
// m_singleExplosionTriggerTimer->stop();
// m_data.effectDuration = m_data.originalEffectDuration;
// m_data.effectStart = m_data.originalEffectStart;
// m_publishDataTimer->start();
// }
// else
// {
// // check if a new explosion is triggered
// if (curExplosionNumber == m_lastSentTriggeredExplosionNumber)
// return; // nothing changed -> do not publish anything
// m_lastSentTriggeredExplosionNumber = curExplosionNumber;
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " checkNextSingleExplosion: curExplosionNumber = " << curExplosionNumber;
// DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " checkNextSingleExplosion: m_lastSentTriggeredExplosionNumber = " << m_lastSentTriggeredExplosionNumber;
// // publish new triggered explosions data here
// // moved to onDetonationLocationReply
// const int curExplosionIndex = curExplosionNumber-1;
// // Effects:
// std::vector<AreaEffectSimulation::SingleEffect> &singleEffectsRef = m_effects.singleEffects();
// if (singleEffectsRef.empty())
// {
// WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " singleEffectsRef empty, curExplosionNumber" << curExplosionNumber;
// }
// else
// {
// bool bChanged = false;
// const qint64 curPartTime = partitionTimeMSec();
// for(int idx = 0; idx <= curExplosionIndex; ++idx) // idx !! <= !! curExplosionIndex
// {
// if (idx >= static_cast<int>(singleEffectsRef.size()))
// continue;
// SingleEffect &ref = singleEffectsRef[idx];
// if (ref.singleEffectState().underlying() != AesSingleEffectState_def::SesTriggered)
// {
// ref.singleEffectState(AesSingleEffectState_def::SesTriggered);
// ref.singleEffectTime(curPartTime);
// bChanged = true;
// }
// } // for
// if (bChanged)
// {
// m_effects.sampleTime(curPartTime);
// m_mainHandler->dds()->dts()->publish(m_effects);
// }
// } // if (singleEffectsRef.empty())
// }
//}
void ObjectExplosionCluster::onFiringTimerFiring(int currentFiringNumber, int totalAmmoCount)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " currentFiringNumber = " << currentFiringNumber << " totalAmmoCount = " << totalAmmoCount;
Core::FunctionReplyObject ret = m_mainHandler->chargeAdministrationHandler()->consumeCharge(m_data.chargeTypeId, m_data.partyId, 1);
if (ret.error())
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " consumeCharge failed: " << ret.translated();
generateTrajectory(currentFiringNumber);
}
void ObjectExplosionCluster::onFiringTimerImpact(int currentExplosionNumber, int totalAmmoCount)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " currentExplosionNumber = " << currentExplosionNumber << " totalAmmoCount = " << totalAmmoCount;
m_lastSentTriggeredExplosionNumber = currentExplosionNumber;
const int curExplosionIndex = currentExplosionNumber - 1;
// Effects:
std::vector<AreaEffectSimulation::SingleEffect> &singleEffectsRef = m_effects.singleEffects();
if (singleEffectsRef.empty())
{
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " singleEffectsRef empty, currentExplosionNumber" << currentExplosionNumber;
}
else
{
bool bChanged = false;
const qint64 curPartTime = partitionTimeMSec();
for (int idx = 0; idx <= curExplosionIndex; ++idx) // idx !! <= !! curExplosionIndex
{
if (idx >= static_cast<int>(singleEffectsRef.size()))
continue;
SingleEffect &ref = singleEffectsRef[idx];
if (ref.singleEffectState().underlying() != AesSingleEffectState_def::SesTriggered)
{
ref.singleEffectState(AesSingleEffectState_def::SesTriggered);
ref.singleEffectTime(curPartTime);
bChanged = true;
}
} // for
if (bChanged)
{
m_effects.sampleTime(curPartTime);
m_mainHandler->dds()->dts()->publish(m_effects);
}
} // if (singleEffectsRef.empty())
}
void ObjectExplosionCluster::onFiringTimerStateChangeDelayedCommandAsInt(int cmdAsInt, int firedAmmoCount)
{
const FiringTimer::CMD cmd = static_cast<FiringTimer::CMD>(cmdAsInt);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " got signal stateChangeDelayed, delay " << cmd << ", firedAmmoCount = " << firedAmmoCount;
// if (effectState() != AesEffectState::EsPrepared &&
// effectState() != AesEffectState::EsPaused)
// {
// DEBUG_LOG(m_logger) << id() << " trySwitchToActivated failed - not prepared/paused: " << effectState();
// return false;
// }
// if (!isActivationTimeReached())
// {
// DEBUG_LOG(m_logger) << id() << " trySwitchToActivated failed - activation time not reached: "
// << isActivationTimeSet() << " - " << time(nullptr) << " - " << activationTime().toSecsSinceEpoch();
// return false;
// }
// DEBUG_LOG(m_logger) << id() << " trySwitchToActivated succeeded";
// setEffectState(AesEffectState::EsActivated);
// return true;
AesEffectState newState = AesEffectState_def::EsUnknown;
switch (cmd)
{
case FiringTimer::CMD::CMD_START:
case FiringTimer::CMD::CMD_RESUME:
newState = AesEffectState_def::EsActivated;
m_data.effectStart = partitionTime();
break;
case FiringTimer::CMD::CMD_PAUSE:
{
newState = AesEffectState_def::EsPaused;
m_data.effectStart = QDateTime(); // Invalid QDateTime to mark it paused
//
// update remaining:
//
// calculate remaining duration:
if (firedAmmoCount >= 0)
{
const int remainingAmmoCount = m_data.impactCount - firedAmmoCount;
m_data.remainExplosionNumber = firedAmmoCount + 1; // more like _NEXT_ ExplosionNumber
m_data.effectDuration = remainingAmmoCount * m_firingTimer.intervallMsec();
}
// keep in mind:
// from .../Lts/Proxies/TrainingAreaBase/recv_from_dds_aes.cpp:
// int getSequelNumber(int startSequelNumber, int impactCount, qint64 originalEffectDurationMs, qint64 effectDurationMs)
// {
// const double delta_t = double(originalEffectDurationMs) / impactCount; // msecs between impacts
// const int impactsTriggered = impactCount - qRound(effectDurationMs / delta_t);
// for (int i = 1; i < impactsTriggered; ++i)
// startSequelNumber = (Math::AZ * startSequelNumber + Math::CZ) % Math::MZ;
// return startSequelNumber;
// }
// : calculate consumed duration
}
break;
case FiringTimer::CMD::CMD_EXPIRED_PLUS:
newState = AesEffectState_def::EsExpired;
DEBUG_LOG(m_logger) << "Expired!";
m_data.effectDuration = m_data.originalEffectDuration;
m_data.effectStart = m_data.originalEffectStart;
break;
case FiringTimer::CMD::CMD_UNDEFINED:
case FiringTimer::CMD::CMD_EXPIRED:
// noop
break;
} // switch
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " got signal stateChangeDelayed, setNewState " << newState
<< ", m_data.effectDuration = " << m_data.effectDuration << ", m_data.remainExplosionNumber (nextExplosion) = " << m_data.remainExplosionNumber;
if (newState != AesEffectState_def::EsUnknown)
setEffectState(newState);
}
double ObjectExplosionCluster::getElevationFor(const QPointF &originGeoRadiant, const QString &context) const
{
static LtsElevation::LtsElevationManager *eleMgr = nullptr;
if (!eleMgr)
eleMgr = getTrajectoryInstance(m_mainHandler)->getElevationManager();
double zValue = s_defaultZ_Value; // default Elevation for GUEZ / Altmark. Hopefully fine for other areas..
if (eleMgr)
zValue = eleMgr->elevation(originGeoRadiant.x(), originGeoRadiant.y()); // double lon_rad, double lat_rad
else
{
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " no ElevationManager, assuming '100', context='" << context << "'"
<< " zValue= " << zValue;
}
if (zValue < -300)
{
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " elevation for originGeoRadiant '" << originGeoRadiant << "' is really small: " << zValue
<< ", assuming '100', context='" << context << "'";
zValue = s_defaultZ_Value;
}
return zValue;
}
bool ObjectExplosionCluster::generateTrajectory(int explosionNumber, bool startDetonationLocationRequest, QString *reasonOut)
{
// generate Trajectory:
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START generate Trajectory: explosionNumber = " << explosionNumber
<< ", startDetonationLocationRequest=" << startDetonationLocationRequest;
// Q_ASSERT(isVulcano() == false);
const int zoneOfImpactCoord = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.impactPosition).zone;
QPointF endPosition_geo2D;
const int explosionIndex = explosionNumber - 1;
bool bIsGunToTargetPreview = false;
if (explosionNumber <= 0)
{
bIsGunToTargetPreview = true;
// center of target:
endPosition_geo2D = AreaEffectSimulation::Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, m_data.rotationAngle);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " *** generateTrajectory to: center"
<< ", endPosition_geo2D = " << endPosition_geo2D;
}
else
{
// take impactPosition from form:
const std::vector<Core::GeoPosition> &formRef = m_aiv2.form();
Q_ASSERT(formRef.size() > 0);
Q_ASSERT(explosionIndex >= 0);
const Core::GeoPosition &impactPosition_geo3D = formRef[explosionIndex];
endPosition_geo2D = QPointF(impactPosition_geo3D.lon(), impactPosition_geo3D.lat());
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " *** generateTrajectory to: explosionIndex=" << explosionIndex
<< ", endPosition_geo2D = " << endPosition_geo2D;
}
// generate 'ideal' trajectory geometry, will be adjusted via reply and adjustTrajectoryToDetonationLocation
// save to m_trajectoryCont:
TracjectoryData &tdRef = m_trajectoryCont[explosionIndex];
tdRef.index = explosionIndex;
// reduce input values
// in: a) explosionCluster.peakHeight
// b) explosionCluster.flightDuration[msec] and LowOrHigh
// reduce to angle: to support generateTrajectoryBiedziak
const LtsCoordTrans::UtmCoordinate &endPosUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(endPosition_geo2D, zoneOfImpactCoord);
// add z-Value:
const double zValue = getElevationFor(m_data.impactPosition, QString(QStringLiteral("impactPosition_explosionIndex_%1")).arg(explosionIndex));
const LtsUtils::QPoint3D endPosUtmPoint3D = LtsUtils::QPoint3D(endPosUtm.x, endPosUtm.y, zValue);
double betaRadiant = 0; // elevation_angle_degree
double vOut = 0;
bool success = true;
QString reason, reasonTraj;
// firingOrigin.... should have an absolute zValue:
// which cannon?
LtsUtils::QPoint3D firingOriginGeoPoint3D;
LtsUtils::QPoint3D firingOriginUtmPoint3D;
if (bIsGunToTargetPreview)
{
firingOriginGeoPoint3D = m_data.firingCenterPosition;
firingOriginUtmPoint3D = m_temporaryData.m_firingUnitOriginUtmPoint3D;
}
else
{
firingOriginGeoPoint3D = getFiringCannonGeoPosition(explosionIndex);
firingOriginUtmPoint3D = getFiringCannonUtmPosition(explosionIndex, zoneOfImpactCoord);
}
// Which data is given by user? UI::peakHeight is relative to firingPosition.z()
if (m_data.peakHeight > 0)
{
tdRef.peakHeightAbs = m_temporaryData.m_firingUnitOriginUtmPoint3D.z() + m_data.peakHeight; // checked: GUEZE-2310
// For flightDurationSec the center should be used. (FlightDuration should be identical for all trajectories
tdRef.flightDurationMSec
= TrajectoryHelper::flightDurationSec(m_temporaryData.m_firingUnitOriginUtmPoint3D, endPosUtmPoint3D, tdRef.peakHeightAbs, &betaRadiant, &vOut, &reasonTraj)
* 1000;
if (tdRef.flightDurationMSec <= 0 || !reasonTraj.isEmpty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", got flightDurationSecError: "
<< ", explosionIndex=" << explosionIndex << ", m_temporaryData.m_firingUnitOriginUtmPoint3D = " << m_temporaryData.m_firingUnitOriginUtmPoint3D
<< ", m_data.peakHeight (relToFiring) " << m_data.peakHeight << ", tdRef.peakHeightAbs " << tdRef.peakHeightAbs
<< ", endPosUtmPoint3D = " << endPosUtmPoint3D;
success = false;
reason = QString(QStringLiteral("(flightDurationSecError (%1;%2))")).arg(tdRef.flightDurationMSec).arg(reasonTraj);
}
}
else if (m_data.flightDuration > 0)
{
tdRef.flightDurationMSec = m_data.flightDuration;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", isAirSupportSource = " << m_isAirSupportSource;
if (m_isAirSupportSource)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", endPosUtmPoint3D = " << endPosUtmPoint3D;
// NASAT: firingCenterPosition is position of AirCraft, means apogaeum:
const LtsCoordTrans::UtmCoordinate &firingOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.firingCenterPosition.toPointF(), zoneOfImpactCoord);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", peak==NASAT = " << firingOriginUtm;
const LtsUtils::QPoint3D apogaeum(firingOriginUtm.x, firingOriginUtm.y, m_data.firingCenterPosition.z());
const LtsUtils::QPoint3D endPoint3D = endPosUtmPoint3D;
const LtsUtils::QPoint3D realStartPointUTM = TrajectoryHelper::realStartPoint(apogaeum, endPoint3D);
// and now let that be used for Trajectory...:
{
LtsCoordTrans::UtmCoordinate temp(zoneOfImpactCoord, realStartPointUTM.x(), realStartPointUTM.y());
QPointF tempGeo = temp.geographic();
firingOriginUtmPoint3D = realStartPointUTM;
firingOriginGeoPoint3D = LtsUtils::QPoint3D(tempGeo.x(), tempGeo.y(), realStartPointUTM.z());
}
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", XXXXX endPoint3D = " << endPoint3D << ", apogaeum = " << apogaeum
<< ", realStartPointUTM = " << realStartPointUTM;
}
tdRef.peakHeightAbs
= TrajectoryHelper::peakHeightAbs(firingOriginUtmPoint3D, endPosUtmPoint3D, m_data.flightDuration / 1000.0, /*trajectoryMode,*/ &betaRadiant, &vOut, &reasonTraj);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", XXXXX tdRef.peakHeightAbs = " << tdRef.peakHeightAbs;
if ((tdRef.peakHeightAbs < -1000) || (tdRef.peakHeightAbs > 400000) || !reasonTraj.isEmpty()) // Beginn der Atmosphaere: 400-700km
{
success = false;
reason = QString(QStringLiteral("(peakHeightAbs outOfRange (%1))")).arg(reasonTraj);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", got peakHeight with error: "
<< ", reason ='" << reason << "'"
<< ", explosionIndex=" << explosionIndex << ", m_data.peakHeight (relToFiring) " << m_data.peakHeight
<< ", m_firingUnitOriginUtmPoint3D = " << m_temporaryData.m_firingUnitOriginUtmPoint3D << ", endPosUtmPoint3D = " << endPosUtmPoint3D;
}
} // if (m_data.peakHeight > 0)
if (bIsGunToTargetPreview)
{
m_temporaryData.calculatedFiringSpeed = vOut;
m_temporaryData.calculatedFlightDurationMsec = tdRef.flightDurationMSec;
m_temporaryData.calculatedPeakHeightAbs = tdRef.peakHeightAbs;
{
// calculate mhpRotation using UTM and identical zone!
m_temporaryData.calculatedMhpRotationNorthDeg = getAngleDegreeNorth(m_temporaryData.m_firingUnitOriginUtmPoint3D.toPointF(), endPosUtmPoint3D.toPointF());
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", angleDegreeNorth = " << m_temporaryData.calculatedMhpRotationNorthDeg;
}
}
if (success)
{
const double elevation_angle_degree = betaRadiant * LtsCoordTrans::rad2deg();
if ((elevation_angle_degree < 0) || (elevation_angle_degree > 90))
{
success = false;
reason += QString(QStringLiteral("(elevation_angle_degree outOfRange (%1))")).arg(elevation_angle_degree);
}
if (success)
{
m_temporaryData.calculatedFiringAngle = elevation_angle_degree;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " XXXXX explosionIndex=" << explosionIndex << ", firingOriginGeoPoint3D " << firingOriginGeoPoint3D
<< ", firingOriginUtmPoint3D " << firingOriginUtmPoint3D << ", m_data.peakHeight (relToFiring) " << m_data.peakHeight
<< ", tdRef.peakHeightAbs " << tdRef.peakHeightAbs << ", m_data.flightDuration=" << m_data.flightDuration
<< ", endPosUtmPoint3D = " << endPosUtmPoint3D << " -> tdRef.flightDurationMSec = " << tdRef.flightDurationMSec
<< ", elevation_angle_degree = " << elevation_angle_degree << ", vOut = " << vOut;
// create data in place (tdRef.trajectoryGeometry)
static TrajectoryGenerator *trajectoryGenerator = getTrajectoryInstance(m_mainHandler);
success = trajectoryGenerator->generateTrajectoryBiedziak(firingOriginGeoPoint3D,
LtsUtils::QPoint3D(endPosition_geo2D.x(), endPosition_geo2D.y(), zValue),
elevation_angle_degree,
tdRef.trajectoryGeometry);
// for NASAT wa can remove first half:
if (m_isAirSupportSource)
{
// Note: tdRef.trajectoryGeometry is not time-aware
// not really nice but: since pop_front is not available create a temorary partial copy;
// avoid start in den back of the AirCraft
std::vector<Core::GeoPosition> trajectoryGeometrySecondHalf(tdRef.trajectoryGeometry.begin() + (tdRef.trajectoryGeometry.size() / 2.0),
tdRef.trajectoryGeometry.end());
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " XXXXX explosionIndex=" << explosionIndex << " tdRef.trajectoryGeometry.size"
<< tdRef.trajectoryGeometry.size() << " tdRef.trajectoryGeometrySecondHalf.size" << trajectoryGeometrySecondHalf.size();
tdRef.trajectoryGeometry = trajectoryGeometrySecondHalf;
}
if (!success)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " ERROR *** trajectoryGenerator->generateTrajectory failed, explosionIndex=" << explosionIndex;
reason += QStringLiteral("(generateTrajectory failed)");
}
Core::GeoPosition first;
if (!tdRef.trajectoryGeometry.empty())
first = tdRef.trajectoryGeometry.front();
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " *** generateTrajectory, explosionIndex=" << explosionIndex << " DONE"
<< ", trajectory.size = " << tdRef.trajectoryGeometry.size() << ", first = " << first;
if (startDetonationLocationRequest)
{
// Schnittpunktberechnung nur bei bei zentralen Wirkfeldern
if (m_data.isCentral)
{
// start DetonationLocation request
const DetonationLocationRequest &request = detonationLocationRequest(explosionIndex);
m_mainHandler->dds()->cesFtc()->detonationLocationRequest(request, this);
}
else
{
DetonationLocationReply reply;
reply.id(m_data.id);
reply.index(explosionIndex);
// reply.position(); // leave on default :-)
onDetonationLocationReply(reply);
}
// reply is processed at onDetonationLocationReply
}
} // if (success)
} // if (success)
if (reasonOut)
*reasonOut = reason;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END generate Trajectory: explosionNumber = " << explosionNumber
<< ", startDetonationLocationRequest=" << startDetonationLocationRequest;
return success;
}
bool ObjectExplosionCluster::generateTrajectoryVulcanoSpecific(int explosionNumber, const TrajectoryGeneratorVulcano::Options options, QString *reasonOut)
{
// const int zoneOfImpactCoord = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.impactPosition).zone;
const int explosionIndex = explosionNumber - 1;
const bool bIsGunToTargetPreview = true;
// center of target:
const QPointF &endPosition_geo2D
= AreaEffectSimulation::Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, m_data.rotationAngle);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " *** generateTrajectory to: center"
<< ", endPosition_geo2D = " << endPosition_geo2D << ", explosionNumber=" << explosionNumber;
// save to m_trajectoryCont:
TracjectoryData &tdRef = m_trajectoryCont[explosionIndex];
tdRef.index = explosionIndex;
// which cannon?
LtsUtils::QPoint3D firingOriginGeoPoint3D;
// LtsUtils::QPoint3D firingOriginUtmPoint3D;
if (bIsGunToTargetPreview)
{
firingOriginGeoPoint3D = m_data.firingCenterPosition;
// firingOriginUtmPoint3D = m_temporaryData.m_firingUnitOriginUtmPoint3D;
}
else
{
firingOriginGeoPoint3D = getFiringCannonGeoPosition(explosionIndex);
// firingOriginUtmPoint3D = getFiringCannonUtmPosition(explosionIndex, zoneOfImpactCoord);
}
bool success = false;
QString localReasonOut;
// Which data is given by user? UI::peakHeight is relative to firingPosition.z()
if (m_data.peakHeight > 0)
{
const double v0 = TrajectoryGeneratorVulcano::defaultv0();
const double peakHeightRelToOrigin = m_data.peakHeight;
const double betaRad = XY::calculateBetaRad(v0, peakHeightRelToOrigin, &localReasonOut);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START" << " id/name= '" << m_data.name << "'/" << m_data.id << " IS_VULCANO = "
<< " betaDeg " << betaRad * LtsCoordTrans::rad2deg();
if (!localReasonOut.isEmpty())
{
ERROR_LOG(m_logger) << "generateTrajectoryVulcano failed, localReasonOut = " << localReasonOut;
if (reasonOut)
*reasonOut = localReasonOut;
return false;
}
const double alphaRad = m_data.dropAngle * LtsCoordTrans::deg2rad();
const double tapTime = TrajectoryGeneratorVulcano::defaultTapTimeSec();
m_data.launchAngleDeg = betaRad * LtsCoordTrans::rad2deg();
// const TrajectoryGeneratorVulcano::Options opts =
// TrajectoryGeneratorVulcano::OptRasterApoTap |
// TrajectoryGeneratorVulcano::OptIncludeIdealHit |
// TrajectoryGeneratorVulcano::OptRasterTapHit;
const QPoint3D &originatorPosition_geo = firingOriginGeoPoint3D;
// add z-Value:
const double zValue = getElevationFor(m_data.impactPosition, QString(QStringLiteral("impactPosition_explosionIndex_%1")).arg(explosionIndex));
const QPoint3D idealHit_geo(endPosition_geo2D.x(), endPosition_geo2D.y(), zValue);
static TrajectoryGenerator *trajectoryGenerator = getTrajectoryInstance(m_mainHandler);
Core::GeoPosition idealTapAbsoluteGeoOut;
success = trajectoryGenerator->generateTrajectoryVulcano(originatorPosition_geo,
idealHit_geo,
v0,
betaRad,
alphaRad,
tapTime,
options,
tdRef.trajectoryTimeAware.geometry(),
idealTapAbsoluteGeoOut,
&localReasonOut);
if (!localReasonOut.isEmpty())
{
ERROR_LOG(m_logger) << "generateTrajectoryVulcano failed, localReasonOut = " << localReasonOut;
if (reasonOut)
*reasonOut = localReasonOut;
return false;
}
if (!success)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START" << " id/name= '" << m_data.name << "'/" << m_data.id
<< " trajectoryGenerator->generateTrajectoryVulcano failed";
if (reasonOut)
*reasonOut = localReasonOut;
return false;
}
DEBUG_LOG(m_logger) << " cloudBase modification = " << m_data.cloudBase;
// honor cloudBase if valid:
if (m_data.cloudBase > std::numeric_limits<int>::min())
{
// get access to point tapT:
std::vector<Core::GeoPositionRelativeTime> &geometryRef = tdRef.trajectoryTimeAware.geometry();
std::vector<Core::GeoPositionRelativeTime>::reverse_iterator rit = geometryRef.rbegin();
const Core::GeoPositionRelativeTime impact = *rit;
// one step to point to tapT
++rit;
const Core::GeoPositionRelativeTime tapT = *rit;
const bool bTapT_strichRequested = (m_data.cloudBase < tapT.z()) && (impact.z() < m_data.cloudBase);
ERROR_LOG(m_logger) << " XXX cloudBase modification: "
<< ", tapT = " << tapT << ", m_data.cloudBase = " << m_data.cloudBase << ", impact = " << impact
<< ", bTapT_strichRequested = " << bTapT_strichRequested;
if (bTapT_strichRequested)
{
const LtsCoordTrans::UtmCoordinate impactUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(impact.lon(), impact.lat());
const LtsCoordTrans::UtmCoordinate tapTUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(tapT.lon(), tapT.lat(), impactUtm.zone);
const QVector3D impactVec3D(impactUtm.x, impactUtm.y, impact.z());
const QVector3D tapTVec3D(tapTUtm.x, tapTUtm.y, tapT.z());
/* x | Tap (aka TapT)
// \ |
// \ |
// \ |
// \ |
// -------x | CloudBase (Tap_strich)
// \ |
// \ |
// ----------x | Impact
//
// ----------------| Surface
*/
// Geometry
// aus Sicht von Impact
QVector3D offset = tapTVec3D - impactVec3D;
const double offset_z = offset.z();
const double scaleFactor = (static_cast<double>(m_data.cloudBase) - impactVec3D.z()) / offset_z;
const QVector3D scaledOffset = offset * scaleFactor;
const QVector3D tapT_strich = impactVec3D + scaledOffset;
// DEBUG_LOG(m_logger) << " offset_z " << offset_z;
// DEBUG_LOG(m_logger) << " scaleFactor " << scaleFactor;
// DEBUG_LOG(m_logger) << " scaledOffset " << scaledOffset;
// DEBUG_LOG(m_logger) << " tapTVec3D " << tapTVec3D;
// DEBUG_LOG(m_logger) << " offset " << offset;
// DEBUG_LOG(m_logger) << " tapT_strich " << tapT_strich;
// DEBUG_LOG(m_logger) << " impactVec3D " << impactVec3D;
const LtsCoordTrans::UtmCoordinate tapT_strichUtm(impactUtm.zone, tapT_strich.x(), tapT_strich.y());
const QPointF &tapT_strichGeo = tapT_strichUtm.geographic();
// Time
double timeOffset = impact.relativeTime() - tapT.relativeTime();
timeOffset *= scaleFactor;
Core::GeoPositionRelativeTime tapT_strichRelTime;
tapT_strichRelTime.lon() = tapT_strichGeo.x();
tapT_strichRelTime.lat() = tapT_strichGeo.y();
tapT_strichRelTime.z() = tapT_strich.z();
tapT_strichRelTime.relativeTime(impact.relativeTime() - timeOffset);
DEBUG_LOG(m_logger) << " tapT_strichRelTime " << tapT_strichRelTime;
// (.. n-4, n-3, n-2, n-1, n)
// ^
// Inserts value before pos:
auto it = geometryRef.end();
geometryRef.insert(--it, tapT_strichRelTime);
}
}
// "convert" from time-aware curve 'back' to time-unaware trajectoryGeometry
tdRef.trajectoryGeometry.clear();
for (const Core::GeoPositionRelativeTime &geoPosRelTime : std::as_const(tdRef.trajectoryTimeAware.geometry()))
tdRef.trajectoryGeometry.emplace_back(geoPosRelTime); // implicit // downcast
tdRef.peakHeightAbs = peakHeightRelToOrigin + originatorPosition_geo.z();
}
else
{
// For vulcano, peakHeight is required, not optional
localReasonOut = QString(QStringLiteral("peakHeight is invalid (%1)")).arg(m_data.peakHeight);
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START" << " id/name= '" << m_data.name << "'/" << m_data.id << " no peakHeight";
}
if (reasonOut)
*reasonOut = localReasonOut;
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END generate Trajectory: explosionNumber = " << explosionNumber;
return success;
}
bool ObjectExplosionCluster::getFiringUnitOrigin(const int firingParticipantId, LtsUtils::QPoint3D &firingOriginOut, QString *reasonOut)
{
// firingParticipantId is Feuereinheit/GeschuetzStellung
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START, firingParticipantId = " << firingParticipantId;
QString reason;
if (firingParticipantId <= 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", participantId invalid: " << firingParticipantId;
reason = QString(QStringLiteral("firingParticipantId is invalid (%1)")).arg(firingParticipantId);
if (reasonOut)
*reasonOut = reason;
return false;
}
std::shared_ptr<ParticipantManagement::Interface::Dtc> ppmDtc = m_mainHandler->dds()->ppmDtc();
if (!ppmDtc)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", ERROR, no ppmDtc";
reason = QString(QStringLiteral("firingParticipantId not found (%1)")).arg(firingParticipantId);
if (reasonOut)
*reasonOut = reason;
return false;
}
const bool isLeaf = ppmDtc->participant(firingParticipantId).inferiorIds().empty(); // isLeaf means single (Participant and not a Unit)
if (isLeaf)
{
const ParticipantManagement::ParticipantPosition &partPos = ppmDtc->participantPosition(firingParticipantId);
if (partPos.participantId() != firingParticipantId)
return false;
// relative zValue -> absolute zValue:
// Keep NASAT in mind sending 'height' via NasatPosition (normally, 'height' is relative to ground)
const double zValue = getElevationFor({partPos.pos().lon(), partPos.pos().lat()}, QString(QStringLiteral("participantPosition_partId=%1")).arg(firingParticipantId));
firingOriginOut = LtsUtils::QPoint3D(partPos.pos().lon(), partPos.pos().lat(), partPos.pos().z() + zValue);
}
else
{
// See also [GUEZE-2309]
// Using the ppmDtc->unitPosition has some drawbacks:
// The PPM::UnitPositionCalculator ignore outdated positions
// So in case of Replay/Snapshot/Restart of SV-SW the resulting position may differ
// So calculate on our own:
// const ParticipantManagement::UnitPosition &unitPos = ppmDtc->unitPosition(firingParticipantId);
// if (unitPos.participantId() != firingParticipantId)
// return false;
firingOriginOut
= Polygons::calculateFiringUnitCenterNonRecursive(m_mainHandler->dds()->ppmDtc(), firingParticipantId, m_temporaryData.singleFiringPositions, m_logger);
// set z-Value:
int idx = 0;
for (Core::GeoPosition &geoPosRef : m_temporaryData.singleFiringPositions)
{
const double zValue = getElevationFor({geoPosRef.lon(), geoPosRef.lat()}, QString(QStringLiteral("firingCannonPosition_index_%1")).arg(idx++));
geoPosRef.z(zValue);
}
if (m_logger.isDebugEnabled())
for (const Core::GeoPosition geoPosRef : m_temporaryData.singleFiringPositions)
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " singleFiringPositions = " << geoPosRef;
}
// and some checks:
if (std::isnan(firingOriginOut.x()) || std::isnan(firingOriginOut.y()))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " invalid position for firingParticipantId = " << firingParticipantId << ", firingOrigin is NaN";
reason = QString(QStringLiteral("firingParticipantId '%1' has invalid position (NaN)")).arg(firingParticipantId);
if (reasonOut)
*reasonOut = reason;
return false;
}
if (qFuzzyIsNull(firingOriginOut.x()) || qFuzzyIsNull(firingOriginOut.y()))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " invalid position for firingParticipantId = " << firingParticipantId << ", firingOrigin is 0";
reason = QString(QStringLiteral("firingParticipantId '%1' has invalid position (0,0)")).arg(firingParticipantId);
if (reasonOut)
*reasonOut = reason;
return false;
}
// const double zValue = getElevationFor(firingOriginOut.toPointF(), QString(QStringLiteral("firingParticipantId%1")).arg(firingParticipantId));
// firingOriginOut.setZ(zValue);
// check provided zValue; add elevation if neccessary:
// (e.g. NASAT aircrafts are already having a valid zValue, do not overwrite)
double zValue = firingOriginOut.z();
if (std::isnan(zValue) || qFuzzyIsNull(zValue))
{
zValue = getElevationFor(firingOriginOut.toPointF(), QString(QStringLiteral("firingParticipantId%1")).arg(firingParticipantId));
firingOriginOut.setZ(zValue);
}
m_temporaryData.m_firingUnitOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(firingOriginOut.toPointF());
m_temporaryData.m_firingUnitOriginUtmPoint3D = LtsUtils::QPoint3D(m_temporaryData.m_firingUnitOriginUtm.x, m_temporaryData.m_firingUnitOriginUtm.y, zValue);
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END, firingParticipantId = " << firingParticipantId << ", firingOriginOut = " << firingOriginOut
<< ", m_firingUnitOriginUtm = " << m_temporaryData.m_firingUnitOriginUtm.mgrs()
<< ", m_firingUnitOriginUtmPoint3D = " << m_temporaryData.m_firingUnitOriginUtmPoint3D;
return true;
}
bool ObjectExplosionCluster::firingOriginIsValid(const LtsUtils::QPoint3D &firingOrigin, QString *reasonOut) const
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START, originatorOrigin=" << firingOrigin;
// check if valid
// Maybe in future we will check also the distance to the target
if (std::isnan(firingOrigin.x()) || std::isnan(firingOrigin.y()) || std::isnan(firingOrigin.z()))
{
if (reasonOut)
*reasonOut = QStringLiteral("originatorOrigin isNaN");
TRACE_LOG(m_logger) << LOGFUNCTION << " END, originatorOrigin isNaN, " << firingOrigin;
return false;
}
if (firingOrigin.isNull())
{
if (reasonOut)
*reasonOut = QStringLiteral("originatorOrigin isNull");
TRACE_LOG(m_logger) << LOGFUNCTION << " END, originatorOrigin isNull, " << firingOrigin;
return false;
}
TRACE_LOG(m_logger) << LOGFUNCTION << " END, originatorOrigin=" << firingOrigin;
return true;
}
LtsUtils::QPoint3D ObjectExplosionCluster::getFiringCannonGeoPosition(int explosionIndex) const
{
Core::GeoPosition geoPos = m_temporaryData.getFiringCannonGeoPosition(explosionIndex);
// in case of missing coordinates for single cannons (or ModuleTest):
if (std::isnan(geoPos.lon()) || std::isnan(geoPos.lat()) || std::isnan(geoPos.z()))
return m_data.firingCenterPosition;
return LtsUtils::QPoint3D(geoPos.lon(), geoPos.lat(), geoPos.z());
}
LtsUtils::QPoint3D ObjectExplosionCluster::getFiringCannonUtmPosition(int explosionIndex, int zoneToUse) const
{
const LtsUtils::QPoint3D &geoPoint3D = ObjectExplosionCluster::getFiringCannonGeoPosition(explosionIndex);
const LtsCoordTrans::UtmCoordinate &temp = LtsCoordTrans::UtmCoordinate::fromGeographic(geoPoint3D.toPointF(), zoneToUse);
return LtsUtils::QPoint3D(temp.x, temp.y, geoPoint3D.z());
}
std::vector<Core::GeoPosition> ObjectExplosionCluster::generateGunToTargetForm(QString *reasonOut)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START generateGunToTargetForm"
<< ", partyId: " << m_data.partyId << ", participantId: " << m_data.participantId << "---";
// origin has to be setup (via setupParticipantOrigin)
QString reason;
if (isVulcano())
{
TrajectoryGeneratorVulcano::Options opts =
// TrajectoryGeneratorVulcano::OptRasterApoTap |
TrajectoryGeneratorVulcano::OptIncludeIdealHit
// | TrajectoryGeneratorVulcano::OptRasterTapHit
;
generateTrajectoryVulcanoSpecific(0, opts, &reason); // explosionNumber = 0 -> explosionIndex = -1, means to center of area
}
else
{
generateTrajectory(0, false, &reason); // explosionNumber = 0 -> explosionIndex = -1, means to center of area
}
if (!reason.isEmpty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " reason: " << reason;
if (reasonOut)
*reasonOut = reason;
return {};
}
// explosionNumber = 0 -> explosionIndex = -1, means to center of area
const std::vector<Core::GeoPosition> &trajOut = m_trajectoryCont[-1].trajectoryGeometry;
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END, GunToTarget.size = " << trajOut.size();
return trajOut;
}
void ObjectExplosionCluster::publishTrajectory(int explosionIndex)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START publishTrajectory" << ", explosionIndex=" << explosionIndex;
if (m_mainHandler->dds()->ppmDtc()->saaConnectedState(m_data.partyId) == ParticipantManagement::ConnectState::Connected)
{
DEBUG_LOG(m_logger) << "publishTrajectory isSaaConnected => keine Trajectory verschicken!";
return;
}
if (!m_trajectoryCont.contains(explosionIndex))
{
ERROR_LOG(m_logger) << "unknown explosionIndex "
<< ", m_data.id = " << m_data.id << ", explosionIndex = " << explosionIndex;
return;
}
// enable access to TracjectoryData and add time-awarenes
TracjectoryData &tdRef = m_trajectoryCont[explosionIndex];
TrajectoryTimeAware &trajectoryTimeAwareRef = tdRef.trajectoryTimeAware;
trajectoryTimeAwareRef.id(1000 * m_data.id + 100 + explosionIndex);
trajectoryTimeAwareRef.sampleTime(partitionTimeMSec());
// [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId)
// trajectoryTimeAwareRef.aesItemId(m_data.id);
// trajectoryTimeAwareRef.aesTypeId(AesType::AtExplosionClusterSingleTrajectory);
trajectoryTimeAwareRef.index(explosionIndex);
if (isVulcano())
{
// Time:
trajectoryTimeAwareRef.trajectoryStartTime(m_data.effectStart.toMSecsSinceEpoch() - m_temporaryData.calculatedFlightDurationMsec);
#ifdef USE_TRAJECTORY_VIA_HANDLER
publishTrajectoryViaHandler(trajectoryTimeAwareRef);
#else
bool bPublished = m_mainHandler->dds()->dts()->publish(trajectoryTimeAwareRef);
if (!bPublished)
ERROR_LOG(m_logger) << LOGFUNCTION << " ERROR, publish failed" << m_data.id << " explosionIndex=" << explosionIndex
<< ", trajectoryStartTime = " << trajectoryTimeAwareRef.trajectoryStartTime()
<< ", geometry().size() = " << trajectoryTimeAwareRef.geometry().size();
#endif // USE_TRAJECTORY_VIA_HANDLER
}
else
{
// Time:
// use sample time since FiringTimer is initiates trajectory calculation
trajectoryTimeAwareRef.trajectoryStartTime(trajectoryTimeAwareRef.sampleTime());
const std::vector<Core::GeoPosition> &trajectoryGeometry = tdRef.trajectoryGeometry;
Core::GeoPosition first;
if (!trajectoryGeometry.empty())
first = trajectoryGeometry.front();
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " trajectoryGeometry=" << trajectoryGeometry.size() << ", first = " << first;
std::vector<Core::GeoPositionRelativeTime> &geometryRef = trajectoryTimeAwareRef.geometry();
geometryRef.clear();
geometryRef.reserve(trajectoryGeometry.size());
int idx = 0;
const float flightDurationMSecSeg = static_cast<float>(tdRef.flightDurationMSec) / (trajectoryGeometry.size() - 1); // enforce float calculation
#define REDUCED_DISTRIBUTION // [GUEZE-2462] Darstellung Flugparabel Steilfeuer
#ifdef REDUCED_DISTRIBUTION
const int trajSize = trajectoryGeometry.size();
for (const Core::GeoPosition &pos3D : std::as_const(trajectoryGeometry))
{
// if (!m_isAirSupportSource)
{
// High resolution at beginning to enable initial visualisation of trajectory at map display
if ((trajSize > 20) && (idx > 1))
{
// !m_isAirSupportSource:
// In der erste Haelfte der Kurve (Steigphase) nur jedes 3 item nutzen...
if ((!m_isAirSupportSource) && (idx < (trajSize / 2.0)) && (idx % 3)) // modulo 3, also nur jeden dritten nutzen
{
++idx;
continue;
}
// .. ansonsten (in der zweiten Haelfte / Fallphase) nur jedes 2 item nutzen... aber die letzten 10 direkt:
else if (idx < (trajSize - 10) && (idx % 2)) // modulo 2, also nur jeden zweiten nutzen
{
++idx;
continue;
}
} // if ((trajSize > 20) && (idx>1))
} // if (!m_isAirSupportSource)
geometryRef.emplace_back(Core::GeoPositionRelativeTime(pos3D.lon(), pos3D.lat(), pos3D.z(), idx * flightDurationMSecSeg));
++idx;
} // for
#else
for (const Core::GeoPosition &pos3D : std::as_const(trajectoryGeometry))
{
geometryRef.emplace_back(Core::GeoPositionRelativeTime(pos3D.lon(), pos3D.lat(), pos3D.z(), idx * flightDurationMSecSeg));
++idx;
} // for
#endif
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " explosionIndex=" << explosionIndex << " has flightDurationMSecSeg=" << flightDurationMSecSeg
<< ", startTime = " << trajectoryTimeAwareRef.trajectoryStartTime() << ", geometry().size() = " << trajectoryTimeAwareRef.geometry().size();
#ifdef USE_TRAJECTORY_VIA_HANDLER
publishTrajectoryViaHandler(trajectoryTimeAwareRef);
#else
bool bPublished = m_mainHandler->dds()->dts()->publish(trajectoryTimeAwareRef);
if (!bPublished)
ERROR_LOG(m_logger) << LOGFUNCTION << " ERROR, publish failed" << m_data.id << " explosionIndex=" << explosionIndex
<< " has flightDurationMSecSeg=" << flightDurationMSecSeg << ", startTime = " << trajectoryTimeAwareRef.trajectoryStartTime()
<< ", geometry().size() = " << trajectoryTimeAwareRef.geometry().size();
#endif // USE_TRAJECTORY_VIA_HANDLER
}
m_publisedTrajectoriesForDebug[explosionIndex] = QString(QStringLiteral("trajectoryPublished m_data.id=%1 - explosionIndex=%2 - trajectoryStarttime=%3 - size=%4"))
.arg(m_data.id)
.arg(explosionIndex)
.arg(trajectoryTimeAwareRef.trajectoryStartTime())
.arg(trajectoryTimeAwareRef.geometry().size());
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " add to m_publisedTrajectoriesForDebug " << m_publisedTrajectoriesForDebug[explosionIndex];
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " add to m_publisedTrajectoriesForDebug.size = " << m_publisedTrajectoriesForDebug.size();
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " XXXX 4 trajectoryTimeAware.geometry().size() = " << trajectoryTimeAwareRef.geometry().size()
// << ", bPublished=" << bPublished
;
}
void ObjectExplosionCluster::publishTrajectoryViaHandler(const TrajectoryTimeAware &trajectoryTimeAwareRef)
{
const int explosionIndex = trajectoryTimeAwareRef.index();
TrajectoryData trajData;
trajData.id = m_trajectoryHandlerExplosionIndexToTopicId.value(explosionIndex, -1); // default value of -1 mean, create
// [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId)
// trajData.aesItemId = m_data.id;
// trajData.aesTypeId = AesType::AtExplosionClusterSingleTrajectory;
trajData.index = explosionIndex;
trajData.originatorId = m_data.id; // m_data.id == aesItemId
trajData.originatorTypeId = ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation;
trajData.startTime = trajectoryTimeAwareRef.trajectoryStartTime();
trajData.geometry = trajectoryTimeAwareRef.geometry();
const int id = m_mainHandler->trajectoryHandler()->createUpdate(trajData);
DEBUG_LOG(m_logger) << LOGFUNCTION << "trajectoryHandler::createUpdate with id= " << id;
if (id < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " ERROR, publish failed" << m_data.id << " explosionIndex=" << explosionIndex
<< ", startTime = " << trajectoryTimeAwareRef.trajectoryStartTime() << ", geometry().size() = " << trajectoryTimeAwareRef.geometry().size();
}
// update internal lookup table:
m_trajectoryHandlerExplosionIndexToTopicId[explosionIndex] = id;
}
void ObjectExplosionCluster::dump(const char *file, int lineNo) const
{
// const int currentExplosionNumberVal = currentExplosionNumber();
DEBUG_LOG(m_logger) << "START ******* DUMP: id = " << m_data.id << ", name = '" << m_data.name << "' **************";
DEBUG_LOG(m_logger) << "START ******* DUMP: id = " << m_data.id << ", caller= " << file << ":" << lineNo;
DEBUG_LOG(m_logger) << "START ******* DUMP: id = " << m_data.id << ", this= " << this;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: ** isValid / annotationString ** = '" << m_temporaryData.annotationString << "'";
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: isCentral = " << m_data.isCentral;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: effectStateId = " << m_data.effectStateId;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: effectDuration = " << m_data.effectDuration;
const qint64 nowMsec = partitionTimeMSec();
const qint64 activationMsec = activationTime().toMSecsSinceEpoch();
const qint64 preStartMsec = activationMsec - m_temporaryData.estimatedMaxFlightDurationMSec;
const bool bReached = nowMsec >= preStartMsec;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: nowMsec = " << nowMsec << " - " << QDateTime::fromMSecsSinceEpoch(nowMsec).toString();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: activationMsec = " << activationMsec << " - " << QDateTime::fromMSecsSinceEpoch(activationMsec).toString();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: preStartMsec = " << preStartMsec << " - " << QDateTime::fromMSecsSinceEpoch(preStartMsec).toString();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: leftMsec = " << preStartMsec - nowMsec;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: bReached = " << bReached;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: originalEffectDuration = " << m_data.originalEffectDuration;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: originalEffectStart (Msec) = " << m_data.originalEffectStart.toMSecsSinceEpoch() << " - "
<< m_data.originalEffectStart.toString();
// DEBUG_LOG(m_logger) << m_data.id << ", DUMP: impactDeltaMSec() = " << impactDeltaMSec();
// DEBUG_LOG(m_logger) << m_data.id << ", DUMP: currentExplosionNumber() = " << currentExplosionNumberVal;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_lastSentTriggeredExplosionNumber = " << m_lastSentTriggeredExplosionNumber;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_publishDataTimer->isActive = " << m_publishDataTimer->isActive();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_singleExplosionTriggerTimer->isActive = " << m_singleExplosionTriggerTimer->isActive();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_preStartTimer->isActive = " << m_preStartTimer->isActive();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: impactCount = " << m_data.impactCount;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: chargeTypeId = " << m_data.chargeTypeId;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: impactRadius = "
<< m_mainHandler->dds()->pdmDtc()->impactRadius(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId);
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: impactEdgeLength = "
<< m_mainHandler->dds()->pdmDtc()->impactEdgeLength(m_data.chargeTypeId, m_data.igniterTypeId, m_data.igniterFunctionId);
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: firingParticipantId = " << m_data.participantId;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_data.firingGeoPosition = " << m_data.firingCenterPosition;
const LtsCoordTrans::UtmCoordinate &firingOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.firingCenterPosition.toPointF());
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_data.firingOriginUtm = " << firingOriginUtm;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_temporaryData.m_firingUnitOriginUtm = " << m_temporaryData.m_firingUnitOriginUtm;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_temporaryData.m_firingUnitOriginUtmPoint3D = " << m_temporaryData.m_firingUnitOriginUtmPoint3D;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_temporaryData.mhPoints.size() = " << m_temporaryData.mhPoints.size();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_temporaryData.mhpWidth X = " << m_temporaryData.mhpWidth;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_temporaryData.mhpLength Y = " << m_temporaryData.mhpLength;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_temporaryData.mhpRotation = " << m_temporaryData.mhpRotation;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: estimatedMaxFlightDurationMSec = " << m_temporaryData.estimatedMaxFlightDurationMSec;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: estimatedMaxFlightDurationSec = " << m_temporaryData.estimatedMaxFlightDurationMSec / 1000.0;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_data.peakHeight (relToFiring) = " << m_data.peakHeight;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_data.flightDuration = " << m_data.flightDuration;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: calculatedFiringAngle = " << m_temporaryData.calculatedFiringAngle;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: calculatedFiringSpeed = " << m_temporaryData.calculatedFiringSpeed;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: calculatedFlightDurationMsec = " << m_temporaryData.calculatedFlightDurationMsec;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: calculatedPeakHeightAbs = " << m_temporaryData.calculatedPeakHeightAbs;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_data.mhpRotationRequest = " << m_data.mhpRotationRequest;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: calculatedMhpRotationNorthDeg = " << m_temporaryData.calculatedMhpRotationNorthDeg;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: launchVelocity [m/s] = " << m_data.launchVelocity;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: launchAngleDeg = " << m_data.launchAngleDeg;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: dropAngle = " << m_data.dropAngle;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: tapTime/timepoint [msec] = " << m_data.tapTimepoint;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: lcAttack (lcPrf) = " << m_data.lcPrf;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: lcTermination = " << m_data.lcTermination;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: cloudBase = " << m_data.cloudBase;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: impactPosition (X/Y) = " << m_data.impactPosition.x() << "/" << m_data.impactPosition.y();
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: isObjectModifiable = " << m_temporaryData.isObjectModifiable;
if (m_trajectoryCont.contains(0))
{
const TracjectoryData &tdRef = m_trajectoryCont[0];
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: trajectoryCont[0].flightDuration= " << tdRef.flightDurationMSec;
}
QString trajectoryContKeys;
const QList<int> &keysCont = m_trajectoryCont.keys();
for (auto key : keysCont)
trajectoryContKeys.append(QString(QStringLiteral("%1;")).arg(key));
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_trajectoryCont.keys()= " << trajectoryContKeys;
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_publisedTrajectoriesForDebug.size = " << m_publisedTrajectoriesForDebug.size();
for (const auto &item : std::as_const(m_publisedTrajectoriesForDebug))
DEBUG_LOG(m_logger) << m_data.id << ", DUMP: m_publisedTrajectoriesForDebug = " << item;
DEBUG_LOG(m_logger) << "END ******* DUMP: id = " << m_data.id << ", name = '" << m_data.name << "' **************";
}
// qint64 ObjectExplosionCluster::impactDeltaMSec() const
//{
// const qint64 impactCount = m_data.impactCount; // double for calculation below
// if (impactCount <= 0)
// return 24 * 60 * 60 * 1000; // 1day - more or less invalid
// if (m_data.originalEffectDuration > 0)
// return m_data.originalEffectDuration / impactCount; // msecs between impacts
// else
// return 500; // In case of invalid (0) effect duration time, return 500 msecs.
// }
// int ObjectExplosionCluster::currentExplosionNumber() const
//{
// // Context ExplosionCluster Point geometry, single impact:
// // Sometimes trySwitchToActivated is executed wit a delay. (reason is unknown, most likely timing)
// // Then more than a half of the duration is already over!
// // Under that conditions 'lrint(completedEffectDuration / delta_t);' return 1, but 0 was assumed
// // lrint(0.1) -> 0
// // lrint(0.4) -> 0
// // lrint(0.5) -> 0
// // lrint(0.501) -> 1
// // See: 2024-06-12 09:22:34.558 DEBUG AES::ObjectExplosionCluster |#| Current explosion number is 1, ic: 1, dt: 1000, cd: 556.00, exd: 0.00, curD: 556.00, odur:
// 1000, edur: 1000, hasEffectStart: 1 (/AreaEffectSimulation/object_explosion_cluster.cpp:2505)
// // So the caller assumes that there is no exposion left! This wrong!
// if ((m_data.impactCount == 1) &&
// (m_lastSentTriggeredExplosionNumber <= 0) )
// {
// const int ret = 1; // 1 for proper bit setting
// DEBUG_LOG(m_logger) << "Current explosion number is " << ret;
// return ret;
// }
// const qint64 delta_t = impactDeltaMSec();
// const bool hasEffectStart = m_data.effectStart.isValid();
// const double curDuration = hasEffectStart ? partitionTimeMSec() - m_data.effectStart.toMSecsSinceEpoch() : 0; // exhaused between last start and now
// if (curDuration < 0) // effectStart not reached;
// return 0;
// const double exhausedDuration = m_data.originalEffectDuration - m_data.effectDuration; // exhaused till last start
// const double completedEffectDuration = exhausedDuration + curDuration;
// const int ret = lrint(completedEffectDuration / delta_t);
// DEBUG_LOG(m_logger) << "Current explosion number is " << ret
// << ", ic: " << m_data.impactCount
// << ", dt: " << delta_t
// << ", cd: " << QString::number(completedEffectDuration,'f', 2)
// << ", exd: " << QString::number(exhausedDuration,'f', 2)
// << ", curD: " << QString::number(curDuration,'f', 2)
// << ", odur: " << m_data.originalEffectDuration
// << ", edur: " << m_data.effectDuration
// << ", hasEffectStart: " << hasEffectStart
// ;
// return ret + 1; // start at 1 for proper bit setting
//}
DetonationLocationRequest ObjectExplosionCluster::detonationLocationRequest(int explosionIndex) const
{
DetonationLocationRequest req;
req.id(m_data.id); // m_data.id == AesItemId
req.index(explosionIndex);
req.chargeTypeId(m_data.chargeTypeId);
req.igniterTypeId(m_data.igniterTypeId);
req.igniterFunctionId(m_data.igniterFunctionId);
req.trajectory(m_trajectoryCont[explosionIndex].trajectoryGeometry);
return req;
}
void ObjectExplosionCluster::setAesProperty(QJsonObject property, bool pubish)
{
QJsonObject propertiesObject = QJsonDocument::fromJson(m_data.properties.toUtf8()).object();
// same as QJsonObject.merge
for (auto it = property.begin(); it != property.end(); ++it)
{
propertiesObject.insert(it.key(), it.value());
}
QJsonDocument doc(propertiesObject);
QByteArray propertiesArr = QJsonDocument(doc).toJson(QJsonDocument::Compact);
m_data.properties = QString::fromUtf8(propertiesArr);
if (pubish)
onPublishData();
}
QPolygonF ObjectExplosionCluster::generateForm() { return Polygons::explosionClusterArea(m_data); }
} // Namespace AreaEffectSimulation