Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions src/domain/CompressionSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
#include "domain/AgentComponents.h"
#include "domain/FacilityLayout2D.h"
#include "domain/Metrics.h"
#include "domain/PressureTuning.h"

#include <algorithm>
#include <cmath>

namespace safecrowd::domain {
namespace {

constexpr float kForceThreshold = 0.5f;
constexpr float kExposureThreshold = 2.0f;
constexpr double kPi = 3.14159265358979323846;
constexpr double kReferenceDistanceMeters = kPressureReferenceDistanceMeters;

double distanceBetween(const Point2D& lhs, const Point2D& rhs) {
const double dx = lhs.x - rhs.x;
Expand Down Expand Up @@ -39,7 +40,7 @@ double distancePointToSegment(const Point2D& point, const Point2D& start, const
return distanceBetween(point, projection);
}

double barrierCompression(const Barrier2D& barrier, const Point2D& position, double radius) {
double barrierCompression(const Barrier2D& barrier, const Point2D& position, double referenceDistance) {
if (!barrier.blocksMovement || barrier.geometry.vertices.size() < 2) {
return 0.0;
}
Expand All @@ -49,21 +50,28 @@ double barrierCompression(const Barrier2D& barrier, const Point2D& position, dou

for (std::size_t index = 0; index + 1 < vertices.size(); ++index) {
const double distance = distancePointToSegment(position, vertices[index], vertices[index + 1]);
if (distance < radius) {
force += radius - distance;
if (distance < referenceDistance) {
force += (referenceDistance - distance) / referenceDistance;
}
}

if (barrier.geometry.closed) {
const double distance = distancePointToSegment(position, vertices.back(), vertices.front());
if (distance < radius) {
force += radius - distance;
if (distance < referenceDistance) {
force += (referenceDistance - distance) / referenceDistance;
}
}

return force;
}

double localDensityRatio(std::size_t nearbyCount) {
const auto areaSquareMeters = kPi * kReferenceDistanceMeters * kReferenceDistanceMeters;
const auto densityPeoplePerSquareMeter =
static_cast<double>(nearbyCount) / areaSquareMeters;
return densityPeoplePerSquareMeter / kPressureHighDensityThresholdPeoplePerSquareMeter;
}

} // namespace

CompressionSystem::CompressionSystem(double timeStepSeconds)
Expand All @@ -80,41 +88,42 @@ void CompressionSystem::update(engine::EngineWorld& world,

for (const auto entity : agentEntities) {
const auto& position = query.get<Position>(entity);
const auto& agent = query.get<Agent>(entity);
auto& compression = query.get<CompressionData>(entity);

double currentForce = 0.0;
std::size_t nearbyCount = 0;
double proximityScore = 0.0;

for (const auto otherEntity : agentEntities) {
if (otherEntity == entity) {
continue;
}

const auto& otherPosition = query.get<Position>(otherEntity);
const auto& otherAgent = query.get<Agent>(otherEntity);
const double distance = distanceBetween(position.value, otherPosition.value);
const double combinedRadius = static_cast<double>(agent.radius + otherAgent.radius);

if (distance < combinedRadius) {
currentForce += combinedRadius - distance;
if (distance < kReferenceDistanceMeters) {
proximityScore += (kReferenceDistanceMeters - distance) / kReferenceDistanceMeters;
++nearbyCount;
}
}

double currentForce = std::max(proximityScore, localDensityRatio(nearbyCount));
for (const auto barrierEntity : barrierEntities) {
currentForce += barrierCompression(
query.get<Barrier2D>(barrierEntity),
position.value,
static_cast<double>(agent.radius));
currentForce = std::max(
currentForce,
proximityScore + barrierCompression(
query.get<Barrier2D>(barrierEntity),
position.value,
kReferenceDistanceMeters));
}

compression.force = static_cast<float>(currentForce);
if (compression.force > kForceThreshold) {
if (compression.force >= kPressureCriticalScoreThreshold) {
compression.exposure += timeStepSeconds_;
}

compression.isCritical =
compression.force > kForceThreshold &&
compression.exposure >= kExposureThreshold;
compression.force >= kPressureCriticalScoreThreshold &&
compression.exposure >= kPressureCriticalExposureThresholdSeconds;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/domain/DemoFixtureService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <utility>

#include "domain/DemoLayouts.h"
#include "domain/PressureTuning.h"

namespace safecrowd::domain {
namespace {
Expand Down Expand Up @@ -704,7 +705,8 @@ ScenarioResultArtifacts makeBlockedDoorResultArtifacts(std::vector<SimulationFra
};

artifacts.densitySummary.cellSizeMeters = 1.5;
artifacts.densitySummary.highDensityThresholdPeoplePerSquareMeter = 4.0;
artifacts.densitySummary.highDensityThresholdPeoplePerSquareMeter =
kPressureHighDensityThresholdPeoplePerSquareMeter;
artifacts.densitySummary.peakDensityPeoplePerSquareMeter = 11.1111;
artifacts.densitySummary.peakAgentCount = 25;
artifacts.densitySummary.peakAtSeconds = 0.0;
Expand Down
11 changes: 11 additions & 0 deletions src/domain/PressureTuning.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

namespace safecrowd::domain {

inline constexpr double kPressureHotspotCellSizeMeters = 1.5;
inline constexpr double kPressureReferenceDistanceMeters = 1.0;
inline constexpr double kPressureHighDensityThresholdPeoplePerSquareMeter = 3.55;
inline constexpr float kPressureCriticalScoreThreshold = 1.0f;
inline constexpr float kPressureCriticalExposureThresholdSeconds = 2.0f;

} // namespace safecrowd::domain
6 changes: 3 additions & 3 deletions src/domain/ScenarioRiskMetrics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ const char* scenarioStalledDefinition() noexcept {
}

const char* scenarioHotspotDefinition() noexcept {
return "A hotspot is a 1.5 m by 1.5 m cell containing at least 5 active agents.";
return "A hotspot is a 1.5 m by 1.5 m cell whose density reaches 3.55 occupants per square meter.";
}

const char* scenarioPressureHotspotDefinition() noexcept {
return "A pressure hotspot is a 1.5 m by 1.5 m cell containing at least 5 active agents "
"with overlapping personal-space intrusion between nearby occupants.";
return "A pressure hotspot is a 1.5 m by 1.5 m cell whose density reaches 3.55 occupants "
"per square meter and whose 1.0 m interpersonal-pressure score reaches 1.0.";
}

const char* scenarioBottleneckDefinition() noexcept {
Expand Down
18 changes: 11 additions & 7 deletions src/domain/ScenarioRiskMetrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,32 @@
#include <vector>

#include "domain/Geometry2D.h"
#include "domain/PressureTuning.h"
#include "domain/ScenarioSimulationFrame.h"

namespace safecrowd::domain {

inline constexpr double kScenarioStalledSpeedThreshold = 0.12;
inline constexpr double kScenarioStalledSecondsThreshold = 0.75;
inline constexpr double kScenarioHotspotCellSize = 1.5;
inline constexpr std::size_t kScenarioHotspotAgentThreshold = 5;
inline constexpr std::size_t kScenarioPressureHotspotAgentThreshold = 5;
inline constexpr double kScenarioPressureScoreThreshold = 1.0;
inline constexpr double kScenarioHotspotCellSize = kPressureHotspotCellSizeMeters;
inline constexpr double kScenarioHotspotDensityThresholdPeoplePerSquareMeter =
kPressureHighDensityThresholdPeoplePerSquareMeter;
inline constexpr double kScenarioPressureHotspotDensityThresholdPeoplePerSquareMeter =
kPressureHighDensityThresholdPeoplePerSquareMeter;
inline constexpr double kScenarioPressureScoreThreshold = kPressureCriticalScoreThreshold;
inline constexpr double kScenarioPressureFeedbackForceThreshold = 0.18;
inline constexpr double kScenarioPressureFeedbackExposureRecoveryPerSecond = 1.0;
inline constexpr double kScenarioPressureFeedbackNeighborProbeRadius = 0.9;
inline constexpr double kScenarioPressureFeedbackNeighborProbeRadius = kPressureReferenceDistanceMeters;
inline constexpr std::size_t kScenarioPressureFeedbackDenseNeighborThreshold = 2;
inline constexpr std::uint64_t kScenarioPressureFeedbackCrowdedUpdateDivisor = 2;
inline constexpr std::uint64_t kScenarioPressureFeedbackQuietUpdateDivisor = 3;
inline constexpr double kScenarioPressureFeedbackMaxExposedSlowdown = 0.10;
inline constexpr double kScenarioPressureFeedbackMaxCriticalSlowdown = 0.25;
inline constexpr double kScenarioPressureFeedbackMaxAvoidanceScale = 1.35;
inline constexpr double kScenarioPressureFeedbackMaxBarrierScale = 1.25;
inline constexpr double kScenarioCriticalPressureForceThreshold = 0.5;
inline constexpr double kScenarioCriticalPressureExposureThresholdSeconds = 2.0;
inline constexpr double kScenarioCriticalPressureForceThreshold = kPressureCriticalScoreThreshold;
inline constexpr double kScenarioCriticalPressureExposureThresholdSeconds =
kPressureCriticalExposureThresholdSeconds;
inline constexpr double kScenarioCriticalPressureEventDurationThresholdSeconds = 1.0;
inline constexpr std::size_t kScenarioCriticalPressureEventAgentThreshold = 2;
inline constexpr double kScenarioBottleneckRadius = 1.25;
Expand Down
81 changes: 48 additions & 33 deletions src/domain/ScenarioRiskMetricsSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ constexpr std::size_t kMaxReportedPressureHotspots = 5;
constexpr std::size_t kMaxReportedPressureAgents = 5;
constexpr std::size_t kMaxReportedCriticalPressureEvents = 5;
constexpr std::size_t kMaxReportedBottlenecks = 5;
constexpr double kScenarioPressureScoreThreshold = 1.0;

struct RiskCellAccumulator {
Point2D positionSum{};
Expand Down Expand Up @@ -235,7 +234,7 @@ bool barrierMatchesFloor(const Barrier2D& barrier, const std::string& floorId) {
return barrier.floorId.empty() || floorId.empty() || barrier.floorId == floorId;
}

double barrierCompression(const Barrier2D& barrier, const Point2D& position, double radius) {
double barrierCompression(const Barrier2D& barrier, const Point2D& position, double referenceDistance) {
if (!barrier.blocksMovement || barrier.geometry.vertices.size() < 2) {
return 0.0;
}
Expand All @@ -244,19 +243,27 @@ double barrierCompression(const Barrier2D& barrier, const Point2D& position, dou
const auto& vertices = barrier.geometry.vertices;
for (std::size_t index = 0; index + 1 < vertices.size(); ++index) {
const auto distance = distancePointToSegment(position, vertices[index], vertices[index + 1]);
if (distance < radius) {
force += radius - distance;
if (distance < referenceDistance) {
force += (referenceDistance - distance) / referenceDistance;
}
}
if (barrier.geometry.closed) {
const auto distance = distancePointToSegment(position, vertices.back(), vertices.front());
if (distance < radius) {
force += radius - distance;
if (distance < referenceDistance) {
force += (referenceDistance - distance) / referenceDistance;
}
}
return force;
}

double localDensityRatio(std::size_t nearbyCount) {
constexpr double kPi = 3.14159265358979323846;
const auto areaSquareMeters = kPi * kPressureReferenceDistanceMeters * kPressureReferenceDistanceMeters;
const auto densityPeoplePerSquareMeter =
static_cast<double>(nearbyCount) / areaSquareMeters;
return densityPeoplePerSquareMeter / kPressureHighDensityThresholdPeoplePerSquareMeter;
}

double pressureFeedbackLevel(double compressionForce, double exposureSeconds, bool critical) {
if (critical) {
return 1.0;
Expand Down Expand Up @@ -774,33 +781,40 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
tracking.previousElapsedSeconds = elapsedSeconds;
tracking.hasPreviousElapsedSeconds = true;

std::vector<double> forces(activeAgents.size(), 0.0);
std::vector<double> proximityForces(activeAgents.size(), 0.0);
std::vector<std::size_t> nearbyCounts(activeAgents.size(), 0);
for (std::size_t lhsIndex = 0; lhsIndex < activeAgents.size(); ++lhsIndex) {
for (std::size_t rhsIndex = lhsIndex + 1; rhsIndex < activeAgents.size(); ++rhsIndex) {
if (activeAgents[lhsIndex].floorId != activeAgents[rhsIndex].floorId) {
continue;
}
const auto distance = distanceBetween(activeAgents[lhsIndex].position, activeAgents[rhsIndex].position);
const auto combinedRadius = activeAgents[lhsIndex].radius + activeAgents[rhsIndex].radius;
if (distance >= combinedRadius) {
if (distance >= kPressureReferenceDistanceMeters) {
continue;
}

const auto overlap = combinedRadius - distance;
forces[lhsIndex] += overlap;
forces[rhsIndex] += overlap;
const auto proximityScore =
(kPressureReferenceDistanceMeters - distance) / kPressureReferenceDistanceMeters;
proximityForces[lhsIndex] += proximityScore;
proximityForces[rhsIndex] += proximityScore;
++nearbyCounts[lhsIndex];
++nearbyCounts[rhsIndex];
}
}

std::vector<double> forces(activeAgents.size(), 0.0);
for (std::size_t index = 0; index < activeAgents.size(); ++index) {
forces[index] = std::max(proximityForces[index], localDensityRatio(nearbyCounts[index]));
for (const auto& barrier : layout.barriers) {
if (!barrierMatchesFloor(barrier, activeAgents[index].floorId)) {
continue;
}
forces[index] += barrierCompression(
barrier,
activeAgents[index].position,
activeAgents[index].radius);
forces[index] = std::max(
forces[index],
proximityForces[index] + barrierCompression(
barrier,
activeAgents[index].position,
kPressureReferenceDistanceMeters));
}
}

Expand All @@ -812,14 +826,14 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
activeAgentIds.insert(context.agentId);
auto& state = tracking.agentStates[context.agentId];
state.currentForce = forces[index];
if (state.currentForce > kScenarioCriticalPressureForceThreshold) {
if (state.currentForce >= kScenarioCriticalPressureForceThreshold) {
state.exposureSeconds += deltaSeconds;
}

const bool critical =
state.currentForce > kScenarioCriticalPressureForceThreshold
state.currentForce >= kScenarioCriticalPressureForceThreshold
&& state.exposureSeconds >= kScenarioCriticalPressureExposureThresholdSeconds;
if (state.exposureSeconds > 0.0 || state.currentForce > kScenarioCriticalPressureForceThreshold) {
if (state.exposureSeconds > 0.0 || state.currentForce >= kScenarioCriticalPressureForceThreshold) {
++snapshot.pressureExposedAgentCount;
snapshot.pressureAgents.push_back({
.agentId = context.agentId,
Expand Down Expand Up @@ -863,8 +877,12 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
ScenarioRiskSnapshot& snapshot,
const std::unordered_map<long long, RiskCellAccumulator>& cells) const {
snapshot.hotspots.reserve(cells.size());
const auto hotspotCellArea = kScenarioHotspotCellSize * kScenarioHotspotCellSize;
for (const auto& [_, cell] : cells) {
if (cell.agentCount < kScenarioHotspotAgentThreshold) {
const auto densityPeoplePerSquareMeter = hotspotCellArea <= 0.0
? 0.0
: static_cast<double>(cell.agentCount) / hotspotCellArea;
if (densityPeoplePerSquareMeter < kScenarioHotspotDensityThresholdPeoplePerSquareMeter) {
continue;
}
const auto count = static_cast<double>(cell.agentCount);
Expand Down Expand Up @@ -893,7 +911,10 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
const auto cellArea = kScenarioHotspotCellSize * kScenarioHotspotCellSize;

for (const auto& [_, cell] : cells) {
if (cell.agentCount < kScenarioPressureHotspotAgentThreshold) {
const auto densityPeoplePerSquareMeter = cellArea <= 1e-9
? 0.0
: static_cast<double>(cell.agentCount) / cellArea;
if (densityPeoplePerSquareMeter < kScenarioPressureHotspotDensityThresholdPeoplePerSquareMeter) {
continue;
}

Expand All @@ -902,22 +923,16 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
for (std::size_t lhsIndex = 0; lhsIndex < cell.entities.size(); ++lhsIndex) {
const auto lhsEntity = cell.entities[lhsIndex];
const auto& lhsPosition = query.get<Position>(lhsEntity);
const auto& lhsAgent = query.get<Agent>(lhsEntity);
for (std::size_t rhsIndex = lhsIndex + 1; rhsIndex < cell.entities.size(); ++rhsIndex) {
const auto rhsEntity = cell.entities[rhsIndex];
const auto& rhsPosition = query.get<Position>(rhsEntity);
const auto& rhsAgent = query.get<Agent>(rhsEntity);
const auto comfortDistance =
static_cast<double>(lhsAgent.radius + rhsAgent.radius) + kPersonalSpaceBuffer;
if (comfortDistance <= 1e-9) {
continue;
}
const auto distance = distanceBetween(lhsPosition.value, rhsPosition.value);
if (distance >= comfortDistance) {
if (distance >= kPressureReferenceDistanceMeters) {
continue;
}

pressureScore += (comfortDistance - distance) / comfortDistance;
pressureScore +=
(kPressureReferenceDistanceMeters - distance) / kPressureReferenceDistanceMeters;
++intrudingPairCount;
}
}
Expand All @@ -934,7 +949,7 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
.floorId = cell.floorId,
.agentCount = cell.agentCount,
.intrudingPairCount = intrudingPairCount,
.densityPeoplePerSquareMeter = cellArea <= 1e-9 ? 0.0 : count / cellArea,
.densityPeoplePerSquareMeter = densityPeoplePerSquareMeter,
.pressureScore = pressureScore,
});
}
Expand Down Expand Up @@ -975,9 +990,9 @@ class ScenarioRiskMetricsSystem final : public engine::EngineSystem {
const auto& state = stateIt->second;
const bool exposed =
state.exposureSeconds > 0.0
|| state.currentForce > kScenarioCriticalPressureForceThreshold;
|| state.currentForce >= kScenarioCriticalPressureForceThreshold;
const bool critical =
state.currentForce > kScenarioCriticalPressureForceThreshold
state.currentForce >= kScenarioCriticalPressureForceThreshold
&& state.exposureSeconds >= kScenarioCriticalPressureExposureThresholdSeconds;
if (exposed) {
++exposedAgentCount;
Expand Down
Loading
Loading