Files
qt5-legatus-legacy/Lts/Proxies/Buildings/BuildingControlUnit/VWM/vwm_building.cpp
T
2026-08-13 18:33:10 +02:00

948 lines
32 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Building VWM info
* $Id$
* $HeadURL$
* $Author$
* Created by Gerlinde Rudolphus, SLSA, 2017-01-15
*/
#include "vwm_building.h"
#include "vwm_math.h"
#include <QSet>
#include <QVector>
#include <QLineF>
#include <settings.h>
#include <algorithm>
namespace BuildingControlUnit
{
namespace VWM
{
static NGL::INGLLogger &logger()
{
return BuildingControlUnit::getLogger("VWM.Building");
}
static inline bool isEffectNegligible(double effect)
{
return (effect < Settings::minimumRemainEffectThreshold());
}
static inline bool isEffectNegligible(double RW, double ZF, double TF)
{
return isEffectNegligible(ZF * RW * TF);
}
// effect on walls (inside the room hit when TF = 1.0, otherwise neighbour room), see chapter 3.4/3.5
static inline double calcProtectionOfWallAfterHit(double SG_alt, double RW, double ZF, double TF)
{
const double SG_diff = ZF * RW * TF;
if (isEffectNegligible(SG_diff))
return SG_alt;
return std::max(0.0, SG_alt - SG_diff);
}
//
// WallExclusionZoneBase
//
void WallExclusionZoneBase::setInitalProtection(double initialProtection, bool bResetProtection)
{
m_initialProtection = initialProtection;
if (bResetProtection)
m_protection = initialProtection;
}
void WallExclusionZoneBase::updateProtection(const double RW, const double ZF, const double TF)
{
const double SG_alt = m_protection;
const double SG_neu = calcProtectionOfWallAfterHit(SG_alt, RW, ZF, TF);
m_protection = SG_neu;
}
//
// Room
//
void Room::setFortifyFactor(double factorNew, bool bRecalcValues)
{
const double factorOld = m_fortifyFactor;
if (qFuzzyIsNull(factorOld) || qFuzzyIsNull(factorNew))
return;
m_fortifyFactor = factorNew;
if (bRecalcValues)
{
for (OuterWall &wall : m_outerWalls)
{
wall.setProtection(wall.protection() / factorOld * factorNew);
wall.setInitalProtection(wall.initialProtection() / factorOld * factorNew, false);
for (ExclusionZone &exZone : wall.exclusionZones())
{
exZone.setProtection(exZone.protection() / factorOld * factorNew);
exZone.setInitalProtection(exZone.initialProtection() / factorOld * factorNew, false);
}
}
}
}
double Room::damagePercent() const
{
if (m_outerWalls.isEmpty())
return 0; // should never happen - see Building::init()
double currentProtection = 0.0;
double initialProtection = 0.0;
for (const OuterWall &wall : qAsConst(m_outerWalls))
{
currentProtection += wall.protection();
initialProtection += wall.initialProtection();
for (const ExclusionZone &exZone : qAsConst(wall.exclusionZones()))
{
currentProtection += exZone.protection();
initialProtection += exZone.initialProtection();
}
}
if (qFuzzyIsNull(initialProtection))
return 100;
return (1.0 - currentProtection / initialProtection) * 100.0;
}
double Room::calcRemainingEffect(const HitPosition &hitPos, const Charge &ammu) const
{
const double SG_alt = protectionForHitPosition(hitPos);
const double ZF = ammu.destructiveFactor;
const double WF = ammu.penetrationEffect;
const double RW = calcRemainingEffect(SG_alt, ZF, WF);
return RW;
}
void Room::updateProtectionDirectHit(const HitPosition &hitPos, const Charge &ammu)
{
Q_ASSERT(hitPos.state != HitPosition::State::NoHit);
const double SG_alt = protectionForHitPosition(hitPos);
const double ZF = ammu.destructiveFactor;
// da RW und TF = 1.0 ist es die gleiche Formel für direkten treffer und sekundärwirkung auf andere elemente
const double SG_neu = calcProtectionOfWallAfterHit(SG_alt, 1.0, ZF, 1.0);
setProtectionForHitPosition(hitPos, SG_neu);
}
static bool isDirectHitOuterWall(const HitPosition &hitPos, int roomId, int idxOw)
{
if (hitPos.state != HitPosition::State::OuterWall)
return false;
return hitPos.roomId == roomId && hitPos.outerWallIdx == idxOw;
}
static bool isDirectHitExlusionZone(const HitPosition &hitPos, int roomId, int idxOw, int idxEz)
{
if (hitPos.state != HitPosition::State::ExclusionZone)
return false;
return hitPos.roomId == roomId && hitPos.outerWallIdx == idxOw && hitPos.exclusionZoneIdx == idxEz;
}
void Room::updateProtectionIndirectHit(const HitPosition &hitPos, const Charge &ammu, double remainingEffect)
{
// same calculation as for neighbours when transitionFactor is set to 1.0
updateProtectionNeighbourHit(hitPos, ammu, remainingEffect, 1.0);
}
bool Room::updateProtectionNeighbourHit(const HitPosition &hitPos, const Charge &ammu, double remainingEffect, double transitionFactor)
{
const double ZF = ammu.destructiveFactor;
const double RW = remainingEffect;
const double TF = transitionFactor;
DEBUG_LOG(logger()) << "Effect for room " << m_id
<< " (RW="<< RW << ", ZF=" << ZF << " , TF=" << TF << ") ; effect=" << RW*ZF*TF
<< " -> damagePercent VOR= " << this->damagePercent();
if (isEffectNegligible(RW, ZF, TF))
{
DEBUG_LOG(logger()) << "Effect is Negligible for room " << m_id;
for (int idxOw = 0; idxOw < m_outerWalls.size(); ++idxOw)
{
OuterWall &ow = m_outerWalls[idxOw];
DEBUG_LOG(logger()) << "room " << m_id << "[ow=" << idxOw
<< "] new protection = " << ow.protection()
<< " (" << ow.protectionPercent() << "%)";
}
return false;
}
const char *hitReason = (hitPos.roomId == m_id ? "indirect hit" : "neighbour hit");
for (int idxOw = 0; idxOw < m_outerWalls.size(); ++idxOw)
{
OuterWall &ow = m_outerWalls[idxOw];
if (!isDirectHitOuterWall(hitPos, m_id, idxOw))
{
ow.updateProtection(RW, ZF, TF);
DEBUG_LOG(logger()) << "room " << m_id << "[ow=" << idxOw
<< "] new protection = " << ow.protection()
<< " (" << ow.protectionPercent() << "%) - " << hitReason;
}
for (int idxEz = 0; idxEz < ow.exclusionZones().size(); ++idxEz)
{
if (!isDirectHitExlusionZone(hitPos, m_id, idxOw, idxEz))
{
ExclusionZone &ez = ow.exclusionZones()[idxEz];
ez.updateProtection(RW, ZF, TF);
DEBUG_LOG(logger()) << "room " << m_id << "[ow=" << idxOw
<< "][ez=" << idxEz
<< "] new protection = " << ez.protection()
<< " (" << ez.protectionPercent() << "%) - " << hitReason;
}
}
}
DEBUG_LOG(logger()) << "Effect for room " << m_id << " -> damagePercent NACH = " << this->damagePercent();
return true;
}
double Room::protectionForHitPosition(const HitPosition &hitPos) const
{
const OuterWall &ow = m_outerWalls.at(hitPos.outerWallIdx);
if (hitPos.state == HitPosition::State::OuterWall)
return ow.protection();
return ow.exclusionZones().at(hitPos.exclusionZoneIdx).protection();
}
void Room::setProtectionForHitPosition(const HitPosition &hitPos, double SG_neu)
{
Q_ASSERT(hitPos.state != HitPosition::State::NoHit);
OuterWall &ow = m_outerWalls[hitPos.outerWallIdx];
if (hitPos.state == HitPosition::State::OuterWall)
{
ow.setProtection(SG_neu);
// // m_directHit = true; // 'moved' to Building::hit()
DEBUG_LOG(logger()) << "room " << m_id << "[ow=" << hitPos.outerWallIdx
<< "] new protection = " << ow.protection()
<< " (" << ow.protectionPercent() << "%)"
<< " - direct hit";
}
else
{
ExclusionZone &ez = ow.exclusionZones()[hitPos.exclusionZoneIdx];
ez.setProtection(SG_neu);
DEBUG_LOG(logger()) << "room " << m_id << "[ow=" << hitPos.outerWallIdx
<< "][ez=" << hitPos.exclusionZoneIdx
<< "] new protection = " << ez.protection()
<< " (" << ez.protectionPercent() << "%)"
<< " - direct hit";
}
}
// effect on the wall/exclusion zone which was hit, see chapter 3.2 vulnerability model document
double Room::calcRemainingEffect(double SG_alt, double ZF, double WF)
{
// keine Schutzwirkung -> remainingEffect = 100%
if (SG_alt < 0.0001)
return 1.0;
if (ZF < 0.0001)
return 0;
double D_Wand = 0.0;
if (SG_alt < ZF * 0.95)
D_Wand = ZF * 0.95 - SG_alt;
const double D_Zufall = ZF * WF * 0.05 / SG_alt;
double RW = 100.0 * (D_Wand + D_Zufall) / ZF;
if (RW > 1.0)
RW = 1.0;
return RW;
}
//
// Building
//
Building::Building()
: m_lastHitPosition(HitPosition())
, m_logger(BuildingControlUnit::getLogger("VWM.Building"))
{}
int Building::checkPolygons()
{
int numberOfDuplicates = 0;
for (const Room &room : qAsConst(m_rooms))
{
for (int owIdx = 0; owIdx < room.outerWalls().size(); ++owIdx)
{
const OuterWall &ow = room.outerWalls().at(owIdx);
for (int elemIdx = 0; elemIdx < ow.elements().size(); ++elemIdx)
{
const WallElement &wallElement = ow.elements().at(elemIdx);
numberOfDuplicates += checkDublicatePointsInPolygon(room.id(), owIdx, elemIdx, "QuterWall", wallElement);
/*
const auto &points = wallElement;
for (int i = 0; i < points.size() - 1; ++i)
{
const QLineF l1 = QLineF(points.at(i), points.at(i+1));
for (int j = i + 1; j < points.size() - 1; ++j)
{
const QLineF l2 = QLineF(points.at(j), points.at(j+1));
if (l1.intersect(l2, nullptr))
{
WARNING_LOG(m_logger) << "Polygon lines " << i << "," << i+1 << " (" << l1
<< ") and " << j << "," << j+1 << " (" << l2
<< ") in room " << roomId << ", " << type << "[" << idx
<< "], element[" << subIdx << "] are interesectiong each other!";
}
}
}*/
}
/*
for (int exIdx = 0; exIdx < ow.exclusionZones().size(); ++exIdx)
{
const ExclusionZone &ez = ow.exclusionZones().at(exIdx);
checkDublicatePointsInPolygon(room.id(), owIdx, exIdx, "exclusion zone", ez);
}*/
}
}
return numberOfDuplicates;
}
int Building::cleanupPolygons()
{
int numberOfRemoved = 0;
for (Room &room : m_rooms)
{
for (int owIdx = 0; owIdx < room.outerWalls().size(); ++owIdx)
{
OuterWall &ow = room.outerWalls()[owIdx];
for (int elemIdx = 0; elemIdx < ow.elements().size(); ++elemIdx)
{
WallElement &wallElement = ow.elements()[elemIdx];
numberOfRemoved += cleanupDublicatePointsInPolygon(room.id(), owIdx, elemIdx, "room", wallElement);
bool bMakePlanarDone = false;
// make planes planar
MakePlanar makePlanar(wallElement); // just a reference
//try (keep bldg_3280 in mind -> final poly was reduced to 2 coordinate (cleanupDublicatePointsInPolygon , make planar, getCoordsForPlane/removeLinearDependency)
QVector<LtsUtils::QPoint3D> candidate = makePlanar.calculate(&bMakePlanarDone);
cleanupDublicatePointsInPolygon(room.id(), owIdx, elemIdx, "room", candidate);
if (candidate.size() < 4)
{
NOTICE_LOG(m_logger) << "wall was made planar: Building '" << m_name << "' -> "
<< " in room " << room.id() << " OuterWall[" << owIdx
<< "], element[" << elemIdx << "] "
<< " makePlanar and cleanupDublicatePointsInPolygon would generate short polygon, size=" << candidate.size();
for (auto point : qAsConst(candidate))
NOTICE_LOG(m_logger) << " point= " << point;
bMakePlanarDone = false;
} else {
wallElement = candidate;
}
if (bMakePlanarDone)
NOTICE_LOG(m_logger) << "wall was made planar: Building '" << m_name << "' -> "
<< " in room " << room.id() << " OuterWall[" << owIdx
<< "], element[" << elemIdx << "] "
<< " smallestDim=" << (int)makePlanar.getSmallestDim()
<< " smallestRange=" << makePlanar.getSmallestRange();
//
// Bei Bldg 1193 (rev. 5032.55466)
// ((( if (ratioOfSmallestAndBiggestDimension() >= 10.0
// && getRange(m_smallestDim).range() < 0.10 )) )
// OHNE makePlanar -> 7 HitPosition::State::OuterWall (vwm_math.cpp:539)
// MIT makePlanar -> 8 HitPosition::State::OuterWall (vwm_math.cpp:539)
// Neu: HitPosition::State::OuterWall, roomId = 2103, outerWallIndex = 1
}
}
}
return numberOfRemoved;
}
int Building::cleanupDublicatePointsInPolygon(int roomId, int idx, int subIdx, const char *type, QVector<LtsUtils::QPoint3D> &points) const
{
QVector<int> idxToRemove;
idxToRemove.reserve(points.size());
for (int i = 0; i < points.size(); ++i)
{
const auto &p1 = points.at(i);
for (int j = i + 1; j < points.size(); ++j)
{
auto p2 = points.at(j);
if (qFuzzyCompare(p1, p2))
{
DEBUG_LOG(m_logger) << "Building '" << m_name << "' -> "
<< "Polygon point " << i << " (" << p1 << ") and " << j << " (" << p2
<< ") in room " << roomId << ", " << type << "[" << idx
<< "], element[" << subIdx << "] are the same! -> REMOVE";
if (!idxToRemove.contains(j))
idxToRemove.push_back(j);
}
}
}
// std::sort works with QVector :-)
std::sort(idxToRemove.begin(), idxToRemove.end(), std::greater<int>());
int numberOfRemoved = idxToRemove.size();
// remove from end/high to beginning/low :-)
for (auto idxRemove : idxToRemove)
points.removeAt(idxRemove);
return numberOfRemoved;
}
Room Building::room(int roomId)
{
auto it = m_rooms.find(roomId);
if (it == m_rooms.end())
return {};
return *it;
}
// calculated values
int Building::damagePercent() const
{
double roomDamagePercent = 0.0;
for (const Room &room : m_rooms)
roomDamagePercent += room.damagePercent();
return qRound(roomDamagePercent / m_rooms.size());
}
QVector<HitRoom> Building::roomStates() const
{
QVector<HitRoom> ret;
ret.reserve(m_rooms.size() / 2);
for (const Room &room : m_rooms)
{
const int dmgPercent = qRound(room.damagePercent());
// if ((dmgPercent > 0 && damageState() != DamageState::Destroyed) || !qFuzzyCompare(room.fortifyFactor(), 1.0))
// {
HitRoom hr;
hr.roomId = room.id();
hr.damagePercent = dmgPercent;
hr.remainingEffectPercent = qRound(room.remainingEffect() * 100);
hr.fortifyFactor = room.fortifyFactor();
hr.directHit = room.isDirectHit();
ret += hr;
DEBUG_LOG(m_logger) << "HitRoom " << hr.roomId
<< " damage: " << hr.damagePercent
<< "%, remainEffect: " << hr.remainingEffectPercent << "%"
<< ", remainingEffectChargeTypeId: " << hr.remainingEffectChargeTypeId << "%"
<< ", fortifyFactor: " << hr.fortifyFactor
<< ", isDirectHit:" << room.isDirectHit();
// }
}
return ret;
}
DamageState Building::damageState() const
{
if (Building::damagePercent() >= Settings::killThreshold())
return DamageState::Destroyed;
return DamageState::NoDamage;
}
// effects on building
void Building::init()
{
for (Room &room : m_rooms)
initRoom(room);
}
void Building::destroyAllRooms()
{
for (Room &room : m_rooms)
destroyRoom(room);
}
void Building::setRoomState(DamageState damageState, const QVector<int> &afffectedRooms)
{
for (const int roomId : afffectedRooms)
{
auto it = m_rooms.find(roomId);
if (it == m_rooms.end())
{
WARNING_LOG(m_logger) << "Could not find room with id " << roomId;
continue;
}
if (damageState == DamageState::Destroyed)
destroyRoom(it.value());
else
initRoom(it.value());
}
}
void Building::setFortifyInfo(const QVector<FortifyInfo> &foritfyInfos)
{
for (const FortifyInfo &info : foritfyInfos)
{
auto it = m_rooms.find(info.roomId);
if (it == m_rooms.end())
{
WARNING_LOG(m_logger) << "Could not find room with id " << info.roomId;
continue;
}
Room &room = it.value();
room.setFortifyFactor(info.fortifyFactor);
}
}
void Building::initRoom(Room &room) const
{
const double BGPL = m_buildingProtectionLevel;
const double OWPL = m_outerWallProtectionLevel;
const double EZPL = m_exclusionZoneProtectionLevel;
room.setRemainingEffect(0);
room.setDirectHit(false);
const double fortifyFactor = room.fortifyFactor();
if (room.outerWalls().isEmpty())
{
// create a dummy wall for internal rooms
room.outerWalls() += OuterWall();
}
for (OuterWall &ow : room.outerWalls())
{
if (ow.isDummyWall())
{
// see chapter 3.5
ow.setInitalProtection(250 * fortifyFactor, true);
}
else
{
const double OWPF = ow.protectiveFactor();
const double SG_0_Wall = BGPL * OWPL * OWPF * fortifyFactor;
ow.setInitalProtection(SG_0_Wall, true);
for (ExclusionZone &ez : ow.exclusionZones())
{
const double EZPF = ez.protectiveFactor();
const double SG_0_EZ = BGPL * EZPL * EZPF * fortifyFactor;
ez.setInitalProtection(SG_0_EZ, true);
}
}
}
}
void Building::destroyRoom(Room &room)
{
room.setRemainingEffect(1); // 0..1
for (OuterWall &ow : room.outerWalls())
{
ow.setProtection(0);
for (ExclusionZone &ez : ow.exclusionZones())
ez.setProtection(0);
}
}
bool Building::hit(const LtsUtils::QPoint3D &pos, const LtsUtils::QPoint3D &dir, Charge charge, HitPosition *preCalculatedHitPosition)
{
for (Room &room : m_rooms)
room.setDirectHit(false);
if (preCalculatedHitPosition)
{
m_lastHitPosition = *preCalculatedHitPosition; // 3DWirksim
} else {
// hit position berechnen
m_lastHitPosition = Math::getHitPosition(pos, dir, m_rooms);
}
// Hack Unterirdisch:
const bool bBelowGround = isLastHitPositionBelowGroundHack();
if (bBelowGround)
{
INFO_LOG(m_logger) << " XXXX CES belowGround, reduce charge: " << charge;
charge.destructiveFactor *= 0.25;
charge.penetrationEffect *= 0.25;
INFO_LOG(m_logger) << " XXXX CES belowGround, reduce charge: " << charge;
}
INFO_LOG(m_logger) << "XXXXX HitPosition: state=" << m_lastHitPosition.state
<< ", m_name=" << m_name
<< ", m_modelId=" << m_modelId
<< ", roomId=" << m_lastHitPosition.roomId
<< ", outerWallIndex=" << m_lastHitPosition.outerWallIdx
<< ", exclusionZoneIndex=" << m_lastHitPosition.exclusionZoneIdx
<< ", bBelowGround=" << bBelowGround;
// nicht getroffen -> return
if (m_lastHitPosition.state == HitPosition::State::NoHit)
return false;
// zugehörigen Raum suchen
Room &hitRoom = m_rooms[m_lastHitPosition.roomId];
if (!hitRoom.isValid())
{
ERROR_LOG(m_logger) << "Internal error in hit calculation - could not find room with id " << m_lastHitPosition.roomId;
return false;
}
// GUEZE-1013 Kundenrückläufer Bldg 1158
// 3 REF_MODs für Raum 101 @ Bldg 1158: 11001, 11022, 11029
// Bei allen 3 wird die Trefferlinie in der 2D-Lage dargestellt.
// Bei 11022, 11029 wird die BuildingHitLineInfo im Bldg-Viewer angezeigt.
// Die BuildingHitLineInfo für 11001 fehlt allerdings!!
// Grund: Bei 11022, 29 wird eine OuterWall vom VWM detektiert.
// Bei 11001 hingegen ist es eine ExclusionZone.
// Bei der Weiterleitung vom VWM gibt es in der Raumliste ein Flag 'm_directHit',
// welches eingeführt wurde mit r43258
// Wir setzen dieses Flag nun unabhängig davon, ob der Raum via
// Outerwall oder ExclusionZone getroffen wurde:
hitRoom.setDirectHit(true);
// Implementation laut Dokument:
// wir zerstören hier die Nachbarräume mit dem remainingEffect. Es werden alle
// Nachbarschaftsbeziehungen berückichtigt, Abbruch ist wenn isEffectNegligible() true zurück liefert
// vorher werden alle Räume dem 'Verwandschaftsgrad' nach durchlaufen. D.h. erst alle direkt angrenzenden
// Räume, dann alle, die an diesen angegrenzt sind usw. Der Effekt geht immer nur von Vater zu Kind, nie
// von Kind zu Vater oder Kind zu Kind.
// alten Remaining Effekt entfernen
for (Room &room : m_rooms)
room.setRemainingEffect(0);
// Remaining Effect auf andere Gebäudeteile berechnen (dafür ist die alte protection (SG_alt) nötig)
const double remainingEffect = hitRoom.calcRemainingEffect(m_lastHitPosition, charge);
hitRoom.setRemainingEffect(remainingEffect);
// neue protection für getroffenen Raum berechnen und setzen
hitRoom.updateProtectionDirectHit(m_lastHitPosition, charge);
// neue protection für die anderen Elemente des getroffenen Raumes berechnen
hitRoom.updateProtectionIndirectHit(m_lastHitPosition, charge, remainingEffect);
// neue protection für die Nachbarräume berechnen
const QMap<int, NeighbourRoom> &neighbours = hitRoom.neighbours();
QMap<int, NeighboursToCheck> neighboursToCheck;
QSet<int> currentGradeNeighbours;
for (const NeighbourRoom &neighbour : neighbours)
{
neighboursToCheck.insert(neighbour.id(), NeighboursToCheck(neighbour, hitRoom.remainingEffect()));
currentGradeNeighbours.insert(neighbour.id());
}
QSet<int> lowerGradeNeighbours;
lowerGradeNeighbours.insert(hitRoom.id());
// DEBUG_LOG(m_logger) << " XXXXX start recursive ; neighboursToCheck= " << toString(neighboursToCheck);
auto ret = checkHitNeighbours(neighboursToCheck, charge, currentGradeNeighbours, lowerGradeNeighbours);
while (!ret.isEmpty())
{
// DEBUG_LOG(m_logger) << " XXXXX recursive ; ret= " << toString(ret);
ret = checkHitNeighbours(ret, charge, currentGradeNeighbours, lowerGradeNeighbours);
// currentGradeNeighbours will contain the new neighbours for the next grade
// lowerGradeNeighbours will contain all currently lower grade neighbours now
}
return true;
}
bool Building::isLastHitPositionBelowGroundHack() const
{
// Hack:
return
(m_name.contains(QStringLiteral("BN_Kanaele"))) ||
(m_name.contains(QStringLiteral("U_Bahn")) && m_lastHitPosition.roomId < 2300)
;
}
QMap<int, Building::NeighboursToCheck> Building::checkHitNeighbours(const QMap<int, NeighboursToCheck> &neighbours,
const Charge &charge,
QSet<int> &currentGradeNeighbours,
QSet<int> &lowerGradeNeighbours)
{
// DEBUG_LOG(m_logger) << " neighbours " << toString(neighbours);
// DEBUG_LOG(m_logger) << " currentGradeNeighbours " << toString(currentGradeNeighbours);
// DEBUG_LOG(m_logger) << " lowerGradeNeighbours " << toString(currentGradeNeighbours);
QMap<int, NeighboursToCheck> neighboursToCheck;
QSet<int> newCurrentGradeNeighbours;
for (const NeighboursToCheck &neighbourRoom : neighbours)
{
const int neighbourRoomId = neighbourRoom.room.id();
if (lowerGradeNeighbours.contains(neighbourRoomId)) // this should not happen
continue;
auto itRoom = m_rooms.find(neighbourRoomId);
if (itRoom == m_rooms.end())
{
WARNING_LOG(m_logger) << "Could not find room with id " << neighbourRoomId;
continue;
}
Room &neighbour = itRoom.value();
// REGG-5736: "Die Wirkung von der KPZ-Kanone muss ein Gebäude nahezu komplett zerstören. Diese Wirkung wird zur Zeit nicht erreicht."
double TF = Settings::transitionMultiplier() * neighbourRoom.room.transitionFactor();
// GUEZE-605 / GUEZE-707: ..aber begrenzen auf 1. Ein WEITER-leitungswert größer ist unlogisch
if (TF > 1.0)
TF = 1.0;
// hitPos is not needed here but internally we use the same calculation for updateProtectionIndirectHit() ...
if (!neighbour.updateProtectionNeighbourHit(m_lastHitPosition, charge, neighbourRoom.remainEffect, TF))
continue;
// there was an effect on this room -> set remainEffect and add all next grade neighbours to the list for the next check
const double oldRe = neighbour.remainingEffect();
neighbour.setRemainingEffect(neighbourRoom.remainEffect * TF);
DEBUG_LOG(m_logger) << "room " << neighbourRoomId << " remaining effect old: "
<< oldRe << " -> new: " << neighbour.remainingEffect();
// check grade2 neighbours, remove all lower grade neighbours
for (const NeighbourRoom &grade2Neighbour : qAsConst(neighbour.neighbours()))
{
if (lowerGradeNeighbours.contains(grade2Neighbour.id()) ||
currentGradeNeighbours.contains(grade2Neighbour.id()))
continue;
// [GUEZE-605] / [GUEZE-707]
// Problem: 701 hat in Etage 6 diverse Nachbarräume: 601, 602,... 610.
// 603 hat aber auch 602 und 604 als Nachbarn.
// Bei solch einer Konstellation wurden Räume mehrfach (bis zu 12 mal) eingefügt.
// Und dementsprechend die Zerstörung mehrfach berechnet und akkumuliert.
// Bei Bldg 3101 wurde 601 getroffen (50%) und 106 wurde (durch 12-fache Berechnung) zu 100% zerstört (!!!)
auto it = neighboursToCheck.find(grade2Neighbour.id());
if (it == neighboursToCheck.end())
{
neighboursToCheck.insert(grade2Neighbour.id(), NeighboursToCheck(grade2Neighbour, neighbour.remainingEffect()));
} else {
// check remainingEffect and use maximum
const NeighboursToCheck &curEntry = it.value();
if (neighbour.remainingEffect() > curEntry.remainEffect)
neighboursToCheck.insert(grade2Neighbour.id(), NeighboursToCheck(grade2Neighbour, neighbour.remainingEffect()));
}
newCurrentGradeNeighbours.insert(grade2Neighbour.id());
}
}
lowerGradeNeighbours += currentGradeNeighbours;
currentGradeNeighbours = newCurrentGradeNeighbours;
return neighboursToCheck;
}
QString Building::buildingProperties() const
{
return m_buildingProperties;
}
void Building::setBuildingProperties(const QString &buildingProperties)
{
m_buildingProperties = buildingProperties;
}
QString Building::segmentProperties(const QString &hostname) const
{
return m_segmentProperties.value(hostname);
}
void Building::setSegmentProperties(const QString &hostname, const QString &properties)
{
m_segmentProperties.insert(hostname, properties);
}
int Building::checkDublicatePointsInPolygon(int roomId, int idx, int subIdx, const char *type,
const QVector<LtsUtils::QPoint3D> &points) const
{
int numberOfDuplicates = 0;
for (int i = 0; i < points.size(); ++i)
{
const auto &p1 = points.at(i);
for (int j = i + 1; j < points.size(); ++j)
{
const auto &p2 = points.at(j);
if (qFuzzyCompare(p1, p2))
{
++numberOfDuplicates;
WARNING_LOG(m_logger) << "Building '" << m_name << "' -> "
<< "Polygon point " << i << " (" << p1 << ") and " << j << " (" << p2
<< ") in room " << roomId << ", " << type << "[" << idx
<< "], element[" << subIdx << "] are the same! (size=" << points.size() << ")";
}
}
}
if (numberOfDuplicates > 0)
for (auto point : points)
WARNING_LOG(m_logger) << "Building '" << m_name << "' -> " << "point =" << point << " (same points)";
return numberOfDuplicates;
}
QString Building::toString(const QVector<Building::NeighboursToCheck> &cont)
{
QString str;
for (auto item : cont)
str += QString(QStringLiteral("%1;")).arg(item.room.id());
return str;
}
QString Building::toString(const QMap<int, Building::NeighboursToCheck> &cont)
{
QString str;
for (auto item : cont)
str += QString(QStringLiteral("%1;")).arg(item.room.id());
return str;
}
QString Building::toString(const QSet<int> &cont)
{
QString str;
for (auto item : cont)
str += QString(QStringLiteral("%1;")).arg(item);
return str;
}
//
// class MakePlanar
//
MakePlanar::MakePlanar(const QVector<LtsUtils::QPoint3D> &poly)
: m_poly(poly)
{
ranges.resize(3);
}
QVector<LtsUtils::QPoint3D> MakePlanar::calculate(bool *bMakePlanarDone)
{
// Folgende Polygon hat eine Abweichung bei 17cm:
// 2.859 - 2.689 = 0.17
// static const QVector<LtsUtils::QPoint3D> polyBldg1131_201_3({ // sollte eigentlich waagerechte Wand darstellen
// {0.000, 5.987, 2.689}, // idx=0
// {0.000, 5.791, 2.695}, // idx=1 // idx=1 "stört"
// {0.000, 0.170, 2.859}, // idx=2
// {3.098, 0.170, 2.859}, // idx=3
// {3.098, 5.791, 2.695}, // idx=4
// {2.928, 5.987, 2.689} // idx=5
// });
if (ratioOfSmallestAndBiggestDimension() >= 10.0
&& getRange(m_smallestDim).range() < 0.20
)
{
// const int numCoords = m_poly.size();
makePlanar();
if (bMakePlanarDone)
{
*bMakePlanarDone = true;
// std::cout << " MakePlanar smallestRange=_" << getRange(m_smallestDim).range() << "_ ratio=" << m_ratio << " org-numCoords=" << numCoords << std::endl;
}
}
return m_poly;
}
double MakePlanar::ratioOfSmallestAndBiggestDimension()
{
// m_poly describes an area. Maybe the area is not planar.
// So for this use case, 4 items are required
if (m_poly.size() < 4)
return -1;
MyRange &rangeX = ranges[static_cast<int>(DIMENSION::X)];
MyRange &rangeY = ranges[static_cast<int>(DIMENSION::Y)];
MyRange &rangeZ = ranges[static_cast<int>(DIMENSION::Z)];
// Note: first() required at least an item:
rangeX.min = m_poly.first().x(); rangeX.max = m_poly.first().x();
rangeY.min = m_poly.first().y(); rangeY.max = m_poly.first().y();
rangeZ.min = m_poly.first().z(); rangeZ.max = m_poly.first().z();
for (const LtsUtils::QPoint3D &pnt : qAsConst(m_poly))
{
rangeX.min = std::min(rangeX.min, pnt.x());
rangeX.max = std::max(rangeX.max, pnt.x());
rangeY.min = std::min(rangeY.min, pnt.y());
rangeY.max = std::max(rangeY.max, pnt.y());
rangeZ.min = std::min(rangeZ.min, pnt.z());
rangeZ.max = std::max(rangeZ.max, pnt.z());
}
if (rangeY.range() < rangeX.range())
m_smallestDim = DIMENSION::Y;
if (rangeZ.range() < getRange(m_smallestDim).range())
m_smallestDim = DIMENSION::Z;
if (rangeY.range() > rangeX.range())
m_biggistDim = DIMENSION::Y;
if (rangeZ.range() > getRange(m_biggistDim).range())
m_biggistDim = DIMENSION::Z;
// avoid division by zero
if (!qFuzzyIsNull(getRange(m_smallestDim).range()))
m_ratio = getRange(m_biggistDim).range() / getRange(m_smallestDim).range();
return m_ratio; // could be inf / inifite
}
void MakePlanar::makePlanar()
{
// choose new value:
const double newValue = (getRange(m_smallestDim).min + getRange(m_smallestDim).max) / 2.0;
switch (m_smallestDim)
{
case DIMENSION::X:
for (LtsUtils::QPoint3D &pnt_ref : m_poly)
pnt_ref.rx() = newValue;
break;
case DIMENSION::Y:
for (LtsUtils::QPoint3D &pnt_ref : m_poly)
pnt_ref.ry() = newValue;
break;
case DIMENSION::Z:
for (LtsUtils::QPoint3D &pnt_ref : m_poly)
pnt_ref.rz() = newValue;
break;
case DIMENSION::Undefined:
{ // empty
}
} // switch
}
void wallElementToBottomLeftTopRight3D(const WallElement &wallElement,
LtsUtils::QPoint3D &bottomLeftOut,
LtsUtils::QPoint3D &topRightOut)
{
const LtsUtils::QPoint3D &frontItem = wallElement.front();
bottomLeftOut = frontItem;
topRightOut = frontItem;
for (auto coord : qAsConst(wallElement))
{
if (coord.x() < bottomLeftOut.x())
bottomLeftOut.setX(coord.x());
if (coord.y() < bottomLeftOut.y())
bottomLeftOut.setY(coord.y());
if (coord.z() < bottomLeftOut.z())
bottomLeftOut.setZ(coord.z());
if (coord.x() > topRightOut.x())
topRightOut.setX(coord.x());
if (coord.y() > topRightOut.y())
topRightOut.setY(coord.y());
if (coord.z() > topRightOut.z())
topRightOut.setZ(coord.z());
}
}
LtsUtils::QPoint3D centerOfWallElement3D(const WallElement &wallElement)
{
LtsUtils::QPoint3D bottomLeftOut, topRightOut;
wallElementToBottomLeftTopRight3D(wallElement, bottomLeftOut, topRightOut);
const LtsUtils::QPoint3D &center = (bottomLeftOut + topRightOut) / 2;
return center;
}
} // namespace VWM
} // namespace BuildingControlUnit