1728 lines
61 KiB
C++
Executable File
1728 lines
61 KiB
C++
Executable File
/**
|
||
* @RDE_HEADER@
|
||
* DDS IFC to BuildingViewer
|
||
* $Id$
|
||
* $HeadURL$
|
||
* $Author$
|
||
* Created by Patric Karwat, SLSE, 2016-08-17
|
||
*/
|
||
|
||
#include "bdv_dds_controller.h"
|
||
|
||
#include <Plugins/InfrastructureManagement/Shared/ism_mounting_point.h>
|
||
#include <InfrastructureManagement/ism_dtc.h>
|
||
#include <LtsSession/lts_session_data.h>
|
||
#include <LtsWidgets/lts_message_dialog.h>
|
||
#include <LtsUtilsCore/outdated_position.h>
|
||
#include <LtsUtilsSettings/settings.h>
|
||
#include <ngltools/ngltools_filesystem.h>
|
||
#include "bdv_helper.h"
|
||
#include "bdv_plugin_provider.h"
|
||
#include "bdv_controller.h"
|
||
#include "logger.h"
|
||
#include "bdv_communication_interface_internal.h"
|
||
|
||
#include <QDateTime>
|
||
#include <QDir>
|
||
#include <QFile>
|
||
#include <QJsonArray>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QJsonParseError>
|
||
#include <QPixmap>
|
||
#include <QTimer>
|
||
#include <QXmlStreamWriter>
|
||
|
||
namespace BuildingViewer
|
||
{
|
||
static inline QString gStrSymbolFolderPath()
|
||
{
|
||
return QDir::tempPath() + QStringLiteral("/excon_bgviewer_symbols/");
|
||
}
|
||
|
||
BuildingViewerDdsController::BuildingViewerDdsController(CommunicationInterfaceInternal *connection, VCtrl::ViewerInterface* viewerInterface)
|
||
: m_connection(connection)
|
||
, m_viewerInterface(viewerInterface)
|
||
, m_logger(getLogger("BuildingViewerDdsController"))
|
||
{}
|
||
|
||
|
||
BuildingViewerDdsController::~BuildingViewerDdsController()
|
||
{
|
||
stopAndClearTimer();
|
||
QDir dir(gStrSymbolFolderPath());
|
||
dir.removeRecursively();
|
||
QFile::remove(getBuildingConfigurationXml());
|
||
}
|
||
|
||
|
||
bool BuildingViewerDdsController::init(QWidget *irreplacableWidget, const QString &modelPath, PluginProvider *session)
|
||
{
|
||
DEBUG_LOG(m_logger) << "BDV:: init";
|
||
m_session = session;
|
||
m_irreplacableWidget = irreplacableWidget;
|
||
//DEBUG_LOG(m_logger) << getSessionInfo() << "BDV:: BuildingViewerDdsController: m_session is new!";
|
||
|
||
m_buildingHeight.clear();
|
||
|
||
if (!m_session)
|
||
{
|
||
ERROR_LOG(m_logger) << "No m_session for BuildingViewerController!";
|
||
return false;
|
||
}
|
||
|
||
connect(m_session, &PluginProvider::destroyed, this, [this]()
|
||
{
|
||
if (static_cast<QObject *>(m_session) == sender())
|
||
{
|
||
// don´t use m_session if destroyed (otherwise coredump)
|
||
m_session = nullptr;
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "BDV:: BuildingViewerDdsController: m_session has been destroyed!";
|
||
stopAndClearTimer();
|
||
uninitialize();
|
||
}
|
||
});
|
||
//ERROR_LOG(m_logger) << "INIT DDS CONTROLLER: "<< m_session->exerciseId() << " is replay: " << m_session->isReplayPartition();
|
||
m_asbIfc = session->asbIfc();
|
||
if (!m_asbIfc)
|
||
{
|
||
ERROR_LOG(m_logger) << "Fetching 'm_asbIfc' failed!";
|
||
return false;
|
||
}
|
||
|
||
m_bldDtc = session->bldDtc();
|
||
if (!m_bldDtc)
|
||
{
|
||
ERROR_LOG(m_logger) << "Fetching 'm_bldDtc' failed!";
|
||
return false;
|
||
}
|
||
|
||
m_ismDtc = session->ismDtc();
|
||
if (!m_ismDtc)
|
||
{
|
||
ERROR_LOG(m_logger) << "Fetching 'm_ismDtc' failed!";
|
||
return false;
|
||
}
|
||
|
||
m_pdmDtc = session->pdmDtc();
|
||
if (!m_pdmDtc)
|
||
{
|
||
ERROR_LOG(m_logger) << "Fetching 'm_pdmDtc' failed!";
|
||
return false;
|
||
}
|
||
|
||
m_ismIfc = session->ismDataIfc();
|
||
if (!m_ismIfc)
|
||
{
|
||
WARNING_LOG(m_logger) << "Fetching 'm_ismIfc' failed!";
|
||
return false;
|
||
}
|
||
connect(m_ismIfc.get(), &InfrastructureManagement::ForeignDataInterface::mountingPointChanged,
|
||
this, &BuildingViewerDdsController::onMountingPointChanged);
|
||
|
||
// optional interfaces
|
||
m_avmIfc = session->avmDataIfc();
|
||
m_blgGuiIfc = session->bldInfoIfc();
|
||
m_ppmGuiIfc = session->ppmParticipantInfoIfc();
|
||
m_aesDtc = session->aesDtc();
|
||
m_ppmDtc = session->ppmDtc();
|
||
m_srpDtc = session->srpDtc();
|
||
|
||
/*
|
||
* Store exercise id locally since m_session might become a nullptr if
|
||
* the building viewer app is closed/destroyed while starting up / initializing...
|
||
*/
|
||
m_exerciseId = m_session->exerciseId();
|
||
|
||
// Create folder structure to store dynamically generated player symbol files (*.png)
|
||
QDir dir(gStrSymbolFolderPath());
|
||
dir.removeRecursively(); // Delete old symbol files
|
||
|
||
if (!dir.mkpath(QStringLiteral(".")))
|
||
{
|
||
ERROR_LOG(m_logger) << getSessionInfo() << "Could not create path \"" << gStrSymbolFolderPath() << "\" which is required to store BgViewer symbol files!";
|
||
return false;
|
||
}
|
||
|
||
connect(m_connection, &CommunicationInterfaceInternal::signalPlayerPicked, this, &BuildingViewerDdsController::onPlayerPicked, Qt::UniqueConnection);
|
||
connect(m_connection, &CommunicationInterfaceInternal::signalAssetPicked, this, &BuildingViewerDdsController::onAssetPicked, Qt::UniqueConnection);
|
||
connect(m_connection, &CommunicationInterfaceInternal::signalBgViewerInitFinished, this, &BuildingViewerDdsController::onBgViewerInitFinished, Qt::UniqueConnection);
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "BuildingViewerDdsController::init: The order of the following output must be [1],[2],[3],[4],[5],[6]";
|
||
|
||
/*
|
||
* Will cause m_connection to emit signalBgViewerInitFinished
|
||
* when the BgViewer has loaded all 3D models from specified XML configuration file
|
||
*/
|
||
setCurrentTrainingAreaMap(modelPath);
|
||
|
||
if (m_session->isReplayPartition())
|
||
{
|
||
if (m_reCheckTimer)
|
||
{
|
||
m_reCheckTimer->stop();
|
||
delete m_reCheckTimer;
|
||
m_reCheckTimer = nullptr;
|
||
}
|
||
m_reCheckTimer = new QTimer(this);
|
||
connect(m_reCheckTimer, &QTimer::timeout, this, &BuildingViewerDdsController::reCheckHitLine);
|
||
m_reCheckTimer->start(5000);
|
||
//ERROR_LOG(m_logger) << "Start eeCheckTimer";
|
||
}else
|
||
{
|
||
m_connection->setSimuationPace(1.0);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onBgViewerInitFinished()
|
||
{
|
||
DEBUG_LOG(m_logger) << "onBgViewerInitFinished";
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[2] Training area initialization acknowledged by BgViewer";
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[3] Sending mounting points, teams, ..., to BgViewer";
|
||
|
||
if (!m_pSimTimeSyncTimer)
|
||
{
|
||
m_pSimTimeSyncTimer = new QTimer(this);
|
||
m_pSimTimeSyncTimer->setInterval(m_bgviewer_simSyncTimeInterval_ms);
|
||
connect(m_pSimTimeSyncTimer, &QTimer::timeout, this, &BuildingViewerDdsController::onSynchronizeSimulationTime);
|
||
}
|
||
m_pSimTimeSyncTimer->start();
|
||
onSynchronizeSimulationTime();
|
||
|
||
syncTeams();
|
||
setPaused(false);
|
||
|
||
sendAssets();
|
||
|
||
LtsUtils::ProjectSettings *ps = LtsUtils::ProjectSettings::getInstance();
|
||
const bool useExactPositioning = ps->value(QStringLiteral("bld"), QStringLiteral("useExactPositioning")).toBool();
|
||
m_connection->excUseExactPositioning(useExactPositioning);
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[4] Getting building DDS data ('mainStructureElements')";
|
||
connect(m_ismDtc.get(), &InfrastructureManagement::Interface::Dtc::mainStructureElementChanged,
|
||
this, &BuildingViewerDdsController::onMainStructureElementChanged);
|
||
// Get initial DDS data
|
||
m_ismDtc->mainStructureElements([this](const InfrastructureManagement::MainStructureElement & data)
|
||
{
|
||
onMainStructureElementChanged(data, DataModification::eAdded);
|
||
});
|
||
INFO_LOG(m_logger) << getSessionInfo() << "\t[4.1] Done";
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[5] Creating participants";
|
||
// participants need to be created after m_roomId2RoomNumber is filled by onMainStructureElementChanged()
|
||
createParticipants();
|
||
INFO_LOG(m_logger) << getSessionInfo() << "\t[5.1] Done";
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[5] Creating Booby Trap";
|
||
createBoobyTraps();
|
||
INFO_LOG(m_logger) << getSessionInfo() << "\t[5.2] Done";
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[6] Getting building DDS data ('buildingStates')";
|
||
connect(m_bldDtc.get(), &Buildings::Interface::Dtc::buildingStateChanged,
|
||
this, &BuildingViewerDdsController::onBuildingStateChanged, Qt::UniqueConnection);
|
||
m_bldDtc->buildingStates([this](const Buildings::BuildingState & data)
|
||
{
|
||
onBuildingStateChanged(data, DataModification::eAdded);
|
||
});
|
||
connect(m_bldDtc.get(), &Buildings::Interface::Dtc::buildingHitLineInfoChanged,
|
||
this, &BuildingViewerDdsController::onBuildingHitLineInfoChanged, Qt::UniqueConnection);
|
||
INFO_LOG(m_logger) << getSessionInfo() << "\t[6.1] Done";
|
||
|
||
// Apply Global Settings from GLobal Settings dialog
|
||
m_connection->excSetGlobalTransparency(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("GlobalTransparency"), 75).toInt() );
|
||
m_connection->excSetGlobalViewMode(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("UseIsoMode"), false).toBool());
|
||
m_connection->excSetGlobalDotsMode(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("UseDotsMode"), false).toBool() );
|
||
m_hitlineDuration_ms = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("maxHitlineDurationTime_s"), 10).toInt() * 1000;
|
||
if (m_hitlineDuration_ms < 0)
|
||
m_hitlineDuration_ms = 0;
|
||
m_connection->excSetHitlineDuration(m_hitlineDuration_ms / 1000);
|
||
|
||
// GearFlashing parameter is checked every second within 'onFireEventFlashTimer'
|
||
if (!m_pFireEventFlashTimer)
|
||
{
|
||
m_pFireEventFlashTimer = new QTimer(this);
|
||
m_pFireEventFlashTimer->setInterval(1000);
|
||
connect(m_pFireEventFlashTimer, &QTimer::timeout, this, &BuildingViewerDdsController::onFireEventFlashTimer);
|
||
}
|
||
m_pFireEventFlashTimer->start();
|
||
|
||
if (m_srpDtc)
|
||
{
|
||
connect(m_srpDtc.get(), &SituationReplay::Interface::Dtc::sitReplayChanged, this, &BuildingViewerDdsController::onReplayChanged, Qt::UniqueConnection);
|
||
if (m_srpDtc && m_srpDtc->isSitReplayEnabled())
|
||
{
|
||
m_srpDtc->sitReplays([this](const SituationReplay::SitReplay &srp)
|
||
{
|
||
onReplayChanged(srp, DataModification::eAdded);
|
||
});
|
||
}
|
||
}
|
||
|
||
if (!m_pBuildingHitlineCleanupTimer)
|
||
{
|
||
m_pBuildingHitlineCleanupTimer = new QTimer(this);
|
||
m_pBuildingHitlineCleanupTimer->setInterval(1000 * 60 * 5); // every 5 min should be enough
|
||
connect(m_pBuildingHitlineCleanupTimer, &QTimer::timeout, this, &BuildingViewerDdsController::onCleanupBuildingHitlines);
|
||
}
|
||
m_pBuildingHitlineCleanupTimer->start();
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[7] Emitting 'signalDdsInitFinished'";
|
||
m_ddsInitFinished = true;
|
||
emit signalDdsInitFinished();
|
||
onSynchronizeSimulationTime();
|
||
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::stopAndClearTimer()
|
||
{
|
||
DEBUG_LOG(m_logger) << "stopAndClearTimer";
|
||
|
||
if (m_pFireEventFlashTimer)
|
||
m_pFireEventFlashTimer->stop();
|
||
|
||
if (m_pBuildingHitlineCleanupTimer)
|
||
m_pBuildingHitlineCleanupTimer->stop();
|
||
|
||
if (m_pSimTimeSyncTimer)
|
||
m_pSimTimeSyncTimer->stop();
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::uninitialize()
|
||
{
|
||
DEBUG_LOG(m_logger) << "uninitialize";
|
||
if (!m_bldDtc) // already called once?
|
||
return;
|
||
/*
|
||
* Has BgViewer initialization completely finished? 'm_ddsInitFinished' will be true in this case.
|
||
* This is important for the following case: The user opens the BgViewer App and immediately closes it. In this case,
|
||
* the BgViewer App might still be within the initialization phase (getting DDS data, creating players, ...).
|
||
*/
|
||
if (m_ddsInitFinished)
|
||
DEBUG_LOG(m_logger) << "Flag 'm_ddsInitFinished' set which means that initialization had been finished completely";
|
||
else
|
||
WARNING_LOG(m_logger) << "Flag 'm_ddsInitFinished' has not been set within timeout value, forcing quit!";
|
||
|
||
stopAndClearTimer();
|
||
|
||
m_connection->disconnect(this);
|
||
m_connection->systemDisconnect();
|
||
|
||
// Avoid receiving any more signals
|
||
m_aesDtc->disconnect(this);
|
||
m_ppmDtc->disconnect(this);
|
||
m_ismDtc->disconnect(this);
|
||
m_ismIfc->disconnect(this);
|
||
m_bldDtc->disconnect(this);
|
||
|
||
|
||
// Release
|
||
m_avmIfc = nullptr;
|
||
m_asbIfc = nullptr;
|
||
m_aesDtc = nullptr;
|
||
m_ppmDtc = nullptr;
|
||
m_ppmGuiIfc = nullptr;
|
||
m_ismDtc = nullptr;
|
||
m_ismIfc = nullptr;
|
||
m_bldDtc = nullptr;
|
||
m_blgGuiIfc = nullptr;
|
||
|
||
m_ddsInitFinished = false;
|
||
m_buildingHeight.clear();
|
||
|
||
DEBUG_LOG(m_logger) << "BuildingViewerDdsController::uninitialize() finished";
|
||
}
|
||
|
||
QPointF BuildingViewerDdsController::toRelativePosition(const Core::GeoPosition &geoPos) const
|
||
{
|
||
if (std::isnan(geoPos.lon()) || std::isnan(geoPos.lat()) ||
|
||
qIsNull(geoPos.lon()) || qIsNull(geoPos.lat()))
|
||
return {};
|
||
|
||
LtsCoordTrans::UtmCoordinate coord(LtsCoordTrans::UtmCoordinate::fromGeographic(geoPos.lon(), geoPos.lat(), m_baseCoordinate.zone));
|
||
return coord.relativeTo(m_baseCoordinate);
|
||
}
|
||
|
||
LtsUtils::QPoint3D BuildingViewerDdsController::toRelative3D(const Core::GeoPosition &geoPos) const
|
||
{
|
||
const auto ret = toRelativePosition(geoPos);
|
||
if (ret.isNull())
|
||
return {};
|
||
return LtsUtils::QPoint3D(ret.x(), ret.y(), geoPos.z());
|
||
}
|
||
|
||
bool BuildingViewerDdsController::clear()
|
||
{
|
||
DEBUG_LOG(m_logger) << "clear m_connection->playerClear();";
|
||
m_connection->playerClear();
|
||
return true;
|
||
}
|
||
|
||
|
||
bool BuildingViewerDdsController::syncTeams()
|
||
{
|
||
if (!m_ppmDtc)
|
||
return false;
|
||
INFO_LOG(m_logger) << "BuildingViewerDdsController::syncTeams";
|
||
|
||
m_connection->setTeamConfiguration(m_ppmDtc->partys());
|
||
return true;
|
||
}
|
||
|
||
|
||
bool BuildingViewerDdsController::setPaused(bool bPaused)
|
||
{
|
||
m_connection->systemSetPaused(bPaused);
|
||
return true;
|
||
}
|
||
|
||
bool BuildingViewerDdsController::calcBaseCoordinate(const QMap<qint32, InfrastructureManagement::MainStructureElement> &mseMap)
|
||
{
|
||
bool bAtLeasOneFound = false;
|
||
Core::GeoPosition geoPosition;
|
||
for (const auto &mse : mseMap)
|
||
{
|
||
if (!bAtLeasOneFound)
|
||
{
|
||
bAtLeasOneFound = true;
|
||
geoPosition = mse.geoPosition();
|
||
}
|
||
else
|
||
{
|
||
geoPosition.lat() = std::min(geoPosition.lat(), mse.geoPosition().lat());
|
||
geoPosition.lon() = std::min(geoPosition.lon(), mse.geoPosition().lon());
|
||
}
|
||
}
|
||
if (!bAtLeasOneFound)
|
||
{
|
||
WARNING_LOG(m_logger) << "No building available in exercise " << m_exerciseId;
|
||
return false;
|
||
}
|
||
m_baseCoordinate = LtsCoordTrans::UtmCoordinate::fromGeographic(geoPosition.lon(), geoPosition.lat());
|
||
return true;
|
||
}
|
||
|
||
bool BuildingViewerDdsController::isOutdatedPosition(qint64 sampleTime) const
|
||
{
|
||
if (m_session == nullptr)
|
||
return true; // is outdated if session is missing ... ?!
|
||
return LtsUtils::isPositionTimeOutdated(m_session->partitionTime(), sampleTime);
|
||
}
|
||
|
||
bool BuildingViewerDdsController::writeBuildingDataToXML(const QString &modelPath)
|
||
{
|
||
QFile f(getBuildingConfigurationXml());
|
||
if (!f.open(QIODevice::WriteOnly))
|
||
{
|
||
WARNING_LOG(m_logger) << "Can not open file " << f.fileName() << " for writing: " << f.errorString();
|
||
return false;
|
||
}
|
||
m_knownBuildings.clear();
|
||
|
||
QMap<qint32, InfrastructureManagement::MainStructureElement> mseMap;
|
||
|
||
QFile jsonConfig(modelPath + QStringLiteral("/buildings.json"));
|
||
if (!jsonConfig.exists())
|
||
jsonConfig.setFileName(modelPath + QStringLiteral("/../buildings.json"));
|
||
|
||
DEBUG_LOG(m_logger) << "jsonConfig:" << jsonConfig.fileName();
|
||
if (jsonConfig.exists() && jsonConfig.open(QIODevice::ReadOnly))
|
||
{
|
||
m_ismDtc->disconnect(this); // we don't need this, since all needed data is in the json file
|
||
InfrastructureManagement::MainStructureElement mse;
|
||
const QJsonArray arr = QJsonDocument::fromJson(jsonConfig.readAll()).array();
|
||
for (const auto &ele : arr)
|
||
{
|
||
const auto bi = BuildingInfo::fromJson(ele.toObject());
|
||
if (!bi.isValid())
|
||
continue;
|
||
mse.id(bi.id);
|
||
mse.angle(bi.angleDegree);
|
||
mse.geoPosition(Core::GeoPosition(bi.position.x(), bi.position.y(), bi.position.z()));
|
||
mse.name(bi.name.toStdString());
|
||
mseMap.insert(mse.id(), mse);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
m_ismDtc->waitForHistoricalDataMainStructureElement(m_dtc_timeout_ms);
|
||
const auto mMap = m_ismDtc->mainStructureElements();
|
||
for (const auto &mse : mMap)
|
||
{
|
||
if (mse.modelId() == 0)
|
||
continue;
|
||
if (!m_ismDtc->isElementAssignedToExercise(mse, m_exerciseId))
|
||
{
|
||
DEBUG_LOG(m_logger) << "Not writing mse with id " << mse.id() << " to building xml file since it is not assigned to the current exercise which has id " << m_exerciseId;
|
||
continue;
|
||
}
|
||
mseMap.insert(mse.id(), mse);
|
||
}
|
||
}
|
||
INFO_LOG(m_logger) << getSessionInfo() << "writeBuildingDataToXML(): Identified Main Structure Elements: " << mseMap.size();
|
||
|
||
if (!calcBaseCoordinate(mseMap))
|
||
{
|
||
WARNING_LOG(m_logger) << "failed to calc base coordinate";
|
||
return false;
|
||
}
|
||
|
||
QXmlStreamWriter sw(&f);
|
||
sw.setAutoFormatting(true);
|
||
sw.setAutoFormattingIndent(2);
|
||
sw.setCodec("UTF-8");
|
||
sw.writeStartDocument(QStringLiteral("1.0"), true);
|
||
sw.writeStartElement(QStringLiteral("MetaData"));
|
||
{
|
||
sw.writeStartElement(QStringLiteral("Origin"));
|
||
{
|
||
// degree
|
||
const auto geo = m_baseCoordinate.geographic();
|
||
sw.writeAttribute(QStringLiteral("Longitude"), QString::number(geo.x() * 180 / M_PI, 'f', 13));
|
||
sw.writeAttribute(QStringLiteral("Latitude"), QString::number(geo.y() * 180 / M_PI, 'f', 13));
|
||
}
|
||
sw.writeEndElement();
|
||
|
||
sw.writeStartElement(QStringLiteral("Tag"));
|
||
sw.writeStartElement(QStringLiteral("Group"));
|
||
|
||
for (const InfrastructureManagement::MainStructureElement &mse : qAsConst(mseMap))
|
||
{
|
||
m_knownBuildings += mse.id();
|
||
|
||
sw.writeStartElement(QStringLiteral("Model"));
|
||
sw.writeAttribute(QStringLiteral("Type"), QStringLiteral("House"));
|
||
{
|
||
const QString name(QString::fromStdString(mse.name()));
|
||
sw.writeStartElement(QStringLiteral("Description"));
|
||
sw.writeAttribute(QStringLiteral("Name"), name);
|
||
sw.writeEndElement();
|
||
sw.writeStartElement(QStringLiteral("Feature"));
|
||
sw.writeAttribute(QStringLiteral("Id"), BuildingViewer::databaseId2FeatureId(mse.id()));
|
||
sw.writeEndElement();
|
||
sw.writeStartElement(QStringLiteral("Orientation"));
|
||
// counter-clockwise, degree
|
||
sw.writeAttribute(QStringLiteral("Angle"), QString::number(360 - mse.angle()));
|
||
sw.writeEndElement();
|
||
|
||
const QPointF offset(toRelativePosition(mse.geoPosition()));
|
||
sw.writeStartElement(QStringLiteral("Position"));
|
||
sw.writeAttribute(QStringLiteral("X"), QString::number(offset.x(), 'f', 13));
|
||
sw.writeAttribute(QStringLiteral("Y"), QString::number(offset.y(), 'f', 13));
|
||
sw.writeAttribute(QStringLiteral("Z"), QString::number(mse.geoPosition().z(), 'f', 13));
|
||
sw.writeEndElement();
|
||
sw.writeStartElement(QStringLiteral("File"));
|
||
sw.writeAttribute(QStringLiteral("Name"), name + QStringLiteral(".flt"));
|
||
sw.writeEndElement();
|
||
sw.writeStartElement(QStringLiteral("STREIF"));
|
||
sw.writeAttribute(QStringLiteral("Name"), name + QStringLiteral(".flt.xml"));
|
||
sw.writeEndElement();
|
||
}
|
||
sw.writeEndElement();
|
||
}
|
||
sw.writeEndElement();
|
||
sw.writeEndElement();
|
||
}
|
||
sw.writeEndElement();
|
||
sw.writeEndDocument();
|
||
f.close();
|
||
|
||
std::sort(m_knownBuildings.begin(), m_knownBuildings.end());
|
||
return true;
|
||
}
|
||
|
||
|
||
QString BuildingViewerDdsController::getBuildingConfigurationXml() const
|
||
{
|
||
return QDir::tempPath() + QStringLiteral("/buildings_%1.xml").arg(m_exerciseId);
|
||
}
|
||
|
||
void BuildingViewerDdsController::clearAssets()
|
||
{
|
||
INFO_LOG(m_logger) << "clearAssets: Removing " << m_knownAssetsInBgViewer.size() << " assets";
|
||
|
||
for (const auto &strUniqueAsset : qAsConst(m_knownAssetsInBgViewer))
|
||
m_connection->extHideMountingPointAsset(strUniqueAsset);
|
||
|
||
m_mountingPointId2AssetType.clear();
|
||
m_knownAssetsInBgViewer.clear();
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::sendAssets()
|
||
{
|
||
clearAssets();
|
||
|
||
std::function<void (const InfrastructureManagement::MountingPoint &)> cb = [this](const InfrastructureManagement::MountingPoint &mp)
|
||
{
|
||
handleMountingPoint(mp, DataModification::eAdded);
|
||
};
|
||
m_ismIfc->mountingPoints(cb);
|
||
}
|
||
|
||
void BuildingViewerDdsController::onReplayChanged(const SituationReplay::SitReplay &srp, DataModification modType)
|
||
{
|
||
Q_UNUSED(modType)
|
||
|
||
switch (srp.state().underlying()) {
|
||
case SituationReplay::SrpState::Stopped:
|
||
m_connection->setSimuationPace(1.0);
|
||
//ERROR_LOG(m_logger) << "HOTLINE TIME FOR A TIME OF CHANGE" << 1.0;
|
||
break;
|
||
case SituationReplay::SrpState::Started:
|
||
case SituationReplay::SrpState::Resumed:
|
||
m_connection->setSimuationPace(srp.pace());
|
||
//ERROR_LOG(m_logger) << "HOTLINE TIME FOR A TIME OF CHANGE" << srp.pace();
|
||
break;
|
||
case SituationReplay::SrpState::Paused:
|
||
m_connection->setSimuationPace(0.0);
|
||
//ERROR_LOG(m_logger) << "HOTLINE TIME FOR A TIME OF CHANGE" << 0.0;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
void BuildingViewerDdsController::handleMountingPoint(const InfrastructureManagement::MountingPoint &mp, DataModification modType)
|
||
{
|
||
if (!isBuildingKnown(mp.buildingId()))
|
||
return;
|
||
if (m_session->isReplayPartition()) //we do not display mounting points in replay!
|
||
return;
|
||
|
||
const int mountingPointId = mp.id();
|
||
|
||
auto itMp = m_knownAssetsInBgViewer.find(mountingPointId);
|
||
if (itMp != m_knownAssetsInBgViewer.end())
|
||
{
|
||
if (modType == DataModification::eRemoved || !mp.hasAssociatedAsset())
|
||
{
|
||
m_connection->extHideMountingPointAsset(itMp.value());
|
||
m_knownAssetsInBgViewer.erase(itMp);
|
||
}
|
||
|
||
m_mountingPointId2AssetType.remove(mountingPointId);
|
||
if (modType == DataModification::eRemoved)
|
||
return;
|
||
}
|
||
|
||
if (!mp.hasAssociatedAsset())
|
||
return;
|
||
|
||
BuildingViewer::AssetTypes assetType;
|
||
QString strAssetType;
|
||
switch (mp.type().underlying())
|
||
{
|
||
case InfrastructureManagement::MountingPointType_def::AnnouncementUnit:
|
||
assetType = BuildingViewer::AssetTypes::AnnouncementUnit;
|
||
strAssetType = QStringLiteral("AnnouncementUnit");
|
||
break;
|
||
case InfrastructureManagement::MountingPointType_def::EffectGenerationControlUnit:
|
||
assetType = BuildingViewer::AssetTypes::EffectGenerationUnit;
|
||
strAssetType = QStringLiteral("EffectGenerationControlUnit");
|
||
break;
|
||
case InfrastructureManagement::MountingPointType_def::IndoorCamera:
|
||
{
|
||
bool hasSpeaker = false;
|
||
QJsonParseError jError;
|
||
const QJsonDocument doc(QJsonDocument::fromJson(mp.properties(), &jError));
|
||
if (jError.error == QJsonParseError::NoError)
|
||
{
|
||
const QJsonObject obj(doc.object());
|
||
hasSpeaker = obj[QStringLiteral("hasSpeaker")].toBool();
|
||
}
|
||
assetType = hasSpeaker ? BuildingViewer::AssetTypes::IndoorCameraWithSpeaker : BuildingViewer::AssetTypes::IndoorCamera;
|
||
strAssetType = hasSpeaker ? QStringLiteral("IndoorCameraWithSpeaker") : QStringLiteral("IndoorCamera");
|
||
break;
|
||
}
|
||
default:
|
||
return;
|
||
}
|
||
|
||
QVector3D position(mp.relPosition().offsetX(),mp.relPosition().offsetY(),mp.relPosition().offsetZ());
|
||
QString strfeatureID = BuildingViewer::databaseId2FeatureId(mp.buildingId());
|
||
QString strMountingPointID = strfeatureID + QStringLiteral("_MountingPoint_") + QString::number(mountingPointId);
|
||
|
||
DEBUG_LOG(m_logger) << "Sending asset for building id " << mp.buildingId() << " ("<<strfeatureID<<")"
|
||
<< " with asset_id " << mp.associatedAssetId()
|
||
<< " matched to structure_element_id " << mp.id() << " as \"" << strMountingPointID <<" \""
|
||
<< " with asset type \"" << strAssetType << "\"";
|
||
|
||
if (m_mountingPointId2AssetType.contains(mountingPointId))
|
||
WARNING_LOG(m_logger) << "Mounting point id " << mountingPointId << " already used for other asset?! Overriding...";
|
||
|
||
m_mountingPointId2AssetType[mountingPointId] = assetType;
|
||
m_knownAssetsInBgViewer[mountingPointId] = strMountingPointID;
|
||
|
||
m_connection->extSetMountingPointAsset(strfeatureID, strMountingPointID, position, assetType);
|
||
}
|
||
|
||
void BuildingViewerDdsController::setCurrentTrainingAreaMap(const QString &modelPath)
|
||
{
|
||
if (!m_connection)
|
||
{
|
||
WARNING_LOG(m_logger) << "BuildingViewerDdsController::setCurrentTrainingAreaMap: (!m_connection)";
|
||
return;
|
||
}
|
||
|
||
// Clear everything that might have been loaded
|
||
m_connection->excDeleteBuildingList();
|
||
|
||
INFO_LOG(m_logger) << getSessionInfo() << "[1] Initializing BgViewer with training area...";
|
||
|
||
if (modelPath.isEmpty())
|
||
{
|
||
QString strErr = tr("Error initializing Building Viewer - can not find map directory.");
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "setCurrentTrainingAreaMap: " << strErr;
|
||
QWidget *parentWidgetx = (m_viewerInterface ? m_viewerInterface->getMainWindow() : nullptr);
|
||
LtsWidgets::LtsMessageDialog::showMessage(parentWidgetx, strErr, tr("Error"));
|
||
return;
|
||
}
|
||
INFO_LOG(m_logger) << "Map path for building viewer: " << modelPath;
|
||
|
||
writeBuildingDataToXML(modelPath);
|
||
|
||
QString pathToXml = getBuildingConfigurationXml();
|
||
|
||
QFileInfo file(pathToXml);
|
||
if (!file.exists())
|
||
{
|
||
FATAL_LOG(m_logger) << getSessionInfo() << "setCurrentTrainingAreaMap: Required buildings xml file does not exist: \"" << pathToXml << "\", BuildingViewer initialization FAILED!";
|
||
return;
|
||
}
|
||
// TODO: copy them for THP
|
||
const QString shadersPath = NGL::directory(NGL::FileType::ConfigFile, QStringLiteral("BuildingViewer"));
|
||
|
||
QString additionalPath;
|
||
QVector<QString> directoriesToSearch = NGL::directories(NGL::FileType::ConfigFile);
|
||
#ifndef WIN32
|
||
directoriesToSearch += QStringLiteral("/opt");
|
||
#endif
|
||
|
||
for (const QString &d : qAsConst(directoriesToSearch))
|
||
{
|
||
const QDir dir(d + QStringLiteral("/BuildingModels/StaticMeshes/"));
|
||
if (dir.exists() && dir.isReadable())
|
||
additionalPath = dir.absolutePath();
|
||
INFO_LOG(getLogger("BgViewerController")) << "Map path '" << dir.absolutePath() << "' either does not exist or is not readable - skipping";
|
||
}
|
||
|
||
std::vector<std::string> paths;
|
||
paths.push_back(modelPath.toStdString());
|
||
paths.push_back(shadersPath.toStdString());
|
||
if (!additionalPath.isEmpty())
|
||
{
|
||
paths.push_back(additionalPath.toStdString());
|
||
}
|
||
m_connection->excConnect(paths, pathToXml);
|
||
}
|
||
|
||
|
||
bool BuildingViewerDdsController::isValidParticipant(const ParticipantManagement::Participant &participant) const
|
||
{
|
||
if (participant.id() == 0)
|
||
{
|
||
if (m_logger.isDebugEnabled())
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "isValidParticipant: Not handling invalid participant with invalid id (0)";
|
||
return false;
|
||
}
|
||
|
||
// Participant is marked as deleted so ignore
|
||
if (participant.isDeleted())
|
||
{
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "isValidParticipant: Not handling participant with id "
|
||
<< participant.id() << " since it is marked as 'deleted'";
|
||
return false;
|
||
}
|
||
|
||
if (m_session == nullptr)
|
||
return false;
|
||
|
||
// Participant not visible due to user restrictions/settings? Don't do anything
|
||
if (!m_session->isVisibleParty(participant.partyId()))
|
||
{
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "isValidParticipant: Not handling participant with id "
|
||
<< participant.id() << " since it is not visible for party with id " << participant.partyId();
|
||
return false;
|
||
}
|
||
|
||
if (!m_ppmDtc)
|
||
return false;
|
||
|
||
// Do we actually have a participant here?
|
||
ParticipantManagement::TypeClass tc = m_ppmDtc->type(participant.typeId()).typeClass();
|
||
if(tc == ParticipantManagement::TypeClass_def::SupportUnit ||
|
||
tc == ParticipantManagement::TypeClass_def::Unit)
|
||
{
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "isValidParticipant: Not handling participant with id "
|
||
<< participant.id() << " since its type is 'SupportUnit', 'Unit' or 'Structure'";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void BuildingViewerDdsController::createBoobyTraps()
|
||
{
|
||
if (!m_aesDtc)
|
||
return;
|
||
DEBUG_LOG(m_logger) << "BDV:: create Booby Traps: "<< m_session->exerciseId() << " is replay: " << m_session->isReplayPartition();
|
||
|
||
m_aesDtc->enableBoobyTrap();
|
||
|
||
|
||
const int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
// const QMap<int, AreaEffectSimulation::BoobyTrap> btMap = m_aesDtc->boobyTraps();
|
||
|
||
auto cb = [this, sizeL] (const AreaEffectSimulation::BoobyTrap &trap)
|
||
{
|
||
createBoobyTrap(trap, sizeL);
|
||
};
|
||
m_aesDtc->boobyTraps(cb);
|
||
|
||
|
||
/*
|
||
* Connect to Dds changes (See slots below).
|
||
* 'boobyTrapChanged' is also called if a new participant has been added
|
||
*/
|
||
// 1. Slot
|
||
connect(m_aesDtc.get(), &AreaEffectSimulation::Interface::Dtc::boobyTrapChanged,
|
||
this, &BuildingViewerDdsController::onBoobyTrapChanged, Qt::UniqueConnection);
|
||
|
||
|
||
|
||
}
|
||
|
||
void BuildingViewerDdsController::createParticipants()
|
||
{
|
||
if (!m_ppmDtc)
|
||
return;
|
||
DEBUG_LOG(m_logger) << "BDV:: create participants: "<< m_session->exerciseId() << " is replay: " << m_session->isReplayPartition();
|
||
|
||
const int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
|
||
const QMap<int, ParticipantManagement::ParticipantPosition> positionMap = m_ppmDtc->participantPositions();
|
||
auto cb = [this, &positionMap, sizeL] (const ParticipantManagement::Participant &part)
|
||
{
|
||
if (!isValidParticipant(part))
|
||
return;
|
||
createParticipant(part, positionMap.value(part.id()), sizeL);
|
||
};
|
||
m_ppmDtc->participants(cb);
|
||
|
||
/*
|
||
* Connect to Dds changes (See slots below).
|
||
* 'participantChanged' is also called if a new participant has been added
|
||
*/
|
||
// 1. Slot
|
||
m_ppmDtc->enableParticipant();
|
||
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantChanged,
|
||
this, &BuildingViewerDdsController::onParticipantChanged, Qt::UniqueConnection);
|
||
|
||
// 2. Slot
|
||
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::hitLineInfoChanged,
|
||
this, &BuildingViewerDdsController::onHitLineInfoChanged, Qt::UniqueConnection);
|
||
m_ppmDtc->enableHitLineInfo();
|
||
|
||
// 3. Slot
|
||
m_ppmDtc->enableParty();
|
||
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::partyChanged,
|
||
this, &BuildingViewerDdsController::onPartyChanged, Qt::UniqueConnection);
|
||
|
||
// 4. Slot
|
||
m_ppmDtc->enableParticipantPosition();
|
||
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantPositionChanged,
|
||
this, &BuildingViewerDdsController::onPositionChanged, Qt::UniqueConnection);
|
||
|
||
// 5. Slot
|
||
m_ppmDtc->enableParticipantGearEvent();
|
||
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::participantGearEventChanged,
|
||
this, &BuildingViewerDdsController::onParticipantGearEventChanged,
|
||
Qt::UniqueConnection);
|
||
|
||
// 6. Slot
|
||
m_ppmDtc->enableDynamicParticipantData();
|
||
connect(m_ppmDtc.get(), &ParticipantManagement::Interface::Dtc::dynamicParticipantDataChanged,
|
||
this, &BuildingViewerDdsController::onDynParticipantChanged, Qt::UniqueConnection);
|
||
|
||
m_ppmDtc->participantGearEvents([this](const ParticipantManagement::ParticipantGearEvent & data)
|
||
{
|
||
onParticipantGearEventChanged(data, DataModification::eAdded);
|
||
});
|
||
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onBoobyTrapChanged(const AreaEffectSimulation::BoobyTrap &boobyTrap, DataModification modType)
|
||
{
|
||
switch (modType)
|
||
{
|
||
case DataModification::eModified:
|
||
deleteParticipant(990000+boobyTrap.id());
|
||
Q_FALLTHROUGH();
|
||
case DataModification::eAdded:
|
||
{
|
||
int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
createBoobyTrap(boobyTrap, sizeL);
|
||
break;
|
||
}
|
||
case DataModification::eRemoved:
|
||
{
|
||
deleteParticipant(990000+boobyTrap.id());
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 1. Slot
|
||
void BuildingViewerDdsController::onParticipantChanged(const ParticipantManagement::Participant &participant, DataModification modType)
|
||
{
|
||
if (!isValidParticipant(participant))
|
||
return;
|
||
|
||
switch (modType)
|
||
{
|
||
case DataModification::eModified:
|
||
deleteParticipant(participant.id());
|
||
Q_FALLTHROUGH();
|
||
case DataModification::eAdded:
|
||
{
|
||
int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
createParticipant(participant, m_ppmDtc->participantPosition(participant.id()), sizeL);
|
||
break;
|
||
}
|
||
case DataModification::eRemoved:
|
||
{
|
||
deleteParticipant(participant.id());
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
//Slot 6
|
||
void BuildingViewerDdsController::onDynParticipantChanged(const ParticipantManagement::DynamicParticipantData &data, DataModification modType)
|
||
{
|
||
int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
switch (modType)
|
||
{
|
||
case DataModification::eAdded:
|
||
case DataModification::eModified:
|
||
if (m_ppmDtc)
|
||
createParticipant(m_ppmDtc->participant(data.participantId()), m_ppmDtc->participantPosition(data.participantId()), sizeL);
|
||
break;
|
||
case DataModification::eRemoved:
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// 2. Slot
|
||
void BuildingViewerDdsController::onHitLineInfoChanged(const ParticipantManagement::HitLineInfo &hli, DataModification modType)
|
||
{
|
||
DEBUG_LOG(m_logger) << "X 0000 onHitLineInfoChanged id=" << hli.id();
|
||
|
||
if (modType == DataModification::eRemoved)
|
||
{
|
||
DEBUG_LOG(m_logger) << "onHitLineInfoChanged removeHit id=" << hli.id();
|
||
m_connection->removeRoomHit(hli.id());
|
||
return;
|
||
}
|
||
|
||
if (!m_ppmDtc)
|
||
return;
|
||
m_ppmDtc->waitForHistoricalDataParticipant();
|
||
|
||
const ParticipantManagement::Participant &partSrc = m_ppmDtc->participant(hli.originatorParticipantId());
|
||
const ParticipantManagement::Participant &partDst = m_ppmDtc->participant(hli.injuryParticipantId());
|
||
if (partSrc.id() == 0 || partDst.id() == 0)
|
||
{
|
||
ERROR_LOG(m_logger) << "no knownn part: " << partSrc.id() << " dst: " << partDst.id();
|
||
return;
|
||
}
|
||
|
||
const QString &strFeatureID = BuildingViewer::databaseId2FeatureId(hli.injuryBuildingId());
|
||
if (!m_viewerInterface->isViewOpen(strFeatureID.toStdString()))
|
||
{
|
||
DEBUG_LOG(m_logger) << "building not opened: hli.buildingId()=" << hli;
|
||
return;
|
||
}
|
||
// if (!isBuildingKnown(hli.injuryBuildingId()))
|
||
// {
|
||
// ERROR_LOG(m_logger) << "not known building";
|
||
// return;
|
||
// }
|
||
|
||
if (hli.injuryBuildingId() != 0)
|
||
{
|
||
auto itBuildingHit = m_buildingHits.constFind(hli.injuryBuildingId());
|
||
if (itBuildingHit != m_buildingHits.constEnd())
|
||
{
|
||
const QVector<BuildingHit> &bHits = itBuildingHit.value();
|
||
// player is in a building which got hit in the last few minutes
|
||
DEBUG_LOG(m_logger) << "Got Hit lines to add? " << bHits.size();
|
||
for (const BuildingHit &bh : bHits)
|
||
{
|
||
|
||
const bool testFlag = (bh.chargeTypeId == hli.chargeTypeId() &&
|
||
bh.originatorParticipantId == hli.originatorParticipantId() &&
|
||
hli.sampleTime() - m_hitlineDuration_ms < bh.sampleTime);
|
||
DEBUG_LOG(m_logger) << "testFlag " << testFlag;
|
||
|
||
|
||
if (bh.chargeTypeId == hli.chargeTypeId() &&
|
||
bh.originatorParticipantId == hli.originatorParticipantId() &&
|
||
hli.sampleTime() - m_hitlineDuration_ms < bh.sampleTime)
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
DEBUG_LOG(m_logger) << "onHitLineInfoChanged: " << hli;
|
||
|
||
int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
DEBUG_LOG(m_logger) << "onHitLineInfoChanged createPlayerSymbols";
|
||
createPlayerSymbols(partDst, sizeL);
|
||
|
||
|
||
// Draw 'Trefferlinie'
|
||
LtsUtils::QPoint3D originatorSrc = toRelative3D(hli.originatorPos());
|
||
LtsUtils::QPoint3D injuryDest = toRelative3D(hli.injuryPos());
|
||
|
||
DEBUG_LOG(m_logger) << "onHitLineInfoChanged playerHit"
|
||
<< " hli.originatorPos() z=" << hli.originatorPos().z()
|
||
<< " hli.injuryPos() z=" <<hli.injuryPos().z()
|
||
<< " Src=" << originatorSrc.x() << "/" << originatorSrc.y() << "/" << originatorSrc.z()
|
||
<< " Dest= " << injuryDest.x() << "/" << injuryDest.y() << "/" << injuryDest.z()
|
||
<< " hli.originatorBuildingId=" << hli.originatorBuildingId()
|
||
<< " hli.originatorRoomId=" << hli.originatorRoomId()
|
||
<< " hli.injuryBuildingId=" << hli.injuryBuildingId()
|
||
<< " hli.injuryRoomId=" << hli.injuryRoomId();
|
||
// Shift BuildingHeiht to Positions
|
||
// Required for IPS-Positions (rel positions), but what happend with WLS (absolute positions!?)
|
||
DEBUG_LOG(m_logger) << "Before z-correction:"
|
||
<< " Src=" << originatorSrc.x() << "/" << originatorSrc.y() << "/" << originatorSrc.z()
|
||
<< " Dest= " << injuryDest.x() << "/" << injuryDest.y() << "/" << injuryDest.z();
|
||
|
||
// NLDEVCTRL-1349
|
||
// Die z-Komponente der Positionen im System ist nicht wirklich definiert.
|
||
// Die Gebäude in Letzlingen sind bei ~90m. Die Tln bei 0...
|
||
// Somit ergab sich im Fehlerfall, das ein Tln (Getroffener im Bldg) auf
|
||
// ca. 92-95m "gehoben" wurde. Der Schuetzte (GPS-Position) aber bei 0 verblieb.
|
||
// (Diese ist bei der ISO-metrische Darstellung aufgefallen.)
|
||
// Fuer diesen Fall wird der Schuetze auch um den Wert des Bldgs angehoben.
|
||
double zAddOriginator = m_buildingHeight.value(hli.originatorBuildingId(), 0);
|
||
double zAddInjury = m_buildingHeight.value(hli.injuryBuildingId(), 0);
|
||
DEBUG_LOG(m_logger) << "z-correction: zAddOriginator=" << zAddOriginator;
|
||
DEBUG_LOG(m_logger) << "z-correction: zAddInjury=" << zAddInjury;
|
||
|
||
if (hli.originatorBuildingId() <= 0) // no id -> no zAdd
|
||
zAddOriginator = zAddInjury; // use zAdd of other
|
||
|
||
if (hli.injuryBuildingId() <= 0) // no id -> no zAdd
|
||
zAddInjury = zAddOriginator; // use zAdd of other
|
||
|
||
originatorSrc.setZ(originatorSrc.z() + zAddInjury);
|
||
injuryDest.setZ(injuryDest.z() + zAddInjury);
|
||
|
||
DEBUG_LOG(m_logger) << "After z-correction:"
|
||
<< " Src=" << originatorSrc.x() << "/" << originatorSrc.y() << "/" << originatorSrc.z()
|
||
<< " Dest= " << injuryDest.x() << "/" << injuryDest.y() << "/" << injuryDest.z();
|
||
|
||
//
|
||
// translate buildingId, roomId
|
||
// at ParticipantPosition: buildingId like 364 ; roomId like 6437 (db-values)
|
||
//
|
||
int buildingIdSrc = -1;
|
||
int roomNumberSrc = -1;
|
||
{
|
||
auto itId2Number = m_roomId2RoomNumber.constFind(hli.originatorBuildingId());
|
||
if (itId2Number != m_roomId2RoomNumber.constEnd())
|
||
{
|
||
const auto &mapRooms = itId2Number.value();
|
||
const auto itRoom = mapRooms.constFind(hli.originatorRoomId());
|
||
if (itRoom != mapRooms.constEnd())
|
||
{
|
||
buildingIdSrc = hli.originatorBuildingId();
|
||
roomNumberSrc = itRoom.value();
|
||
}
|
||
else
|
||
WARNING_LOG(m_logger) << "onHitLineInfoChanged: Room id can not be mapped! Building id: " << hli.injuryBuildingId()<<", originatorRoomId: "<< hli.originatorRoomId();
|
||
}
|
||
}
|
||
|
||
int buildingIdDst = -1;
|
||
int roomNumberDst = -1;
|
||
{
|
||
auto itId2Number = m_roomId2RoomNumber.constFind(hli.injuryBuildingId());
|
||
if (itId2Number != m_roomId2RoomNumber.constEnd())
|
||
{
|
||
const auto &mapRooms = itId2Number.value();
|
||
const auto itRoom = mapRooms.constFind(hli.injuryRoomId());
|
||
if (itRoom != mapRooms.constEnd())
|
||
{
|
||
buildingIdDst = hli.injuryBuildingId();
|
||
roomNumberDst = itRoom.value();
|
||
}
|
||
else
|
||
WARNING_LOG(m_logger) << "onHitLineInfoChanged: Room id can not be mapped! Building id: " << hli.injuryBuildingId()<<", room id: "<< hli.injuryRoomId();
|
||
}
|
||
}
|
||
|
||
DEBUG_LOG(m_logger) << "After translate buildingId, roomId:"
|
||
<< " hli.originatorBuildingId=" << hli.originatorBuildingId() << " -> " << buildingIdSrc
|
||
<< " hli.originatorRoomId=" << hli.originatorRoomId() << " -> " << roomNumberSrc
|
||
<< " hli.injuryBuildingId=" << hli.injuryBuildingId() << " -> " << buildingIdDst
|
||
<< " hli.injuryRoomId=" << hli.injuryBuildingId() << " -> " << roomNumberDst
|
||
<< " hli.sampleTime=" << hli.sampleTime() ;
|
||
int lineType =2;
|
||
m_pdmDtc->chargeType(hli.chargeTypeId());
|
||
// NOTE: igniterType/IgniterFunciton = 0 as long as HitLineInfo does not contain values
|
||
lineType = m_pdmDtc->getChargeTypeSpecialProperty(hli.chargeTypeId(),0,0).underlying();
|
||
|
||
m_connection->playerHit(hli.id(), partSrc.id(), partDst.id(),
|
||
originatorSrc, injuryDest,
|
||
buildingIdSrc, roomNumberSrc, buildingIdDst, roomNumberDst,
|
||
lineType, hli.sampleTime());
|
||
|
||
}
|
||
|
||
|
||
// 3. Slot
|
||
void BuildingViewerDdsController::onPartyChanged(const ParticipantManagement::Party &party, DataModification modType)
|
||
{
|
||
if (modType == DataModification::eRemoved)
|
||
return;
|
||
|
||
int sizeL = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("PlayerSymbolSize"), 20).toInt();
|
||
updatePlayerSymbols(sizeL, party.id());
|
||
syncTeams();
|
||
}
|
||
|
||
// 4. Slot
|
||
void BuildingViewerDdsController::onPositionChanged(const ParticipantManagement::ParticipantPosition &position, DataModification modType)
|
||
{
|
||
// TODO: make player invisible if ParticipantPosition was removed?
|
||
/*
|
||
* Only show Positions that HAVE actual Room and Building Ids
|
||
* If someone gets a normal position teleport to Zero or Hide
|
||
* As outdoor IPS are not attached to a room we need to specify that to..
|
||
*
|
||
*/
|
||
|
||
if (modType == DataModification::eRemoved)
|
||
{
|
||
return;
|
||
}
|
||
|
||
DEBUG_LOG(m_logger) << "onPositionChanged: " << position;
|
||
|
||
//
|
||
// translate buildingId, roomId
|
||
// at ParticipantPosition: buildingId like 364 ; roomId like 6437 (db-values)
|
||
//
|
||
int buildingID = -1;
|
||
int roomNumber = -1;
|
||
auto itId2Number = m_roomId2RoomNumber.constFind(position.buildingId());
|
||
if (itId2Number != m_roomId2RoomNumber.constEnd())
|
||
{
|
||
const auto &mapRooms = itId2Number.value();
|
||
const auto itRoom = mapRooms.constFind(position.roomId());
|
||
if (itRoom != mapRooms.constEnd())
|
||
{
|
||
buildingID = position.buildingId();
|
||
roomNumber = itRoom.value();
|
||
}
|
||
else
|
||
{
|
||
WARNING_LOG(m_logger) << "onPositionChanged: Room id can not be mapped! Building id: " << position.buildingId()<<", room id: "<< position.roomId();
|
||
}
|
||
}
|
||
|
||
// in case of outdoor IPS or WLS
|
||
// (OutdoorIps: buildingIdValue is set, but roomIdValue is not set)
|
||
if ((roomNumber < 0) && (buildingID <= 0) && (position.buildingId() > 0) &&
|
||
(position.reportingSource() == Core::PositionReportingSource::Ops || position.reportingSource() == Core::PositionReportingSource::Wls))
|
||
{
|
||
buildingID = position.buildingId();
|
||
}
|
||
|
||
|
||
const int playerId = position.participantId();
|
||
auto it = m_lastPositionTime.find(playerId);
|
||
if (it == m_lastPositionTime.end())
|
||
{
|
||
m_lastPositionTime.insert(playerId, position.sampleTime());
|
||
if (isOutdatedPosition(position))
|
||
m_connection->setPlayerVisible(playerId, false);
|
||
}
|
||
else
|
||
{
|
||
if (isOutdatedPosition(it.value()) && !isOutdatedPosition(position))
|
||
m_connection->setPlayerVisible(playerId, true);
|
||
it.value() = position.sampleTime();
|
||
}
|
||
//Only Show participants WITH both, room and building ID, but place it anyway
|
||
if (buildingID <= 0 && roomNumber <= 0)
|
||
{
|
||
m_connection->setPlayerVisible(playerId, false);
|
||
}
|
||
else
|
||
{
|
||
m_connection->setPlayerVisible(playerId, true);
|
||
}
|
||
|
||
if (!m_baseCoordinate.isValid())
|
||
{
|
||
WARNING_LOG(m_logger) << "not calling playerPlace as there is no base coordinate set - are there no buildings in this exercise?";
|
||
return;
|
||
}
|
||
|
||
// buildingId like House_364 ; roomNumber like 2025 (id of xml-file)
|
||
m_connection->playerPlace(position.participantId(), toRelative3D(position.pos()),
|
||
buildingID, roomNumber, position.reportingSource());
|
||
}
|
||
|
||
|
||
// 5. Slot
|
||
void BuildingViewerDdsController::onParticipantGearEventChanged(const ParticipantManagement::ParticipantGearEvent &data, DataModification modType)
|
||
{
|
||
switch (modType)
|
||
{
|
||
case DataModification::eAdded:
|
||
case DataModification::eModified:
|
||
{
|
||
m_participantGearEvents[data.participantId()] = data.sampleTime();
|
||
break;
|
||
}
|
||
case DataModification::eRemoved:
|
||
{
|
||
// todo: was wenn participant gelöscht?
|
||
const int playerId = data.participantId();
|
||
m_participantGearEvents.remove(playerId);
|
||
m_connection->playerUpdatePlayerSymbol(playerId, buildPlayerSymbolFilePath(playerId, cColorSymbolId));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::createParticipant(const ParticipantManagement::Participant &participant,
|
||
const ParticipantManagement::ParticipantPosition &position,
|
||
int sizeL)
|
||
{
|
||
if (!m_baseCoordinate.isValid())
|
||
{
|
||
WARNING_LOG(m_logger) << "not calling playerPlace as there is no base coordinate set - are there no buildings in this exercise?";
|
||
return;
|
||
}
|
||
|
||
if (!isValidParticipant(participant))
|
||
return;
|
||
|
||
createPlayerSymbols(participant, sizeL);
|
||
|
||
// Place player
|
||
m_connection->playerCreate(participant.id(), participant.partyId(),
|
||
buildPlayerSymbolFilePath(participant.id()),
|
||
toRelative3D(position.pos()),
|
||
position.buildingId(), m_roomId2RoomNumber.value(position.buildingId()).value(position.roomId()),
|
||
position.reportingSource());
|
||
|
||
const bool isOutdated = isOutdatedPosition(position);
|
||
const int participantId = participant.id();
|
||
const bool isValid = (position.buildingId() > 0 && m_roomId2RoomNumber.value(position.buildingId()).value(position.roomId()) > 0);
|
||
m_connection->setPlayerVisible(participantId, (!isOutdated && isValid));
|
||
m_lastPositionTime.insert(participantId, position.sampleTime());
|
||
}
|
||
|
||
void BuildingViewerDdsController::createBoobyTrap(const AreaEffectSimulation::BoobyTrap &trap, int sizeL)
|
||
{
|
||
if (trap.effectStateId() != AreaEffectSimulation::AesEffectState::EsActivated &&
|
||
trap.effectStateId() != AreaEffectSimulation::AesEffectState::EsPrepared)
|
||
return;
|
||
|
||
ParticipantManagement::Participant participant;
|
||
if(trap.partyId().is_set())
|
||
participant.partyId() = trap.partyId().get();
|
||
participant.designator() = trap.name();
|
||
participant.designator() = trap.name();
|
||
participant.id() = trap.id()+990000;
|
||
participant.sampleTime() = trap.sampleTime();
|
||
participant.symbol() = "AP6D|CMPL|10011500002104000000";
|
||
|
||
|
||
|
||
createPlayerSymbols(participant, sizeL);
|
||
Core::GeoPosition pos ;
|
||
pos.lat() = trap.impactPosition().lat();
|
||
pos.lon() = trap.impactPosition().lon();
|
||
|
||
DEBUG_LOG(m_logger) << "Try create Booby trap with id " << trap.id() << " internal id " << participant.id();
|
||
DEBUG_LOG(m_logger) << "Try create Booby trap with pos " << pos.lat() << " " << pos.lon();
|
||
m_connection->playerCreate(participant.id(), participant.partyId(),
|
||
buildPlayerSymbolFilePath(participant.id()),
|
||
toRelative3D(pos),
|
||
-1, -1,
|
||
Core::PositionReportingSource::External);
|
||
m_connection->setPlayerVisible(participant.id(), true);
|
||
|
||
|
||
}
|
||
|
||
|
||
/*
|
||
* Performs 2 actions:
|
||
*
|
||
* 1. (Re-)Creates the current symbol png files on the hard disk based on global settings symbol size
|
||
* 2. Tells the building viewers to use the new player symbol files
|
||
*/
|
||
void BuildingViewerDdsController::updatePlayerSymbols(const int sizeL, const int forParty)
|
||
{
|
||
if (!m_ppmDtc)
|
||
return;
|
||
|
||
auto cb = [this, sizeL, forParty](const ParticipantManagement::Participant &participant)
|
||
{
|
||
if (!isValidParticipant(participant))
|
||
return;
|
||
|
||
if (forParty == -1 || participant.partyId() == forParty)
|
||
createPlayerSymbols(participant, sizeL);
|
||
};
|
||
m_ppmDtc->participants(cb);
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::createPlayerSymbols(const ParticipantManagement::Participant &participant, const int sizeL)
|
||
{
|
||
SymbolInfo symbolinfo;
|
||
for (int i = 0; i < m_vecFlashColors.size(); ++i)
|
||
{
|
||
Q_ASSERT(participant.id() != 0); // invalid participant
|
||
symbolinfo.participantId = participant.id();
|
||
symbolinfo.strPathToSymbolFile = buildPlayerSymbolFilePath(participant.id(), i);
|
||
symbolinfo.color = m_vecFlashColors.at(i);
|
||
symbolinfo.useThisSymbolNow = !symbolinfo.color.isValid(); // Create several colors but only show the first symbol which uses the actual party color (See declaration of 'm_vecFlashColors')
|
||
createSymbolResultWatcher(participant, symbolinfo, sizeL);
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* Two participant changes require the generation of a new player symbol:
|
||
*
|
||
* 1. The initial creation of a player
|
||
* 2. Player has been hit
|
||
*
|
||
* The player symbol file is created asynchronously. This means, the player may already be placed within the
|
||
* BgViewer when the symbol generation has not yet finished. Hence, the symbol may appear a bit time delayed.
|
||
*
|
||
* Deletion of the object behind 'pSymbolWatcher' takes place in 'slot_SymbolWatcherResultReady' when
|
||
* symbol generation has been finished.
|
||
*/
|
||
void BuildingViewerDdsController::createSymbolResultWatcher(const ParticipantManagement::Participant &participant,
|
||
const SymbolInfo &symbolInfo,
|
||
const int sizeL)
|
||
{
|
||
AdvancedSymbology::SymbolResultWatcher *pSymbolWatcher = new AdvancedSymbology::SymbolResultWatcher(this);
|
||
|
||
bool symbolWatcherBenchmark = false;//(!LtsSessionData::LtsSessionData::getInstance()->hasDebugFlag(QStringLiteral("BDV.SymbolWatcherBenchmark")));
|
||
if (symbolWatcherBenchmark)
|
||
{
|
||
pSymbolWatcher->m_et = new QElapsedTimer;
|
||
pSymbolWatcher->m_et->start();
|
||
}
|
||
|
||
bool writeParticipantSymbols = true;//(!LtsSessionData::LtsSessionData::getInstance()->hasDebugFlag(QStringLiteral("BDV.DontWriteParticipantSymbols")));
|
||
if (writeParticipantSymbols)
|
||
{
|
||
QFile f(symbolInfo.strPathToSymbolFile);
|
||
if (!f.exists())
|
||
{
|
||
QPixmap pm(1, 1);
|
||
pm.fill(Qt::black);
|
||
pm.save(symbolInfo.strPathToSymbolFile, "PNG");
|
||
}
|
||
}
|
||
|
||
|
||
connect(pSymbolWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady, // =>
|
||
this, [this, pSymbolWatcher, symbolInfo, writeParticipantSymbols, symbolWatcherBenchmark]()
|
||
{
|
||
std::shared_ptr<int> symbolWatcherDeleter(nullptr, [this, pSymbolWatcher, symbolWatcherBenchmark](int *)
|
||
{
|
||
if (symbolWatcherBenchmark && pSymbolWatcher->m_et != nullptr)
|
||
DEBUG_LOG(m_logger) << "Symbol creation took " << pSymbolWatcher->m_et->elapsed() << " ms";
|
||
|
||
pSymbolWatcher->deleteLater();
|
||
});
|
||
(void)symbolWatcherDeleter;
|
||
|
||
if (!pSymbolWatcher->symbolResult()->symbolImage().isValid())
|
||
{
|
||
WARNING_LOG(m_logger) << getSessionInfo() << "'slot_SymbolWatcherResultReady': Symbol creation failed -> '!symbolImage.isValid()', BgViewer might not display symbol(s)!";
|
||
return;
|
||
}
|
||
|
||
int32_t playerID = symbolInfo.participantId;
|
||
|
||
if (writeParticipantSymbols)
|
||
if (!pSymbolWatcher->symbolResult()->symbolImage().image().save(symbolInfo.strPathToSymbolFile))
|
||
{
|
||
WARNING_LOG(m_logger) << getSessionInfo() << "Could not write symbol file for BgViewer: \"" << symbolInfo.strPathToSymbolFile << "\", BgViewer might not display symbol(s)!";
|
||
return;
|
||
}
|
||
|
||
// Notify BgViewer that a player symbol has been changed
|
||
if (symbolInfo.useThisSymbolNow)
|
||
m_connection->playerUpdatePlayerSymbol(playerID, symbolInfo.strPathToSymbolFile);
|
||
});
|
||
|
||
pSymbolWatcher->setSymbolResult(m_asbIfc->findImage(participant, sizeL, symbolInfo.color));
|
||
}
|
||
|
||
void BuildingViewerDdsController::reCheckHitLine()
|
||
{
|
||
if (m_ppmDtc && m_ppmDtc->isHitLineInfoEnabled())
|
||
{
|
||
m_ppmDtc->hitLineInfos([this](const ParticipantManagement::HitLineInfo &hli)
|
||
{
|
||
onHitLineInfoChanged(hli, DataModification::eAdded);
|
||
});
|
||
}
|
||
|
||
if (m_bldDtc && m_bldDtc->isBuildingHitLineInfoEnabled())
|
||
{
|
||
QMap<qint32,Buildings::BuildingHitLineInfo> hitMap = m_bldDtc->buildingHitLineInfos();
|
||
|
||
for (const auto &hli : hitMap)
|
||
{
|
||
if (hli.sampleTime() < (m_session->partitionTime() - 3000))
|
||
onBuildingHitLineInfoChanged(hli, DataModification::eAdded);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
QString BuildingViewerDdsController::buildPlayerSymbolFilePath(int playerID, int postfix)
|
||
{
|
||
return gStrSymbolFolderPath() + QStringLiteral("symbol_player_%1_%2.png").arg(playerID).arg(postfix);
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onSynchronizeSimulationTime()
|
||
{
|
||
if (m_session != nullptr)
|
||
{
|
||
//FATAL_LOG(m_logger) << "Syn to Time " <<m_session->partitionTime() ;
|
||
m_connection->systemSetSimulationTime(m_session->partitionTime());
|
||
}
|
||
else
|
||
WARNING_LOG(m_logger) << getSessionInfo() << "'slotSynchronizeSimulationTime': m_session is nullptr, could not query partitionTime!";
|
||
}
|
||
|
||
void BuildingViewerDdsController::updateBuildingRoomNames(const InfrastructureManagement::MainStructureElement& building)
|
||
{
|
||
/*
|
||
* The MainStructureElement, in our case the building, contains a vector which includes (among other things)
|
||
* all floors and rooms belonging to this building.
|
||
*/
|
||
const std::vector<InfrastructureManagement::StructureElement> &vecElements = building.elements();
|
||
|
||
int32_t buildingDatabaseId = building.id();
|
||
QString featureID = BuildingViewer::databaseId2FeatureId(buildingDatabaseId);
|
||
for (const auto &se : vecElements)
|
||
{
|
||
if (se.structureElementTypeId() == InfrastructureManagement::StructureElementType_def::Building)
|
||
m_connection->excSetBuildingDisplayName(featureID, QString::fromStdString(se.displayName()));
|
||
if (se.structureElementTypeId() == InfrastructureManagement::StructureElementType_def::Room)
|
||
m_connection->excSetRoomDisplayName(featureID, QString::number(se.mountingPointId()), QString::fromStdString(se.displayName()));
|
||
}
|
||
}
|
||
|
||
|
||
// Old code
|
||
void BuildingViewerDdsController::deleteParticipant(int participantId)
|
||
{
|
||
m_connection->playerDelete(participantId);
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onPlayerPicked(int playerId)
|
||
{
|
||
if (m_ppmGuiIfc)
|
||
m_ppmGuiIfc->showParticipantInfoWindow(playerId, true);
|
||
else
|
||
INFO_LOG(m_logger) << "No m_ppmGuiIfc";
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::effectGenerationUnitPicked(int structureElementId)
|
||
{
|
||
if (m_blgGuiIfc)
|
||
m_blgGuiIfc->showEffectGenerationUnitWindow(structureElementId);
|
||
else
|
||
INFO_LOG(m_logger) << "No m_blgGuiIfc";
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onAssetPicked(int structureElementId)
|
||
{
|
||
DEBUG_LOG(m_logger) << "User clicked on asset with structureElementId " << structureElementId << " from " << QString::number((quintptr)m_session->adr(), 16);
|
||
|
||
if (m_session->isReplayPartition())
|
||
return;
|
||
//not on ABT in GueZ
|
||
if (strcmp(PROJECTBUILD, "guez") == 0 && LtsSessionData::LtsSessionData::getInstance()->isTablet())
|
||
return;
|
||
auto it = m_mountingPointId2AssetType.find(structureElementId);
|
||
if (it == m_mountingPointId2AssetType.end())
|
||
{
|
||
WARNING_LOG(m_logger) << "onAssetPicked: User clicked on asset mounted to structureElementId " << structureElementId << " which was not added beforehand?! Ignoring...";
|
||
return;
|
||
}
|
||
|
||
const BuildingViewer::AssetTypes assetType = it.value();
|
||
switch (assetType)
|
||
{
|
||
case BuildingViewer::AssetTypes::AnnouncementUnit:
|
||
DEBUG_LOG(m_logger) << "Clicked asset is of type is AnnouncementUnit";
|
||
if (m_avmIfc)
|
||
m_avmIfc->showAnnouncementUnit(structureElementId);
|
||
return;
|
||
|
||
case BuildingViewer::AssetTypes::EffectGenerationUnit:
|
||
DEBUG_LOG(m_logger) << "Clicked asset is of type is EffectGenerationUnit";
|
||
effectGenerationUnitPicked(structureElementId);
|
||
return;
|
||
|
||
case BuildingViewer::AssetTypes::IndoorCameraWithSpeaker:
|
||
DEBUG_LOG(m_logger) << "Clicked asset is of type is IndoorCameraWithSpeaker";
|
||
if (m_avmIfc)
|
||
{
|
||
m_avmIfc->showCamera(m_irreplacableWidget, structureElementId);
|
||
m_avmIfc->showAnnouncementUnit(structureElementId);
|
||
}
|
||
return;
|
||
|
||
case BuildingViewer::AssetTypes::IndoorCamera:
|
||
DEBUG_LOG(m_logger) << "Clicked asset is of type is IndoorCamera";
|
||
if (m_avmIfc)
|
||
m_avmIfc->showCamera(m_irreplacableWidget, structureElementId);
|
||
return;
|
||
}
|
||
WARNING_LOG(m_logger) << "onAssetPicked: BuildingViewer::AssetTypes is unknown: " << assetType;
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onMountingPointChanged(const InfrastructureManagement::MountingPoint &data, DataModification modType)
|
||
{
|
||
handleMountingPoint(data, modType);
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onBuildingStateChanged(const Buildings::BuildingState &data, DataModification modType)
|
||
{
|
||
if (modType != DataModification::eAdded &&
|
||
modType != DataModification::eModified)
|
||
return;
|
||
// in this case the topicId is identical to the buildingId
|
||
// const QString &strFeatureID = BuildingViewer::databaseId2FeatureId(data.id());
|
||
// if (!m_viewerInterface->isViewOpen(strFeatureID.toStdString()))
|
||
// return;
|
||
// if (!isBuildingKnown(data.id()))
|
||
// return;
|
||
|
||
QMap<int,int> roomDestructions;
|
||
for (const Buildings::RoomState &rs : data.roomStates())
|
||
{
|
||
roomDestructions.insert(rs.mountingPointId(), rs.damagePercent());
|
||
}
|
||
// in this case the topicId is identical to the buildingId
|
||
m_connection->buildingHit(data.id(), data.id(), data.damagePercent(), roomDestructions);
|
||
}
|
||
|
||
void BuildingViewerDdsController::onBuildingHitLineInfoChanged(const Buildings::BuildingHitLineInfo &hli, DataModification modType)
|
||
{
|
||
int topidId = static_cast <int>(hli.id());
|
||
|
||
DEBUG_LOG(m_logger) << "onBuildingHitLineInfoChanged: " << hli;
|
||
|
||
const QString &strFeatureID = BuildingViewer::databaseId2FeatureId(hli.buildingId());
|
||
if (!m_viewerInterface->isViewOpen(strFeatureID.toStdString()))
|
||
{
|
||
DEBUG_LOG(m_logger) << "building not opened: hli.buildingId()=" << hli.buildingId() ;
|
||
return;
|
||
}
|
||
// if (!isBuildingKnown(hli.buildingId()))
|
||
// {
|
||
// ERROR_LOG(m_logger) << "building unknown: hli.buildingId()=" << hli.buildingId() ;
|
||
// return;
|
||
// }
|
||
|
||
const bool isRemove = (modType == DataModification::eRemoved);
|
||
DEBUG_LOG(m_logger) << "isRemove=" << isRemove;
|
||
|
||
if (isRemove)
|
||
{
|
||
DEBUG_LOG(m_logger) << "onBuildingHitLineInfoChanged: remove ";
|
||
QVector<BuildingHit> &bhl = m_buildingHits[hli.buildingId()];
|
||
|
||
auto it = std::find_if(bhl.begin(), bhl.end(), [hli](const BuildingHit &bh)
|
||
{
|
||
return (bh.id == hli.id());
|
||
});
|
||
|
||
if (it != bhl.end())
|
||
{
|
||
DEBUG_LOG(m_logger) << "bhl.erase(it)";
|
||
bhl.erase(it);
|
||
}
|
||
m_connection->removeRoomHit(hli.id());
|
||
return;
|
||
}
|
||
else // added or modified
|
||
{
|
||
BuildingHit bh;
|
||
bh.id = hli.id();
|
||
bh.sampleTime = hli.sampleTime();
|
||
bh.originatorParticipantId = hli.originatorParticipantId();
|
||
bh.chargeTypeId = hli.chargeTypeId();
|
||
//only add once
|
||
//ERROR_LOG(m_logger) << "HOTLINE add hitline? " << hli.id();
|
||
bool contains = false;
|
||
for (const auto &it : m_buildingHits[hli.buildingId()] )
|
||
{
|
||
if (it.id == bh.id)
|
||
contains = true;
|
||
}
|
||
if (!contains)
|
||
{
|
||
//ERROR_LOG(m_logger) << " HOTLINE YES" ;
|
||
m_buildingHits[hli.buildingId()].push_back(bh);
|
||
}
|
||
}
|
||
int lineType = 2;
|
||
// Draw 'Trefferlinie'
|
||
m_connection->roomHit(topidId, hli.originatorParticipantId(), hli.buildingId(), hli.hitOnRoomId(),
|
||
toRelative3D(hli.originatorPos()), lineType, isRemove ? 0 : hli.sampleTime());
|
||
}
|
||
|
||
void BuildingViewerDdsController::onCleanupBuildingHitlines()
|
||
{
|
||
DEBUG_LOG(m_logger) << "onCleanupBuildingHitlines: ";
|
||
const qint64 tNow = QDateTime::currentMSecsSinceEpoch();
|
||
const auto isOutdated = [tNow](const BuildingHit &bh)
|
||
{
|
||
return LtsUtils::isPositionTimeOutdated(tNow, bh.sampleTime);
|
||
};
|
||
for (auto &hitLines : m_buildingHits)
|
||
hitLines.erase(std::remove_if(hitLines.begin(), hitLines.end(), isOutdated), hitLines.end());
|
||
}
|
||
|
||
void BuildingViewerDdsController::onMainStructureElementChanged(const InfrastructureManagement::MainStructureElement &mse, DataModification modType)
|
||
{
|
||
if (!isBuildingKnown(mse.id()))
|
||
return;
|
||
|
||
switch (modType)
|
||
{
|
||
case DataModification::eAdded:
|
||
case DataModification::eModified:
|
||
{
|
||
if (mse.modelId() != 0 && m_ismDtc->isElementAssignedToExercise(mse, m_exerciseId))
|
||
{
|
||
DEBUG_LOG(m_logger) << "m_buildingHeight.insert id=" << mse.id() << " z=" << mse.geoPosition().z();
|
||
m_buildingHeight.insert(mse.id(), mse.geoPosition().z());
|
||
QHash<int, int> roomId2RoomNumber;
|
||
for (const InfrastructureManagement::StructureElement &se : mse.elements())
|
||
{
|
||
if (se.structureElementTypeId() != InfrastructureManagement::StructureElementType::Room)
|
||
continue;
|
||
roomId2RoomNumber[se.id()] = se.mountingPointId();
|
||
}
|
||
m_roomId2RoomNumber[mse.id()] = roomId2RoomNumber;
|
||
|
||
updateBuildingRoomNames(mse);
|
||
}
|
||
break;
|
||
}
|
||
case DataModification::eRemoved:
|
||
m_roomId2RoomNumber.remove(mse.id());
|
||
m_buildingHeight.remove(mse.id());
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
QString BuildingViewerDdsController::getSessionInfo() const
|
||
{
|
||
if (m_session == nullptr)
|
||
return QStringLiteral("session == nullptr: ");
|
||
|
||
return m_session->toString() + QStringLiteral(": ");
|
||
}
|
||
|
||
|
||
void BuildingViewerDdsController::onFireEventFlashTimer()
|
||
{
|
||
static int currFlashState = 0;
|
||
|
||
if (m_session == nullptr)
|
||
{
|
||
DEBUG_LOG(m_logger) << getSessionInfo() << "onFireEventFlashTimer: Session is not available, flashing not possible!";
|
||
return;
|
||
}
|
||
|
||
++currFlashState;
|
||
|
||
if (currFlashState >= m_vecFlashColors.size())
|
||
currFlashState = 0;
|
||
|
||
|
||
qint64 currTime_ms = m_session->partitionTime();
|
||
int gearFlashTime_ms = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("GearFlashTime_s"), 10).toInt() * 1000;
|
||
int maxGearFlashTime_ms = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("BgViewer"), QStringLiteral("maxGearFlashTime_s"), 120).toInt() * 1000;
|
||
|
||
auto itB = m_participantGearEvents.begin();
|
||
while (itB != m_participantGearEvents.end())
|
||
{
|
||
const int playerID = itB.key();
|
||
qint64 eventStartTime_ms = itB.value();
|
||
|
||
qint64 diff_ms = currTime_ms - eventStartTime_ms;
|
||
|
||
if(diff_ms > gearFlashTime_ms)
|
||
{
|
||
if(diff_ms > maxGearFlashTime_ms)
|
||
itB = m_participantGearEvents.erase(itB);
|
||
else
|
||
++itB;
|
||
|
||
m_connection->playerUpdatePlayerSymbol(playerID, buildPlayerSymbolFilePath(playerID, cColorSymbolId));
|
||
continue;
|
||
}
|
||
|
||
m_connection->playerUpdatePlayerSymbol(playerID, buildPlayerSymbolFilePath(playerID, currFlashState));
|
||
++itB;
|
||
}
|
||
|
||
for (auto it = m_lastPositionTime.cbegin(); it != m_lastPositionTime.cend(); ++it)
|
||
{
|
||
const int playerId = it.key();
|
||
const qint64 sampleTime = it.value();
|
||
if (isOutdatedPosition(sampleTime))
|
||
m_connection->setPlayerVisible(playerId, false);
|
||
}
|
||
}
|
||
|
||
} // namespace BuildingViewer
|
||
|
||
|
||
|