/** * @RDE_HEADER@ * DDS IFC to BuildingViewer * $Id$ * $HeadURL$ * $Author$ * Created by Patric Karwat, SLSE, 2016-08-17 */ #include "bdv_dds_controller.h" #include #include #include #include #include #include #include #include "bdv_helper.h" #include "bdv_plugin_provider.h" #include "bdv_controller.h" #include "logger.h" #include "bdv_communication_interface_internal.h" #include #include #include #include #include #include #include #include #include #include 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(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 &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 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 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() << " ("<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 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 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 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 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 &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=" < " << 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 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 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 " <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 &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 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 (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 &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 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