Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
MeshRuntime.cpp
Go to the documentation of this file.
1#include "MeshRuntime.hpp"
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <utility>
7
8namespace monopoly::data
9{
10 namespace
11 {
12 constexpr float FixedNormalScale = 4096.0F;
13
14 MeshRuntimeError runtimeError(MeshRuntimeErrorCode code, std::string detail)
15 {
16 MeshRuntimeError result;
17 result.code = code;
18 result.detail = std::move(detail);
19 return result;
20 }
21
22 MeshMaterial materialFrom(std::uint32_t raw)
23 {
24 MeshMaterial result;
26 result.rawDiffuse = raw;
27 result.diffuse = {
28 static_cast<float>(raw & 0xFFU) / 255.0F,
29 static_cast<float>((raw >> 8U) & 0xFFU) / 255.0F,
30 static_cast<float>((raw >> 16U) & 0xFFU) / 255.0F,
31 1.0F
32 };
33 return result;
34 }
35
36 MeshVertex vertexFrom(const HmdTriangle& triangle, std::size_t index,
37 const std::optional<MeshTextureRegion>& texture)
38 {
39 const auto& sourceVertex = triangle.vertices[index];
40 const auto& sourceNormal = triangle.normals[index];
41 MeshVertex result;
42 result.position = {
43 static_cast<float>(sourceVertex.x),
44 -static_cast<float>(sourceVertex.y),
45 static_cast<float>(sourceVertex.z)
46 };
47 result.normal = {
48 static_cast<float>(sourceNormal.x) / FixedNormalScale,
49 -static_cast<float>(sourceNormal.y) / FixedNormalScale,
50 static_cast<float>(sourceNormal.z) / FixedNormalScale
51 };
52 if (texture)
53 {
54 result.uv = {
55 (static_cast<float>(triangle.texturePoints[index].u) -
56 static_cast<float>(texture->x)) /
57 static_cast<float>(texture->width),
58 (static_cast<float>(triangle.texturePoints[index].v) -
59 static_cast<float>(texture->y)) /
60 static_cast<float>(texture->height)
61 };
62 }
63 return result;
64 }
65
66 bool sameVertex(const MeshVertex& left, const MeshVertex& right,
67 bool textured) noexcept
68 {
69 if (left.position != right.position || left.normal != right.normal)
70 return false;
71 // NewMesh.cpp::FindVertex deliberately ignores UV for untextured
72 // vertices, but requires an exact match when texture coordinates exist.
73 return !textured || left.uv == right.uv;
74 }
75
76 bool sameMaterial(const MeshMaterial& left, const MeshMaterial& right) noexcept
77 {
78 return left.rawDiffuse == right.rawDiffuse;
79 }
80 }
81
82 std::expected<MeshXRuntime, MeshRuntimeError> MeshXRuntime::build(
83 std::shared_ptr<const LegacyMeshData> source,
85 {
86 if (!source)
88 "MESHX runtime requires immutable HMD source ownership"));
89 if (limits.maximumVertices == 0)
91 "MESHX vertex budget must be nonzero"));
92 if (limits.maximumGroups == 0)
94 "MESHX group budget must be nonzero"));
95 if (limits.maximumIndices < 3)
97 "MESHX index budget must allow at least one triangle"));
98
99 MeshXRuntime result;
100 result.source_ = std::move(source);
101 std::size_t skippedUnsupported{};
102 std::size_t skippedTextured{};
103 std::size_t totalIndices{};
104 std::optional<std::vector<std::shared_ptr<const HmdTextureImage>>> embeddedTextures;
105
106 auto resolveEmbeddedTexture = [&](const MeshTextureLookup& lookup)
107 -> std::expected<std::optional<MeshTextureRegion>, MeshRuntimeError>
108 {
109 if (!embeddedTextures)
110 {
111 auto decoded = result.source_->textureImages();
112 if (!decoded)
113 {
115 "embedded HMD texture image could not be decoded");
116 failure.sourceError = decoded.error();
117 failure.skippedUnsupportedSections = skippedUnsupported;
118 failure.skippedTexturedTriangles = skippedTextured;
119 return std::unexpected(std::move(failure));
120 }
121 embeddedTextures.emplace();
122 embeddedTextures->reserve(decoded->size());
123 for (auto& image : *decoded)
124 embeddedTextures->push_back(
125 std::make_shared<const HmdTextureImage>(std::move(image)));
126 }
127
128 // NewMesh.cpp::FindTexture scans newest-to-oldest and includes edges.
129 for (std::size_t index = embeddedTextures->size(); index-- > 0;)
130 {
131 const auto& image = (*embeddedTextures)[index];
132 const auto u = static_cast<std::int32_t>(lookup.u);
133 const auto v = static_cast<std::int32_t>(lookup.v);
134 const auto right = image->logicalX + static_cast<std::int32_t>(image->width);
135 const auto bottom = image->logicalY + static_cast<std::int32_t>(image->height);
136 if (lookup.page == image->texturePage &&
137 u >= image->logicalX && u <= right &&
138 v >= image->logicalY && v <= bottom)
139 return std::optional<MeshTextureRegion>{MeshTextureRegion{
140 static_cast<std::uint64_t>(index + 1U), image->texturePage,
141 image->logicalX, image->logicalY, image->width, image->height, image}};
142 }
143 return std::optional<MeshTextureRegion>{};
144 };
145 for (std::size_t primitiveIndex = 0;
146 primitiveIndex < result.source_->primitives().size(); ++primitiveIndex)
147 {
148 const auto& primitive = result.source_->primitives()[primitiveIndex];
149 for (std::size_t sectionIndex = 0;
150 sectionIndex < primitive.sections.size(); ++sectionIndex)
151 {
152 const auto& section = primitive.sections[sectionIndex];
153 for (std::size_t triangleIndex = 0;
154 triangleIndex < section.elementCount; ++triangleIndex)
155 {
156 const auto triangle = result.source_->triangle(
158 if (!triangle)
159 {
160 if (triangle.error().code == MeshDataErrorCode::UnsupportedSection)
161 {
163 break;
164 }
166 "HMD triangle could not be converted into MESHX runtime geometry");
167 failure.sourceError = triangle.error();
168 failure.skippedUnsupportedSections = skippedUnsupported;
169 failure.skippedTexturedTriangles = skippedTextured;
170 return std::unexpected(std::move(failure));
171 }
172
173 std::optional<MeshTextureRegion> texture;
174 const bool textured = triangle->texturePage != 0xFFFFU;
175 if (textured)
176 {
177 const MeshTextureLookup lookup{triangle->texturePage,
178 triangle->texturePoints[0].u, triangle->texturePoints[0].v};
179 if (textureResolver)
180 texture = textureResolver(lookup);
181 else
182 {
183 auto embedded = resolveEmbeddedTexture(lookup);
184 if (!embedded)
185 return std::unexpected(std::move(embedded.error()));
186 texture = std::move(*embedded);
187 }
188 if (!texture)
189 {
190 // NewMesh.cpp::AddTriangle returns without adding a face
191 // when FindTexture cannot resolve the historical TIM region.
193 continue;
194 }
195 if (texture->width == 0 || texture->height == 0 ||
196 texture->page != triangle->texturePage)
197 {
198 auto failure = runtimeError(
200 "resolved HMD texture region is empty or belongs to another page");
201 failure.skippedUnsupportedSections = skippedUnsupported;
202 failure.skippedTexturedTriangles = skippedTextured;
203 return std::unexpected(std::move(failure));
204 }
205 }
206
207 const auto material = materialFrom(triangle->colours[0]);
208 auto group = std::find_if(result.groups_.begin(), result.groups_.end(),
209 [&](const MeshGroupRuntime& candidate)
210 {
211 return candidate.texture == texture &&
212 sameMaterial(candidate.material, material);
213 });
214 if (group == result.groups_.end())
215 {
216 if (result.groups_.size() >= limits.maximumGroups)
217 return std::unexpected(runtimeError(
219 "MESHX material/texture group budget exceeded"));
220 result.groups_.push_back({material, texture, {}});
221 group = std::prev(result.groups_.end());
222 }
223 if (totalIndices > limits.maximumIndices - 3)
224 return std::unexpected(runtimeError(
226 "MESHX total index budget exceeded"));
227
228 for (std::size_t vertexIndex = 0; vertexIndex < 3; ++vertexIndex)
229 {
230 const auto vertex = vertexFrom(*triangle, vertexIndex, texture);
231 const auto found = std::find_if(result.vertices_.begin(),
232 result.vertices_.end(), [&](const MeshVertex& candidate)
233 { return sameVertex(candidate, vertex, textured); });
234 std::size_t index{};
235 if (found == result.vertices_.end())
236 {
237 if (result.vertices_.size() >= limits.maximumVertices ||
238 result.vertices_.size() >=
239 static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max()))
240 return std::unexpected(runtimeError(
242 "MESHX vertex budget or 32-bit index range exceeded"));
243 index = result.vertices_.size();
244 result.vertices_.push_back(vertex);
245 result.sourceIndices_.push_back({
246 triangle->vertexIndices[vertexIndex],
247 triangle->normalIndices[vertexIndex]});
248 }
249 else index = static_cast<std::size_t>(
250 std::distance(result.vertices_.begin(), found));
251 group->indices.push_back(static_cast<std::uint32_t>(index));
252 }
253 totalIndices += 3;
254 }
255 }
256 }
257
258 auto mime = result.source_->mimePoses();
259 if (!mime)
260 {
262 "HMD MIMe diff blocks could not be decoded");
263 failure.sourceError = mime.error();
264 failure.skippedUnsupportedSections = skippedUnsupported;
265 failure.skippedTexturedTriangles = skippedTextured;
266 return std::unexpected(std::move(failure));
267 }
268 result.mimePoses_ = std::move(*mime);
269
270 if (result.vertices_.empty())
271 {
273 "HMD did not produce any supported renderable MESHX geometry");
274 failure.skippedUnsupportedSections = skippedUnsupported;
275 failure.skippedTexturedTriangles = skippedTextured;
276 return std::unexpected(std::move(failure));
277 }
278
279 result.bounds_.minimum = result.vertices_.front().position;
280 result.bounds_.maximum = result.vertices_.front().position;
281 for (const auto& vertex : result.vertices_)
282 for (std::size_t axis = 0; axis < 3; ++axis)
283 {
284 result.bounds_.minimum[axis] = std::min(
285 result.bounds_.minimum[axis], vertex.position[axis]);
286 result.bounds_.maximum[axis] = std::max(
287 result.bounds_.maximum[axis], vertex.position[axis]);
288 }
289 return result;
290 }
291
292 std::shared_ptr<const LegacyMeshData> MeshXRuntime::source() const noexcept
293 { return source_; }
294 const std::vector<MeshVertex>& MeshXRuntime::vertices() const noexcept
295 { return vertices_; }
296 const std::vector<MeshGroupRuntime>& MeshXRuntime::groups() const noexcept
297 { return groups_; }
299 { return bounds_; }
300
302 { return mimePoses_.size() + 1U; }
303
304 std::expected<MeshPoseData, MeshRuntimeError> MeshXRuntime::evaluatePose(
305 std::int32_t poseA, std::int32_t poseB, float proportion) const
306 {
307 const auto validPose = [&](std::int32_t pose)
308 {
309 return pose >= 0 &&
310 static_cast<std::size_t>(pose) <= mimePoses_.size();
311 };
312 if (!validPose(poseA) || !validPose(poseB))
313 return std::unexpected(runtimeError(MeshRuntimeErrorCode::InvalidPose,
314 "MESHX pose index is outside base-plus-MIMe pose range"));
315 if (vertices_.size() != sourceIndices_.size())
316 return std::unexpected(runtimeError(MeshRuntimeErrorCode::InvalidPose,
317 "MESHX source-index map no longer matches base vertices"));
318
319 auto applyPose = [&](std::size_t vertexIndex, std::int32_t pose)
320 {
321 auto value = vertices_[vertexIndex];
322 if (pose == 0)
323 return value;
324 const auto& mime = mimePoses_[static_cast<std::size_t>(pose - 1)];
325 const auto sourceVertex = sourceIndices_[vertexIndex][0];
326 const auto sourceNormal = sourceIndices_[vertexIndex][1];
327 if (mime.vertex && sourceVertex >= mime.vertex->startIndex)
328 {
329 const auto diffIndex = static_cast<std::size_t>(
330 sourceVertex - mime.vertex->startIndex);
331 if (diffIndex < mime.vertex->diffs.size())
332 {
333 const auto& diff = mime.vertex->diffs[diffIndex];
334 value.position[0] += static_cast<float>(diff.x);
335 value.position[1] -= static_cast<float>(diff.y);
336 value.position[2] += static_cast<float>(diff.z);
337 }
338 }
339 if (mime.normal && sourceNormal >= mime.normal->startIndex)
340 {
341 const auto diffIndex = static_cast<std::size_t>(
342 sourceNormal - mime.normal->startIndex);
343 if (diffIndex < mime.normal->diffs.size())
344 {
345 // hmdload.cpp::SetVertexAndSiblings adds normal MIMe
346 // SVECTOR values directly after the base /4096 conversion.
347 const auto& diff = mime.normal->diffs[diffIndex];
348 value.normal[0] += static_cast<float>(diff.x);
349 value.normal[1] -= static_cast<float>(diff.y);
350 value.normal[2] += static_cast<float>(diff.z);
351 }
352 }
353 return value;
354 };
355
356 MeshPoseData result;
357 result.vertices.reserve(vertices_.size());
360 bool first = true;
361 for (std::size_t index = 0; index < vertices_.size(); ++index)
362 {
363 const auto a = applyPose(index, poseA);
364 const auto b = applyPose(index, poseB);
365 auto value = a;
366 if (poseA != poseB && proportion != 0.0F)
367 {
368 if (proportion == 1.0F)
369 value = b;
370 else
371 {
372 for (std::size_t axis = 0; axis < 3; ++axis)
373 {
374 value.position[axis] = a.position[axis] +
375 (b.position[axis] - a.position[axis]) * proportion;
376 value.normal[axis] = a.normal[axis] +
377 (b.normal[axis] - a.normal[axis]) * proportion;
378 }
379 // Legacy HMD_interpolate copies UVs from pose A.
380 value.uv = a.uv;
381 }
382 }
383 result.vertices.push_back(value);
384
385 if (first)
386 {
387 boundsA.minimum = boundsA.maximum = a.position;
388 boundsB.minimum = boundsB.maximum = b.position;
389 first = false;
390 }
391 else
392 {
393 for (std::size_t axis = 0; axis < 3; ++axis)
394 {
395 boundsA.minimum[axis] = std::min(boundsA.minimum[axis], a.position[axis]);
396 boundsA.maximum[axis] = std::max(boundsA.maximum[axis], a.position[axis]);
397 boundsB.minimum[axis] = std::min(boundsB.minimum[axis], b.position[axis]);
398 boundsB.maximum[axis] = std::max(boundsB.maximum[axis], b.position[axis]);
399 }
400 }
401 }
402
403 if (poseA == poseB || proportion == 0.0F)
404 result.bounds = boundsA;
405 else if (proportion == 1.0F)
406 result.bounds = boundsB;
407 else
408 {
409 for (std::size_t axis = 0; axis < 3; ++axis)
410 {
411 result.bounds.minimum[axis] = boundsA.minimum[axis] +
412 (boundsB.minimum[axis] - boundsA.minimum[axis]) * proportion;
413 result.bounds.maximum[axis] = boundsA.maximum[axis] +
414 (boundsB.maximum[axis] - boundsA.maximum[axis]) * proportion;
415 }
416 }
417 return result;
418 }
419
421 {
422 MeshRenderData result;
423 result.vertices = mesh.vertices();
424 result.bounds = mesh.bounds();
425 for (const auto& group : mesh.groups())
426 {
427 const auto first = result.indices.size();
428 result.indices.insert(result.indices.end(), group.indices.begin(), group.indices.end());
429 result.batches.push_back({first, group.indices.size(),
430 group.material, group.texture});
431 }
432 return result;
433 }
434
435 std::expected<MeshRenderData, MeshRuntimeError> makeMeshRenderData(
436 const MeshXRuntime& mesh, std::int32_t poseA, std::int32_t poseB,
437 float proportion)
438 {
440 if (!evaluated)
441 return std::unexpected(evaluated.error());
442 MeshRenderData result;
443 result.vertices = std::move(evaluated->vertices);
444 result.bounds = evaluated->bounds;
445 for (const auto& group : mesh.groups())
446 {
447 const auto first = result.indices.size();
448 result.indices.insert(result.indices.end(), group.indices.begin(), group.indices.end());
449 result.batches.push_back({first, group.indices.size(),
450 group.material, group.texture});
451 }
452 return result;
453 }
454
455 std::expected<MeshXRuntime, MeshRuntimeError> MeshXRuntime::withTextureImages(
456 std::span<const std::shared_ptr<const HmdTextureImage>> images) const
457 {
458 for (std::size_t i = 0; i < images.size(); ++i)
459 {
460 const auto& image = images[i];
461 if (!image || image->width == 0 || image->height == 0 ||
462 std::uint64_t{image->width} * image->height >
463 std::numeric_limits<std::size_t>::max() / 4U ||
464 image->rgba.size() != std::uint64_t{image->width} * image->height * 4U)
466 "substituted texture must contain width*height RGBA8 bytes"));
467 for (std::size_t j = 0; j < i; ++j)
468 if (images[j]->texturePage == image->texturePage &&
469 images[j]->rawX == image->rawX && images[j]->rawY == image->rawY)
471 "multiple substituted textures have the same raw HMD coordinates"));
472 const bool matched = std::any_of(groups_.begin(), groups_.end(),
473 [&](const MeshGroupRuntime& group)
474 {
475 return group.texture && group.texture->sourceImage &&
476 group.texture->sourceImage->texturePage == image->texturePage &&
477 group.texture->sourceImage->rawX == image->rawX &&
478 group.texture->sourceImage->rawY == image->rawY;
479 });
480 if (!matched)
482 "substituted texture coordinates are absent from the mesh"));
483 }
484
485 MeshXRuntime replacement = *this;
486 // Modern may split one texture into several material groups. They all
487 // refer to the same original image and must receive the same payload.
488 for (auto& group : replacement.groups_)
489 {
490 if (!group.texture || !group.texture->sourceImage) continue;
491 const auto& original = *group.texture->sourceImage;
492 const auto found = std::find_if(images.begin(), images.end(),
493 [&](const auto& image)
494 {
495 return image->texturePage == original.texturePage &&
496 image->rawX == original.rawX && image->rawY == original.rawY;
497 });
498 if (found == images.end()) continue;
499 // Keys stay local to the immutable mesh asset. Its replacement
500 // pointer invalidates GPU ownership; other mesh banks are untouched.
501 group.texture->sourceImage = *found;
502 group.texture->width = (*found)->width;
503 group.texture->height = (*found)->height;
504 }
505 return replacement;
506 }
507
509 std::shared_ptr<const ResourceSnapshot> resources,
511 MeshRuntimeLimits limits,
513 : resources_(std::move(resources)),
514 textureResolver_(std::move(textureResolver)),
515 limits_(limits),
516 modernMeshResolver_(std::move(modernMeshResolver))
517 {
518 }
519
520 std::expected<std::shared_ptr<const MeshRuntimeAsset>, MeshRuntimeError>
522 DataId id,
523 std::optional<DataId> rootSequenceDataId,
524 std::uint16_t priority)
525 {
526 if (!resources_)
528 "MESHX cache requires an immutable resource snapshot"));
529
530 // Ask the context-sensitive modern resolver before consulting the
531 // legacy cache. A given HMD may be modern in its resting sequence but
532 // must remain retail inside a movement sequence.
533 if (modernMeshResolver_)
534 {
535 const auto modern =
536 modernMeshResolver_(id, rootSequenceDataId, priority);
537 if (modern && *modern &&
538 (!rootSequenceDataId || !rejectedModernSequences_.contains(*rootSequenceDataId)) &&
539 std::none_of(rejectedModernAssets_.begin(), rejectedModernAssets_.end(),
540 [&](const auto& rejected) { return rejected == **modern; }))
541 {
542 if (const auto found = modernAssets_.find(id);
543 found != modernAssets_.end())
544 for (const auto& cached : found->second)
545 if (cached->renderData == **modern) return cached;
546
547 auto asset = std::make_shared<const MeshRuntimeAsset>(
549 id,
551 {},
552 std::move(**modern)
553 });
554 modernAssets_[id].push_back(asset);
555 return asset;
556 }
557 }
558
559 if (const auto found = assets_.find(id); found != assets_.end())
560 return found->second;
561
562 auto source = openLegacyMeshData(resources_->data(), id);
563 if (!source)
564 {
566 "MESHX cache could not open the requested HMD DATA item");
567 failure.sourceError = source.error();
568 return std::unexpected(std::move(failure));
569 }
570 auto immutableSource = std::make_shared<const LegacyMeshData>(std::move(*source));
571 auto built = MeshXRuntime::build(immutableSource, textureResolver_, limits_);
572 if (!built) return std::unexpected(built.error());
573 if (const auto configured = textureOverrides_.find(id); configured != textureOverrides_.end())
574 {
575 auto substituted = built->withTextureImages(configured->second);
576 if (!substituted) return std::unexpected(substituted.error());
577 built = std::move(substituted);
578 }
579 auto mesh = std::make_shared<const MeshXRuntime>(std::move(*built));
580 auto renderData = std::make_shared<const MeshRenderData>(makeMeshRenderData(*mesh));
581 auto asset = std::make_shared<const MeshRuntimeAsset>(
583 id,
585 std::move(mesh),
586 std::move(renderData)
587 });
588 assets_.emplace(id, asset);
589 return asset;
590 }
591
592 std::expected<void, MeshRuntimeError> MeshRuntimeCache::replaceTextureImages(
593 DataId id, std::span<const std::shared_ptr<const HmdTextureImage>> images)
594 {
595 if (!resources_)
597 "texture substitution requires an immutable resource snapshot"));
598 auto source = openLegacyMeshData(resources_->data(), id);
599 if (!source)
600 {
602 "texture substitution could not open the requested HMD DATA item");
603 failure.sourceError = source.error();
604 return std::unexpected(std::move(failure));
605 }
607 std::make_shared<const LegacyMeshData>(std::move(*source)), textureResolver_, limits_);
608 if (!built) return std::unexpected(built.error());
609 auto substituted = built->withTextureImages(images);
610 if (!substituted) return std::unexpected(substituted.error());
611 auto mesh = std::make_shared<const MeshXRuntime>(std::move(*substituted));
612 auto renderData = std::make_shared<const MeshRenderData>(makeMeshRenderData(*mesh));
613 auto asset = std::make_shared<const MeshRuntimeAsset>(
615 id,
617 std::move(mesh),
618 std::move(renderData)
619 });
620 // Publish only after the complete replacement has passed validation.
621 // Image configuration owns no HMD source lease and survives eviction.
622 std::vector<std::shared_ptr<const HmdTextureImage>> configured(images.begin(), images.end());
623 if (configured.empty()) textureOverrides_.erase(id);
624 else textureOverrides_.insert_or_assign(id, std::move(configured));
625 assets_.insert_or_assign(id, std::move(asset));
626 return {};
627 }
628
630 {
631 if (!renderData || std::any_of(rejectedModernAssets_.begin(), rejectedModernAssets_.end(),
632 [&](const auto& rejected) { return rejected.get() == renderData; }))
633 return false;
634 for (const auto& [id, variants] : modernAssets_)
635 {
636 (void)id;
637 for (const auto& asset : variants)
638 if (asset->renderData.get() == renderData)
639 {
640 rejectedModernAssets_.push_back(asset->renderData);
641 return true;
642 }
643 }
644 return false;
645 }
646
648 {
649 rejectedModernSequences_.insert(rootSequenceDataId);
650 }
651
653 {
654 auto total = assets_.size();
655 for (const auto& [id, variants] : modernAssets_)
656 {
657 (void)id;
658 total += variants.size();
659 }
660 return total;
661 }
662
664 {
665 const auto legacyReleased =
666 std::erase_if(assets_, [](const auto& entry)
667 { return entry.second.use_count() == 1; });
668 std::size_t modernReleased{};
669 for (auto iterator = modernAssets_.begin(); iterator != modernAssets_.end();)
670 {
671 modernReleased += std::erase_if(iterator->second,
672 [](const auto& asset) { return asset.use_count() == 1; });
673 if (iterator->second.empty()) iterator = modernAssets_.erase(iterator);
674 else ++iterator;
675 }
677 }
678
680 {
681 assets_.clear();
682 modernAssets_.clear();
683 }
684 std::shared_ptr<const ResourceSnapshot> MeshRuntimeCache::resources() const noexcept
685 { return resources_; }
686}
std::uint16_t group
std::atomic< float > left
std::atomic< float > right
LegacyBitmapRGBA8 image
int bottom
data::DataId id
std::uint16_t priority
std::shared_ptr< const data::HmdTextureImage > source
SDL_GPUTexture * texture
unsigned index
data::DataTag first
std::uint16_t value
Definition Timers.cpp:16
std::size_t releaseUnused() noexcept
MeshRuntimeCache(std::shared_ptr< const ResourceSnapshot > resources, MeshTextureResolver textureResolver={}, MeshRuntimeLimits limits={}, ModernMeshResolver modernMeshResolver={})
std::expected< void, MeshRuntimeError > replaceTextureImages(DataId id, std::span< const std::shared_ptr< const HmdTextureImage > > images)
std::expected< std::shared_ptr< const MeshRuntimeAsset >, MeshRuntimeError > resolve(DataId id, std::optional< DataId > rootSequenceDataId=std::nullopt, std::uint16_t priority=0)
bool rejectModernAsset(const MeshRenderData *renderData)
std::size_t size() const noexcept
void rejectModernSequence(DataId rootSequenceDataId)
std::shared_ptr< const ResourceSnapshot > resources() const noexcept
const std::vector< MeshGroupRuntime > & groups() const noexcept
std::expected< MeshXRuntime, MeshRuntimeError > withTextureImages(std::span< const std::shared_ptr< const HmdTextureImage > > images) const
static std::expected< MeshXRuntime, MeshRuntimeError > build(std::shared_ptr< const LegacyMeshData > source, MeshTextureResolver textureResolver={}, MeshRuntimeLimits limits={})
std::expected< MeshPoseData, MeshRuntimeError > evaluatePose(std::int32_t poseA, std::int32_t poseB, float proportion) const
std::shared_ptr< const LegacyMeshData > source() const noexcept
const std::vector< MeshVertex > & vertices() const noexcept
const MeshBounds & bounds() const noexcept
std::size_t poseCount() const noexcept
std::expected< LegacyMeshData, MeshDataError > openLegacyMeshData(const DataSource &source, DataId id, MeshParseLimits limits)
std::uint32_t DataId
Definition DataBanks.hpp:10
std::function< std::expected< std::optional< std::shared_ptr< const MeshRenderData > >, MeshRuntimeError >(DataId, std::optional< DataId > rootSequenceDataId, std::uint16_t priority)> ModernMeshResolver
std::function< std::optional< MeshTextureRegion >(const MeshTextureLookup &)> MeshTextureResolver
MeshRenderData makeMeshRenderData(const MeshXRuntime &mesh)
std::array< float, 3 > maximum
std::array< float, 3 > minimum
std::vector< MeshVertex > vertices
std::vector< MeshRenderBatch > batches
std::vector< std::uint32_t > indices
std::vector< MeshVertex > vertices