Skip to content

Commit 4b749bb

Browse files
[Domain] Add two-floor evacuation demo map
1 parent b14212f commit 4b749bb

9 files changed

Lines changed: 692 additions & 11 deletions

docs/demo/시연 평면 가이드.md

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

33
Sprint 1 시연에서 사용할 도면 자산과 각 도면이 강조하는 결과 지표를 정리합니다.
44
도면 파일은 [`assets/demo-layouts/`](../../assets/demo-layouts/) 에 있습니다.
5+
앱 시작 화면의 built-in 프로젝트에는 코드 fixture 기반의 `Two-floor Evacuation Demo`도 포함됩니다.
56

67
## 시연 흐름 요약
78

@@ -47,6 +48,16 @@ Sprint 1 시연에서 사용할 도면 자산과 각 도면이 강조하는 결
4748
- `DensitySummary.peakField` 의 피크 셀이 복도 중앙 또는 로비 출구 직전에 형성
4849
- **보여줄 화면**: 존별 완료 시간 표, 출구 사용 막대그래프, 복도/로비 영역의 밀도 히트맵, 리플레이로 군중이 복도를 통해 모이는 흐름
4950

51+
### 4. 앱 내장 `Two-floor Evacuation Demo` — 2층 대피 시나리오 시연
52+
53+
- **구조**: 2층의 서측 교육실, 중앙 브리핑룸, 동측 교육실에서 복도로 나온 뒤 양쪽 U자형 계단을 통해 1층 로비와 전실을 거쳐 서측/동측 출구로 대피하는 내장 layout.
54+
- **메시지**: "다층 평면에서 층 선택, 계단 전환, 출구 유도 시나리오가 앱에서 바로 실행되는가."
55+
- **강조 지표**:
56+
- 2층 보행자가 계단 연결을 지나 1층 출구로 이동
57+
- baseline과 우측 출구 유도 alternative를 같은 run 화면에서 실행 가능
58+
- 결과 화면의 floor selector로 1층/2층 상태를 나누어 확인 가능
59+
- **보여줄 화면**: Project Navigator에서 `Two-floor Evacuation Demo` 열기, Run Workspace 실행, Result Summary의 층별 canvas와 출구 사용 지표
60+
5061
## 시연 셋업 권장값 (참고)
5162

5263
| 항목 | 권장 |

src/application/MainWindow.cpp

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,9 @@ void applySavedReviewState(const ProjectMetadata& metadata, safecrowd::domain::I
3535
ProjectPersistence::loadProjectReview(metadata, importResult);
3636
}
3737

38-
safecrowd::domain::ImportResult makeDemoImportResult() {
39-
safecrowd::domain::DemoFixtureService fixtureService;
40-
const auto fixture = fixtureService.createSprint1DemoFixture();
41-
38+
safecrowd::domain::ImportResult makeImportResultForDemoLayout(safecrowd::domain::FacilityLayout2D layout) {
4239
safecrowd::domain::ImportResult result;
43-
result.layout = fixture.layout;
40+
result.layout = std::move(layout);
4441

4542
safecrowd::domain::ImportValidationService validator;
4643
result.issues = validator.validate(*result.layout);
@@ -50,6 +47,18 @@ safecrowd::domain::ImportResult makeDemoImportResult() {
5047
return result;
5148
}
5249

50+
safecrowd::domain::ImportResult makeSprint1DemoImportResult() {
51+
safecrowd::domain::DemoFixtureService fixtureService;
52+
const auto fixture = fixtureService.createSprint1DemoFixture();
53+
return makeImportResultForDemoLayout(fixture.layout);
54+
}
55+
56+
safecrowd::domain::ImportResult makeTwoFloorEvacuationDemoImportResult() {
57+
safecrowd::domain::DemoFixtureService fixtureService;
58+
const auto fixture = fixtureService.createTwoFloorEvacuationDemoFixture();
59+
return makeImportResultForDemoLayout(fixture.layout);
60+
}
61+
5362
ProjectWorkspaceState makeEvacuationScenarioDemoWorkspace() {
5463
using namespace safecrowd::domain;
5564

@@ -90,6 +99,35 @@ ProjectWorkspaceState makeEvacuationScenarioDemoWorkspace() {
9099
return workspace;
91100
}
92101

102+
ProjectWorkspaceState makeTwoFloorEvacuationDemoWorkspace() {
103+
using namespace safecrowd::domain;
104+
105+
safecrowd::domain::DemoFixtureService fixtureService;
106+
auto fixture = fixtureService.createTwoFloorEvacuationDemoFixture();
107+
108+
SavedScenarioAuthoringState authoring;
109+
authoring.scenarios.push_back({
110+
.draft = fixture.baselineScenario,
111+
.baseScenarioId = {},
112+
.stagedForRun = true,
113+
});
114+
authoring.scenarios.push_back({
115+
.draft = fixture.alternativeScenario,
116+
.baseScenarioId = fixture.baselineScenario.scenarioId,
117+
.stagedForRun = true,
118+
});
119+
authoring.currentScenarioIndex = 1;
120+
authoring.navigationView = SavedNavigationView::Events;
121+
authoring.rightPanelMode = SavedRightPanelMode::Scenario;
122+
123+
ProjectWorkspaceState workspace;
124+
workspace.activeView = ProjectWorkspaceView::ScenarioRun;
125+
workspace.authoring = std::move(authoring);
126+
workspace.runningScenario = fixture.alternativeScenario;
127+
workspace.runningScenarios = {fixture.baselineScenario, fixture.alternativeScenario};
128+
return workspace;
129+
}
130+
93131
safecrowd::domain::ImportResult makeBlankImportResult(const QString& projectName) {
94132
safecrowd::domain::ImportResult result;
95133
result.layout = safecrowd::domain::FacilityLayout2D{
@@ -111,8 +149,11 @@ safecrowd::domain::ImportResult makeBlankImportResult(const QString& projectName
111149
}
112150

113151
safecrowd::domain::ImportResult importProjectLayout(const ProjectMetadata& metadata) {
114-
if (metadata.isBuiltInDemo()) {
115-
return makeDemoImportResult();
152+
if (metadata.isBuiltInTwoFloorEvacuationDemo()) {
153+
return makeTwoFloorEvacuationDemoImportResult();
154+
}
155+
if (metadata.layoutPath == builtInDemoLayoutPath() || metadata.isBuiltInEvacuationScenarioDemo()) {
156+
return makeSprint1DemoImportResult();
116157
}
117158
if (metadata.isBlankLayoutProject()) {
118159
return makeBlankImportResult(metadata.name);
@@ -404,6 +445,8 @@ void MainWindow::openProject(const ProjectMetadata& metadata) {
404445
ProjectWorkspaceState workspace;
405446
if (metadata.isBuiltInEvacuationScenarioDemo()) {
406447
workspace = makeEvacuationScenarioDemoWorkspace();
448+
} else if (metadata.isBuiltInTwoFloorEvacuationDemo()) {
449+
workspace = makeTwoFloorEvacuationDemoWorkspace();
407450
} else if (!ProjectPersistence::loadProjectWorkspace(metadata, &workspace)) {
408451
showLayoutReview(metadata, std::move(importResult));
409452
return;

src/application/ProjectMetadata.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,30 @@ inline QString builtInEvacuationScenarioDemoLayoutPath() {
1212
return QStringLiteral("safecrowd://demo/evacuation-scenario");
1313
}
1414

15+
inline QString builtInTwoFloorEvacuationDemoLayoutPath() {
16+
return QStringLiteral("safecrowd://demo/two-floor-evacuation");
17+
}
18+
1519
struct ProjectMetadata {
1620
QString name{};
1721
QString folderPath{};
1822
QString layoutPath{};
1923
QString savedAt{};
2024

2125
bool isBuiltInDemo() const noexcept {
22-
return layoutPath == builtInDemoLayoutPath() || isBuiltInEvacuationScenarioDemo();
26+
return layoutPath == builtInDemoLayoutPath()
27+
|| isBuiltInEvacuationScenarioDemo()
28+
|| isBuiltInTwoFloorEvacuationDemo();
2329
}
2430

2531
bool isBuiltInEvacuationScenarioDemo() const noexcept {
2632
return layoutPath == builtInEvacuationScenarioDemoLayoutPath();
2733
}
2834

35+
bool isBuiltInTwoFloorEvacuationDemo() const noexcept {
36+
return layoutPath == builtInTwoFloorEvacuationDemoLayoutPath();
37+
}
38+
2939
bool isBlankLayoutProject() const noexcept {
3040
return layoutPath.isEmpty();
3141
}
@@ -52,4 +62,11 @@ inline ProjectMetadata makeBuiltInEvacuationScenarioDemoProject() {
5262
};
5363
}
5464

65+
inline ProjectMetadata makeBuiltInTwoFloorEvacuationDemoProject() {
66+
return {
67+
.name = QStringLiteral("Two-floor Evacuation Demo"),
68+
.layoutPath = builtInTwoFloorEvacuationDemoLayoutPath(),
69+
};
70+
}
71+
5572
} // namespace safecrowd::application

src/application/ProjectPersistence.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,7 @@ QList<ProjectMetadata> ProjectPersistence::loadRecentProjects() {
16211621
QList<ProjectMetadata> projects;
16221622
projects.push_back(makeBuiltInDemoProject());
16231623
projects.push_back(makeBuiltInEvacuationScenarioDemoProject());
1624+
projects.push_back(makeBuiltInTwoFloorEvacuationDemoProject());
16241625

16251626
const auto document = readJsonDocument(recentProjectsPath());
16261627
if (!document.isObject()) {

src/domain/DemoFixtureService.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ ScenarioDraft makeSprint1BlockedDoorAlternative(const ScenarioDraft& baseline) {
2222
return alternative;
2323
}
2424

25+
ScenarioDraft makeTwoFloorEastExitGuidanceAlternative(const ScenarioDraft& baseline) {
26+
using Ids = DemoLayouts::TwoFloorEvacuationIds;
27+
28+
auto alternative = duplicateScenarioDraft(
29+
baseline,
30+
"two-floor-guidance",
31+
"East exit guidance alternative");
32+
alternative.control.routeGuidances.push_back({
33+
.id = "guidance-east-exit",
34+
.startSeconds = 0.0,
35+
.endSeconds = 180.0,
36+
.periods = {{.startSeconds = 0.0, .endSeconds = 180.0}},
37+
.guidedExitZoneId = Ids::EastExitZoneId,
38+
.installConnectionId = Ids::UpperCorridorEastStairConnectionId,
39+
.baseComplianceRate = 0.85,
40+
.guidanceStrength = 0.85,
41+
.maxDetourMeters = 30.0,
42+
});
43+
alternative.variationDiffKeys = computeScenarioDiffKeys(baseline, alternative);
44+
return alternative;
45+
}
46+
2547
DensityCellMetric densityCell(
2648
double centerX,
2749
double centerY,
@@ -779,6 +801,70 @@ DemoFixture DemoFixtureService::createSprint1DemoFixture() const {
779801
return fixture;
780802
}
781803

804+
DemoAuthoringFixture DemoFixtureService::createTwoFloorEvacuationDemoFixture() const {
805+
using Ids = DemoLayouts::TwoFloorEvacuationIds;
806+
807+
DemoAuthoringFixture fixture;
808+
fixture.layout = DemoLayouts::twoFloorEvacuationFacility();
809+
810+
fixture.population.initialPlacements.push_back({
811+
.id = "two-floor-west-training-crowd",
812+
.zoneId = Ids::UpperWestTrainingZoneId,
813+
.floorId = Ids::UpperFloorId,
814+
.area = {
815+
.outline = {
816+
{2.0, 2.0},
817+
{8.5, 2.0},
818+
{8.5, 5.0},
819+
{2.0, 5.0},
820+
},
821+
},
822+
.targetAgentCount = 30,
823+
});
824+
fixture.population.initialPlacements.push_back({
825+
.id = "two-floor-briefing-crowd",
826+
.zoneId = Ids::UpperBriefingZoneId,
827+
.floorId = Ids::UpperFloorId,
828+
.area = {
829+
.outline = {
830+
{11.0, 2.0},
831+
{17.0, 2.0},
832+
{17.0, 5.0},
833+
{11.0, 5.0},
834+
},
835+
},
836+
.targetAgentCount = 20,
837+
});
838+
fixture.population.initialPlacements.push_back({
839+
.id = "two-floor-east-training-crowd",
840+
.zoneId = Ids::UpperEastTrainingZoneId,
841+
.floorId = Ids::UpperFloorId,
842+
.area = {
843+
.outline = {
844+
{19.5, 2.0},
845+
{26.0, 2.0},
846+
{26.0, 5.0},
847+
{19.5, 5.0},
848+
},
849+
},
850+
.targetAgentCount = 30,
851+
});
852+
853+
fixture.baselineScenario.scenarioId = "two-floor-baseline";
854+
fixture.baselineScenario.name = "Two-floor evacuation baseline";
855+
fixture.baselineScenario.role = ScenarioRole::Baseline;
856+
fixture.baselineScenario.population = fixture.population;
857+
fixture.baselineScenario.execution.timeLimitSeconds = 600.0;
858+
fixture.baselineScenario.execution.sampleIntervalSeconds = 1.0;
859+
fixture.baselineScenario.execution.repeatCount = 1;
860+
fixture.baselineScenario.execution.baseSeed = 42;
861+
fixture.baselineScenario.sourceTemplateId = "two-floor-evacuation-baseline";
862+
863+
fixture.alternativeScenario = makeTwoFloorEastExitGuidanceAlternative(fixture.baselineScenario);
864+
865+
return fixture;
866+
}
867+
782868
DemoScenarioResultFixture DemoFixtureService::createSprint1BlockedDoorResultFixture() const {
783869
const auto baselineFixture = createSprint1DemoFixture();
784870

src/domain/DemoFixtureService.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ struct DemoFixture {
1515
ScenarioDraft baselineScenario{};
1616
};
1717

18+
struct DemoAuthoringFixture {
19+
FacilityLayout2D layout{};
20+
PopulationSpec population{};
21+
ScenarioDraft baselineScenario{};
22+
ScenarioDraft alternativeScenario{};
23+
};
24+
1825
struct DemoScenarioResultFixture {
1926
FacilityLayout2D layout{};
2027
PopulationSpec population{};
@@ -28,6 +35,7 @@ struct DemoScenarioResultFixture {
2835
class DemoFixtureService {
2936
public:
3037
DemoFixture createSprint1DemoFixture() const;
38+
DemoAuthoringFixture createTwoFloorEvacuationDemoFixture() const;
3139
DemoScenarioResultFixture createSprint1BlockedDoorResultFixture() const;
3240
};
3341

0 commit comments

Comments
 (0)