Files
qt5-legatus-legacy/Lts/Plugins/BuildingViewer/Common/bdv_dds_controller.cpp
T
2026-08-13 18:33:10 +02:00

1728 lines
61 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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