3683 lines
157 KiB
C++
Executable File
3683 lines
157 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* ObjectMap for EffectCluster
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*/
|
|
|
|
#include "object_effect_cluster.h"
|
|
#include "Utils/polygons.h"
|
|
#include "handler_charge_administration.h"
|
|
#include "handler_charge_effect.h"
|
|
#include "handler_effector.h"
|
|
#include "object_charge_effect.h"
|
|
#include "object_effector.h"
|
|
#include "settings.h"
|
|
#include "special_area_provider.h"
|
|
|
|
#include <Trajectory/trajectory_generator.h>
|
|
#include <Trajectory/trajectory_generator_vulcano.h>
|
|
#include <Trajectory/trajectory_helper.h>
|
|
|
|
#include <TrajectorySplitter/trajectory_splitter.h>
|
|
|
|
#include <LtsCoordTrans/lts_coord_trans.h>
|
|
#include <LtsUtilsCore/dds_bitarray.h>
|
|
#include <Proxies/TrainingAreaBase/Plugins/AdlerJson/telegrams.h>
|
|
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QPolygonF>
|
|
#include <QTimer>
|
|
#include <QVector3D>
|
|
|
|
// ToDo: mark the end of aes enum and update following (check Endemarke)
|
|
static_assert((int)AreaEffectSimulation::AesType_def::AtEffectClusterGunToTargetLine < 100, "id may overlap at runtime");
|
|
|
|
using DetonationLocationRequest = CentralEffectSimulation::DetonationLocationRequest;
|
|
using DetonationLocationReply = CentralEffectSimulation::DetonationLocationReply;
|
|
|
|
//
|
|
// AreaEffectSimulation
|
|
//
|
|
|
|
namespace AreaEffectSimulation
|
|
{
|
|
|
|
inline std::vector<AreaEffectSimulation::EffectorData> toEffectorVec(const QList<Database::AesEffectorData> effectorDataVec)
|
|
{
|
|
if (effectorDataVec.size() == 0)
|
|
return {};
|
|
|
|
std::vector<AreaEffectSimulation::EffectorData> ret;
|
|
for (const auto &effectorData : effectorDataVec)
|
|
{
|
|
AreaEffectSimulation::EffectorData data;
|
|
data.angleOfFall() = effectorData.angleOfFall;
|
|
data.chargeAmount() = effectorData.chargeAmount;
|
|
data.chargeStruct().chargeTypeId() = effectorData.chargeTypeId;
|
|
data.flightTime() = effectorData.flightTime;
|
|
data.chargeStruct().igniterTypeId() = effectorData.igniterTypeId;
|
|
data.chargeStruct().igniterFunctionId() = effectorData.igniterFunctionId;
|
|
data.interval() = effectorData.interval;
|
|
data.mhpPosition().lat() = effectorData.mhpPosition.y();
|
|
data.mhpPosition().lon() = effectorData.mhpPosition.x();
|
|
data.mhpPosition().z() = effectorData.mhpPosition.z();
|
|
data.mhpRotation() = effectorData.mhpRotation;
|
|
data.mhpScatterLenght() = effectorData.mhpScatterLenght;
|
|
data.mhpScatterLengthDetonationHeight() = effectorData.mhpScatterLengthDetonationHeight;
|
|
data.mhpScatterWidth() = effectorData.mhpScatterWidth;
|
|
data.mhpScatterWidthDetonationHeight() = effectorData.mhpScatterWidthDetonationHeight;
|
|
data.participantId() = effectorData.participantId;
|
|
data.peakHeight() = effectorData.peakHeight;
|
|
data.trajectoryRelative() = TrajectoryRelative::fromString(effectorData.trajectory);
|
|
data.properties() = effectorData.properties.toStdString();
|
|
ret.push_back(data);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ObjectEffectCluster::ObjectEffectCluster(MainHandler *mainHandler, QObject *parent)
|
|
: ObjectBaseTmpl(mainHandler, QStringLiteral("EffectCluster"), parent)
|
|
, m_singleExplosionTriggerTimer(new QTimer(this))
|
|
, m_preStartTimer(new QTimer(this))
|
|
, m_automatonTimer(new QTimer(this))
|
|
, m_lastSentTriggeredExplosionNumber(-1)
|
|
, m_chargeCountUntilLastStart(0)
|
|
, m_explosionIndex(0)
|
|
{
|
|
// connect(m_singleExplosionTriggerTimer, &QTimer::timeout,
|
|
// this, &ObjectEffectCluster::checkNextSingleExplosion);
|
|
connect(m_preStartTimer, &QTimer::timeout, this, &ObjectEffectCluster::checkNextFire);
|
|
connect(m_automatonTimer, &QTimer::timeout, this, &ObjectEffectCluster::checkAutomaton);
|
|
|
|
m_automatonTimer->setInterval(500);
|
|
|
|
m_bSupportsPausing = true;
|
|
}
|
|
|
|
ObjectEffectCluster::~ObjectEffectCluster() { removeTopics(); }
|
|
|
|
bool ObjectEffectCluster::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 ObjectEffectCluster::isObjectModifiable() const
|
|
{
|
|
return ObjectBase::isObjectModifiable(); // ToDo: check: && m_temporaryData.isObjectModifiable
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::createObject(const Database::AesEffectCluster &data, const QVector<Database::AesEffectorData> &effectorDatas)
|
|
{
|
|
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) && data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER
|
|
&& data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE
|
|
&& (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;
|
|
|
|
// EffectClusters are always central
|
|
ret = ObjectBaseTmpl::createObject(data, false, true);
|
|
|
|
if (ret.error())
|
|
{
|
|
if (bDoClaimPlannedCharge)
|
|
m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, (-1) * data.impactCount);
|
|
return ret;
|
|
}
|
|
|
|
// If an EffectCluster 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 EffectCluster is marked invalid.
|
|
// But if the user re-enables the EffectCluster 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;
|
|
|
|
bool isRocket = m_mainHandler->dds()->pdmDtc()->isRocket(data.chargeTypeId);
|
|
|
|
// -- Module MHP
|
|
// If there are not MHP Positions then we generate them.
|
|
// normaly this will also start the trajectory module
|
|
// uses polygones to create mhp inside the given geometry
|
|
DEBUG_LOG(m_logger) << " start check MHP Mpdule";
|
|
auto effectorDataCopy = effectorDatas;
|
|
if (data.effectSourceId == ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE && !isRocket)
|
|
{
|
|
DEBUG_LOG(m_logger) << " start Generate";
|
|
effectorDataCopy = generateMHPs(effectorDatas, data.explosionGeometryId);
|
|
DEBUG_LOG(m_logger) << " end MHP Mpdule";
|
|
}
|
|
// -- end Module MHP
|
|
|
|
// setupFiringOrigin
|
|
if (bFromOpwUser && isOriginatorParticipantIdProvided())
|
|
{
|
|
Core::FunctionReplyObject retSFO = setupFiringOrigin();
|
|
m_temporaryData.annotationString = retSFO.translated().toStdString();
|
|
if (!isEffectClusterMarkedValid())
|
|
m_data.effectStart = QDateTime();
|
|
}
|
|
|
|
Core::FunctionReplyObject retCPC = checkPreConditionsComplete();
|
|
m_temporaryData.annotationString = retCPC.translated().toStdString();
|
|
|
|
if (!isEffectClusterMarkedValid())
|
|
m_data.effectStart = QDateTime();
|
|
|
|
if (data.id <= 0) // new, not read from db
|
|
{
|
|
m_data.originalEffectDuration = data.effectDuration;
|
|
m_data.originalEffectStart = data.effectStart;
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << " start check trajectory Module";
|
|
|
|
// -- start Module Trajectory
|
|
// If there are no Trajectories then we generate them.
|
|
// normaly this will normaly happen when there were no MHPs too.
|
|
// but could possibly used for other stuff
|
|
// uses function calls to TAB -> to generate Trajectores.
|
|
// might fail and end with no usable EffectCluster
|
|
if (data.effectSourceId == ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE && data.effectStateId != AesEffectState::EsExpired
|
|
&& data.effectStateId != AesEffectState::EsRemoved && !isRocket)
|
|
{
|
|
// if (m_mainHandler->dds()->ppmDtc()->isSaaConnected(m_data.partyId) && m_trajectoryCont.size() <= 0)
|
|
{
|
|
DEBUG_LOG(m_logger) << " start check trajectory Mpdule";
|
|
// NOT NOW
|
|
sendCalculateTrajectoryAdlerRequest(effectorDataCopy);
|
|
}
|
|
}
|
|
// -- END Module Trajectory
|
|
|
|
QVector<Database::AesEffectorData> finalEffectorDatas;
|
|
for (auto effectorData : effectorDataCopy)
|
|
{
|
|
if (effectorData.entityId == 0 && effectorData.id == 0) // need to create
|
|
{
|
|
effectorData.entityId = m_data.id;
|
|
if (!m_mainHandler->db()->insert(effectorData)) // ID will be set!
|
|
ERROR_LOG(m_logger) << "Error while inserting effector Data " << m_mainHandler->db()->getLastError().text();
|
|
}
|
|
finalEffectorDatas.append(effectorData);
|
|
}
|
|
|
|
for (auto effectorData : finalEffectorDatas)
|
|
DEBUG_LOG(m_logger) << "Created Object new effector " << effectorData.id << " for " << effectorData.entityId;
|
|
|
|
// if (data.effectStateId > AreaEffectSimulation::AesEffectState::EsExpired)
|
|
setEffectors(finalEffectorDatas);
|
|
|
|
if (!data.properties.isEmpty())
|
|
{
|
|
QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(data.properties.toStdString())).object();
|
|
m_data.isUsingEffectStateReceived = propertiesObject.value(QStringLiteral("useReceived")).toBool();
|
|
}
|
|
|
|
// cargeCounting
|
|
m_data.impactCount = 0;
|
|
for (const auto &effector : m_effectorData)
|
|
{
|
|
m_data.impactCount += effector.chargeAmount;
|
|
}
|
|
|
|
if (m_data.effectStateId != AesEffectState::EsCreated && m_data.effectStateId != AesEffectState::EsPrepared && m_data.effectStateId != AesEffectState::EsReceived)
|
|
m_firstTimeReady = false;
|
|
// set reaminExplosion = impactCount
|
|
m_data.remainExplosionNumber = m_data.impactCount;
|
|
|
|
// -- Module FireAutomaton
|
|
// If we are an internal Effect Cluster we need someone who will tell the Effectors
|
|
// for action
|
|
// this will be initatlizes here
|
|
|
|
// check if we have a firingType
|
|
QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(data.properties.toStdString())).object();
|
|
FiringType firingType = FiringType::ReportReadyToFire;
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSART()))
|
|
firingType = static_cast<FiringType::inner_enum>(probObject[AdlerJson::JsonKeys::ABFEUERUNGSART()].toInt());
|
|
|
|
if (data.effectSourceId == ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE && data.effectStateId != AesEffectState::EsExpired
|
|
&& data.effectStateId != AesEffectState::EsRemoved && firingType != FiringType::FireOnCommand)
|
|
{
|
|
bool automaInitOk = true;
|
|
if (m_fireAutomaton)
|
|
m_fireAutomaton->deleteLater();
|
|
m_fireAutomaton = new FireAutomaton(this);
|
|
|
|
const auto &gearTypeIds = m_mainHandler->dds()->pdmDtc()->gearTypeIdFromChargeTypeId(m_data.chargeTypeId);
|
|
int cadence = 35000;
|
|
if (gearTypeIds.size())
|
|
cadence = m_mainHandler->dds()->pdmDtc()->cadence(gearTypeIds.at(0));
|
|
DEBUG_LOG(m_logger) << "Fire Automaton cadence: " << cadence;
|
|
|
|
switch (firingType.underlying())
|
|
{
|
|
case FiringType::FireNow:
|
|
case FiringType::FireWithIndicationTOT:
|
|
case FiringType::FireWithIndicationTTF:
|
|
case FiringType::ReportReadyToFire:
|
|
{
|
|
if (m_data.effectDuration != DEFAULT_EFFECT_DURATION)
|
|
{
|
|
// use duration
|
|
const int maxRoundEffector = std::ceil((double)m_data.impactCount / m_effectorData.size());
|
|
|
|
// when using the effect duration, we need enough time to spread the volleys inside the duration.
|
|
// using multiple effectors, we can split the charges within the duration, but never faster than the cadence allowes
|
|
|
|
// so we will check if duration is at least cadence times impactCount
|
|
if (m_data.effectDuration <= ((cadence * maxRoundEffector) + 500)) // add some more time
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error while creating EffectCluster, duration not valid"));
|
|
|
|
DEBUG_LOG(m_logger) << "Update Init after new trajectory Fire Automaton with effect duration: " << m_data.effectDuration;
|
|
automaInitOk = m_fireAutomaton->init(m_data.effectDuration,
|
|
FireAutomaton::TimeType::Duration,
|
|
m_data.impactCount,
|
|
m_effectorData.keys().toVector(),
|
|
QStringLiteral("next*"));
|
|
}
|
|
else
|
|
{
|
|
// use cadence
|
|
DEBUG_LOG(m_logger) << "Init Fire Automaton with cadence: " << cadence;
|
|
automaInitOk = m_fireAutomaton->init(cadence, FireAutomaton::TimeType::Cadence, m_data.impactCount, m_effectorData.keys().toVector(), QStringLiteral("all*"));
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
if (!automaInitOk)
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error while creating EffectCluster, fire automaton not valid"));
|
|
|
|
// connects
|
|
// m_fireAutomaton->stop();
|
|
connect(m_fireAutomaton, &FireAutomaton::fire, this, QOverload<int>::of(&ObjectEffectCluster::sendFireCommand));
|
|
connect(m_fireAutomaton, &FireAutomaton::finished, this, &ObjectEffectCluster::onAutomatonFinished);
|
|
if (!data.effectStart.isNull())
|
|
m_automatonTimer->start();
|
|
}
|
|
|
|
// -- END Module FireAutomaton
|
|
|
|
DEBUG_LOG(m_logger) << "end of create Object";
|
|
return ret;
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::updateObject(const Database::AesEffectCluster &data, const QVector<Database::AesEffectorData> &effectorDatas)
|
|
{
|
|
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;
|
|
|
|
bool updateEffectCount = false;
|
|
// if (data.id == 0) // should not happen, maybe an assert here ?
|
|
// return ret;
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(data.properties.toStdString())).object();
|
|
FiringType firingType = FiringType::FireNow;
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSART()))
|
|
firingType = static_cast<FiringType::inner_enum>(probObject[AdlerJson::JsonKeys::ABFEUERUNGSART()].toInt());
|
|
// only allow some fields
|
|
Database::AesEffectCluster tmpData(m_data);
|
|
tmpData.id = std::abs(data.id);
|
|
tmpData.name = data.name;
|
|
tmpData.element = data.element;
|
|
|
|
if (firingType == FiringType::FireWithIndicationTTF && probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSZEIT()) && effectorDatas.at(0).flightTime > 0)
|
|
{
|
|
const double ttf_time = probObject[AdlerJson::JsonKeys::ABFEUERUNGSZEIT()].toDouble();
|
|
TRACE_LOG(m_logger) << "update ttf and effect start: ttf_time" << ttf_time << " efector flight time: " << effectorDatas.at(0).flightTime;
|
|
TRACE_LOG(m_logger) << "new ttf " << QDateTime::fromMSecsSinceEpoch((ttf_time * 1000)).toString();
|
|
TRACE_LOG(m_logger) << "effect start " << QDateTime::fromMSecsSinceEpoch((ttf_time * 1000) + effectorDatas.at(0).flightTime).toString();
|
|
|
|
tmpData.effectStart = QDateTime::fromMSecsSinceEpoch((ttf_time * 1000) + effectorDatas.at(0).flightTime);
|
|
}
|
|
else
|
|
tmpData.effectStart = data.effectStart;
|
|
tmpData.effectDuration = data.effectDuration;
|
|
|
|
tmpData.tla = data.tla;
|
|
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.fuseTypeId = data.fuseTypeId;
|
|
|
|
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.esmParticipantIds = LtsUtils::IntArray(data.esmParticipantIds());
|
|
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;
|
|
|
|
// // update effect state
|
|
// if (m_data.esmParticipantIds() != data.esmParticipantIds())
|
|
// data.esmParticipantIds().empty() ? tmpData.effectorState = AesEffectorState::EsReadyToFire : tmpData.effectorState = AesEffectorState::EsNotReadyToFire;
|
|
|
|
m_data.esmParticipantIds() = data.esmParticipantIds();
|
|
// chargeType update
|
|
if (effectorDatas.size() > 0)
|
|
{
|
|
tmpData.chargeTypeId = effectorDatas.at(0).chargeTypeId;
|
|
}
|
|
|
|
if (!m_mainHandler->db()->update(tmpData))
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating effect cluster with id %1 in database.")).arg(m_data.id);
|
|
QSet<int> oldIds;
|
|
QSet<int> newIds;
|
|
|
|
bool updateMHP = false;
|
|
|
|
DEBUG_LOG(m_logger) << " impactpos " << (m_data.impactPosition != data.impactPosition) << " impactLen " << (m_data.impactLength != data.impactLength) << " width "
|
|
<< (m_data.impactWidth != data.impactWidth);
|
|
if (data.effectSourceId == ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE
|
|
&& (m_data.impactPosition != data.impactPosition || m_data.impactLength != data.impactLength || m_data.impactWidth != data.impactWidth || m_data.tla != data.tla))
|
|
{
|
|
updateMHP = true;
|
|
}
|
|
|
|
int oldImpactCount = 0;
|
|
int newImpactCount = 0;
|
|
for (const auto &oldEffector : qAsConst(m_effectorData))
|
|
oldImpactCount += oldEffector.chargeAmount;
|
|
for (const auto &newEffector : qAsConst(effectorDatas))
|
|
newImpactCount += newEffector.chargeAmount;
|
|
|
|
if (oldImpactCount != newImpactCount)
|
|
updateEffectCount = true;
|
|
|
|
// update internal data
|
|
m_data = std::move(tmpData);
|
|
|
|
auto newEffectorDatas = effectorDatas;
|
|
bool updateTrajectory = false;
|
|
if (updateMHP)
|
|
{
|
|
updateTrajectory = true;
|
|
// generate new MTP if changed
|
|
DEBUG_LOG(m_logger) << "Update need to update efecotrs";
|
|
newEffectorDatas = generateMHPs(effectorDatas, data.explosionGeometryId);
|
|
}
|
|
|
|
QVector<Database::AesEffectorData> newEffectorData;
|
|
for (auto effectorData : newEffectorDatas)
|
|
{
|
|
for (const auto &oldEffector : m_effectorData)
|
|
{
|
|
// DEBUG_LOG(m_logger) << " looking through old effectorData: " << oldEffector.id << " with " << oldEffector.entityId << " and participantId " <<
|
|
// oldEffector.participantId
|
|
//<<" same as " << effectorData.entityId << " " << effectorData.participantId;
|
|
oldIds.insert(oldEffector.id);
|
|
if (oldEffector.entityId == effectorData.entityId && oldEffector.participantId == effectorData.participantId)
|
|
{
|
|
effectorData.id = oldEffector.id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (effectorData.id > 0)
|
|
{
|
|
DEBUG_LOG(m_logger) << "UPDATE EFFECTORDATA ";
|
|
if (!m_mainHandler->db()->update(effectorData))
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating effectorData with id %1 in database.")).arg(effectorData.id);
|
|
newIds.insert(effectorData.id);
|
|
}
|
|
else
|
|
{
|
|
|
|
DEBUG_LOG(m_logger) << "UPDATE EFFECTORDATA ";
|
|
if (!m_mainHandler->db()->insert(effectorData))
|
|
return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating effectorData with id %1 in database.")).arg(effectorData.id);
|
|
newIds.insert(effectorData.id);
|
|
}
|
|
newEffectorData.append(effectorData);
|
|
}
|
|
|
|
//(delete no longer needed effectorData)
|
|
QSet<int> idsToDelete = oldIds - newIds;
|
|
if (idsToDelete.size())
|
|
{
|
|
for (auto idToDelete : idsToDelete)
|
|
m_mainHandler->db()->removeAesEffectorData(idToDelete);
|
|
}
|
|
|
|
// set new Effectors to internal Map and then publish
|
|
setEffectors(newEffectorData);
|
|
|
|
if (data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER && data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE
|
|
&& (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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// reset some data:
|
|
m_aiv2.form().clear(); // enforce recalculation
|
|
m_effects.singleEffects().clear();
|
|
m_aivGunToTargetTrajectoryMap.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 (!isEffectClusterMarkedValid())
|
|
m_data.effectStart = QDateTime();
|
|
}
|
|
|
|
// do checkPreConditions
|
|
{
|
|
Core::FunctionReplyObject retCPC = checkPreConditionsComplete();
|
|
if (retCPC.error())
|
|
abortPreStartTimer();
|
|
m_temporaryData.annotationString = retCPC.translated().toStdString();
|
|
if (!isEffectClusterMarkedValid())
|
|
m_data.effectStart = QDateTime();
|
|
}
|
|
|
|
if (!m_data.effectStart.isValid())
|
|
abortPreStartTimer();
|
|
// update reaminExplosion = impactCount if changed
|
|
if (updateEffectCount)
|
|
m_data.remainExplosionNumber = newImpactCount;
|
|
else
|
|
m_data.remainExplosionNumber = data.remainExplosionNumber;
|
|
|
|
m_publishDataTimer->start();
|
|
checkEnterNewState();
|
|
|
|
if (m_fireAutomaton)
|
|
{
|
|
|
|
DEBUG_LOG(m_logger) << "!!!!!!!!!!!!! AUOTMATON NEDD UPDATE need update keys: " << m_effectorData.keys() << " duration " << data.effectDuration << "used duration "
|
|
<< data.effectDuration - (int)m_effectorData.begin().value().flightTime << " impactCount_ " << data.impactCount;
|
|
|
|
if (m_data.effectDuration != DEFAULT_EFFECT_DURATION)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Update Init Fire Automaton with effect duration: " << m_data.effectDuration;
|
|
|
|
// use duration
|
|
m_fireAutomaton->init(m_data.effectDuration, FireAutomaton::TimeType::Duration, m_data.impactCount, m_effectorData.keys().toVector(), QStringLiteral("next*"));
|
|
}
|
|
else
|
|
{
|
|
// use cadence
|
|
const auto &gearTypeIds = m_mainHandler->dds()->pdmDtc()->gearTypeIdFromChargeTypeId(m_data.chargeTypeId);
|
|
int cadence = 35000;
|
|
if (gearTypeIds.size())
|
|
cadence = m_mainHandler->dds()->pdmDtc()->cadence(gearTypeIds.at(0));
|
|
|
|
DEBUG_LOG(m_logger) << "Update Init Fire Automaton with cadence: " << cadence;
|
|
m_fireAutomaton->init(cadence, FireAutomaton::TimeType::Cadence, m_data.impactCount, m_effectorData.keys().toVector(), QStringLiteral("all*"));
|
|
}
|
|
|
|
if (m_data.effectStateId == AesEffectState::EsPrepared)
|
|
m_automatonTimer->start();
|
|
else
|
|
m_automatonTimer->stop();
|
|
}
|
|
|
|
if (updateTrajectory)
|
|
{
|
|
DEBUG_LOG(m_logger) << "need update trajectory";
|
|
sendCalculateTrajectoryAdlerRequest(newEffectorData);
|
|
}
|
|
publishAiv();
|
|
return Core::FunctionReplyObject(data.id); // data not m_data for the possible negative id in the oc-laptop
|
|
}
|
|
|
|
bool ObjectEffectCluster::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())
|
|
{
|
|
double singleWidthOut, singleHeightOut;
|
|
singleWidthOut = singleHeightOut = 0;
|
|
|
|
DEBUG_LOG(m_logger) << "Calculate new zone with TLA " << m_data.tla;
|
|
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.tla);
|
|
|
|
DEBUG_LOG(m_logger) << "Area without TLA Width" << areaBLOrgWithoutTla.m_width;
|
|
|
|
DEBUG_LOG(m_logger) << "Area with TLA Width" << areaBlWithTla.m_width;
|
|
|
|
const QPointF center = Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, 0.0);
|
|
const LtsCoordTrans::UtmCoordinate ¢erUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center, impactPositionUtm.zone);
|
|
const LtsCoordTrans::UtmCoordinate &firingOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.firingCenterPosition.toPointF());
|
|
|
|
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 : qAsConst(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 = 10.0; // Polygons::EffectClusterCalculatedFlightDuration2DMSec(m_data);
|
|
|
|
if (m_isAirSupportSource)
|
|
m_temporaryData.estimatedMaxFlightDurationMSec = m_temporaryData.calculatedFlightDurationMsec;
|
|
else
|
|
m_temporaryData.estimatedMaxFlightDurationMSec
|
|
= 10.0; // Polygons::EffectClusterEstimatedMaxFlightDuration2DMSec(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 ObjectEffectCluster::abortPreStartTimer()
|
|
{
|
|
m_preStartTimer->stop();
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << ", preStartTimer STOPPED";
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::removeObject()
|
|
{
|
|
|
|
Core::FunctionReplyObject ret = ObjectBaseTmpl::removeObject();
|
|
if (ret.error())
|
|
return ret;
|
|
|
|
// avoid unneeded calculations:
|
|
m_preStartTimer->stop();
|
|
|
|
DEBUG_LOG(m_logger) << "Impact count: " << m_data.impactCount;
|
|
|
|
int remainingAmmoCount = 0; // calc
|
|
for (const auto &effectorData : m_effectorData)
|
|
{
|
|
remainingAmmoCount += effectorData.chargeAmount - m_firedPerEfector.value(effectorData.participantId);
|
|
}
|
|
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
|
|
if (m_data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER && m_data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE)
|
|
m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, 0 - remainingAmmoCount);
|
|
|
|
return ret;
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::checkObjectData(const Database::AesEffectCluster &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 (data.tla < 0)
|
|
reason = QString(QStringLiteral("TLA invalid (%1)")).arg(data.tla);
|
|
// 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", "Effect cluster with id %1 and name '%2' invalid. (%3)")).arg(data.id).arg(data.name).arg(reason);
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " " << data.id << " Error: " << ret.translated();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool ObjectEffectCluster::removeObjectFromDb() { return m_mainHandler->db()->removeAesEffectCluster(m_data.id); }
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::resumePause(const EffectClusterPauseResumeRequest &req)
|
|
{
|
|
Core::FunctionReplyObject ret;
|
|
if (req.resumeExecution())
|
|
{
|
|
m_data.effectStart = partitionTime();
|
|
// resume
|
|
if (!trySwitchToActivated())
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "EffectCluster is currently not paused."));
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " RESUME"
|
|
<< " ... ";
|
|
}
|
|
else
|
|
{
|
|
// pause
|
|
if (!trySwitchToPaused())
|
|
return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Switching EffectCluster to pause is not possible in the current state."));
|
|
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " PAUSE"
|
|
<< " ... ";
|
|
}
|
|
|
|
if (ret.error())
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " PAUSE"
|
|
<< " ... resumePause failed"
|
|
<< ", errorStr = " << ret.translated();
|
|
|
|
return ret;
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::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;
|
|
|
|
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;
|
|
|
|
// 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 ObjectEffectCluster::checkPreConditionsComplete()
|
|
{
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START";
|
|
|
|
QString reason;
|
|
Core::FunctionReplyObject ret;
|
|
|
|
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";
|
|
return Core::FunctionReplyObject(m_data.id, reason.toStdString().c_str());
|
|
}
|
|
|
|
if ((m_data.participantId > 0) && !firingOriginIsValid(m_data.firingCenterPosition, &reason))
|
|
{
|
|
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " firingOriginIsValid failed";
|
|
if (!reason.isEmpty())
|
|
{
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
|
|
return Core::FunctionReplyObject(m_data.id, reason.toStdString().c_str());
|
|
}
|
|
}
|
|
|
|
if (!initialCalculations(&reason) || !reason.isEmpty())
|
|
{
|
|
WARNING_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " initialCalculations failed";
|
|
return Core::FunctionReplyObject(m_data.id, reason.toStdString().c_str());
|
|
}
|
|
|
|
} // if (m_data.externalId <= 0)
|
|
|
|
// keep start of svc in mind:
|
|
if (m_data.effectStateId.underlying() != AreaEffectSimulation::AesEffectState_def::EsExpired)
|
|
ret = checkPreConditions();
|
|
|
|
if (ret.error())
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " checkPreConditions failed";
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ObjectEffectCluster::setEffectState(AesEffectState newState)
|
|
{
|
|
|
|
// more or less a hack for ec of HLA (m_EffectCluster.externalId() > 0)
|
|
// PATRIC
|
|
if (newState == AesEffectState::EsExpired || newState == AesEffectState::EsRemoved || newState == AesEffectState::EsError)
|
|
{
|
|
freeEffectors();
|
|
if (m_fireAutomaton)
|
|
{
|
|
m_automatonTimer->stop();
|
|
}
|
|
}
|
|
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 ObjectEffectCluster::onPublishData()
|
|
{
|
|
ObjectBaseTmpl::onPublishData();
|
|
|
|
if (!m_mainHandler->db()->update(m_data))
|
|
ERROR_LOG(m_logger) << "Error updating ObjectEffectCluster-data in database";
|
|
// base from first
|
|
EffectCluster ddsData(m_data.id,
|
|
m_data.name.toStdString(),
|
|
effectState() == AesEffectState::EsRemoved,
|
|
false,
|
|
0, // partitionTimeMSec(),
|
|
{m_effectorData.begin().value().chargeTypeId, m_effectorData.begin().value().igniterTypeId, m_effectorData.begin().value().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(),
|
|
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.tla,
|
|
toEffectorVec(m_effectorData.values()),
|
|
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(),
|
|
static_cast<AesEffectorState::type>(m_data.effectorState),
|
|
m_temporaryData.annotationString);
|
|
|
|
// publish only on real changes:
|
|
if (m_EffectCluster != ddsData)
|
|
{
|
|
m_EffectCluster = ddsData;
|
|
ddsData.sampleTime(partitionTimeMSec()); // publish sampleTime, but do not store
|
|
m_mainHandler->dds()->dts()->publish(ddsData);
|
|
}
|
|
|
|
publishAiv();
|
|
}
|
|
|
|
void ObjectEffectCluster::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 (AtEffectClusterSingleExplosions) 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 (AtEffectClusterSingleExplosions) for TriggeredExplosions SKIPPED, explosionIndex "
|
|
<< explosionIndex;
|
|
|
|
publishTrajectory(explosionIndex);
|
|
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
|
|
}
|
|
|
|
void ObjectEffectCluster::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, 0, 0).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 ObjectEffectCluster::publishAiv()
|
|
{
|
|
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.tla);
|
|
}
|
|
else
|
|
poly = Polygons::effectClusterArea(m_data);
|
|
|
|
const QRectF bounds(poly.boundingRect());
|
|
|
|
//
|
|
// Major
|
|
//
|
|
AesItemView aiv(m_aiv);
|
|
aiv.aesItemId(m_data.id);
|
|
aiv.chargeStruct({m_data.chargeTypeId, 0, 0});
|
|
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::AtEffectCluster);
|
|
aiv.effectStateId(effectState());
|
|
aiv.id(1000 * m_data.id + AesType::AtEffectCluster);
|
|
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
|
|
//
|
|
|
|
{
|
|
AesItemView aiv2(m_aiv2);
|
|
aiv2.aesItemId(m_data.id);
|
|
aiv2.chargeStruct({m_data.chargeTypeId, 0, 0});
|
|
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::effectClusterSingleExplosionsGAUSS_Mtp(m_data, m_effectorData);
|
|
ddsVecFromPoly(singleExplosions, aiv2.form());
|
|
}
|
|
}
|
|
aiv2.boundingBoxBottomLeft(aiv.boundingBoxBottomLeft());
|
|
aiv2.boundingBoxTopRight(aiv.boundingBoxTopRight());
|
|
aiv2.aesTypeId(AesType::AtEffectClusterSingleExplosions);
|
|
aiv2.id(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions);
|
|
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::AtEffectClusterSingleExplosions);
|
|
m_effects.aesItemId(m_data.id);
|
|
m_effects.aesTypeId(AesType::AtEffectClusterSingleExplosions);
|
|
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::effectClusterSingleExplosionsGAUSS_Mtp(m_data, m_effectorData);
|
|
|
|
singleEffectsRef.reserve(singleExplosions.size());
|
|
for (const QPointF &singleExplosion : qAsConst(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);
|
|
} // for
|
|
} // if (isVulcano())
|
|
}
|
|
}
|
|
/*
|
|
else {
|
|
auto dts = m_mainHandler->dds()->dts();
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions);
|
|
|
|
// up to s_maxTrajectoriesPerEffectCluster are supported. One sample per trajectory.
|
|
const bool errorMsgIfSampleDoesNotExists = false;
|
|
dts->removeEffectsByKey(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
|
|
}
|
|
*/
|
|
|
|
//
|
|
// GunToTargetTrajectory
|
|
//
|
|
|
|
// EsUnknown = 0,
|
|
// EsError = 255,
|
|
// EsExpired = 1,
|
|
// EsRemoved = 2,
|
|
// EsCreated = 4,
|
|
// EsPrepared = 5,
|
|
// EsActivated = 6,
|
|
// EsPaused = 7
|
|
|
|
// AtEffectClusterGunToTargetLine 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::EsReceived,
|
|
AesEffectState_def::EsCreated,
|
|
AesEffectState_def::EsPrepared,
|
|
AesEffectState_def::EsFired,
|
|
AesEffectState_def::EsActivated, // Jira [LS-647] gefordert dauerhaft sichtbar
|
|
AesEffectState_def::EsExpired}; // Jira [LS-647] gefordert dauerhaft sichtbar
|
|
|
|
DEBUG_LOG(m_logger) << " AtEffectClusterGunToTargetLine RELEVANT " << relevantEffStates.contains(effectState().underlying()) << ", id " << m_data.id;
|
|
for (const auto &effector : m_effectorData)
|
|
{
|
|
if (isOriginatorParticipantIdProvided() && // no Originator -> no curve
|
|
relevantEffStates.contains(effectState().underlying()))
|
|
{
|
|
|
|
AesItemView aiv3(m_aivGunToTargetTrajectoryMap.value(effector.participantId));
|
|
aiv3.aesItemId(m_data.id);
|
|
aiv3.chargeStruct({effector.chargeTypeId, 0, 0});
|
|
// aiv3.effectStart(0);
|
|
|
|
if (aiv3.form().empty())
|
|
{
|
|
// E01562-294: Falsche Darstellung Flugkurve SMArt
|
|
const auto &trajectoryRelativeToOriginator = TrajectoryRelative::fromString(effector.trajectory);
|
|
const auto &trajectoryRelativeToOriginatorSingleDueToSMArt = reduceSMArtTrajectoryToSingle(trajectoryRelativeToOriginator);
|
|
|
|
if (trajectoryRelativeToOriginator.size() != trajectoryRelativeToOriginatorSingleDueToSMArt.size())
|
|
DEBUG_LOG(m_logger) << "generateGunToTargetForm for id: " << " " << m_data.id << ": there is a step backward in time, reduce input used as GunToTargetLine.";
|
|
|
|
const std::vector<Core::GeoPosition> &form = ObjectEffectCluster::generateGunToTargetForm(trajectoryRelativeToOriginatorSingleDueToSMArt, effector.participantId);
|
|
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::AtEffectClusterGunToTargetLine);
|
|
aiv3.id(1000 * m_data.id * effector.participantId + AesType::AtEffectClusterGunToTargetLine);
|
|
// aiv3.effectStateId(effectState());
|
|
if (m_data.partyId != 0)
|
|
aiv3.partyId(m_data.partyId);
|
|
|
|
m_aivGunToTargetTrajectoryMap.insert(effector.participantId, 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 * effector.participantId + AesType::AtEffectClusterGunToTargetLine, errorMsgIfSampleDoesNotExists);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
bool ObjectEffectCluster::intersectWithCircle(double lonRad, double latRad, int radius)
|
|
{
|
|
LtsCoordTrans::UtmCoordinate bottomLeftUtm
|
|
= LtsCoordTrans::UtmCoordinate::fromGeographic(m_EffectCluster.area().cornerBottomLeft().lon(), m_EffectCluster.area().cornerBottomLeft().lat());
|
|
|
|
LtsCoordTrans::UtmCoordinate circleCenterUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonRad, latRad, bottomLeftUtm.zone);
|
|
const double xMin = bottomLeftUtm.x;
|
|
const double yMin = bottomLeftUtm.y;
|
|
const double xMax = bottomLeftUtm.x + m_EffectCluster.area().width();
|
|
const double yMax = bottomLeftUtm.y + m_EffectCluster.area().height();
|
|
|
|
const double farthestX = (std::abs(circleCenterUtm.x - xMin) > std::abs(circleCenterUtm.x - xMax)) ? xMin : xMax;
|
|
const double farthestY = (std::abs(circleCenterUtm.y - yMin) > std::abs(circleCenterUtm.y - yMax)) ? yMin : yMax;
|
|
|
|
const double distanceX = circleCenterUtm.x - farthestX;
|
|
const double distanceY = circleCenterUtm.y - farthestY;
|
|
|
|
const double distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
|
|
const double radiusSquard = (radius * radius);
|
|
|
|
DEBUG_LOG(m_logger) << "Farthest Point Check:"
|
|
<< "\n - Rect Bounds: x[" << xMin << " to " << xMax << "], y[" << yMin << " to " << yMax << "]"
|
|
<< "\n - Circle Center: (" << circleCenterUtm.x << ", " << circleCenterUtm.y << ")"
|
|
<< "\n - Farthest Point: (" << farthestX << ", " << farthestY << ")"
|
|
<< "\n - Dist Squared: " << distanceSquared << "\n - Radius: " << radius << "\n - Radius Squared: " << radiusSquard;
|
|
|
|
return distanceSquared <= radiusSquard;
|
|
;
|
|
}
|
|
|
|
void ObjectEffectCluster::markAsExpired(bool fireLoadedRound)
|
|
{
|
|
|
|
DEBUG_LOG(m_logger) << "Got Mark as Expired with fireLoadedRound " << fireLoadedRound;
|
|
// delete timer just in case
|
|
if (m_fireHoldWaitforLateFire)
|
|
{
|
|
m_fireHoldWaitforLateFire->stop();
|
|
delete m_fireHoldWaitforLateFire;
|
|
m_fireHoldWaitforLateFire = nullptr;
|
|
}
|
|
|
|
if (!fireLoadedRound)
|
|
{
|
|
setEffectState(AreaEffectSimulation::AesEffectState::EsExpired);
|
|
}
|
|
else
|
|
{
|
|
// We will wait for fire events from the SAA and then send confirmation reports
|
|
// as of now: the effect cluster will be set to expired after the timeout
|
|
// (like the current implementation of markAsExpired without fireLoadedRound!)
|
|
// but the charge effect will be probably still midair,
|
|
// resulting in a visual mismatch of charge effects and the Effectcluster's EffectState
|
|
// Idea: changing the "m_lookoutChargeEffectEnd" for the last round
|
|
DEBUG_LOG(m_logger) << "Need To wait as it is fireloadedround=" << fireLoadedRound;
|
|
|
|
m_fireHoldWaitforLateFire = new QTimer(this);
|
|
m_fireHoldWaitforLateFire->setSingleShot(true);
|
|
m_fireHoldWaitforLateFire->setInterval(7 * 1000);
|
|
|
|
connect(m_fireHoldWaitforLateFire,
|
|
&QTimer::timeout,
|
|
this,
|
|
[this]()
|
|
{
|
|
DEBUG_LOG(m_logger) << "TimerTimeout we will now send Confirmation for " << m_data.id;
|
|
|
|
// send confirmation for all Effectors then mark as expired (with false)
|
|
for (const auto effector : qAsConst(m_effectorData))
|
|
{
|
|
onSendConfirmationReportToTab(effector.participantId);
|
|
}
|
|
// give them a little time to set it mark as expired
|
|
QTimer::singleShot(1000, [this]() { markAsExpired(false); });
|
|
});
|
|
// Start it now
|
|
m_fireHoldWaitforLateFire->start();
|
|
}
|
|
}
|
|
|
|
void ObjectEffectCluster::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->removeEffectClusterByKey(m_data.id, errorMsgIfSampleDoesNotExists);
|
|
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtEffectCluster, errorMsgIfSampleDoesNotExists);
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtEffectClusterGunToTargetLine, errorMsgIfSampleDoesNotExists);
|
|
|
|
// ti.id(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions);
|
|
dts->removeTriggeredImpactsByKey(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
|
|
|
|
// up to s_maxTrajectoriesPerEffectCluster are supported. One sample per trajectory.
|
|
for (int explosionIndex = 0; explosionIndex < AreaEffectSimulation::s_maxTrajectoriesPerExplosionCluster; ++explosionIndex)
|
|
dts->removeAesItemViewByKey(1000 * m_data.id + 100 + explosionIndex, errorMsgIfSampleDoesNotExists);
|
|
|
|
// Effects / Impacts
|
|
dts->removeEffectsByKey(1000 * m_data.id + AesType::AtEffectClusterSingleExplosions, errorMsgIfSampleDoesNotExists);
|
|
|
|
emit removeAiv(m_data.id);
|
|
}
|
|
|
|
//------------------------------
|
|
// Maybe no longer needed
|
|
//------------------------------
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::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 Core::FunctionReplyObject(m_data.id, reason.toStdString().c_str());
|
|
}
|
|
//
|
|
// check GunToTargetForm (generates trajectory to center of area.)
|
|
// It will use flightDuration or peakHeight as provided
|
|
//
|
|
if (isOriginatorParticipantIdProvided())
|
|
{
|
|
QString reasonGtlOut;
|
|
m_temporaryData.gunToTargetLine = ObjectEffectCluster::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 Core::FunctionReplyObject(m_data.id, reason.toStdString().c_str());
|
|
}
|
|
}
|
|
|
|
return Core::FunctionReplyObject(m_data.id);
|
|
}
|
|
|
|
// An incomming Trajectory from the SAA will be added to the corresponding effector
|
|
// and set the effect start in case of TTF
|
|
Core::FunctionReplyObject ObjectEffectCluster::trajectoryAdlerModify(const TrajectoryAdlerModifyRequest &req)
|
|
{
|
|
Core::FunctionReplyObject ret;
|
|
|
|
DEBUG_LOG(m_logger) << "ObjectEffectCluster::TrajectoryAdlerModify";
|
|
DEBUG_LOG(m_logger) << "position vector (geometry) size= " << req.geometry().size();
|
|
DEBUG_LOG(m_logger) << "explosion Index = " << req.explosionIndex();
|
|
int firstEffector = {std::numeric_limits<int>::max()};
|
|
|
|
for (const auto &effectorData : m_effectorData)
|
|
{
|
|
if (effectorData.id < firstEffector)
|
|
firstEffector = effectorData.id;
|
|
}
|
|
// find first effector
|
|
auto effectorData = m_effectorData.value(req.explosionIndex());
|
|
if (effectorData.id)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Found Effector for index " << effectorData.participantId;
|
|
|
|
effectorData.flightTime = req.geometry().at(req.geometry().size() - 1).relativeTime();
|
|
if (req.flightTime() > 0.0)
|
|
effectorData.flightTime = req.flightTime();
|
|
if (req.peakAltitude() > 0.0)
|
|
effectorData.peakHeight = req.peakAltitude();
|
|
|
|
effectorData.trajectory = QString::fromStdString(TrajectoryRelative::toString(req.geometry()));
|
|
|
|
if (req.mhpScatterLenght() > 0.0)
|
|
effectorData.mhpScatterLenght = req.mhpScatterLenght();
|
|
else
|
|
effectorData.mhpScatterLenght = Settings::getScatterLenghtDefault();
|
|
|
|
if (req.mhpScatterLengthDetonationHeight() > 0.0)
|
|
effectorData.mhpScatterLengthDetonationHeight = req.mhpScatterLengthDetonationHeight();
|
|
|
|
if (req.mhpScatterWidth() > 0.0)
|
|
effectorData.mhpScatterWidth = req.mhpScatterWidth();
|
|
else
|
|
effectorData.mhpScatterWidth = Settings::getScatterWidthDefault();
|
|
if (req.mhpScatterWidthDetonationHeight() > 0.0)
|
|
effectorData.mhpScatterWidthDetonationHeight = req.mhpScatterWidthDetonationHeight();
|
|
|
|
m_effectorData.insert(effectorData.participantId, effectorData);
|
|
|
|
// updateEffector via effectorHandler
|
|
updateEffector(effectorData.participantId, effectorData);
|
|
|
|
if (m_fireAutomaton && m_data.effectDuration != DEFAULT_EFFECT_DURATION)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Update Init after new trajectory Fire Automaton with effect duration: " << m_data.effectDuration;
|
|
// use duration
|
|
m_fireAutomaton->init(m_data.effectDuration, FireAutomaton::TimeType::Duration, m_data.impactCount, m_effectorData.keys().toVector(), QStringLiteral("next*"));
|
|
}
|
|
// update startTime if needed
|
|
if (m_data.effectStart.isNull() || effectorData.id == firstEffector)
|
|
{
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSZEIT()))
|
|
{
|
|
const double ttf_time = probObject[AdlerJson::JsonKeys::ABFEUERUNGSZEIT()].toDouble();
|
|
m_data.effectStart = QDateTime::fromMSecsSinceEpoch((ttf_time * 1000) + effectorData.flightTime);
|
|
}
|
|
}
|
|
onPublishData();
|
|
// remove aiv to force new generation on publish
|
|
auto aiv = m_aivGunToTargetTrajectoryMap.value(effectorData.participantId);
|
|
if (aiv.form().size())
|
|
{
|
|
aiv.form().clear();
|
|
m_aivGunToTargetTrajectoryMap.insert(effectorData.participantId, aiv);
|
|
}
|
|
publishAiv();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::effectClusterModifyActor(const EffectClusterModifyActorRequest &req)
|
|
{
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << "-----> req: " << req;
|
|
Core::FunctionReplyObject ret;
|
|
|
|
m_data.esmParticipantIds = LtsUtils::IntArray(req.esmParticipantIds());
|
|
onPublishData();
|
|
return ret;
|
|
}
|
|
|
|
Core::FunctionReplyObject ObjectEffectCluster::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";
|
|
return Core::FunctionReplyObject(m_data.id, reason.toStdString().c_str());
|
|
}
|
|
}
|
|
m_data.firingCenterPosition = geoPosOut;
|
|
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " END";
|
|
return Core::FunctionReplyObject();
|
|
}
|
|
|
|
void ObjectEffectCluster::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]";
|
|
}
|
|
}
|
|
|
|
void ObjectEffectCluster::checkAutomaton()
|
|
{
|
|
|
|
// ERROR_LOG(m_logger) << "CHECK AUTOMATON";
|
|
|
|
if (!m_fireAutomaton)
|
|
return;
|
|
if (m_effectorData.empty())
|
|
return;
|
|
if (m_fireAutomaton->getState() == FireAutomaton::State::Running)
|
|
m_automatonTimer->stop();
|
|
|
|
int flightduration = (int)m_effectorData.begin().value().flightTime;
|
|
|
|
// ERROR_LOG(m_logger) << "CHECK AUTOMATON2 "<< flightduration;
|
|
// ERROR_LOG(m_logger) << "CHECK AUTOMATON3 "<< flightduration;
|
|
|
|
if (partitionTime() >= m_data.effectStart.addMSecs(-flightduration))
|
|
{
|
|
DEBUG_LOG(m_logger) << "START AUTOMATON fltime=" << flightduration;
|
|
m_automatonTimer->stop();
|
|
m_fireAutomaton->stop();
|
|
m_fireAutomaton->start();
|
|
}
|
|
}
|
|
|
|
void ObjectEffectCluster::onAutomatonFinished()
|
|
{
|
|
DEBUG_LOG(m_logger) << "END AUTOMATON";
|
|
m_fireAutomaton->stop();
|
|
m_fireAutomaton->deleteLater();
|
|
m_fireAutomaton = nullptr;
|
|
}
|
|
|
|
void ObjectEffectCluster::onEffectorNotReadyToFire(int participantId, int aesItemId)
|
|
{
|
|
if (aesItemId != m_data.id)
|
|
return;
|
|
|
|
Q_UNUSED(participantId)
|
|
}
|
|
|
|
void ObjectEffectCluster::onEffectorReadyToFire(int participantId, int aesItemId)
|
|
{
|
|
if (aesItemId != m_data.id)
|
|
{
|
|
return;
|
|
}
|
|
m_readytoFire.insert(participantId);
|
|
if (m_readytoFire.size() == m_effectorData.count() && m_data.effectorState == AesEffectorState::EsReadyToFire)
|
|
onSendReadinessToTab();
|
|
}
|
|
|
|
void ObjectEffectCluster::onEffectorReceivedOrder(int participantId, int aesItemId)
|
|
{
|
|
if (aesItemId != m_data.id)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Q_UNUSED(participantId)
|
|
}
|
|
|
|
void ObjectEffectCluster::onEffectorReportedFired(int participantId, int aesItemId)
|
|
{
|
|
DEBUG_LOG(m_logger) << "onEffectorReportedFired! 1";
|
|
if (aesItemId != m_data.id)
|
|
return;
|
|
|
|
if (m_data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER)
|
|
return;
|
|
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
|
|
FiringType firingType = FiringType::FireNow;
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSART()))
|
|
firingType = static_cast<FiringType::inner_enum>(probObject[AdlerJson::JsonKeys::ABFEUERUNGSART()].toInt());
|
|
|
|
// We always send the first firedReport regardless of the Firing Type
|
|
bool firstTime = !m_firstFireSend.contains(participantId);
|
|
// Only send FireRediness after the initial one (at prep state) when we are in Mode
|
|
// Fire on Command (K)
|
|
|
|
DEBUG_LOG(m_logger) << "onEffectorReportedFired! 2 first time " << firstTime << " firing type " << firingType;
|
|
if (!firstTime && firingType != FiringType::FireOnCommand)
|
|
return;
|
|
|
|
const QString reference = probObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString();
|
|
TrainingAreaBase::SendExFiredReportRequest request;
|
|
request.reference() = reference.toStdString();
|
|
request.partyId() = m_data.partyId;
|
|
request.participantId() = participantId;
|
|
request.identifier() = m_mainHandler->dds()->ppmDtc()->participant(participantId).name();
|
|
request.unitIdentifier() = m_mainHandler->dds()->ppmDtc()->participant(m_data.participantId).name();
|
|
m_mainHandler->dds()->tabFtc()->sendExFiredReportRequest(request, this);
|
|
m_firstFireSend.insert(participantId);
|
|
}
|
|
|
|
void ObjectEffectCluster::onEffectorFired(int participantId, int aesItemId, const QVector<int> &chargeEffectIds)
|
|
{
|
|
// NOTICE_LOG(m_logger) << "onEffectorFired"
|
|
// << ", participantId=" << participantId << ", aesItemId=" << aesItemId << ", chargeEffectId=" << chargeEffectId;
|
|
if (aesItemId != m_data.id)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Got Effector Fired but not for our id: is " << aesItemId << " should be " << m_data.id;
|
|
return;
|
|
}
|
|
// Fist append to shoot list, then look it it's the last shot and mark as last
|
|
DEBUG_LOG(m_logger) << " effector should " << m_effectorData.value(participantId).chargeAmount << " fires prev fired " << m_firedPerEfector.value(participantId);
|
|
m_firedPerEfector.insert(participantId, m_firedPerEfector.value(participantId) + 1);
|
|
|
|
DEBUG_LOG(m_logger) << " effector should " << m_effectorData.value(participantId).chargeAmount << " fires post fired " << m_firedPerEfector.value(participantId);
|
|
|
|
if (m_firedPerEfector.value(participantId) >= m_effectorData.value(participantId).chargeAmount)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Fired last effect, time to send confirmation in one sec.";
|
|
QTimer::singleShot(1000, this, [this, participantId] { onSendConfirmationReportToTab(participantId); });
|
|
}
|
|
DEBUG_LOG(m_logger) << " New shorts for participant " << participantId << " : " << m_firedPerEfector.value(participantId) << " from "
|
|
<< m_effectorData.value(participantId).chargeAmount;
|
|
|
|
// now look for end if ALL are fired
|
|
bool end = true;
|
|
for (const auto &effector : m_effectorData)
|
|
{
|
|
if (effector.chargeAmount > m_firedPerEfector.value(effector.participantId))
|
|
end = false;
|
|
}
|
|
|
|
if (!chargeEffectIds.size())
|
|
{
|
|
ERROR_LOG(m_logger) << "No ChargeEffects inside Vector, can't to anything!";
|
|
return;
|
|
}
|
|
|
|
QPair<int, QDateTime> firstChargeEffect
|
|
= QPair<int, QDateTime>(chargeEffectIds.first(), m_mainHandler->chargeEffectHandler()->getChargeEffectById(chargeEffectIds.first())->getEffectStart());
|
|
QPair<int, QDateTime> lastChargeEffect = firstChargeEffect;
|
|
|
|
if (chargeEffectIds.size() > 1) // we need to find the last and first one
|
|
{
|
|
for (const auto id : qAsConst(chargeEffectIds))
|
|
{
|
|
const auto &chargeEffect = m_mainHandler->chargeEffectHandler()->getChargeEffectById(id);
|
|
if (chargeEffect->getEffectStart() > lastChargeEffect.second)
|
|
lastChargeEffect = QPair<int, QDateTime>(id, chargeEffect->getEffectStart());
|
|
if (chargeEffect->getEffectStart() < firstChargeEffect.second)
|
|
firstChargeEffect = QPair<int, QDateTime>(id, chargeEffect->getEffectStart());
|
|
}
|
|
}
|
|
|
|
if (end)
|
|
{
|
|
m_data.remainExplosionNumber = 0;
|
|
onPublishData();
|
|
m_lookoutChargeEffectEnd = lastChargeEffect.first;
|
|
DEBUG_LOG(m_logger) << "Set Looking for End to " << m_lookoutChargeEffectEnd;
|
|
}
|
|
else
|
|
{
|
|
m_data.remainExplosionNumber = std::max(0, m_data.remainExplosionNumber - 1);
|
|
onPublishData();
|
|
}
|
|
|
|
// Now if we are still in esPrepared we try to switch to fired
|
|
DEBUG_LOG(m_logger) << "Try set start from chargeEffect " << m_lookoutChargeEffectStart << " " << m_data.effectStateId;
|
|
if (m_data.effectStateId != AreaEffectSimulation::AesEffectState::EsPrepared && m_lookoutChargeEffectStart != 0)
|
|
return;
|
|
if ((m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsPrepared || m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsFired)
|
|
&& m_lookoutChargeEffectStart == 0)
|
|
{
|
|
if (!m_firingTimeReached)
|
|
{
|
|
m_firingTimeReached = true;
|
|
}
|
|
|
|
if (m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsPrepared && !trySwitchToFired())
|
|
{
|
|
WARNING_LOG(m_logger) << "Could not switch to Fired for aesid " << m_data.id;
|
|
return;
|
|
}
|
|
|
|
// first fired ChargeEffect
|
|
m_lookoutChargeEffectStart = firstChargeEffect.first;
|
|
DEBUG_LOG(m_logger) << "Set Looking for Start to " << m_lookoutChargeEffectStart;
|
|
|
|
// update Effect Start to REAL flight end
|
|
const auto chargeEffect = m_mainHandler->chargeEffectHandler()->getChargeEffectById(m_lookoutChargeEffectStart);
|
|
if (chargeEffect)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Use Effect Start from ChargeEffect " << chargeEffect->id() << " effectstarte: " << chargeEffect->getEffectStart().toString();
|
|
m_data.effectStart = chargeEffect->getEffectStart();
|
|
}
|
|
else // Fallback
|
|
{
|
|
DEBUG_LOG(m_logger) << "Use Effect Start fallback" << partitionTime().addMSecs(m_effectorData.value(participantId).flightTime).toString();
|
|
m_data.effectStart = partitionTime().addMSecs(m_effectorData.value(participantId).flightTime);
|
|
}
|
|
// Fnally publish data with ne effect start
|
|
onPublishData();
|
|
}
|
|
}
|
|
|
|
double ObjectEffectCluster::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 ObjectEffectCluster::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) EffectCluster.peakHeight
|
|
// b) EffectCluster.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 ObjectEffectCluster::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);
|
|
|
|
DEBUG_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 : qAsConst(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);
|
|
DEBUG_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 ObjectEffectCluster::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 ObjectEffectCluster::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 ObjectEffectCluster::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 ObjectEffectCluster::getFiringCannonUtmPosition(int explosionIndex, int zoneToUse) const
|
|
{
|
|
const LtsUtils::QPoint3D &geoPoint3D = ObjectEffectCluster::getFiringCannonGeoPosition(explosionIndex);
|
|
const LtsCoordTrans::UtmCoordinate &temp = LtsCoordTrans::UtmCoordinate::fromGeographic(geoPoint3D.toPointF(), zoneToUse);
|
|
return LtsUtils::QPoint3D(temp.x, temp.y, geoPoint3D.z());
|
|
}
|
|
|
|
void ObjectEffectCluster::sendCalculateTrajectoryAdlerRequest(const QVector<Database::AesEffectorData> &effectorData)
|
|
{
|
|
if (m_data.effectStateId == AesEffectState::EsExpired)
|
|
return;
|
|
|
|
QString reason;
|
|
|
|
for (const auto &data : qAsConst(effectorData))
|
|
{
|
|
DEBUG_LOG(m_logger) << "TRY to send Calcualte TrajecotryAdlerReuqest for participantId=" << data.participantId;
|
|
|
|
const ParticipantManagement::ParticipantPosition &partPos = m_mainHandler->dds()->ppmDtc()->participantPosition(data.participantId);
|
|
|
|
LtsUtils::QPoint3D geoPosOut;
|
|
if (!getFiringUnitOrigin(data.participantId, geoPosOut, &reason))
|
|
{
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " getFiringOrigin failed for id " << data.id << ", reason: '" << reason << "'";
|
|
return;
|
|
}
|
|
|
|
const Core::GeoPosition positionLaunch(geoPosOut.x(), geoPosOut.y(), geoPosOut.z());
|
|
const Core::GeoPosition positionTarget(data.mhpPosition.x(), data.mhpPosition.y(), data.mhpPosition.z());
|
|
|
|
DEBUG_LOG(m_logger) << "SAA is connected partyId: " << m_data.partyId << ", participantId: " << data.participantId << " --------------------- ";
|
|
DEBUG_LOG(m_logger) << "positionLaunch = " << positionLaunch;
|
|
DEBUG_LOG(m_logger) << "positionTarget = " << positionTarget;
|
|
|
|
// parse Properties
|
|
const QJsonObject &probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
|
|
int loadTypeId = m_mainHandler->dds()->pdmDtc()->getLoadTypeIdFromAdlerLoad(QStringLiteral("B L8A1"));
|
|
if (probObject.contains(AdlerJson::JsonKeys::LADUNG()))
|
|
loadTypeId = probObject[AdlerJson::JsonKeys::LADUNG()].toInt();
|
|
|
|
ProjectDataManagement::L43_AngleGroup angleGroup = ProjectDataManagement::L43_AngleGroup::Lower;
|
|
if (probObject.contains(AdlerJson::JsonKeys::WINKELGRUPPE()))
|
|
angleGroup = static_cast<ProjectDataManagement::L43_AngleGroup::inner_enum>(probObject[AdlerJson::JsonKeys::WINKELGRUPPE()].toInt());
|
|
|
|
// Send request
|
|
TrainingAreaBase::AdlerCalculateTrajectoryRequest request;
|
|
|
|
const int partyId = m_mainHandler->dds()->ppmDtc()->participant(data.participantId).partyId();
|
|
request.partyId(partyId);
|
|
request.positionLaunch(positionLaunch);
|
|
request.aesItemId(m_data.id);
|
|
request.positionTarget(positionTarget);
|
|
request.properties(m_data.properties.toStdString());
|
|
request.trajectory().chargeTypeId() = data.chargeTypeId;
|
|
request.trajectory().igniterFunctionId() = data.igniterFunctionId;
|
|
request.trajectory().igniterTypeId() = data.igniterTypeId;
|
|
request.trajectory().loadTypeId() = loadTypeId;
|
|
const ProjectDataManagement::GearClass &gc = m_mainHandler->dds()->pdmDtc()->gearClassFromChargeTypeId(m_data.chargeTypeId);
|
|
switch (gc.underlying())
|
|
{
|
|
case ProjectDataManagement::GearClass::Artillery:
|
|
request.weaponSystemType(TrainingAreaBase::WeaponSystemType::PZH);
|
|
break;
|
|
case ProjectDataManagement::GearClass::Mortar:
|
|
request.weaponSystemType(TrainingAreaBase::WeaponSystemType::MRS);
|
|
break;
|
|
case ProjectDataManagement::GearClass::Rocket:
|
|
request.weaponSystemType(TrainingAreaBase::WeaponSystemType::MARS);
|
|
break;
|
|
default:
|
|
request.weaponSystemType(TrainingAreaBase::WeaponSystemType::PZH);
|
|
break;
|
|
}
|
|
|
|
request.trajectory().loadNumber() = probObject[AdlerJson::JsonKeys::LADUNGSNUMMER()].toInt();
|
|
request.angleGroup(angleGroup);
|
|
INFO_LOG(m_logger) << "call TAB::calculateTrajectoryAdler"
|
|
<< ", participantId: " << data.participantId << ", partyId: " << partyId << ", positionLaunch: " << positionLaunch
|
|
<< ", positionTarget: " << positionTarget << ", m_data.impactCount: " << m_data.impactCount << ", m_data.id aesItemId: " << m_data.id;
|
|
|
|
// for ( int i = 0; i < m_data.impactCount; i++)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Trajectory Index:" << data.participantId;
|
|
request.explosionIndex(data.participantId);
|
|
|
|
if (m_mainHandler->dds()->tabFtc() && m_mainHandler->dds()->tabFtc()->isAdlerCalculateTrajectoryEnabled())
|
|
{
|
|
m_mainHandler->dds()->tabFtc()->adlerCalculateTrajectoryRequest(request, this);
|
|
}
|
|
else
|
|
WARNING_LOG(m_logger) << "Could not ask ADLER for Trajectory";
|
|
}
|
|
}
|
|
}
|
|
|
|
void ObjectEffectCluster::onAdlerCalculateTrajectoryReply(const Core::FunctionReplyObject &replyObject)
|
|
{
|
|
DEBUG_LOG(m_logger) << "onSendCalculateTrajectoryAdlerReply "
|
|
<< ", replyObject.id() :" << replyObject.id() << ", m_explosionIndex :" << m_explosionIndex << ", replyObject.error :" << replyObject.error()
|
|
<< ", replyObject.str() :" << replyObject.str() << ", replyObject.translated() :" << replyObject.translated();
|
|
if (replyObject.error() > 0)
|
|
{
|
|
DEBUG_LOG(m_logger) << "onSendCalculateTrajectoryAdlerReply replyObject.error() :" << replyObject.error() << ", sende EffectClusterModifyReply";
|
|
m_temporaryData.annotationString = replyObject.translated().toStdString();
|
|
Core::FunctionReplyObject ret;
|
|
m_mainHandler->dds()->fts()->effectClusterModifyReply(m_cookie, ret);
|
|
onPublishData();
|
|
return;
|
|
}
|
|
if (m_effectorData.contains(replyObject.id()))
|
|
{
|
|
// m_explosionIndex++;
|
|
m_temporaryData.annotationString.clear();
|
|
DEBUG_LOG(m_logger) << "onSendCalculateTrajectoryAdlerReply m_explosionIndex=" << m_explosionIndex;
|
|
if (m_explosionIndex >= m_data.impactCount)
|
|
{
|
|
DEBUG_LOG(m_logger) << " letztes onSendCalculateTrajectoryAdlerReply"
|
|
<< ", sende EffectClusterModifyReply ohne Fehler";
|
|
Core::FunctionReplyObject ret;
|
|
m_mainHandler->dds()->fts()->effectClusterModifyReply(m_cookie, ret);
|
|
return;
|
|
}
|
|
DEBUG_LOG(m_logger) << "onSendCalculateTrajectoryAdlerReply sendCalculateTrajectoryAdlerRequest";
|
|
// sendCalculateTrajectoryAdlerRequest();
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "onCalculateTrajectoryAdlerReply for id " << replyObject.id() << " success= " << replyObject.error();
|
|
}
|
|
|
|
void ObjectEffectCluster::onSendExFireReadinessReply(const Core::FunctionReplyObject &reply)
|
|
{
|
|
DEBUG_LOG(m_logger) << "onSendExFireReadinessReply for id " << reply.id() << " success= " << reply.error();
|
|
}
|
|
|
|
void ObjectEffectCluster::onSendExFiredReportReply(const Core::FunctionReplyObject &reply)
|
|
{
|
|
DEBUG_LOG(m_logger) << "onSendExFiredReportReply for id " << reply.id() << " success= " << reply.error();
|
|
}
|
|
|
|
void ObjectEffectCluster::onSendExConfirmationReportReply(const Core::FunctionReplyObject &reply)
|
|
{
|
|
DEBUG_LOG(m_logger) << "onSendExConfirmationReportReply for id " << reply.id() << " success= " << reply.error();
|
|
}
|
|
|
|
std::vector<Core::GeoPosition> ObjectEffectCluster::generateGunToTargetForm(const std::vector<Core::RelativePositionRelativeTime> &trajectoryRelativeToOriginator,
|
|
int participantId)
|
|
{
|
|
bool isSaaConnected = (m_mainHandler->dds()->ppmDtc()->saaConnectedState(m_data.partyId) == ParticipantManagement::ConnectState::Connected);
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START generateGunToTargetForm"
|
|
<< ", partyId: " << m_data.partyId << ", participantId: " << m_data.participantId << ", isSaaConnected:" << isSaaConnected << "---";
|
|
|
|
// origin has to be setup (via setupParticipantOrigin)
|
|
std::vector<Core::GeoPosition> trajOut;
|
|
QString reason;
|
|
/*
|
|
if (isSaaConnected && input.empty()) // generate via adler
|
|
sendCalculateTrajectoryAdlerRequest();
|
|
*/
|
|
if (isVulcano() && trajectoryRelativeToOriginator.empty())
|
|
{
|
|
TrajectoryGeneratorVulcano::Options opts =
|
|
// TrajectoryGeneratorVulcano::OptRasterApoTap |
|
|
TrajectoryGeneratorVulcano::OptIncludeIdealHit
|
|
// | TrajectoryGeneratorVulcano::OptRasterTapHit
|
|
;
|
|
|
|
generateTrajectoryVulcanoSpecific(0, opts, &reason); // explosionNumber = 0 -> explosionIndex = -1, means to center of area
|
|
}
|
|
else if (trajectoryRelativeToOriginator.empty())
|
|
{
|
|
generateTrajectory(0, false, &reason); // explosionNumber = 0 -> explosionIndex = -1, means to center of area
|
|
}
|
|
else
|
|
{
|
|
// we already have one just add the stuff
|
|
Core::GeoPosition pos;
|
|
m_mainHandler->dds()->ppmDtc()->waitForHistoricalDataParticipantPosition(2000);
|
|
const auto &participantPos = m_mainHandler->dds()->ppmDtc()->participantPosition(participantId);
|
|
|
|
INFO_LOG(m_logger) << "got pos for " << participantId << " " << participantPos;
|
|
|
|
// Append the start pos????
|
|
// trajOut.push_back(participantPos.pos());
|
|
for (const auto adlerPos : trajectoryRelativeToOriginator)
|
|
{
|
|
auto transPos = LtsCoordTrans::UtmCoordinate::fromGeographic(participantPos.pos().lon(), participantPos.pos().lat());
|
|
|
|
transPos.x += adlerPos.offsetX();
|
|
transPos.y += adlerPos.offsetY();
|
|
pos.lat() = transPos.geographic().y(); // participantPos.pos().lat() + (adlerPos.lat() * (M_PI / 180.0));
|
|
pos.lon() = transPos.geographic().x(); // participantPos.pos().lon() + (adlerPos.lon() * (M_PI / 180.0));
|
|
pos.z() = participantPos.pos().z() + adlerPos.offsetZ();
|
|
trajOut.push_back(pos);
|
|
}
|
|
}
|
|
|
|
if (!reason.isEmpty())
|
|
{
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " reason: " << 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;
|
|
}
|
|
|
|
std::vector<Core::GeoPosition> ObjectEffectCluster::generateGunToTargetForm(QString *reasonOut)
|
|
{
|
|
bool isSaaConnected = (m_mainHandler->dds()->ppmDtc()->saaConnectedState(m_data.partyId) == ParticipantManagement::ConnectState::Connected);
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION << " " << m_data.id << " START generateGunToTargetForm"
|
|
<< ", partyId: " << m_data.partyId << ", participantId: " << m_data.participantId << ", isSaaConnected:" << isSaaConnected << "---";
|
|
|
|
// 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;
|
|
}
|
|
|
|
std::vector<Core::RelativePositionRelativeTime> ObjectEffectCluster::reduceSMArtTrajectoryToSingle(
|
|
const std::vector<Core::RelativePositionRelativeTime> &trajectoryRelative)
|
|
{
|
|
// E01562-294: Falsche Darstellung Flugkurve SMArt
|
|
// At SMArt ADLER (or SAA?) generates a polyline which is "dividing":
|
|
// x x
|
|
// x x
|
|
// x x x
|
|
// x x x
|
|
// | Start | T1 | T2 (Target 1 & Target 2)
|
|
// Same diagram, but with order
|
|
// x4 x5
|
|
// x3 x6
|
|
// x2 x7 x9
|
|
// x1 x8 x10
|
|
// The relativeTime step backward between x8 and x9.
|
|
// Approach:
|
|
// Reduce to curve 0 (x1...x8):
|
|
// Where is a step backward in time?
|
|
|
|
TrajectorySplitter<std::vector<Core::RelativePositionRelativeTime>> splitter(trajectoryRelative);
|
|
const auto &trajectory1 = splitter.getTrajectory1();
|
|
|
|
if (!trajectory1.empty())
|
|
return trajectory1;
|
|
|
|
return trajectoryRelative;
|
|
}
|
|
|
|
void ObjectEffectCluster::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());
|
|
// trajectoryTimeAwareRef.aesItemId(m_data.id);
|
|
// trajectoryTimeAwareRef.aesTypeId(AesType::AtEffectClusterSingleTrajectory);
|
|
trajectoryTimeAwareRef.index(explosionIndex);
|
|
// [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId)
|
|
trajectoryTimeAwareRef.originatorId(m_data.id); // m_data.id == aesItemId
|
|
trajectoryTimeAwareRef.originatorTypeId(ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation);
|
|
|
|
if (isVulcano())
|
|
{
|
|
// Time:
|
|
trajectoryTimeAwareRef.trajectoryStartTime(m_data.effectStart.toMSecsSinceEpoch() - m_temporaryData.calculatedFlightDurationMsec);
|
|
|
|
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();
|
|
}
|
|
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 : qAsConst(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 : qAsConst(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();
|
|
|
|
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();
|
|
}
|
|
|
|
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
|
|
;
|
|
}
|
|
|
|
AreaEffectSimulation::EffectorData ObjectEffectCluster::fromDatabase(const Database::AesEffectorData &data)
|
|
{
|
|
EffectorData ret;
|
|
ret.angleOfFall() = data.angleOfFall;
|
|
ret.chargeAmount() = data.chargeAmount;
|
|
ret.chargeStruct().chargeTypeId() = data.chargeTypeId;
|
|
ret.trajectoryRelative() = TrajectoryRelative::fromString(data.trajectory);
|
|
ret.flightTime() = data.flightTime;
|
|
ret.chargeStruct().igniterTypeId() = data.igniterTypeId;
|
|
ret.chargeStruct().igniterFunctionId() = data.igniterFunctionId;
|
|
ret.interval() = data.interval;
|
|
ret.participantId() = data.participantId;
|
|
ret.peakHeight() = data.peakHeight;
|
|
ret.mhpPosition().lat() = data.mhpPosition.y();
|
|
ret.mhpPosition().lon() = data.mhpPosition.x();
|
|
ret.mhpPosition().z() = data.mhpPosition.z();
|
|
ret.mhpRotation() = data.mhpRotation;
|
|
ret.mhpScatterLenght() = data.mhpScatterLenght;
|
|
ret.mhpScatterLengthDetonationHeight() = data.mhpScatterLengthDetonationHeight;
|
|
ret.mhpScatterWidth() = data.mhpScatterWidth;
|
|
ret.mhpScatterWidthDetonationHeight() = data.mhpScatterWidthDetonationHeight;
|
|
ret.properties() = data.properties.toStdString();
|
|
|
|
return ret;
|
|
}
|
|
|
|
DetonationLocationRequest ObjectEffectCluster::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.igniterTypeId(0);
|
|
// req.igniterFunctionId(m_data.igniterFunctionId);
|
|
req.igniterFunctionId(0);
|
|
req.trajectory(m_trajectoryCont[explosionIndex].trajectoryGeometry);
|
|
|
|
return req;
|
|
}
|
|
|
|
bool ObjectEffectCluster::setEffectors(const QVector<Database::AesEffectorData> &effectors)
|
|
{
|
|
|
|
// clear first but only when we need to update them
|
|
m_effectorData.clear();
|
|
|
|
DEBUG_LOG(m_logger) << " Try assing effector from effector size " << effectors.size();
|
|
for (const auto &effectorData : effectors)
|
|
{
|
|
m_effectorData.insert(effectorData.participantId, effectorData);
|
|
// update effector
|
|
if (m_data.effectStateId > AreaEffectSimulation::AesEffectState::EsExpired)
|
|
{
|
|
DEBUG_LOG(m_logger) << " Try assing effector from effector " << effectorData.id << " pos x " << effectorData.mhpPosition.x() << " ";
|
|
if (!assignEffector(effectorData.participantId))
|
|
{
|
|
DEBUG_LOG(m_logger) << " Try assing effector from effector " << effectorData.id << " pos x " << effectorData.mhpPosition.x() << " FAILED";
|
|
return false;
|
|
}
|
|
if (!updateEffector(effectorData.participantId, effectorData))
|
|
{
|
|
DEBUG_LOG(m_logger) << " Try assing effector from effector " << effectorData.id << " pos x " << effectorData.mhpPosition.x() << " FAILED2 ";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ObjectEffectCluster::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 ObjectEffectCluster::generateForm() { return Polygons::effectClusterArea(m_data); }
|
|
|
|
bool ObjectEffectCluster::sendFireCommand(const QString &identifier)
|
|
{
|
|
// just in case we didn't consumed all effectors in the first place
|
|
DEBUG_LOG(m_logger) << "!!!!!!!!!!!!!!! try send fire command to: " << identifier;
|
|
|
|
const auto &participant = getParticipantFromInternalName(identifier);
|
|
|
|
if (!m_effectorNameToIdMap.contains(identifier))
|
|
{
|
|
if (participant.id() <= 0)
|
|
return false;
|
|
assignEffector(participant.id());
|
|
}
|
|
|
|
if (!m_mainHandler->effectorHandler())
|
|
{
|
|
ERROR_LOG(m_logger) << "No effector Handler";
|
|
return false;
|
|
}
|
|
|
|
auto effector = m_mainHandler->effectorHandler()->getObjectEffector(m_effectorNameToIdMap.value(identifier));
|
|
if (!effector)
|
|
{
|
|
ERROR_LOG(m_logger) << "No effector found for: " << identifier;
|
|
return false;
|
|
}
|
|
if (effector->assignedAesItemId() > 0 && effector->assignedAesItemId() != m_data.id)
|
|
{
|
|
ERROR_LOG(m_logger) << "No effector found for participantId: " << m_effectorNameToIdMap.value(identifier);
|
|
return false;
|
|
}
|
|
// else //if (effector->aesId() <= 0) //consume the effector for our effect cluster
|
|
//{
|
|
// XKCD crashes at the time!
|
|
// effector->assignAesItemRequest(m_data.id);
|
|
// }
|
|
|
|
// we got a fireCommand, meybe need to swtich to prepared
|
|
if (m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsCreated)
|
|
{
|
|
|
|
DEBUG_LOG(m_logger) << "move from recieved TO prepared: flightime: " << m_effectorData.value(participant.id()).flightTime
|
|
<< " currentTime:" << partitionTime().toString();
|
|
// Set Actication time to current + flight duration of the current effector
|
|
DEBUG_LOG(m_logger) << "START STIME NEW: " << partitionTime().addSecs(m_effectorData.value(participant.id()).flightTime).toString();
|
|
m_data.effectStart = partitionTime().addSecs(m_effectorData.value(participant.id()).flightTime);
|
|
if (!trySwitchToPrepared())
|
|
{
|
|
WARNING_LOG(m_logger) << "Could not switch to prepared for aesid " << m_data.id;
|
|
}
|
|
}
|
|
|
|
effector->fireOrderRequest();
|
|
DEBUG_LOG(m_logger) << "send: fireorder to " << m_effectorNameToIdMap.value(identifier);
|
|
// now connect to chargeeffects
|
|
connect(m_mainHandler->chargeEffectHandler().get(), &AreaEffectSimulation::HandlerChargeEffect::chargeEffectActive, this, &ObjectEffectCluster::onChargeEffectChanged);
|
|
if (m_fireHoldWaitforLateFire)
|
|
m_fireHoldWaitforLateFire->start(); // restart timer
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ObjectEffectCluster::sendFireCommand(int participantId)
|
|
{
|
|
// if (!m_effectorNameToIdMap.values().contains(participantId))
|
|
assignEffector(participantId);
|
|
|
|
auto effector = m_mainHandler->effectorHandler()->getObjectEffector(participantId);
|
|
if (!effector)
|
|
{
|
|
ERROR_LOG(m_logger) << "No effector found for participantId: " << participantId;
|
|
return false;
|
|
}
|
|
if (effector->assignedAesItemId() > 0 && effector->assignedAesItemId() != m_data.id)
|
|
{
|
|
ERROR_LOG(m_logger) << "Got Fire Command but Effector for participantId: " << participantId << " is not assigned to our aes id: " << m_data.id << " vs "
|
|
<< effector->assignedAesItemId();
|
|
return false;
|
|
}
|
|
// else if (effector->getAssignedAesItemId() <= 0) //consume the effector for our effect cluster
|
|
// effector->assignAesItemRequest(m_data.id);
|
|
DEBUG_LOG(m_logger) << "Send FireCommand to Effector " << participantId;
|
|
|
|
effector->fireOrderRequest();
|
|
// now connect to chargeeffects
|
|
connect(m_mainHandler->chargeEffectHandler().get(),
|
|
&AreaEffectSimulation::HandlerChargeEffect::chargeEffectActive,
|
|
this,
|
|
&ObjectEffectCluster::onChargeEffectChanged,
|
|
Qt::UniqueConnection);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ObjectEffectCluster::overrideEffectorStates(const AesEffectorState &newState)
|
|
{
|
|
|
|
if (!m_mainHandler->effectClusterHandler())
|
|
return false;
|
|
|
|
switch (newState.underlying())
|
|
{
|
|
case AesEffectorState::EsReadyToFire:
|
|
{
|
|
// Override internal state in case DMF is not ready yet
|
|
m_data.effectorState = AesEffectorState::EsReadyToFire;
|
|
DEBUG_LOG(m_logger) << "Set Ready to Fire !";
|
|
onPublishData();
|
|
|
|
if (m_readytoFire.size() == m_effectorData.count() && m_data.effectorState == AesEffectorState::EsReadyToFire)
|
|
{ // we need some time to handle the ack on tab
|
|
QTimer::singleShot(1000,
|
|
this,
|
|
[this]()
|
|
{
|
|
DEBUG_LOG(m_logger) << "Everything is ready send Readiness now";
|
|
onSendReadinessToTab();
|
|
});
|
|
}
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
|
|
FiringType firingType = FiringType::FireOnCommand;
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSART()))
|
|
firingType = static_cast<FiringType::inner_enum>(probObject[AdlerJson::JsonKeys::ABFEUERUNGSART()].toInt());
|
|
|
|
if (m_data.effectSourceId == ProjectDataManagement::EffectSource::ES_ADLER
|
|
|| (m_data.effectSourceId == ProjectDataManagement::EffectSource::ES_ADLER_STANDALONE && firingType != FiringType::FireNow))
|
|
{
|
|
return true;
|
|
}
|
|
// Will also start Fire
|
|
|
|
Q_FALLTHROUGH();
|
|
}
|
|
case AesEffectorState::EsReceivedOrder:
|
|
{
|
|
DEBUG_LOG(m_logger) << "Set Start !";
|
|
// got an early start
|
|
if (m_data.effectStateId == AesEffectState::EsCreated)
|
|
{
|
|
// first try to set prepared
|
|
m_data.effectStart = partitionTime().addMSecs(m_effectorData.begin().value().flightTime);
|
|
DEBUG_LOG(m_logger) << "SWITCH TO PREPARED AND SET FIRING TIME REACHED";
|
|
|
|
m_firingTimeReached = true;
|
|
trySwitchToPrepared();
|
|
}
|
|
if (m_fireAutomaton)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Set Start for fireAutomaton !";
|
|
return m_fireAutomaton->start();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_LOG(m_logger) << "Set Start for Manual Mode !";
|
|
for (const auto &effectorId : m_effectorData.keys())
|
|
{
|
|
// Only fire if there is still a round left
|
|
if (m_firedPerEfector.value(effectorId) < m_effectorData.value(effectorId).chargeAmount)
|
|
{
|
|
if (!sendFireCommand(effectorId))
|
|
{
|
|
WARNING_LOG(m_logger) << "cant fire for effector! " << effectorId;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
connect(m_mainHandler->chargeEffectHandler().get(),
|
|
&AreaEffectSimulation::HandlerChargeEffect::chargeEffectActive,
|
|
this,
|
|
&ObjectEffectCluster::onChargeEffectChanged,
|
|
Qt::UniqueConnection);
|
|
return true;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ParticipantManagement::Participant ObjectEffectCluster::getParticipantFromInternalName(const QString &name)
|
|
{
|
|
if (m_data.participantId <= 0) // No origin Unit
|
|
return {};
|
|
|
|
const auto originator = m_mainHandler->dds()->ppmDtc()->participant(m_data.participantId);
|
|
if (originator.name() == name.toStdString()) // in Case of a single originator and not a unit return the originator
|
|
return originator;
|
|
|
|
if (originator.id() <= 0 || originator.isDeleted()) // no Originator found or is deleted
|
|
return {};
|
|
|
|
const auto originatorType = m_mainHandler->dds()->ppmDtc()->type(originator.typeId());
|
|
|
|
if (originatorType.typeClass() != ParticipantManagement::TypeClass::Unit) // not a unit
|
|
return {};
|
|
|
|
// Now we look up for the inferior participants to find the specific efdfector
|
|
// the return value is either the correct participant or empty
|
|
return m_mainHandler->dds()->ppmDtc()->getParticipantByInternalName(originator.partyId(), QString::fromStdString(originator.name()), name);
|
|
}
|
|
|
|
bool ObjectEffectCluster::assignEffector(int participantId)
|
|
{
|
|
DEBUG_LOG(m_logger) << "TRY ASSIGN Effector " << participantId << "";
|
|
|
|
if (m_data.effectStateId == AesEffectState::EsExpired)
|
|
return false;
|
|
if (m_effectorNameToIdMap.values().contains(participantId))
|
|
{
|
|
DEBUG_LOG(m_logger) << "Effector " << participantId << "Already assigned";
|
|
return true;
|
|
}
|
|
const auto &participant = m_mainHandler->dds()->ppmDtc()->participant(participantId);
|
|
if (participant.id() <= 0)
|
|
{
|
|
ERROR_LOG(m_logger) << "TRY ASSIGN Effector " << participantId << " 2 no participant ID";
|
|
return false;
|
|
}
|
|
if (participant.name().empty() && participant.designator().empty())
|
|
{
|
|
ERROR_LOG(m_logger) << "TRY ASSIGN Effector " << participantId << " 3 no names";
|
|
return false;
|
|
}
|
|
if (!m_mainHandler->effectorHandler())
|
|
{
|
|
ERROR_LOG(m_logger) << "TRY ASSIGN Effector " << participantId << " 4 no effectorHandler";
|
|
return false;
|
|
}
|
|
auto effector = m_mainHandler->effectorHandler()->getObjectEffector(participantId);
|
|
if (!effector)
|
|
{
|
|
ERROR_LOG(m_logger) << "TRY ASSIGN Effector " << participantId << " 5 no effector for participantId";
|
|
return false;
|
|
}
|
|
m_effectorNameToIdMap.insert(!participant.name().empty() ? QString::fromStdString(participant.name()) : QString::fromStdString(participant.name()), participant.id());
|
|
// just in case override timers with cadence
|
|
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
FiringType firingType = FiringType::ReportReadyToFire;
|
|
int cadence = 0;
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSART()))
|
|
firingType = static_cast<FiringType::inner_enum>(probObject[AdlerJson::JsonKeys::ABFEUERUNGSART()].toInt());
|
|
if (m_data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER && firingType == FiringType::FireOnCommand)
|
|
{
|
|
const auto &gearTypeIds = m_mainHandler->dds()->pdmDtc()->gearTypeIdFromChargeTypeId(m_data.chargeTypeId);
|
|
cadence = 20000; // emergency fallback
|
|
if (gearTypeIds.size())
|
|
cadence = m_mainHandler->dds()->pdmDtc()->cadence(gearTypeIds.at(0));
|
|
DEBUG_LOG(m_logger) << "Override with cadence " << cadence;
|
|
}
|
|
if (effector->assignedAesItemId() == m_data.id)
|
|
{
|
|
if (cadence > 0)
|
|
effector->overrideReadynessTimerMinMax(cadence, cadence);
|
|
return true;
|
|
}
|
|
|
|
connect(effector, &AreaEffectSimulation::ObjectEffector::signalFired, this, &ObjectEffectCluster::onEffectorFired, Qt::UniqueConnection);
|
|
connect(effector, &AreaEffectSimulation::ObjectEffector::signalNotReadyToFire, this, &ObjectEffectCluster::onEffectorNotReadyToFire, Qt::UniqueConnection);
|
|
connect(effector, &AreaEffectSimulation::ObjectEffector::signalReadyToFire, this, &ObjectEffectCluster::onEffectorReadyToFire, Qt::UniqueConnection);
|
|
connect(effector, &AreaEffectSimulation::ObjectEffector::signalReceivedOrder, this, &ObjectEffectCluster::onEffectorReceivedOrder, Qt::UniqueConnection);
|
|
connect(effector, &AreaEffectSimulation::ObjectEffector::signalReportedFired, this, &ObjectEffectCluster::onEffectorReportedFired, Qt::UniqueConnection);
|
|
effector->assignAesItemRequest(m_data.id);
|
|
|
|
if (cadence > 0)
|
|
effector->overrideReadynessTimerMinMax(cadence, cadence);
|
|
|
|
DEBUG_LOG(m_logger) << "Try assign Effector " << participantId << " complete!";
|
|
return true;
|
|
}
|
|
|
|
bool ObjectEffectCluster::updateEffector(int participantId, const Database::AesEffectorData &data)
|
|
{
|
|
auto effector = m_mainHandler->effectorHandler()->getObjectEffector(participantId);
|
|
if (!effector)
|
|
{
|
|
ERROR_LOG(m_logger) << "No effector found for partId: " << participantId;
|
|
return false;
|
|
}
|
|
effector->setEffectorData(fromDatabase(data));
|
|
DEBUG_LOG(m_logger) << "DONE Update effectordata for partId: " << participantId;
|
|
return true;
|
|
}
|
|
|
|
void ObjectEffectCluster::freeEffectors()
|
|
{
|
|
for (const auto effectorId : m_effectorNameToIdMap)
|
|
{
|
|
auto effector = m_mainHandler->effectorHandler()->getObjectEffector(effectorId);
|
|
// Only reset effector if it's still our own ?
|
|
if (effector)
|
|
{
|
|
disconnect(effector);
|
|
effector->resetReadynessTimer();
|
|
effector->deleteAssignmentAesItem();
|
|
DEBUG_LOG(m_logger) << "DONE free effector: " << effectorId;
|
|
}
|
|
}
|
|
}
|
|
|
|
QVector<Database::AesEffectorData> ObjectEffectCluster::generateMHPs(const QVector<Database::AesEffectorData> &effectorData, const AesExplosionGeometry &geometry)
|
|
{
|
|
|
|
double singleWidthOut, singleHeightOut;
|
|
singleWidthOut = singleHeightOut = 0;
|
|
QVector<Database::AesEffectorData> ret;
|
|
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.tla);
|
|
// Radial extras
|
|
const QPointF center = Polygons::bottomLeft2CenterPoint(m_data.impactPosition, m_data.impactWidth, m_data.impactLength, 0.0);
|
|
const LtsCoordTrans::UtmCoordinate ¢erUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center, impactPositionUtm.zone);
|
|
const LtsCoordTrans::UtmCoordinate &firingOriginUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(m_data.firingCenterPosition.toPointF());
|
|
|
|
QVector<QPointF> mhps_asUtm;
|
|
switch (geometry.underlying())
|
|
{
|
|
case AesExplosionGeometry::EgLine:
|
|
case AesExplosionGeometry::EgArea:
|
|
{
|
|
mhps_asUtm = Polygons::explosionClusterAreaToMtps(areaBlWithTla, m_data.mhpCountRequest, &singleWidthOut, &singleHeightOut);
|
|
break;
|
|
}
|
|
case AesExplosionGeometry::EgPoint:
|
|
case AesExplosionGeometry::EgAimPoint:
|
|
{
|
|
mhps_asUtm = Polygons::explosionClusterAreaToMtps(areaBlWithTla, 1, &singleWidthOut, &singleHeightOut);
|
|
while (mhps_asUtm.size() < effectorData.size())
|
|
{
|
|
// copy same
|
|
mhps_asUtm.append(mhps_asUtm.at(0));
|
|
}
|
|
break;
|
|
}
|
|
case AesExplosionGeometry::EgRadial:
|
|
{
|
|
mhps_asUtm = Polygons::explosionClusterRadialToMtps(areaBlWithTla, m_data.mhpCountRequest, firingOriginUtm, centerUtm, &singleWidthOut, &singleHeightOut);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
int mhpPos = 0;
|
|
if (mhps_asUtm.size() != effectorData.size())
|
|
return ret;
|
|
for (const auto &mhpPoint : mhps_asUtm)
|
|
{
|
|
auto effectorDataNew = effectorData.at(mhpPos);
|
|
|
|
LtsCoordTrans::UtmCoordinate temp(impactPositionUtm.zone, mhpPoint.x(), mhpPoint.y());
|
|
|
|
effectorDataNew.mhpPosition.setX(temp.geographic().x());
|
|
effectorDataNew.mhpPosition.setY(temp.geographic().y());
|
|
effectorDataNew.mhpScatterWidth = singleWidthOut;
|
|
effectorDataNew.mhpScatterLenght = singleHeightOut;
|
|
mhpPos++;
|
|
|
|
DEBUG_LOG(m_logger) << " append new EffectorData with point: " << temp.geographic().x() << " " << temp.geographic().y();
|
|
ret.append(effectorDataNew);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void ObjectEffectCluster::onSendReadinessToTab()
|
|
{
|
|
DEBUG_LOG(m_logger) << "onSendReadinessToTab";
|
|
|
|
if (effectState() == AesEffectState::EsRemoved)
|
|
return;
|
|
|
|
if (m_data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER)
|
|
return;
|
|
|
|
if (m_data.effectStateId != AesEffectState::EsCreated)
|
|
return;
|
|
// need properties now to know wich firingType is set
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
|
|
FiringType firingType = FiringType::FireNow;
|
|
if (probObject.contains(AdlerJson::JsonKeys::ABFEUERUNGSART()))
|
|
firingType = static_cast<FiringType::inner_enum>(probObject[AdlerJson::JsonKeys::ABFEUERUNGSART()].toInt());
|
|
|
|
// clear ready to fire. Wait for all beeing ready again
|
|
m_readytoFire.clear();
|
|
|
|
// We always send the first fireReadynessReport regardless of the Firing Type
|
|
// Only send FireRediness after the initial one (at prep state) when we are in Mode
|
|
// Fire on Command (K)
|
|
DEBUG_LOG(m_logger) << "onSendReadinessToTab first Time" << m_firstTimeReady << " firing " << firingType << " currente State " << m_data.effectStateId;
|
|
|
|
if (!m_firstTimeReady && firingType != FiringType::FireOnCommand)
|
|
return;
|
|
m_firstTimeReady = false;
|
|
const QString reference = probObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString();
|
|
|
|
TrainingAreaBase::SendExFireReadinessRequest request;
|
|
request.reference() = reference.toStdString();
|
|
request.partyId() = m_data.partyId;
|
|
request.participantId() = m_data.participantId;
|
|
m_mainHandler->dds()->tabFtc()->sendExFireReadinessRequest(request, this);
|
|
}
|
|
|
|
void ObjectEffectCluster::onSendConfirmationReportToTab(int participantId)
|
|
{
|
|
if (m_data.effectSourceId != ProjectDataManagement::EffectSource::ES_ADLER)
|
|
return;
|
|
|
|
DEBUG_LOG(m_logger) << "onSendConfirmationReportToTab!";
|
|
|
|
const QJsonObject probObject = QJsonDocument::fromJson(QByteArray::fromStdString(m_data.properties.toStdString())).object();
|
|
const QString reference = probObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString();
|
|
|
|
TrainingAreaBase::SendExConfirmationReportRequest request;
|
|
request.reference() = reference.toStdString();
|
|
request.partyId() = m_data.partyId;
|
|
request.unitIdentifier() = m_mainHandler->dds()->ppmDtc()->participant(m_data.participantId).name();
|
|
request.identifier() = m_mainHandler->dds()->ppmDtc()->participant(participantId).name();
|
|
m_mainHandler->dds()->tabFtc()->sendExConfirmationReportRequest(request, this);
|
|
}
|
|
|
|
void ObjectEffectCluster::onChargeEffectChanged(int chargeEffectId)
|
|
{
|
|
DEBUG_LOG(m_logger) << "Got chargeEffect Changed " << chargeEffectId << " looking for " << m_lookoutChargeEffectStart << " or " << m_lookoutChargeEffectEnd << " or "
|
|
<< m_lookoutChargeEffectPaused;
|
|
|
|
if (chargeEffectId != m_lookoutChargeEffectStart && chargeEffectId != m_lookoutChargeEffectEnd && chargeEffectId != m_lookoutChargeEffectPaused)
|
|
return;
|
|
|
|
if (m_data.effectStateId != AreaEffectSimulation::AesEffectState::EsFired && m_data.effectStateId != AreaEffectSimulation::AesEffectState::EsPrepared
|
|
&& m_data.effectStateId != AreaEffectSimulation::AesEffectState::EsPaused && m_data.effectStateId != AreaEffectSimulation::AesEffectState::EsActivated)
|
|
return;
|
|
|
|
// in case of a Single Fire: just start the function again after a second (and a half)
|
|
if ((m_lookoutChargeEffectEnd == m_lookoutChargeEffectStart && m_lookoutChargeEffectStart > 0)
|
|
|| (m_lookoutChargeEffectPaused == m_lookoutChargeEffectStart && m_lookoutChargeEffectStart > 0))
|
|
{
|
|
DEBUG_LOG(m_logger) << "SingleFire stop next";
|
|
QTimer::singleShot(1500, this, [this, chargeEffectId]() { onChargeEffectChanged(chargeEffectId); });
|
|
}
|
|
// In Case of Paused , Fired or Prepared: try to switch to active
|
|
// Sometimes when the calcualted start time is faster than the impact the trychangestate might fail
|
|
if (chargeEffectId == m_lookoutChargeEffectStart
|
|
&& (m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsFired || m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsPaused
|
|
|| m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsPrepared))
|
|
{
|
|
m_data.effectStart = partitionTime();
|
|
if (m_data.effectStateId != AreaEffectSimulation::AesEffectState::EsActivated && !trySwitchToActivated())
|
|
{
|
|
DEBUG_LOG(m_logger) << "Could't change to active maybe already done! Current State: " << m_data.effectStateId;
|
|
}
|
|
m_lookoutChargeEffectStart = 0;
|
|
return;
|
|
}
|
|
|
|
if (m_lookoutChargeEffectPaused == chargeEffectId && m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsActivated)
|
|
{
|
|
// reduce effectDuration
|
|
m_data.effectDuration = m_data.effectDuration - (partitionTimeMSec() - m_data.effectStart.toMSecsSinceEpoch());
|
|
DEBUG_LOG(m_logger) << "Last flying shot fired and landed, new duration: " << m_data.effectDuration;
|
|
if (!trySwitchToPaused())
|
|
{
|
|
DEBUG_LOG(m_logger) << "Could not switch to Paused, maybe alredy done. Current State: " << m_data.effectStateId;
|
|
}
|
|
m_lookoutChargeEffectPaused = 0;
|
|
}
|
|
|
|
// In Case of Executed: try to switch to active
|
|
// Sometimes when the calcualted start time is faster than the impact the trychangestate might fail
|
|
if (m_lookoutChargeEffectEnd == chargeEffectId && m_data.effectStateId == AreaEffectSimulation::AesEffectState::EsActivated)
|
|
{
|
|
// truncate effect duration to now
|
|
m_data.effectDuration = partitionTimeMSec() - m_data.effectStart.toMSecsSinceEpoch();
|
|
DEBUG_LOG(m_logger) << "All shots fired and landed, new duration: " << m_data.effectDuration;
|
|
if (!trySwitchToExpired())
|
|
{
|
|
DEBUG_LOG(m_logger) << "Could not switch to Expired, maybe alredy done. Current State: " << m_data.effectStateId;
|
|
}
|
|
disconnect(m_mainHandler->chargeEffectHandler().get());
|
|
m_lookoutChargeEffectEnd = 0;
|
|
}
|
|
}
|
|
|
|
} // Namespace AreaEffectSimulation
|