/** * Handler for player data * $Id$ * $HeadURL$ * $Author$ * Created by hjl, SLSE, 2017-02-20 */ #include "handler_vulcano.h" #include "expand_polygon.h" #include "isInCone.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace CesExplosions { HandlerVulcano::HandlerVulcano(const std::shared_ptr &ddsHandler, QObject *parent) : QObject(parent) , m_ddsHandler(ddsHandler) , m_logger(CentralEffectSimulation::getLogger("Plugin.Explosions.HandlerVulcano")) { } // end of ctor() //----------------------------------------------------------------------------- HandlerVulcano::~HandlerVulcano() { } // end of dtor() //----------------------------------------------------------------------------- bool HandlerVulcano::init() { auto dtcAes = m_ddsHandler->dtcAes(); auto ftcAes = m_ddsHandler->ftcAes(); auto dtcPpm = m_ddsHandler->dtcPpm(); ftcAes->enableTargetPositionsModify(); dtcAes->enableExplosionCluster(); connect(dtcAes.get(), &AreaEffectSimulation::Interface::Dtc::explosionClusterChanged, this, &HandlerVulcano::onExplosionClusterChanged); dtcAes->enableTrajectoryTimeAware(); dtcPpm->enableDynamicParticipantData(); connect(dtcPpm.get(), &ParticipantManagement::Interface::Dtc::dynamicParticipantDataChanged, this, &HandlerVulcano::onDynamicParticipantChanged); float vulcanoViewAngleDeg = CesExplosions::Settings::vulcanoViewAngleDeg(); float vulcanoAttackAngleDeg = CesExplosions::Settings::vulcanoAttackAngleDeg(); float vulcanoAlphaDeg = CesExplosions::Settings::vulcanoMaximumDesignationAngleDeg(); DEBUG_LOG(m_logger) << "vulcanoViewAngleDeg=" << vulcanoViewAngleDeg << " vulcanoAttackAngleDeg=" << vulcanoAttackAngleDeg << " vulcanoAlphaDeg=" << vulcanoAlphaDeg; return true; } // end of init() //----------------------------------------------------------------------------- void HandlerVulcano::onTargetPositionsModifyReply(const Core::FunctionReplyObject &replyObject) { if (replyObject.error()) WARNING_LOG(m_logger) << "Can not send TargetPositionsRequest: " << replyObject(); else DEBUG_LOG(m_logger) << "TargetPositionsRequest for id " << replyObject.id() << " success\n" << " replyObject.str() = " << replyObject.str(); } // /////////////////////////////////// // A E S (AreaEffectSimulation) // /////////////////////////////////// void HandlerVulcano::onExplosionClusterChanged(const AreaEffectSimulation::ExplosionCluster &data, DataModification modType) { // order checks by effort: if ((modType == DataModification::eRemoved) || (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsExpired) || (data.effectStateId() == AreaEffectSimulation::AesEffectState_def::EsActivated)) return; if (!data.effectStart().is_set()) { DEBUG_LOG(m_logger) << "No effectStart"; return; } // Kriterium fuer NASAT/MIDAS if (m_ddsHandler->dtcPdm()->gearClassFromChargeTypeId(data.chargeStruct().chargeTypeId()) == ProjectDataManagement::GearClass_def::AirSupport) return; if (!m_ddsHandler->isLaserGuidable(data.chargeStruct())) return; if (!data.flightCurve().is_set() || !data.laserGuiding().is_set()) return; const AreaEffectSimulation::FlightCurve &fcData = data.flightCurve().get(); const AreaEffectSimulation::LaserGuiding &lgData = data.laserGuiding().get(); DEBUG_LOG(m_logger) << "\n onExplosionClusterChanged id=" << data.id() << " AesEffectState=" << data.effectStateId() << " lcPrf=" << lgData.lcAttack() << " originalEffectDuration=" << data.originalEffectDuration() << " effectStart=" << data.effectStart(); DEBUG_LOG(m_logger) << "m_prfToAesId.insert(" << lgData.lcAttack() << ", " << data.id() << ")" << " tapTimepoint=" << lgData.tapTime() << " currentPartitionTime=" << m_ddsHandler->currentPartitionTime() << " Timer interval =" << lgData.tapTime() - m_ddsHandler->currentPartitionTime(); int aesId = data.id(); QTimer *timer = new QTimer(this); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, this, &HandlerVulcano::onFiringTimeTimeout); if (m_aesTimers.contains(data.id())) { m_aesTimers.value(data.id())->stop(); delete m_aesTimers.value(data.id()); m_aesTimers.remove(data.id()); } m_aesTimers.insert(data.id(), timer); timer->setProperty("aesId", aesId); { long long effectStart = data.effectStart().is_set() ? data.effectStart().get() : 0; long long calculatedFlightDurationMSec = fcData.calculatedFlightDuration(); long long currentPartitionTime = m_ddsHandler->currentPartitionTime(); const int timerTimeout = effectStart - calculatedFlightDurationMSec - currentPartitionTime; DEBUG_LOG(m_logger) << "Timer started mit Abflugzeit des Vulcano=" << timerTimeout; if (timerTimeout >= 0) timer->start(timerTimeout); } m_explosionCluster.insert(data.id(), data); } void HandlerVulcano::onFiringTimeTimeout() { DEBUG_LOG(m_logger) << "++++++++++++++ onFiringTimeTimeout"; QTimer *t = qobject_cast(sender()); int aesId = t->property("aesId").toInt(); delete m_aesTimers.value(aesId); m_aesTimers.remove(aesId); DEBUG_LOG(m_logger) << "onTimerTimeout aesId=" << aesId; const auto &myEC = m_explosionCluster.value(aesId); if (myEC.id() != aesId) return; if (!myEC.flightCurve().is_set()) return; const AreaEffectSimulation::FlightCurve &fcData = myEC.flightCurve().get(); //auto dtcPpm = m_ddsHandler->dtcPpm(); //auto participantId = dtcPpm.get()->getParticipantFromPrf(prf); //auto dpd = dtcPpm.get()->dynamicParticipantData(participantId); //DEBUG_LOG(m_logger) << "onTimerTimeout participantId=" << participantId; QTimer *timer = new QTimer(this); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, this, &HandlerVulcano::onTrajectoryTimeAwareTimeout); if (m_aesTimers.contains(aesId)) { m_aesTimers.value(aesId)->stop(); delete m_aesTimers.value(aesId); m_aesTimers.remove(aesId); } m_aesTimers.insert(aesId, timer); timer->setProperty("aesId", aesId); auto dtcAes = m_ddsHandler->dtcAes(); const AreaEffectSimulation::TrajectoryTimeAware &trajectoryTimeAware = firstTrajectoryTimeAwareFromAesId(aesId); if (trajectoryTimeAware.geometry().size() < 2) { DEBUG_LOG(m_logger) << "return"; return; } /* x | Tap (aka TapT) // \ | // \ | // \ | // \ | // -------x | CloudBase (Tap_strich) // \ | // \ | // ----------x | Impact // // ----------------| Surface */ const std::vector &geometryRef = trajectoryTimeAware.geometry(); int calculatedFlightDurationMSec = fcData.calculatedFlightDuration(); // TAP' kann auch TAP sein -> Es ist immer der vorletzte Wert. = size() - 2. int relTimeTap = geometryRef[geometryRef.size() - 2].relativeTime(); // Der letzte Wert ist immer der Einschlag = size() - 1. int relTimeExplosion = geometryRef[geometryRef.size() - 1].relativeTime(); // Flugzeit zwischen TAP(') und Einschlag int timeDiffTapToExplosion = relTimeExplosion - relTimeTap; // Somit ist die absolute Zeit am TAP(') die Startzeit abzüglich der Flugzeit int absTimeAtTap = myEC.effectStart().get() - timeDiffTapToExplosion; // Und die ist in wieviel Sekunden? int relTimeToTap = absTimeAtTap - m_ddsHandler->currentPartitionTime(); DEBUG_LOG(m_logger) << "timer->start timeout= " << relTimeToTap << " calculatedFlightDurationMSec= " << calculatedFlightDurationMSec; if (relTimeToTap >= 0) timer->start(relTimeToTap); } void HandlerVulcano::onTrajectoryTimeAwareTimeout() { QTimer *t = qobject_cast(sender()); int aesId = t->property("aesId").toInt(); delete m_aesTimers.value(aesId); m_aesTimers.remove(aesId); DEBUG_LOG(m_logger) << "++++++++++++++ onTrajectoryTimeAwareTimeout aesId=" << aesId; const auto &myEC = m_explosionCluster.value(aesId); if (myEC.id() != aesId) return; if (!myEC.laserGuiding().is_set()) return; const AreaEffectSimulation::LaserGuiding &lgData = myEC.laserGuiding().get(); const int prf = lgData.lcAttack(); const auto &dtcPpm = m_ddsHandler->dtcPpm(); const auto &dtcAes = m_ddsHandler->dtcAes(); auto participantId = dtcPpm->getParticipantFromPrf(prf); auto dpd = dtcPpm->dynamicParticipantData(participantId); const auto &designationsVec = dpd.designations(); DEBUG_LOG(m_logger) << "onTimerTimeout participantId=" << participantId; for (const auto &designation : qAsConst(designationsVec)) { if (designation.code() == prf && designation.outputPower() > 0) { const AreaEffectSimulation::TrajectoryTimeAware &trajectoryTimeAware = firstTrajectoryTimeAwareFromAesId(aesId); const std::vector &trajGeometry = trajectoryTimeAware.geometry(); DEBUG_LOG(m_logger) << "trajectoryTimeAware.geometry().size()=" << trajGeometry.size(); DEBUG_LOG(m_logger) << "Designation code=" << designation.code() << " prf=" << prf << " outputPower=" << designation.outputPower() << " designation.spotlocation=" << designation.spotLocation().lon() << "/" << designation.spotLocation().lat(); if (trajGeometry.size() < 2) { DEBUG_LOG(m_logger) << "return"; return; } const Core::GeoPosition topGeo = trajGeometry.at(trajGeometry.size() - 2); const Core::GeoPosition bottomGeo = trajGeometry.at(trajGeometry.size() - 1); const Core::GeoPosition2D pointToTestGeo = designation.spotLocation(); int zone = -1; const QPointF &topPt = CentralEffectSimulation::geographic2Utm(topGeo, &zone); const QPointF &bottomPt = CentralEffectSimulation::geographic2Utm(bottomGeo, &zone); const QPointF &pointToTestPt = CentralEffectSimulation::geographic2Utm(pointToTestGeo, &zone); const QVector3D top(topPt.x(), topPt.y(), topGeo.z()); DEBUG_LOG(m_logger) << "top=" << top.x() << "/" << top.y() << "/" << top.z() << " zone=" << zone; const QVector3D bottom(bottomPt.x(), bottomPt.y(), bottomGeo.z()); DEBUG_LOG(m_logger) << "bottom=" << bottom.x() << "/" << bottom.y() << "/" << bottom.z() << " zone=" << zone; const QVector3D pointToTest(pointToTestPt.x(), pointToTestPt.y(), designation.spotLocation().z()); DEBUG_LOG(m_logger) << "pointToTest=" << pointToTest.x() << "/" << pointToTest.y() << "/" << pointToTest.z() << " zone=" << zone; float vulcanoViewAngleDeg = CesExplosions::Settings::vulcanoViewAngleDeg(); float vulcanoAttackAngleDeg = CesExplosions::Settings::vulcanoAttackAngleDeg(); DEBUG_LOG(m_logger) << "vulcanoViewAngleDeg=" << vulcanoViewAngleDeg << " vulcanoAttackAngleDeg=" << vulcanoAttackAngleDeg; if (! isInCone(top, bottom, vulcanoAttackAngleDeg, pointToTest)) { DEBUG_LOG(m_logger) << "Punkt ist ausserhalb des Attack Angles. "; return; } const auto observerPos = dtcPpm->participantPosition(participantId).pos(); QPointF fePt = CentralEffectSimulation::geographic2Utm(trajGeometry.at(0), &zone); QVector3D fe(fePt.x(), fePt.y(), 0); QVector3D lp = pointToTest; QPointF observerPt = CentralEffectSimulation::geographic2Utm(observerPos, &zone); QVector3D observer(observerPt.x(), observerPt.y(), 0); double angle = CesExplosions::Settings::vulcanoMaximumDesignationAngleDeg(); DEBUG_LOG(m_logger) << "isInAngle fe=" << fe.x() << "/" << fe.y() << ", lp=" << lp.x() << "/" << lp.y() << ", observer=" << observer.x() << "/" << observer.y() << ", angle=" << angle << ", zone=" << zone; if (! isInCone(lp, fe, angle, observer)) { DEBUG_LOG(m_logger) << "Keine neue Vulcano-Pos: Winkel ist zu gross. "; return; } //designation.spotLocation() Core::GeoPosition posVulcano = getTrajectoryPosFromTime(aesId, static_cast(m_ddsHandler->currentPartitionTime())); AreaEffectSimulation::TargetPositionsModifyRequest posModify; posModify.aesItemId(aesId); posModify.positions().push_back(designation.spotLocation()); DEBUG_LOG(m_logger) << "targetPositionsModifyRequest z=" << posModify.positions().at(0).z() << " designation.z=" << designation.spotLocation().z() << " PosVulcano=" << posVulcano.lon() << "/" << posVulcano.lat(); m_ddsHandler->ftcAes()->targetPositionsModifyRequest(posModify, this); return; } } // for } AreaEffectSimulation::TrajectoryTimeAware HandlerVulcano::firstTrajectoryTimeAwareFromAesId(int aesId) { // be aware of: [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId) const auto &dtcAes = m_ddsHandler->dtcAes(); const auto &trajectories = dtcAes->trajectoriesTimeAwareFromOriginatorIdAndType(aesId, ProjectDataManagement::StateChangeOriginator::AreaEffectSimulation); if (trajectories.empty()) { DEBUG_LOG(m_logger) << "no trajectories found for originatorId/aesItemId " << aesId; return {}; } Q_ASSERT_X(trajectories.size() == 1, __FUNCTION__, "For Vulcano there should be just one trajectory"); const auto &trajectoryTimeAwareValue = trajectories[0]; return trajectoryTimeAwareValue; } Core::GeoPosition HandlerVulcano::getTrajectoryPosFromTime(int aesId, long long timeParam) { // be aware of: [E01562-259] use originatorId, originatorTypeId (instead of aesItemId, aesTypeId) const auto &trajectoryTimeAwareValue = firstTrajectoryTimeAwareFromAesId(aesId); const qint64 relTime = timeParam - trajectoryTimeAwareValue.trajectoryStartTime(); //trajectoryTimeAwareValue = trajectoryTimeAware(aesId); DEBUG_LOG(m_logger) << " getTrajectoryPosFromTime aesId=" << aesId << " trajectoryTimeAwareValue.id=" << trajectoryTimeAwareValue.id() << " time=" << timeParam << " trajectoryStartTime=" << trajectoryTimeAwareValue.trajectoryStartTime() << " relTime=" << relTime; if (trajectoryTimeAwareValue.geometry().empty()) { DEBUG_LOG(m_logger) << "getTrajectoryPosFromTime no pos available"; return {}; } Core::GeoPosition result; int i = 0; for (const auto &point : qAsConst(trajectoryTimeAwareValue.geometry())) { DEBUG_LOG(m_logger) << "time=" << point.relativeTime(); if ( (point.relativeTime() > relTime) && (i > 0)) { // Beachte: Geschwindigkeit wird _NICHT_ beruecksichtigt const Core::GeoPositionRelativeTime &geoPosRelTime = trajectoryTimeAwareValue.geometry()[i-1]; // avoid expensive operations like following // result.lat() = trajectoryTimeAwareValue.geometry()[i -1].lat(); // result.lon() = trajectoryTimeAwareValue.geometry()[i -1].lon(); // result.z() = trajectoryTimeAwareValue.geometry()[i -1].z(); // and take benefits from downcasting result = geoPosRelTime; // implicit downcast :-) DEBUG_LOG(m_logger) << "found i=" << i << " result=" << result.lat() << "/" << result.lon() << "/" << result.z(); return result; } ++i; } // for result = trajectoryTimeAwareValue.geometry().back(); DEBUG_LOG(m_logger) << " return last point result=" << " result=" << result.lat() << "/" << result.lon() << "/" << result.z(); return result; } void HandlerVulcano::onDynamicParticipantChanged(const ParticipantManagement::DynamicParticipantData &data, DataModification modType) { if (modType == DataModification::eRemoved) return; DEBUG_LOG(m_logger) << "onDynamicParticipantChanged" << data.participantId() << data.designations().size(); if (data.designations().size() > 0) { for (const ParticipantManagement::LaserDesignation &designation : qAsConst(data.designations())) { DEBUG_LOG(m_logger) << "gearId=" << designation.gearId() << " code=" << designation.code() << " z=" << designation.spotLocation().z() << " outputPower=" << designation.outputPower(); } } } } // namespace CesExplosions