Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
ModernEnvironment.cpp
Go to the documentation of this file.
2
3#include "Display.hpp"
5
6#include <cstdlib>
7#include <atomic>
8#include <charconv>
9#include <thread>
10#include <algorithm>
11#include <cmath>
12#include <iostream>
13#include <limits>
14#include <new>
15#include <string>
16#include <utility>
17
18namespace monopoly::engine
19{
20 namespace
21 {
22 constexpr std::array<ModernEnvironmentDefinition, ModernEnvironmentCount> Definitions{{
23 {"fountain", "environment/paris_fountain.glb",
25 {0.0F, 0.12F, -2.0F}, -90.0F},
26 {"station", "environment/paris_station.glb",
28 {-5.5F, 0.12F, -5.9F}, -90.0F},
29 {"morris", "environment/paris_morris_column.glb",
31 {3.9F, 0.0F, -4.1F}, -90.0F}
32 }};
33 constexpr std::array<ModernEnvironmentDefinition, 2> PresentationDefinitions{{
34 {"table", "presentation/table.glb", data::packDataId(ModernEnvironmentLogicalGroup, 3),
35 ModernEnvironmentNodeBase | 4U, {0,0,0}, 0.0F},
36 {"plinth", "presentation/plinth.glb", data::packDataId(ModernEnvironmentLogicalGroup, 4),
37 ModernEnvironmentNodeBase | 5U, {0,0,0}, 0.0F}
38 }};
39 // Global centred runtime coordinates, exported in independent groups
40 // from the recovered authoring scene. No retail root or rule owns them.
41 constexpr std::array<ModernEnvironmentDefinition, ModernCityCount> CityDefinitions{{
42 {"city front a", "environment/procedural_city_buildings_front_a.glb", data::packDataId(ModernEnvironmentLogicalGroup,5), ModernEnvironmentNodeBase|6U, {0,0,0},0},
43 {"city front b", "environment/procedural_city_buildings_front_b.glb", data::packDataId(ModernEnvironmentLogicalGroup,6), ModernEnvironmentNodeBase|7U, {0,0,0},0},
44 {"city back", "environment/procedural_city_buildings_back.glb", data::packDataId(ModernEnvironmentLogicalGroup,7), ModernEnvironmentNodeBase|8U, {0,0,0},0},
45 {"city east", "environment/procedural_city_buildings_east.glb", data::packDataId(ModernEnvironmentLogicalGroup,8), ModernEnvironmentNodeBase|9U, {0,0,0},0},
46 {"city west a", "environment/procedural_city_buildings_west_a.glb", data::packDataId(ModernEnvironmentLogicalGroup,9), ModernEnvironmentNodeBase|10U, {0,0,0},0},
47 {"city west b", "environment/procedural_city_buildings_west_b.glb", data::packDataId(ModernEnvironmentLogicalGroup,10), ModernEnvironmentNodeBase|11U, {0,0,0},0},
48 {"city skyline", "environment/procedural_city_skyline.glb", data::packDataId(ModernEnvironmentLogicalGroup,11), ModernEnvironmentNodeBase|12U, {0,0,0},0},
49 {"city street furniture", "environment/procedural_city_street_furniture.glb", data::packDataId(ModernEnvironmentLogicalGroup,12), ModernEnvironmentNodeBase|13U, {0,0,0},0},
50 {"city trees a", "environment/procedural_city_trees_a.glb", data::packDataId(ModernEnvironmentLogicalGroup,13), ModernEnvironmentNodeBase|14U, {0,0,0},0},
51 {"city trees b", "environment/procedural_city_trees_b.glb", data::packDataId(ModernEnvironmentLogicalGroup,14), ModernEnvironmentNodeBase|15U, {0,0,0},0},
52 {"city trees c", "environment/procedural_city_trees_c.glb", data::packDataId(ModernEnvironmentLogicalGroup,15), ModernEnvironmentNodeBase|16U, {0,0,0},0},
53 {"city trees d", "environment/procedural_city_trees_d.glb", data::packDataId(ModernEnvironmentLogicalGroup,16), ModernEnvironmentNodeBase|17U, {0,0,0},0},
54 {"city people and vehicles", "environment/procedural_city_people_vehicles.glb", data::packDataId(ModernEnvironmentLogicalGroup,17), ModernEnvironmentNodeBase|18U, {0,0,0},0},
55 {"city prison", "environment/procedural_city_prison.glb", data::packDataId(ModernEnvironmentLogicalGroup,18), ModernEnvironmentNodeBase|19U, {0,0,0},0},
56 {"city grand hotel", "environment/procedural_city_grand_hotel.glb", data::packDataId(ModernEnvironmentLogicalGroup,19), ModernEnvironmentNodeBase|20U, {0,0,0},0},
57 {"city landmarks", "environment/procedural_city_landmarks.glb", data::packDataId(ModernEnvironmentLogicalGroup,20), ModernEnvironmentNodeBase|21U, {0,0,0},0},
58 {"city red hotel", "environment/procedural_city_red_hotel.glb", data::packDataId(ModernEnvironmentLogicalGroup,21), ModernEnvironmentNodeBase|22U, {0,0,0},0}
59 }};
60 const ModernEnvironmentDefinition& definitionAt(std::size_t index)
61 {
62 if(index < Definitions.size()) return Definitions[index];
63 index -= Definitions.size();
66 }
67 constexpr float SourceUnitsPerMetre = 200.0F;
68 constexpr float BoardOrigin = 2430.0F;
69
70 bool validGeometry(const data::MeshRenderData& mesh) noexcept
71 {
72 if (mesh.vertices.empty() || mesh.indices.empty() || mesh.batches.empty() ||
73 mesh.indices.size() % 3U != 0U) return false;
74 for (std::size_t axis = 0; axis < 3; ++axis)
75 if (!std::isfinite(mesh.bounds.minimum[axis]) ||
76 !std::isfinite(mesh.bounds.maximum[axis]) ||
77 mesh.bounds.minimum[axis] > mesh.bounds.maximum[axis]) return false;
78 for (const auto& vertex : mesh.vertices)
79 for (std::size_t axis = 0; axis < 3; ++axis)
80 if (!std::isfinite(vertex.position[axis]) ||
81 !std::isfinite(vertex.normal[axis])) return false;
82 for (const auto index : mesh.indices)
83 if (index >= mesh.vertices.size()) return false;
84 for (const auto& batch : mesh.batches)
85 if (batch.material.model != data::MeshMaterialModel::MetallicRoughness ||
86 !batch.indexCount || batch.indexCount % 3U != 0U ||
87 batch.firstIndex > mesh.indices.size() ||
88 batch.indexCount > mesh.indices.size() - batch.firstIndex) return false;
89 return true;
90 }
91 }
92
93 const std::array<ModernEnvironmentDefinition, ModernEnvironmentCount>&
96
99 : root_(std::move(modernAssetsRoot)), enabled_(enabled),
100 diagnostic_(std::move(diagnostic)), loader_(std::move(loader))
101 {
102 const auto hardware=std::thread::hardware_concurrency();
103 decodeWorkers_=hardware ? std::clamp(hardware/2,1U,8U) : 4U;
104 if (loader_) decodeWorkers_ = 1;
105 else if (const auto* value = std::getenv("MONOPOLY_ENVIRONMENT_WORKERS"))
106 {
107 unsigned workers{};
108 const std::string_view text{value};
109 const auto parsed = std::from_chars(text.data(),text.data()+text.size(),workers);
110 if (parsed.ec==std::errc{} && parsed.ptr==text.data()+text.size() &&
111 (workers==1 || workers==4 || workers==8)) decodeWorkers_=workers;
112 }
113 }
114
115 void ModernEnvironment::report(std::string_view message) const
116 {
117 if (diagnostic_) diagnostic_(message);
118 else std::cerr << "Modern environment: " << message << '\n';
119 }
120
121 void ModernEnvironment::loadPending(const std::vector<std::size_t>& indices)
122 {
123 struct Result { std::shared_ptr<const data::MeshRuntimeAsset> asset; bool failed{}; std::string error; };
124 std::vector<std::size_t> pending;
125 for(const auto index:indices)
126 if(!attempted_[index]) pending.push_back(index);
127 if(pending.empty()) return;
128 std::vector<Result> results(pending.size());
129 // Complete bookkeeping allocations before recording any attempt.
130 for(const auto index:pending) attempted_[index]=true;
131 std::atomic<std::size_t> next{};
132 const auto decode=[&]() noexcept
133 {
134 for(;;)
135 {
136 const auto slot=next.fetch_add(1,std::memory_order_relaxed);
137 if(slot>=pending.size()) return;
138 const auto& definition=definitionAt(pending[slot]);
139 auto& result=results[slot];
140 try
141 {
142 data::ModernGltfLoadOptions options;
143 options.unitsPerMeter=SourceUnitsPerMetre;
144 options.yawDegrees=definition.localYawDegrees;
145 options.localOffset={};options.groundToZero=false;
146 const auto path=root_/definition.relativeGlbPath;
147 auto loaded=loader_ ? loader_(path,options) : data::loadModernGltfMesh(path,options);
148 if(!loaded || !*loaded || !validGeometry(**loaded))
149 {
150 result.failed=true;
151 result.error=std::string(definition.slug)+": skipped "+
152 (loaded ? "invalid native geometry" : loaded.error().detail);
153 continue;
154 }
155 auto asset=std::make_shared<data::MeshRuntimeAsset>();
156 asset->dataId=definition.logicalId;asset->origin=data::MeshAssetOrigin::ModernGltf;
157 asset->renderData=std::move(*loaded);result.asset=std::move(asset);
158 }
159 catch(const std::exception& exception)
160 {
161 result.failed=true;
162 // Diagnostics are optional: even their allocation failure
163 // must never escape a worker and terminate the process.
164 try { result.error=std::string(definition.slug)+": skipped decode exception: "+exception.what(); }
165 catch(...) {}
166 }
167 catch(...)
168 {
169 result.failed=true;
170 try { result.error=std::string(definition.slug)+": skipped unknown decode exception"; }
171 catch(...) {}
172 }
173 }
174 };
175 // Workers only write their isolated result slot. Maps, callbacks and
176 // scene order publish on the calling thread after every worker joins.
177 const auto workers=std::min<std::size_t>(decodeWorkers_,pending.size());
178 std::vector<std::jthread> threads;
179 try
180 {
181 threads.reserve(workers>0?workers-1:0);
182 for(std::size_t worker=1;worker<workers;++worker) threads.emplace_back(decode);
183 }
184 catch(const std::exception&) { /* remaining work runs on caller */ }
185 decode();threads.clear();
186 for(std::size_t slot=0;slot<pending.size();++slot)
187 {
188 assets_[pending[slot]]=std::move(results[slot].asset);
189 if(results[slot].failed)
190 report(results[slot].error.empty() ? std::string_view{"skipped environment decode failure; diagnostic unavailable"} : std::string_view{results[slot].error});
191 }
192 }
193
195 {
196 if (!geometry) return;
197 bool dropped{};
198 for (std::size_t index = 0; index < assets_.size(); ++index)
199 if (assets_[index] && assets_[index]->renderData.get() == geometry)
200 {
201 assets_[index].reset();
202 attempted_[index] = true;
203 dropped = true;
204 }
205 if (dropped) report("dropped rejected native GPU geometry");
206 }
207
208 std::vector<sequence::SequenceMeshRenderItem> ModernEnvironment::items(
209 const sequence::Matrix3D& boardMatrix, std::uint32_t tick, bool includePresentation,
210 bool includeLandmarks, bool includeCity)
211 {
212 std::vector<sequence::SequenceMeshRenderItem> result;
213 if (!enabled_) return result;
214 if (!std::all_of(boardMatrix.values.begin(), boardMatrix.values.end(),
215 [](float value) { return std::isfinite(value); }))
216 {
217 if (!invalidMatrixReported_)
218 {
219 invalidMatrixReported_ = true;
220 report("skipped invalid board transform");
221 }
222 return result;
223 }
224 const auto count = Definitions.size() + PresentationDefinitions.size() +
225 (includeCity ? CityDefinitions.size() : 0);
226 result.reserve(count);
227 std::vector<std::size_t> selected;
228 for (std::size_t index = includeLandmarks ? 0 : Definitions.size(); index < count; ++index)
229 {
230 // Complete city groups include these three source landmarks in
231 // their global placement, so never duplicate the local prototypes.
232 if(includeCity && index < Definitions.size()) continue;
233 if(index >= Definitions.size() && index < Definitions.size()+PresentationDefinitions.size() &&
234 !includePresentation) continue;
235 selected.push_back(index);
236 }
237 loadPending(selected);
238 for (const auto index:selected)
239 {
240 if (!assets_[index]) continue;
241 const auto& definition = definitionAt(index);
242 const auto& position = definition.originalYup;
243 const auto local = sequence::translate3D(
244 BoardOrigin - position[2] * SourceUnitsPerMetre,
245 position[1] * SourceUnitsPerMetre,
246 BoardOrigin - position[0] * SourceUnitsPerMetre);
248 item.node = definition.node;
249 item.contentsDataId = definition.logicalId;
250 // No retail sequence root owns these native scene items.
253 item.clock = static_cast<std::int32_t>(std::min(tick,
254 static_cast<std::uint32_t>(std::numeric_limits<std::int32_t>::max())));
255 // Row vectors: local placement precedes the actual board transform.
256 // Reversing these operands would leave the 2430 origin unscaled.
258 item.asset = assets_[index];
259 item.renderData = assets_[index]->renderData;
260 result.push_back(std::move(item));
261 }
262 return result;
263 }
264}
std::size_t count
std::vector< std::uint8_t > data
std::string_view text
bool enabled
Definition Engine.cpp:170
PlayerNumber slot
unsigned index
std::string path
std::string error
std::uint16_t value
Definition Timers.cpp:16
std::function< std::expected< std::shared_ptr< const data::MeshRenderData >, data::MeshRuntimeError >(const std::filesystem::path &, data::ModernGltfLoadOptions)> Loader
std::function< void(std::string_view)> Diagnostic
std::vector< sequence::SequenceMeshRenderItem > items(const sequence::Matrix3D &boardMatrix, std::uint32_t tick, bool includePresentation=false, bool includeLandmarks=true, bool includeCity=false)
void rejectGeometry(const data::MeshRenderData *geometry)
bool decode(std::span< const std::uint8_t > data, State &result) noexcept
constexpr DataId EmptyDataId
Definition DataBanks.hpp:13
std::expected< std::shared_ptr< const MeshRenderData >, MeshRuntimeError > loadModernGltfMesh(const std::filesystem::path &path, ModernGltfLoadOptions options)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
constexpr std::uint16_t Board3DPriority
Definition Display.hpp:39
const std::array< ModernEnvironmentDefinition, ModernEnvironmentCount > & modernEnvironmentDefinitions() noexcept
constexpr sequence::SequenceNodeId ModernEnvironmentNodeBase
constexpr std::uint16_t ModernEnvironmentLogicalGroup
const SquareDefinition & definition(SquareType square)
Matrix3D translate3D(float x, float y, float z) noexcept
Matrix2D multiply(const Matrix2D &left, const Matrix2D &right) noexcept
std::size_t size()
std::shared_ptr< const data::MeshRuntimeAsset > asset
std::shared_ptr< const data::MeshRenderData > renderData