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

1473 lines
53 KiB
Plaintext
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: bdv_dds_controller.cpp 55079 2021-09-24 10:16:12Z fritsche_h $
* $HeadURL: svn://10.196.0.54/TDS/branches/product/devel/trunk/Lts/Plugins/BuildingViewer/Common/bdv_dds_controller.cpp $
* $Author: fritsche_h $
* 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)
: m_connection(connection)
, m_logger(getLogger("BuildingViewerDdsController"))
{}
BuildingViewerDdsController::~BuildingViewerDdsController()
{
stopAndClearTimer();
QDir dir(gStrSymbolFolderPath());
dir.removeRecursively();
QFile::remove(getBuildingConfigurationXml());
}
bool BuildingViewerDdsController::init(const QString &modelPath, PluginProvider *session)
{
DEBUG_LOG(m_logger) << "init";
m_session = session;
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() << "BuildingViewerDdsController: m_session has been destroyed!";
stopAndClearTimer();
}
});
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_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_ppmDtc = session->ppmDtc();
/*
* 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())
{
m_reCheckTimer = new QTimer(this);
connect(m_reCheckTimer, &QTimer::timeout, this, &BuildingViewerDdsController::reCheckHitLine);
m_reCheckTimer->start(1000);
}
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();
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() << "[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_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();
}
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_ppmDtc->disconnect(this);
m_ismDtc->disconnect(this);
m_ismIfc->disconnect(this);
m_bldDtc->disconnect(this);
// Release
m_avmIfc = nullptr;
m_asbIfc = 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"));
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))
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::handleMountingPoint(const InfrastructureManagement::MountingPoint &mp, DataModification modType)
{
if (!isBuildingKnown(mp.buildingId()))
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;
LtsWidgets::LtsMessageDialog::showMessage(nullptr, 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"));
m_connection->excConnect({modelPath.toStdString(), shadersPath.toStdString()}, pathToXml);
}
bool BuildingViewerDdsController::isValidParticipant(const ParticipantManagement::Participant &participant) const
{
// 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;
}
// 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::createParticipants()
{
if (!m_ppmDtc)
return;
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);
});
}
// 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:
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)
{
return;
}
if (!isBuildingKnown(hli.injuryBuildingId()))
{
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() ;
m_connection->playerHit(hli.id(), partSrc.id(), partDst.id(),
originatorSrc, injuryDest,
buildingIdSrc, roomNumberSrc, buildingIdDst, roomNumberDst,
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);
}
// 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 (!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());
}
/*
* 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)
{
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 = true;//(!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);
});
}
}
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)
{
if (m_session->isReplayPartition())
return;
DEBUG_LOG(m_logger) << "User clicked on asset with structureElementId " << structureElementId;
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(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(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;
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;
if (!isBuildingKnown(hli.buildingId()))
return;
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();
m_buildingHits[hli.buildingId()].push_back(bh);
}
// Draw 'Trefferlinie'
m_connection->roomHit(topidId, hli.originatorParticipantId(), hli.buildingId(), hli.hitOnRoomId(),
toRelative3D(hli.originatorPos()), 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