Skip to content

Commit 8501ceb

Browse files
95x8x9learncold
andauthored
[Domain] issue 16,17 (#127)
* [Domain] issue 16,17 * 빌드 에러 수정 * 빌드 에러 수정 * 빌드 에러 수정 * 빌드 에러 수정 * 빌드 에러 수정 * [Domain] fix compression snapshot review findings * [Build] fix tests after main merge --------- Co-authored-by: learncold <learncold8154@gmail.com>
1 parent de424ed commit 8501ceb

13 files changed

Lines changed: 640 additions & 2 deletions

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ add_library(safecrowd_domain STATIC
9595
src/domain/ImportValidationService.h
9696
src/domain/ImportValidationService.cpp
9797
src/domain/ImportContracts.h
98+
src/domain/AgentComponents.h
99+
src/domain/CompressionSystem.cpp
100+
src/domain/CompressionSystem.h
101+
src/domain/Metrics.h
102+
src/domain/Snapshot.cpp
103+
src/domain/Snapshot.h
98104
)
99105

100106
target_include_directories(safecrowd_domain
@@ -120,6 +126,8 @@ if (BUILD_TESTING)
120126
tests/EngineRuntimeTests.cpp
121127
tests/PackedComponentStorageTests.cpp
122128
tests/SafeCrowdDomainTests.cpp
129+
tests/CompressionSystemTests.cpp
130+
tests/SnapshotTests.cpp
123131
tests/EcsCoreTests.cpp
124132
tests/ImportContractsTests.cpp
125133
tests/DxfImportServiceTests.cpp

docs/제출용/종합설계/[양식 5] 시스템 구조 설계 및 개발 환경.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,8 @@ Project/
373373
| US-07 인원 배치 기반 실행 제어 | `MainWindow`, `SafeCrowdDomain`, `EngineRuntime::play/pause/stop/stepFrame` | Start/Pause/Stop 중심의 application-domain-engine 연결 |
374374
| US-09 실시간 진행 상태 확인 | `SimulationSummary`, `EngineStats`, `MainWindow::refreshStatusLabel` | 상태, frame index, fixed step index, alpha 표시 |
375375
| US-10 병목·정체 탐지 | `FacilityLayout2D.connections/zones`, 향후 `LocalDensityField`, `FlowMeasurementSystem`, `CongestionStateSystem` | 현재 레이아웃 기반 입력을 마련했고, 위험 탐지 로직의 상세 확장은 `docs/product/고급 위험 모델.md`를 기준으로 설계 |
376-
| US-11 압력 집중 위험 탐지 | 향후 `CompressionForce`, `CompressionExposure`, `CompressionLoadSystem`, `AsphyxiationRiskSystem` | 압박 위험 모델은 도메인 확장 설계 항목으로 정의됨 |
377-
| US-15~US-17 결과 시각화 및 비교 | 향후 Result UI, 히트맵 레이어, 비교 요약 뷰 | 현재 계층 구조는 Sprint 2 비교/시각화 기능을 application layer에 추가할 수 있게 설계됨 |
376+
| US-11 압력 집중 위험 탐지 | `CompressionSystem`, `CompressionData`, 향후 hotspot/zone 집계 | 최소 agent 압박 하중과 누적 노출 계산은 domain system으로 연결됐고, hotspot 집계와 고급 판정은 후속 확장으로 남겨 둠 |
377+
| US-15~US-17 결과 시각화 및 비교 | `SimulationSnapshot`, 향후 Result UI, 히트맵 레이어, 비교 요약 뷰 | live snapshot 읽기 경로는 마련됐고, persisted 결과 뷰와 비교 UI는 application layer 후속 작업으로 유지 |
378378
| US-18~US-19 운영 대안 추천 | 향후 Recommendation module, scenario diff, rationale model | 현재 문서 구조와 위험 지표 정의를 기반으로 Sprint 3에서 연결 예정 |
379379

380380
### Traceability Note

src/domain/AgentComponents.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include "domain/Geometry2D.h"
4+
5+
namespace safecrowd::domain {
6+
7+
struct Position {
8+
Point2D value;
9+
};
10+
11+
struct Agent {
12+
float radius{0.25f};
13+
float maxSpeed{1.5f};
14+
};
15+
16+
struct Velocity {
17+
Point2D value;
18+
};
19+
20+
} // namespace safecrowd::domain

src/domain/CompressionSystem.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#include "domain/CompressionSystem.h"
2+
3+
#include "domain/AgentComponents.h"
4+
#include "domain/FacilityLayout2D.h"
5+
#include "domain/Metrics.h"
6+
7+
#include <algorithm>
8+
#include <cmath>
9+
10+
namespace safecrowd::domain {
11+
namespace {
12+
13+
constexpr float kForceThreshold = 0.5f;
14+
constexpr float kExposureThreshold = 2.0f;
15+
16+
double distanceBetween(const Point2D& lhs, const Point2D& rhs) {
17+
const double dx = lhs.x - rhs.x;
18+
const double dy = lhs.y - rhs.y;
19+
return std::sqrt(dx * dx + dy * dy);
20+
}
21+
22+
double distancePointToSegment(const Point2D& point, const Point2D& start, const Point2D& end) {
23+
const double dx = end.x - start.x;
24+
const double dy = end.y - start.y;
25+
const double lengthSquared = (dx * dx) + (dy * dy);
26+
27+
if (lengthSquared == 0.0) {
28+
return distanceBetween(point, start);
29+
}
30+
31+
const double t = std::clamp(
32+
(((point.x - start.x) * dx) + ((point.y - start.y) * dy)) / lengthSquared,
33+
0.0,
34+
1.0);
35+
const Point2D projection{
36+
.x = start.x + (t * dx),
37+
.y = start.y + (t * dy),
38+
};
39+
return distanceBetween(point, projection);
40+
}
41+
42+
double barrierCompression(const Barrier2D& barrier, const Point2D& position, double radius) {
43+
if (!barrier.blocksMovement || barrier.geometry.vertices.size() < 2) {
44+
return 0.0;
45+
}
46+
47+
double force = 0.0;
48+
const auto& vertices = barrier.geometry.vertices;
49+
50+
for (std::size_t index = 0; index + 1 < vertices.size(); ++index) {
51+
const double distance = distancePointToSegment(position, vertices[index], vertices[index + 1]);
52+
if (distance < radius) {
53+
force += radius - distance;
54+
}
55+
}
56+
57+
if (barrier.geometry.closed) {
58+
const double distance = distancePointToSegment(position, vertices.back(), vertices.front());
59+
if (distance < radius) {
60+
force += radius - distance;
61+
}
62+
}
63+
64+
return force;
65+
}
66+
67+
} // namespace
68+
69+
CompressionSystem::CompressionSystem(double timeStepSeconds)
70+
: timeStepSeconds_(static_cast<float>(std::max(0.0, timeStepSeconds))) {
71+
}
72+
73+
void CompressionSystem::update(engine::EngineWorld& world,
74+
const engine::EngineStepContext& step) {
75+
(void)step;
76+
77+
auto& query = world.query();
78+
const auto agentEntities = query.view<Position, Agent, CompressionData>();
79+
const auto barrierEntities = query.view<Barrier2D>();
80+
81+
for (const auto entity : agentEntities) {
82+
const auto& position = query.get<Position>(entity);
83+
const auto& agent = query.get<Agent>(entity);
84+
auto& compression = query.get<CompressionData>(entity);
85+
86+
double currentForce = 0.0;
87+
88+
for (const auto otherEntity : agentEntities) {
89+
if (otherEntity == entity) {
90+
continue;
91+
}
92+
93+
const auto& otherPosition = query.get<Position>(otherEntity);
94+
const auto& otherAgent = query.get<Agent>(otherEntity);
95+
const double distance = distanceBetween(position.value, otherPosition.value);
96+
const double combinedRadius = static_cast<double>(agent.radius + otherAgent.radius);
97+
98+
if (distance < combinedRadius) {
99+
currentForce += combinedRadius - distance;
100+
}
101+
}
102+
103+
for (const auto barrierEntity : barrierEntities) {
104+
currentForce += barrierCompression(
105+
query.get<Barrier2D>(barrierEntity),
106+
position.value,
107+
static_cast<double>(agent.radius));
108+
}
109+
110+
compression.force = static_cast<float>(currentForce);
111+
if (compression.force > kForceThreshold) {
112+
compression.exposure += timeStepSeconds_;
113+
}
114+
115+
compression.isCritical =
116+
compression.force > kForceThreshold &&
117+
compression.exposure >= kExposureThreshold;
118+
}
119+
}
120+
121+
} // namespace safecrowd::domain

src/domain/CompressionSystem.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#include "engine/EngineSystem.h"
4+
5+
namespace safecrowd::domain {
6+
7+
class CompressionSystem final : public engine::EngineSystem {
8+
public:
9+
explicit CompressionSystem(double timeStepSeconds);
10+
11+
void update(engine::EngineWorld& world,
12+
const engine::EngineStepContext& step) override;
13+
14+
private:
15+
float timeStepSeconds_{0.0f};
16+
};
17+
18+
} // namespace safecrowd::domain

src/domain/Metrics.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
namespace safecrowd::domain {
4+
5+
struct CompressionData {
6+
float force{0.0f};
7+
float exposure{0.0f};
8+
bool isCritical{false};
9+
};
10+
11+
} // namespace safecrowd::domain

src/domain/SafeCrowdDomain.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
#include "domain/SafeCrowdDomain.h"
22

3+
#include <memory>
4+
5+
#include "domain/CompressionSystem.h"
6+
#include "engine/SystemDescriptor.h"
7+
#include "engine/TriggerPolicy.h"
8+
#include "engine/UpdatePhase.h"
9+
310
namespace safecrowd::domain {
411

512
SafeCrowdDomain::SafeCrowdDomain(engine::EngineRuntime& runtime)
613
: runtime_(runtime) {
14+
runtime_.addSystem(
15+
std::make_unique<CompressionSystem>(runtime_.config().fixedDeltaTime),
16+
{
17+
.phase = engine::UpdatePhase::FixedSimulation,
18+
.order = 0,
19+
.triggerPolicy = engine::TriggerPolicy::FixedStep,
20+
});
721
}
822

923
void SafeCrowdDomain::start() {
@@ -32,6 +46,19 @@ SimulationSummary SafeCrowdDomain::summary() const {
3246
};
3347
}
3448

49+
SimulationSnapshot SafeCrowdDomain::snapshot() const {
50+
const auto& stats = runtime_.stats();
51+
const double simulationTime =
52+
(static_cast<double>(stats.fixedStepIndex) + stats.alpha) *
53+
runtime_.config().fixedDeltaTime;
54+
55+
return buildSnapshot(
56+
runtime_.world().query(),
57+
stats.frameIndex,
58+
stats.fixedStepIndex,
59+
simulationTime);
60+
}
61+
3562
engine::EngineRuntime& SafeCrowdDomain::runtime() noexcept {
3663
return runtime_;
3764
}

src/domain/SafeCrowdDomain.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cstdint>
44

5+
#include "domain/Snapshot.h"
56
#include "engine/EngineRuntime.h"
67
#include "engine/EngineState.h"
78

@@ -24,6 +25,7 @@ class SafeCrowdDomain {
2425
void update(double deltaSeconds);
2526

2627
SimulationSummary summary() const;
28+
SimulationSnapshot snapshot() const;
2729
engine::EngineRuntime& runtime() noexcept;
2830
const engine::EngineRuntime& runtime() const noexcept;
2931

src/domain/Snapshot.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include "domain/Snapshot.h"
2+
3+
#include "domain/AgentComponents.h"
4+
#include "domain/Metrics.h"
5+
#include "engine/Entity.h"
6+
#include "engine/WorldQuery.h"
7+
8+
namespace safecrowd::domain {
9+
namespace {
10+
11+
std::uint64_t packEntityId(engine::Entity entity) {
12+
return (static_cast<std::uint64_t>(entity.generation) << 32U) |
13+
static_cast<std::uint64_t>(entity.index);
14+
}
15+
16+
bool hasCompressionMetricsForAllAgents(const engine::WorldQuery& query,
17+
const std::vector<engine::Entity>& agentEntities) {
18+
if (agentEntities.empty()) {
19+
return false;
20+
}
21+
22+
for (const auto entity : agentEntities) {
23+
if (!query.contains<CompressionData>(entity)) {
24+
return false;
25+
}
26+
}
27+
28+
return true;
29+
}
30+
31+
} // namespace
32+
33+
const SnapshotScalarChannel* SimulationSnapshot::findScalarChannel(std::string_view key) const noexcept {
34+
for (const auto& channel : scalarChannels) {
35+
if (channel.key == key) {
36+
return &channel;
37+
}
38+
}
39+
40+
return nullptr;
41+
}
42+
43+
const SnapshotFlagChannel* SimulationSnapshot::findFlagChannel(std::string_view key) const noexcept {
44+
for (const auto& channel : flagChannels) {
45+
if (channel.key == key) {
46+
return &channel;
47+
}
48+
}
49+
50+
return nullptr;
51+
}
52+
53+
SimulationSnapshot buildSnapshot(const engine::WorldQuery& query,
54+
std::uint64_t frame,
55+
std::uint64_t fixedStep,
56+
double simulationTime) {
57+
SimulationSnapshot snapshot;
58+
snapshot.frameIndex = frame;
59+
snapshot.fixedStepIndex = fixedStep;
60+
snapshot.simulationTime = simulationTime;
61+
62+
const auto agentEntities = query.view<Position, Agent>();
63+
snapshot.agentCount = static_cast<std::uint32_t>(agentEntities.size());
64+
snapshot.agentIds.reserve(snapshot.agentCount);
65+
snapshot.positions.reserve(snapshot.agentCount);
66+
67+
for (const auto entity : agentEntities) {
68+
snapshot.agentIds.push_back(packEntityId(entity));
69+
snapshot.positions.push_back(query.get<Position>(entity).value);
70+
}
71+
72+
if (!hasCompressionMetricsForAllAgents(query, agentEntities)) {
73+
return snapshot;
74+
}
75+
76+
SnapshotScalarChannel forceChannel{std::string(kCompressionForceChannelName), {}};
77+
SnapshotScalarChannel exposureChannel{std::string(kCompressionExposureChannelName), {}};
78+
SnapshotFlagChannel criticalChannel{std::string(kCompressionCriticalChannelName), {}};
79+
80+
forceChannel.values.reserve(snapshot.agentCount);
81+
exposureChannel.values.reserve(snapshot.agentCount);
82+
criticalChannel.values.reserve(snapshot.agentCount);
83+
84+
for (const auto entity : agentEntities) {
85+
const auto& metrics = query.get<CompressionData>(entity);
86+
forceChannel.values.push_back(metrics.force);
87+
exposureChannel.values.push_back(metrics.exposure);
88+
criticalChannel.values.push_back(metrics.isCritical ? 1U : 0U);
89+
}
90+
91+
snapshot.scalarChannels.push_back(std::move(forceChannel));
92+
snapshot.scalarChannels.push_back(std::move(exposureChannel));
93+
snapshot.flagChannels.push_back(std::move(criticalChannel));
94+
95+
return snapshot;
96+
}
97+
98+
} // namespace safecrowd::domain

0 commit comments

Comments
 (0)