948 lines
32 KiB
C++
Executable File
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> ¤tGradeNeighbours,
|
|
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 ¢er = (bottomLeftOut + topRightOut) / 2;
|
|
return center;
|
|
}
|
|
|
|
|
|
} // namespace VWM
|
|
} // namespace BuildingControlUnit
|