载入中...
搜索中...
未找到
ClimbingAnchorAuthoring.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <cstdio>
7#include <iomanip>
8#include <limits>
9#include <sstream>
10#include <unordered_set>
11
12namespace eve::climbing {
13namespace {
14
15bool finite(float value) { return std::isfinite(value); }
16bool finite(Vec3 value) { return finite(value.x) && finite(value.y) && finite(value.z); }
17Vec3 add(Vec3 lhs, Vec3 rhs) { return {lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z}; }
18Vec3 subtract(Vec3 lhs, Vec3 rhs) { return {lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z}; }
19Vec3 multiply(Vec3 value, float scale) { return {value.x * scale, value.y * scale, value.z * scale}; }
20float dot(Vec3 lhs, Vec3 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; }
21Vec3 cross(Vec3 lhs, Vec3 rhs) {
22 return {lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z,
23 lhs.x * rhs.y - lhs.y * rhs.x};
24}
25float length(Vec3 value) { return std::sqrt(dot(value, value)); }
26
27eve::Result<Vec3> normalized(Vec3 value, std::string path) {
28 if (!finite(value))
30 eve::DiagnosticCode::InvalidArgument, "vector must be finite", path, {}, "climbing.anchor_baker"));
31 const float magnitude = length(value);
32 if (magnitude <= 0.0001f)
34 eve::DiagnosticCode::InvalidArgument, "vector must be non-zero", path, {}, "climbing.anchor_baker"));
35 return eve::Result<Vec3>::success(multiply(value, 1.f / magnitude));
36}
37
38std::string hexHash(const ClimbingAnchorBakeRequest& request) {
39 std::uint64_t hash = 1469598103934665603ull;
40 const auto append = [&hash](std::string_view value) {
41 for (const unsigned char byte : value) {
42 hash ^= byte;
43 hash *= 1099511628211ull;
44 }
45 };
46 const auto appendNumber = [&append](auto value) {
47 std::array<char, 64> buffer{};
48 const int count = std::snprintf(buffer.data(), buffer.size(), "%.9g", static_cast<double>(value));
49 if (count > 0) append(std::string_view(buffer.data(), static_cast<std::size_t>(count)));
50 append("|");
51 };
52 append("evengine.climbing-anchor-bake-v1|");
53 append(request.sourceGeometryContentId);
54 append("|");
55 appendNumber(request.settings.handSpacing);
56 appendNumber(request.settings.hangingFeetDrop);
57 appendNumber(request.settings.ladderMountOffset);
58 appendNumber(request.settings.occupancySlots);
59 std::vector<const ClimbingLedgeBakeSource*> ledges;
60 ledges.reserve(request.ledges.size());
61 for (const auto& ledge : request.ledges) ledges.push_back(&ledge);
62 std::sort(ledges.begin(), ledges.end(), [](const auto* lhs, const auto* rhs) {
63 return lhs->id < rhs->id;
64 });
65 for (const auto* source : ledges) {
66 const auto& ledge = *source;
67 append(ledge.id);
68 appendNumber(ledge.closed ? 1 : 0);
69 appendNumber(ledge.localNormal.x);
70 appendNumber(ledge.localNormal.y);
71 appendNumber(ledge.localNormal.z);
72 for (const Vec3 normal : ledge.localNormals) {
73 appendNumber(normal.x);
74 appendNumber(normal.y);
75 appendNumber(normal.z);
76 }
77 for (const Vec3 point : ledge.points) {
78 appendNumber(point.x);
79 appendNumber(point.y);
80 appendNumber(point.z);
81 }
82 auto tags = ledge.tags;
83 std::sort(tags.begin(), tags.end());
84 for (const auto& tag : tags) append(tag);
85 }
86 std::vector<const ClimbingLadderBakeSource*> ladders;
87 ladders.reserve(request.ladders.size());
88 for (const auto& ladder : request.ladders) ladders.push_back(&ladder);
89 std::sort(ladders.begin(), ladders.end(), [](const auto* lhs, const auto* rhs) {
90 return lhs->id < rhs->id;
91 });
92 for (const auto* source : ladders) {
93 const auto& ladder = *source;
94 append(ladder.id);
95 appendNumber(ladder.bottomCenter.x);
96 appendNumber(ladder.bottomCenter.y);
97 appendNumber(ladder.bottomCenter.z);
98 appendNumber(ladder.localUp.x);
99 appendNumber(ladder.localUp.y);
100 appendNumber(ladder.localUp.z);
101 appendNumber(ladder.localNormal.x);
102 appendNumber(ladder.localNormal.y);
103 appendNumber(ladder.localNormal.z);
104 appendNumber(ladder.rungSpacing);
105 appendNumber(ladder.width);
106 appendNumber(ladder.rungCount);
107 auto tags = ladder.tags;
108 std::sort(tags.begin(), tags.end());
109 for (const auto& tag : tags) append(tag);
110 }
111 std::ostringstream output;
112 output << "fnv1a64:" << std::hex << std::setw(16) << std::setfill('0') << hash;
113 return output.str();
114}
115
116bool uniqueNonEmpty(std::vector<std::string> values) {
117 std::sort(values.begin(), values.end());
118 return !std::any_of(values.begin(), values.end(), [](const auto& value) { return value.empty(); }) &&
119 std::adjacent_find(values.begin(), values.end()) == values.end();
120}
121
122const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
123 const auto found = object.find(std::string(name));
124 return found == object.end() ? nullptr : &found->second;
125}
126
127bool readString(const eve::Value::Object& object, std::string_view name, std::string& output) {
128 const auto* value = field(object, name);
129 const auto* text = value ? value->getIf<std::string>() : nullptr;
130 if (!text) return false;
131 output = *text;
132 return true;
133}
134
135bool readFloat(const eve::Value::Object& object, std::string_view name, float& output) {
136 const auto* value = field(object, name);
137 if (!value) return false;
138 double number = 0.0;
139 if (const auto* integer = value->getIf<std::int64_t>())
140 number = static_cast<double>(*integer);
141 else if (const auto* real = value->getIf<double>())
142 number = *real;
143 else
144 return false;
145 if (!std::isfinite(number) || number < -std::numeric_limits<float>::max() ||
146 number > std::numeric_limits<float>::max())
147 return false;
148 output = static_cast<float>(number);
149 return true;
150}
151
152bool readUInt32(const eve::Value::Object& object, std::string_view name, std::uint32_t& output) {
153 const auto* value = field(object, name);
154 const auto* integer = value ? value->getIf<std::int64_t>() : nullptr;
155 if (!integer || *integer < 0 || static_cast<std::uint64_t>(*integer) > std::numeric_limits<std::uint32_t>::max())
156 return false;
157 output = static_cast<std::uint32_t>(*integer);
158 return true;
159}
160
161bool readBool(const eve::Value::Object& object, std::string_view name, bool& output) {
162 const auto* value = field(object, name);
163 const auto* boolean = value ? value->getIf<bool>() : nullptr;
164 if (!boolean) return false;
165 output = *boolean;
166 return true;
167}
168
169bool readVec(const eve::Value& value, Vec3& output) {
170 const auto* array = value.getIf<eve::Value::Array>();
171 if (!array || array->size() != 3) return false;
172 eve::Value::Object object{{"x", (*array)[0]}, {"y", (*array)[1]}, {"z", (*array)[2]}};
173 return readFloat(object, "x", output.x) && readFloat(object, "y", output.y) &&
174 readFloat(object, "z", output.z);
175}
176
177bool readVecField(const eve::Value::Object& object, std::string_view name, Vec3& output) {
178 const auto* value = field(object, name);
179 return value && readVec(*value, output);
180}
181
182bool readVecArray(const eve::Value::Object& object, std::string_view name, std::vector<Vec3>& output) {
183 const auto* value = field(object, name);
184 const auto* array = value ? value->getIf<eve::Value::Array>() : nullptr;
185 if (!array) return false;
186 output.reserve(array->size());
187 for (const auto& item : *array) {
188 Vec3 vector;
189 if (!readVec(item, vector)) return false;
190 output.push_back(vector);
191 }
192 return true;
193}
194
195bool readStrings(const eve::Value::Object& object, std::string_view name, std::vector<std::string>& output) {
196 const auto* value = field(object, name);
197 const auto* array = value ? value->getIf<eve::Value::Array>() : nullptr;
198 if (!array) return false;
199 output.reserve(array->size());
200 for (const auto& item : *array) {
201 const auto* text = item.getIf<std::string>();
202 if (!text) return false;
203 output.push_back(*text);
204 }
205 return true;
206}
207
208eve::Value vecValue(Vec3 value) {
210}
211
212eve::Value vecArrayValue(const std::vector<Vec3>& values) {
213 eve::Value::Array result;
214 result.reserve(values.size());
215 for (const Vec3 value : values) result.push_back(vecValue(value));
216 return eve::Value(std::move(result));
217}
218
219eve::Value stringArrayValue(const std::vector<std::string>& values) {
220 eve::Value::Array result;
221 result.reserve(values.size());
222 for (const auto& value : values) result.emplace_back(value);
223 return eve::Value(std::move(result));
224}
225
226ClimbingAnchorNodeDefinition ledgeNode(std::string id, ClimbingAnchorKind kind, Vec3 position, Vec3 normal,
227 Vec3 tangent, const ClimbingAnchorBakeSettings& settings,
228 std::vector<std::string> tags) {
229 ClimbingAnchorNodeDefinition node;
230 node.id = std::move(id);
231 node.kind = kind;
232 node.localPosition = position;
233 node.localNormal = normal;
234 node.localTangent = tangent;
235 node.leftHandSocket = add(position, multiply(tangent, -settings.handSpacing * 0.5f));
236 node.rightHandSocket = add(position, multiply(tangent, settings.handSpacing * 0.5f));
237 node.feetSocket = add(position, {0.f, -settings.hangingFeetDrop, 0.f});
238 node.occupancySlots = settings.occupancySlots;
239 node.tags = std::move(tags);
240 return node;
241}
242
243} // namespace
244
246 eve::Value::Array ledges;
247 ledges.reserve(request.ledges.size());
248 for (const auto& source : request.ledges)
249 ledges.push_back(eve::Value::object({
250 {"id", source.id},
251 {"points", vecArrayValue(source.points)},
252 {"localNormal", vecValue(source.localNormal)},
253 {"localNormals", vecArrayValue(source.localNormals)},
254 {"closed", source.closed},
255 {"tags", stringArrayValue(source.tags)},
256 }));
257 eve::Value::Array ladders;
258 ladders.reserve(request.ladders.size());
259 for (const auto& source : request.ladders)
260 ladders.push_back(eve::Value::object({
261 {"id", source.id},
262 {"bottomCenter", vecValue(source.bottomCenter)},
263 {"localUp", vecValue(source.localUp)},
264 {"localNormal", vecValue(source.localNormal)},
265 {"rungSpacing", source.rungSpacing},
266 {"width", source.width},
267 {"rungCount", static_cast<std::int64_t>(source.rungCount)},
268 {"tags", stringArrayValue(source.tags)},
269 }));
271 {"schemaId", std::string(ClimbingAnchorBakeRequest::SchemaId)},
273 {"graphId", request.graphId},
274 {"sourceGeometryContentId", request.sourceGeometryContentId},
275 {"settings", eve::Value::object({
276 {"handSpacing", request.settings.handSpacing},
277 {"hangingFeetDrop", request.settings.hangingFeetDrop},
278 {"ladderMountOffset", request.settings.ladderMountOffset},
279 {"occupancySlots", static_cast<std::int64_t>(request.settings.occupancySlots)},
280 })},
281 {"ledges", eve::Value(std::move(ledges))},
282 {"ladders", eve::Value(std::move(ladders))},
283 }));
284}
285
287 const auto* root = value.getIf<eve::Value::Object>();
288 if (!root)
290 eve::DiagnosticCode::ParseError, "anchor bake request must be an object", {}, {}, "climbing.anchor_baker"));
292 std::string schemaId;
293 std::int64_t schemaVersion = -1;
294 const auto* schema = field(*root, "schemaVersion");
295 if (schema) {
296 if (const auto* integer = schema->getIf<std::int64_t>()) schemaVersion = *integer;
297 }
298 const auto* settingsValue = field(*root, "settings");
299 const auto* settings = settingsValue ? settingsValue->getIf<eve::Value::Object>() : nullptr;
300 const auto* ledgesValue = field(*root, "ledges");
301 const auto* ledges = ledgesValue ? ledgesValue->getIf<eve::Value::Array>() : nullptr;
302 const auto* laddersValue = field(*root, "ladders");
303 const auto* ladders = laddersValue ? laddersValue->getIf<eve::Value::Array>() : nullptr;
304 if (!readString(*root, "schemaId", schemaId) || schemaId != ClimbingAnchorBakeRequest::SchemaId ||
305 schemaVersion != ClimbingAnchorBakeRequest::SchemaVersion || !readString(*root, "graphId", request.graphId) ||
306 !readString(*root, "sourceGeometryContentId", request.sourceGeometryContentId) || !settings || !ledges ||
307 !ladders || !readFloat(*settings, "handSpacing", request.settings.handSpacing) ||
308 !readFloat(*settings, "hangingFeetDrop", request.settings.hangingFeetDrop) ||
309 !readFloat(*settings, "ladderMountOffset", request.settings.ladderMountOffset) ||
310 !readUInt32(*settings, "occupancySlots", request.settings.occupancySlots)) {
311 if (schemaVersion > ClimbingAnchorBakeRequest::SchemaVersion)
313 eve::DiagnosticCode::UnknownVersion, "anchor bake request schema version is unsupported",
314 "schemaVersion", {}, "climbing.anchor_baker"));
316 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "anchor bake request has missing or invalid fields",
317 {}, {}, "climbing.anchor_baker"));
318 }
319 request.ledges.reserve(ledges->size());
320 for (std::size_t index = 0; index < ledges->size(); ++index) {
321 const auto* object = (*ledges)[index].getIf<eve::Value::Object>();
323 if (!object || !readString(*object, "id", source.id) || !readVecArray(*object, "points", source.points) ||
324 !readVecField(*object, "localNormal", source.localNormal) ||
325 !readVecArray(*object, "localNormals", source.localNormals) ||
326 !readBool(*object, "closed", source.closed) || !readStrings(*object, "tags", source.tags))
328 eve::DiagnosticCode::ParseError, "ledge bake source has missing or invalid fields",
329 "ledges." + std::to_string(index), {}, "climbing.anchor_baker"));
330 request.ledges.push_back(std::move(source));
331 }
332 request.ladders.reserve(ladders->size());
333 for (std::size_t index = 0; index < ladders->size(); ++index) {
334 const auto* object = (*ladders)[index].getIf<eve::Value::Object>();
336 if (!object || !readString(*object, "id", source.id) ||
337 !readVecField(*object, "bottomCenter", source.bottomCenter) ||
338 !readVecField(*object, "localUp", source.localUp) ||
339 !readVecField(*object, "localNormal", source.localNormal) ||
340 !readFloat(*object, "rungSpacing", source.rungSpacing) || !readFloat(*object, "width", source.width) ||
341 !readUInt32(*object, "rungCount", source.rungCount) || !readStrings(*object, "tags", source.tags))
343 eve::DiagnosticCode::ParseError, "ladder bake source has missing or invalid fields",
344 "ladders." + std::to_string(index), {}, "climbing.anchor_baker"));
345 request.ladders.push_back(std::move(source));
346 }
348}
349
351 if (request.graphId.empty() || request.sourceGeometryContentId.empty())
353 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "graph and source geometry ids are required",
354 "identity", {}, "climbing.anchor_baker"));
355 if (!finite(request.settings.handSpacing) || request.settings.handSpacing <= 0.f ||
356 !finite(request.settings.hangingFeetDrop) || request.settings.hangingFeetDrop <= 0.f ||
357 !finite(request.settings.ladderMountOffset) || request.settings.ladderMountOffset < 0.f ||
358 request.settings.occupancySlots == 0 || request.settings.occupancySlots > 8)
360 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "bake settings are outside supported bounds",
361 "settings", {}, "climbing.anchor_baker"));
362 if (request.ledges.empty() && request.ladders.empty())
364 eve::DiagnosticCode::InvalidArgument, "at least one ledge or ladder source is required", "sources", {},
365 "climbing.anchor_baker"));
366
368 result.graph.id = request.graphId;
369 result.graph.sourceGeometryContentId = request.sourceGeometryContentId;
370 result.graph.buildSettingsHash = hexHash(request);
371 std::unordered_set<std::string> sourceIds;
372
373 for (std::size_t sourceIndex = 0; sourceIndex < request.ledges.size(); ++sourceIndex) {
374 const auto& source = request.ledges[sourceIndex];
375 const std::string path = "ledges." + std::to_string(sourceIndex);
376 if (source.id.empty() || !sourceIds.emplace(source.id).second || source.points.size() < 2 ||
377 (!source.localNormals.empty() && source.localNormals.size() != source.points.size()) ||
378 !uniqueNonEmpty(source.tags))
380 eve::DiagnosticCode::InvalidArgument, "ledge id/tags/normals are invalid or it needs two points", path,
381 {}, "climbing.anchor_baker"));
382 std::vector<std::string> ids;
383 ids.reserve(source.points.size());
384 for (std::size_t index = 0; index < source.points.size(); ++index) {
385 if (!finite(source.points[index]))
388 path + ".points", {}, "climbing.anchor_baker"));
389 const std::size_t previous = index == 0 ? (source.closed ? source.points.size() - 1 : 0) : index - 1;
390 const std::size_t next = index + 1 == source.points.size() ? (source.closed ? 0 : index) : index + 1;
391 const Vec3 normalValue = source.localNormals.empty() ? source.localNormal : source.localNormals[index];
392 auto normalResult = normalized(normalValue, path + ".localNormals." + std::to_string(index));
393 if (!normalResult) return eve::Result<ClimbingAnchorBakeResult>::failure(normalResult.status());
394 const Vec3 normal = normalResult.value();
395 const Vec3 segment = index + 1 < source.points.size() || source.closed
396 ? subtract(source.points[next], source.points[index])
397 : subtract(source.points[index], source.points[previous]);
398 auto tangentResult = normalized(segment,
399 path + ".points." + std::to_string(index));
400 if (!tangentResult) return eve::Result<ClimbingAnchorBakeResult>::failure(tangentResult.status());
401 Vec3 tangent = tangentResult.value();
402 tangent = subtract(tangent, multiply(normal, dot(tangent, normal)));
403 tangentResult = normalized(tangent, path + ".frame." + std::to_string(index));
404 if (!tangentResult) return eve::Result<ClimbingAnchorBakeResult>::failure(tangentResult.status());
405 tangent = tangentResult.value();
406 ClimbingAnchorKind kind = ClimbingAnchorKind::Ledge;
407 if (index != 0 && (index + 1 != source.points.size() || source.closed)) {
408 auto incoming = normalized(subtract(source.points[index], source.points[previous]), path);
409 auto outgoing = normalized(subtract(source.points[next], source.points[index]), path);
410 if (!incoming) return eve::Result<ClimbingAnchorBakeResult>::failure(incoming.status());
411 if (!outgoing) return eve::Result<ClimbingAnchorBakeResult>::failure(outgoing.status());
412 const float turn = cross(incoming.value(), outgoing.value()).y;
413 if (std::fabs(turn) > 0.05f)
414 kind = turn > 0.f ? ClimbingAnchorKind::CornerOuter : ClimbingAnchorKind::CornerInner;
415 }
416 const std::string id = source.id + "." + std::to_string(index);
417 ids.push_back(id);
418 result.graph.nodes.push_back(ledgeNode(id, kind, source.points[index], normal, tangent,
419 request.settings, source.tags));
420 ++result.ledgeNodeCount;
421 }
422 const std::size_t edgeCount = source.closed ? ids.size() : ids.size() - 1;
423 for (std::size_t index = 0; index < edgeCount; ++index) {
424 const std::size_t next = (index + 1) % ids.size();
425 const auto& target = result.graph.nodes[result.graph.nodes.size() - ids.size() + next];
426 result.graph.edges.push_back({ids[index], ids[next],
427 target.kind == ClimbingAnchorKind::Ledge
428 ? ClimbingAnchorEdgeKind::Shimmy
429 : ClimbingAnchorEdgeKind::Corner,
430 true, {}, {}});
431 }
432 }
433
434 for (std::size_t sourceIndex = 0; sourceIndex < request.ladders.size(); ++sourceIndex) {
435 const auto& source = request.ladders[sourceIndex];
436 const std::string path = "ladders." + std::to_string(sourceIndex);
437 if (source.id.empty() || !sourceIds.emplace(source.id).second || !finite(source.bottomCenter) ||
438 !finite(source.rungSpacing) || source.rungSpacing <= 0.f || !finite(source.width) ||
439 source.width <= 0.f || source.rungCount < 2 || source.rungCount > 1024 ||
440 !uniqueNonEmpty(source.tags))
443 "ladder source is outside supported bounds", path, {}, "climbing.anchor_baker"));
444 auto upResult = normalized(source.localUp, path + ".localUp");
445 auto normalResult = normalized(source.localNormal, path + ".localNormal");
446 if (!upResult) return eve::Result<ClimbingAnchorBakeResult>::failure(upResult.status());
447 if (!normalResult) return eve::Result<ClimbingAnchorBakeResult>::failure(normalResult.status());
448 const Vec3 up = upResult.value();
449 const Vec3 normal = normalResult.value();
450 auto tangentResult = normalized(cross(up, normal), path + ".frame");
451 if (!tangentResult) return eve::Result<ClimbingAnchorBakeResult>::failure(tangentResult.status());
452 const Vec3 tangent = tangentResult.value();
453 std::vector<std::string> ids;
454 ids.reserve(source.rungCount);
455 for (std::uint32_t index = 0; index < source.rungCount; ++index) {
457 node.id = source.id + ".rung." + std::to_string(index);
458 node.kind = ClimbingAnchorKind::LadderRung;
459 node.localPosition = add(source.bottomCenter, multiply(up, source.rungSpacing * static_cast<float>(index)));
460 node.localNormal = normal;
461 node.localTangent = tangent;
462 node.leftHandSocket = add(node.localPosition, multiply(tangent, -source.width * 0.5f));
463 node.rightHandSocket = add(node.localPosition, multiply(tangent, source.width * 0.5f));
464 node.feetSocket = add(node.localPosition, multiply(up, -source.rungSpacing * 2.f));
465 node.occupancySlots = request.settings.occupancySlots;
466 node.tags = source.tags;
467 node.tags.push_back("ladder");
468 std::sort(node.tags.begin(), node.tags.end());
469 node.tags.erase(std::unique(node.tags.begin(), node.tags.end()), node.tags.end());
470 ids.push_back(node.id);
471 result.graph.nodes.push_back(std::move(node));
472 ++result.ladderNodeCount;
473 }
474 auto endpointTags = source.tags;
475 endpointTags.push_back("ladder_mount");
476 std::sort(endpointTags.begin(), endpointTags.end());
477 endpointTags.erase(std::unique(endpointTags.begin(), endpointTags.end()), endpointTags.end());
478 const std::string mountId = source.id + ".mount";
479 result.graph.nodes.push_back(ledgeNode(
480 mountId, ClimbingAnchorKind::Ledge,
481 add(source.bottomCenter, multiply(normal, request.settings.ladderMountOffset)), normal, tangent,
482 request.settings, endpointTags));
483 endpointTags = source.tags;
484 endpointTags.push_back("ladder_dismount");
485 std::sort(endpointTags.begin(), endpointTags.end());
486 endpointTags.erase(std::unique(endpointTags.begin(), endpointTags.end()), endpointTags.end());
487 const std::string dismountId = source.id + ".dismount";
488 const Vec3 top = add(source.bottomCenter,
489 multiply(up, source.rungSpacing * static_cast<float>(source.rungCount - 1)));
490 result.graph.nodes.push_back(ledgeNode(
491 dismountId, ClimbingAnchorKind::Ledge,
492 add(add(top, multiply(up, request.settings.ladderMountOffset)),
493 multiply(normal, request.settings.ladderMountOffset)),
494 normal, tangent, request.settings, endpointTags));
495 result.graph.edges.push_back({mountId, ids.front(), ClimbingAnchorEdgeKind::Mount, false, {}, {}});
496 for (std::size_t index = 0; index + 1 < ids.size(); ++index)
497 result.graph.edges.push_back({ids[index], ids[index + 1], ClimbingAnchorEdgeKind::Climb, true, {}, {}});
498 result.graph.edges.push_back(
499 {ids.back(), dismountId, ClimbingAnchorEdgeKind::Dismount, false, {}, {}});
500 }
501
502 auto valid = validateClimbingAnchorGraphDefinition(result.graph);
504 auto encoded = encodeClimbingAnchorGraphDefinition(result.graph);
505 if (!encoded) return eve::Result<ClimbingAnchorBakeResult>::failure(encoded.status());
506 auto canonical = decodeClimbingAnchorGraphDefinition(encoded.value());
507 if (!canonical) return eve::Result<ClimbingAnchorBakeResult>::failure(canonical.status());
508 result.graph = std::move(canonical.value());
509 return eve::Result<ClimbingAnchorBakeResult>::success(std::move(result));
510}
511
517 overlay.graphId = graph.id;
518 overlay.buildSettingsHash = graph.buildSettingsHash;
519 overlay.nodes.reserve(graph.nodes.size());
520 for (const auto& node : graph.nodes)
521 overlay.nodes.push_back({node.id, node.kind, node.localPosition, node.localNormal, node.localTangent,
522 node.leftHandSocket, node.rightHandSocket, node.feetSocket,
523 node.occupancySlots});
524 overlay.edges.reserve(graph.edges.size());
525 for (const auto& edge : graph.edges)
526 overlay.edges.push_back({edge.from, edge.to, edge.kind, edge.bidirectional});
527 std::sort(overlay.nodes.begin(), overlay.nodes.end(),
528 [](const auto& lhs, const auto& rhs) { return lhs.id < rhs.id; });
529 std::sort(overlay.edges.begin(), overlay.edges.end(), [](const auto& lhs, const auto& rhs) {
530 if (lhs.from != rhs.from) return lhs.from < rhs.from;
531 if (lhs.to != rhs.to) return lhs.to < rhs.to;
532 return lhs.kind < rhs.kind;
533 });
535}
536
537} // namespace eve::climbing
LogicalId target
double value
SQInteger top
int root
Definition AnimSmr.cpp:119
std::string output
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
Deterministic authoring primitives, baker, and renderer-neutral editor overlay for climbing anchors.
std::map< std::string, Var > values
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
const GltfImportRequest & request
glm::uvec4 ids
HexVec3 up
std::string text
TokenKind kind
std::array< float, 3 > position
std::array< float, 3 > scale
std::string name
bool valid
Texture * normal
const std::string * tag
graphics::Canvas * previous
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
bool finite
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
std::string path
Definition PlayHost.cpp:110
std::shared_ptr< const std::vector< glm::vec2 > > points
const RoadNode * node
const RoadEdge * edge
double number
bool found
bool boolean
std::uint32_t count
TacticalUnit::TurnResources turn
TerrainThermalSettings settings
Json object
uint32_t index
const UnitySourceAsset & source
std::vector< int > edges
glm::vec3 point
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
static Value array(Array value)
Compatibility factory for an array value.
Definition Value.h:112
std::vector< Value > Array
Definition Value.h:33
static Value object(Object value)
Compatibility factory for an object value.
Definition Value.h:114
Result< float > readFloat(const Accessor &accessor, std::uint32_t element, std::uint32_t component)
Read one bounded finite FLOAT component.
eve::Result< ClimbingAnchorBakeResult > bakeClimbingAnchorGraph(const ClimbingAnchorBakeRequest &request)
Bakes deterministic ledge and ladder topology from owning semantic geometry samples.
eve::Result< eve::Value > encodeClimbingAnchorBakeRequest(const ClimbingAnchorBakeRequest &request)
Encodes an owning bake request for editor/script transport.
ClimbingAnchorKind
Authored semantic of one explicit climbing anchor.
eve::Result< ClimbingAnchorAuthoringOverlay > inspectClimbingAnchorGraphAuthoring(const ClimbingAnchorGraphDefinition &graph)
Builds a validated owning snapshot for an editor or debug-draw adapter.
eve::Result< void > validateClimbingAnchorGraphDefinition(const ClimbingAnchorGraphDefinition &graph)
Validates all graph identities, frames, slots, tags, and edge endpoints.
eve::Result< ClimbingAnchorBakeRequest > decodeClimbingAnchorBakeRequest(const eve::Value &value)
Decodes all known bake request fields without publishing a graph.
bool readString(const eve::Value::Object &object, const char *name, std::string &output)
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
const EditorValue * field(const EditorValue &value, const char *name)
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Bounded owning editor snapshot; consumers may render it without retaining graph pointers.
std::vector< ClimbingAnchorOverlayNode > nodes
Complete owning input transaction for deterministic anchor-graph baking.
static constexpr std::int64_t SchemaVersion
Current editor-to-baker transaction schema version.
static constexpr std::string_view SchemaId
Canonical schema id for editor-to-baker transactions.
Measured bake result used by tools and build telemetry.
Versioned graph asset; topology is authoritative while collision remains owned by Physics.
std::vector< ClimbingAnchorNodeDefinition > nodes
std::vector< ClimbingAnchorEdgeDefinition > edges
One body-local anchor node with deterministic occupancy slots.
Body-local ladder description supplied by an editor geometry extractor.
Ordered body-local ledge samples supplied by an editor geometry extractor.
Small owning world-space vector used at climbing module boundaries.