载入中...
搜索中...
未找到
GeometryCollectionAsset.cpp
浏览该文件的文档.
2
4
5#include <cmath>
6#include <initializer_list>
7#include <limits>
8#include <string>
9#include <utility>
10
11namespace eve::physics {
12namespace {
13
14const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
15 const auto found = object.find(std::string(name));
16 return found == object.end() ? nullptr : &found->second;
17}
18
19bool hasExactFields(const eve::Value::Object& object, std::initializer_list<std::string_view> expected) {
20 if (object.size() != expected.size()) return false;
21 for (const auto name : expected)
22 if (!object.contains(std::string(name))) return false;
23 return true;
24}
25
26eve::Result<std::int64_t> integer64(const eve::Value::Object& object, std::string_view name) {
27 const eve::Value* value = field(object, name);
28 if (!value || !value->isInt64())
30 eve::DiagnosticCode::InvalidArgument, "geometry-collection field must be an integer", std::string(name)));
32}
33
34eve::Result<float> number(const eve::Value::Object& object, std::string_view name) {
35 const eve::Value* value = field(object, name);
36 if (!value || !value->isNumeric())
38 eve::DiagnosticCode::InvalidArgument, "geometry-collection field must be numeric", std::string(name)));
39 const double parsed = value->isDouble() ? value->asDouble() : static_cast<double>(value->asInt());
40 if (!std::isfinite(parsed) || parsed < -std::numeric_limits<float>::max() ||
41 parsed > std::numeric_limits<float>::max())
43 eve::DiagnosticCode::InvalidArgument, "geometry-collection number must be a finite float",
44 std::string(name)));
45 return eve::Result<float>::success(static_cast<float>(parsed));
46}
47
48eve::Result<bool> boolean(const eve::Value::Object& object, std::string_view name) {
49 const eve::Value* value = field(object, name);
50 if (!value || !value->isBool())
52 eve::DiagnosticCode::InvalidArgument, "geometry-collection field must be boolean", std::string(name)));
53 return eve::Result<bool>::success(value->asBool());
54}
55
56eve::Result<GeometryCollectionBone> decodeBone(const eve::Value& value, std::uint32_t schemaVersion) {
57 const auto* object = value.getIf<eve::Value::Object>();
58 if (!object)
60 eve::DiagnosticCode::InvalidArgument, "geometry-collection bone must be an object", "bones"));
61 const bool v1 = schemaVersion == GeometryCollectionAsset::SchemaVersionV1;
62 const bool v2 = schemaVersion == GeometryCollectionAsset::SchemaVersion;
63 if (!v1 && !v2)
65 eve::DiagnosticCode::Unsupported, "geometry-collection bone schema version is unsupported", "bones"));
66 if (v1 && !hasExactFields(*object, {"halfExtentX", "halfExtentY", "halfExtentZ", "localX", "localY", "localZ",
67 "mass", "density", "friction", "restitution", "anchoredDefault"}))
69 eve::DiagnosticCode::InvalidArgument, "geometry-collection bone has unknown or missing fields", "bones"));
70 if (v2 && !hasExactFields(*object, {"halfExtentX", "halfExtentY", "halfExtentZ", "localX", "localY", "localZ",
71 "mass", "density", "friction", "restitution", "anchoredDefault", "clusterId",
72 "fractureLevel"}))
74 eve::DiagnosticCode::InvalidArgument, "geometry-collection bone has unknown or missing fields", "bones"));
75 GeometryCollectionBone bone;
76 auto hx = number(*object, "halfExtentX");
77 if (!hx) return eve::Result<GeometryCollectionBone>::failure(hx.status());
78 auto hy = number(*object, "halfExtentY");
79 if (!hy) return eve::Result<GeometryCollectionBone>::failure(hy.status());
80 auto hz = number(*object, "halfExtentZ");
81 if (!hz) return eve::Result<GeometryCollectionBone>::failure(hz.status());
82 auto lx = number(*object, "localX");
83 if (!lx) return eve::Result<GeometryCollectionBone>::failure(lx.status());
84 auto ly = number(*object, "localY");
85 if (!ly) return eve::Result<GeometryCollectionBone>::failure(ly.status());
86 auto lz = number(*object, "localZ");
87 if (!lz) return eve::Result<GeometryCollectionBone>::failure(lz.status());
88 auto mass = number(*object, "mass");
89 if (!mass) return eve::Result<GeometryCollectionBone>::failure(mass.status());
90 auto density = number(*object, "density");
91 if (!density) return eve::Result<GeometryCollectionBone>::failure(density.status());
92 auto friction = number(*object, "friction");
93 if (!friction) return eve::Result<GeometryCollectionBone>::failure(friction.status());
94 auto restitution = number(*object, "restitution");
96 auto anchored = boolean(*object, "anchoredDefault");
97 if (!anchored) return eve::Result<GeometryCollectionBone>::failure(anchored.status());
98 bone.halfExtentX = hx.value();
99 bone.halfExtentY = hy.value();
100 bone.halfExtentZ = hz.value();
101 bone.localX = lx.value();
102 bone.localY = ly.value();
103 bone.localZ = lz.value();
104 bone.mass = mass.value();
105 bone.density = density.value();
106 bone.friction = friction.value();
107 bone.restitution = restitution.value();
108 bone.anchoredDefault = anchored.value();
109 bone.clusterId = 0;
110 bone.fractureLevel = 0;
111 if (v2) {
112 auto clusterId = integer64(*object, "clusterId");
114 auto fractureLevel = integer64(*object, "fractureLevel");
115 if (!fractureLevel) return eve::Result<GeometryCollectionBone>::failure(fractureLevel.status());
116 if (clusterId.value() < std::numeric_limits<int>::min() ||
117 clusterId.value() > std::numeric_limits<int>::max() ||
118 fractureLevel.value() < std::numeric_limits<int>::min() ||
119 fractureLevel.value() > std::numeric_limits<int>::max())
121 eve::DiagnosticCode::InvalidArgument, "geometry-collection cluster fields out of int range", "bones"));
122 bone.clusterId = static_cast<int>(clusterId.value());
123 bone.fractureLevel = static_cast<int>(fractureLevel.value());
124 }
126}
127
129 const auto* object = value.getIf<eve::Value::Object>();
130 if (!object)
132 eve::DiagnosticCode::InvalidArgument, "geometry-collection edge must be an object", "edges"));
133 if (!hasExactFields(*object, {"boneA", "boneB", "strainThreshold"}))
135 eve::DiagnosticCode::InvalidArgument, "geometry-collection edge has unknown or missing fields", "edges"));
136 auto a = integer64(*object, "boneA");
137 if (!a) return eve::Result<GeometryCollectionEdge>::failure(a.status());
138 auto b = integer64(*object, "boneB");
139 if (!b) return eve::Result<GeometryCollectionEdge>::failure(b.status());
140 auto threshold = number(*object, "strainThreshold");
141 if (!threshold) return eve::Result<GeometryCollectionEdge>::failure(threshold.status());
142 if (a.value() < std::numeric_limits<int>::min() || a.value() > std::numeric_limits<int>::max() ||
143 b.value() < std::numeric_limits<int>::min() || b.value() > std::numeric_limits<int>::max())
145 eve::DiagnosticCode::InvalidArgument, "geometry-collection edge bone index is out of int range", "edges"));
146 GeometryCollectionEdge edge;
147 edge.boneA = static_cast<int>(a.value());
148 edge.boneB = static_cast<int>(b.value());
149 edge.strainThreshold = threshold.value();
151}
152
153eve::Value encodeBone(const GeometryCollectionBone& bone) {
155 object["halfExtentX"] = static_cast<double>(bone.halfExtentX);
156 object["halfExtentY"] = static_cast<double>(bone.halfExtentY);
157 object["halfExtentZ"] = static_cast<double>(bone.halfExtentZ);
158 object["localX"] = static_cast<double>(bone.localX);
159 object["localY"] = static_cast<double>(bone.localY);
160 object["localZ"] = static_cast<double>(bone.localZ);
161 object["mass"] = static_cast<double>(bone.mass);
162 object["density"] = static_cast<double>(bone.density);
163 object["friction"] = static_cast<double>(bone.friction);
164 object["restitution"] = static_cast<double>(bone.restitution);
165 object["anchoredDefault"] = bone.anchoredDefault;
166 object["clusterId"] = static_cast<std::int64_t>(bone.clusterId);
167 object["fractureLevel"] = static_cast<std::int64_t>(bone.fractureLevel);
168 return eve::Value(std::move(object));
169}
170
171eve::Value encodeEdge(const GeometryCollectionEdge& edge) {
173 object["boneA"] = static_cast<std::int64_t>(edge.boneA);
174 object["boneB"] = static_cast<std::int64_t>(edge.boneB);
175 object["strainThreshold"] = static_cast<double>(edge.strainThreshold);
176 return eve::Value(std::move(object));
177}
178
179} // namespace
180
182 if (bones.empty())
184 "geometry-collection requires at least one bone",
185 "bones"));
186 if (bones.size() > 4096)
188 eve::DiagnosticCode::InvalidArgument, "geometry-collection exceeds the 4096-bone limit", "bones"));
189 if (edges.size() > 16384)
191 eve::DiagnosticCode::InvalidArgument, "geometry-collection exceeds the 16384-edge limit", "edges"));
192 const auto finitePositive = [](float value) { return std::isfinite(value) && value > 0.f; };
193 const auto finiteNonNeg = [](float value) { return std::isfinite(value) && value >= 0.f; };
194 for (std::size_t i = 0; i < bones.size(); ++i) {
195 const auto& bone = bones[i];
196 if (!finitePositive(bone.halfExtentX) || !finitePositive(bone.halfExtentY) || !finitePositive(bone.halfExtentZ))
198 eve::DiagnosticCode::InvalidArgument, "bone half-extents must be finite and positive", "bones"));
199 if (!std::isfinite(bone.localX) || !std::isfinite(bone.localY) || !std::isfinite(bone.localZ))
201 eve::DiagnosticCode::InvalidArgument, "bone local pose must be finite", "bones"));
202 if (!finitePositive(bone.mass) || !finitePositive(bone.density))
204 eve::DiagnosticCode::InvalidArgument, "bone mass and density must be finite and positive", "bones"));
205 if (!finiteNonNeg(bone.friction) || bone.friction > 1.f || !finiteNonNeg(bone.restitution) ||
206 bone.restitution > 1.f)
208 eve::DiagnosticCode::InvalidArgument, "bone friction/restitution must be in [0, 1]", "bones"));
209 if (bone.clusterId < 0 || bone.fractureLevel < 0)
211 eve::DiagnosticCode::InvalidArgument, "bone clusterId and fractureLevel must be non-negative",
212 "bones"));
213 (void)i;
214 }
215 for (const auto& edge : edges) {
216 if (edge.boneA < 0 || edge.boneB < 0 || static_cast<std::size_t>(edge.boneA) >= bones.size() ||
217 static_cast<std::size_t>(edge.boneB) >= bones.size() || edge.boneA == edge.boneB)
219 eve::DiagnosticCode::InvalidArgument, "edge bone indices must name two distinct bones", "edges"));
220 if (!std::isfinite(edge.strainThreshold) || edge.strainThreshold <= 0.f)
222 eve::DiagnosticCode::InvalidArgument, "edge strainThreshold must be finite and positive", "edges"));
223 }
225}
226
228 auto valid = validate();
229 if (!valid) return eve::Result<eve::Value>::failure(valid.status());
230 eve::Value::Array boneValues;
231 boneValues.reserve(bones.size());
232 for (const auto& bone : bones) boneValues.push_back(encodeBone(bone));
233 eve::Value::Array edgeValues;
234 edgeValues.reserve(edges.size());
235 for (const auto& edge : edges) edgeValues.push_back(encodeEdge(edge));
237 object["schema"] = std::string(SchemaId);
238 object["schemaVersion"] = static_cast<std::int64_t>(SchemaVersion);
239 object["bones"] = eve::Value(std::move(boneValues));
240 object["edges"] = eve::Value(std::move(edgeValues));
241 return eve::Result<eve::Value>::success(eve::Value(std::move(object)));
242}
243
247 schema.id = std::string(SchemaId);
248 schema.version = static_cast<int>(SchemaVersion);
249 schema.title = "Geometry Collection Asset";
250 schema.description =
251 "Pre-authored bones (with cluster membership) and connection graph for Chaos-style destruction.";
252 schema.additionalProperties = false;
253 auto makeField = [](std::string name, ValueType type, bool required) {
255 field.name = std::move(name);
256 field.type = type;
257 field.required = required;
258 return field;
259 };
260 schema.fields = {
261 makeField("schema", ValueType::String, true),
262 makeField("schemaVersion", ValueType::Integer, true),
263 makeField("bones", ValueType::Array, true),
264 makeField("edges", ValueType::Array, true),
265 };
266 return schema;
267}
268
276
278 const auto* object = value.getIf<eve::Value::Object>();
279 if (!object)
281 eve::DiagnosticCode::InvalidArgument, "geometry-collection asset must be an object", "asset"));
282 if (!hasExactFields(*object, {"schema", "schemaVersion", "bones", "edges"}))
284 eve::DiagnosticCode::InvalidArgument, "geometry-collection asset has unknown or missing fields", "asset"));
285 const eve::Value* schemaField = field(*object, "schema");
286 if (!schemaField || !schemaField->isString() || schemaField->asString() != SchemaId)
288 eve::DiagnosticCode::InvalidArgument, "geometry-collection schema id mismatch", "schema"));
289 auto version = integer64(*object, "schemaVersion");
290 if (!version) return eve::Result<GeometryCollectionAsset>::failure(version.status());
291 if (version.value() != static_cast<std::int64_t>(SchemaVersion) &&
292 version.value() != static_cast<std::int64_t>(SchemaVersionV1))
294 eve::DiagnosticCode::Unsupported, "geometry-collection schema version is unsupported", "schemaVersion"));
295 const auto schemaVersion = static_cast<std::uint32_t>(version.value());
296 const eve::Value* bonesValue = field(*object, "bones");
297 const auto* boneArray = bonesValue ? bonesValue->getIf<eve::Value::Array>() : nullptr;
298 if (!boneArray)
300 eve::DiagnosticCode::InvalidArgument, "geometry-collection bones must be an array", "bones"));
301 const eve::Value* edgesValue = field(*object, "edges");
302 const auto* edgeArray = edgesValue ? edgesValue->getIf<eve::Value::Array>() : nullptr;
303 if (!edgeArray)
305 eve::DiagnosticCode::InvalidArgument, "geometry-collection edges must be an array", "edges"));
307 asset.bones.reserve(boneArray->size());
308 for (const auto& boneValue : *boneArray) {
309 auto bone = decodeBone(boneValue, schemaVersion);
311 asset.bones.push_back(std::move(bone.value()));
312 }
313 asset.edges.reserve(edgeArray->size());
314 for (const auto& edgeValue : *edgeArray) {
315 auto edge = decodeEdge(edgeValue);
317 asset.edges.push_back(edge.value());
318 }
319 auto valid = asset.validate();
321 auto registered = ensureSchemaRegistered();
322 if (!registered) return eve::Result<GeometryCollectionAsset>::failure(registered.status());
323 return eve::Result<GeometryCollectionAsset>::success(std::move(asset));
324}
325
329 left.halfExtentX = 0.5f;
330 left.halfExtentY = 0.5f;
331 left.halfExtentZ = 0.5f;
332 left.localX = -0.55f;
333 left.localY = 1.0f;
334 left.mass = 2.f;
336 right.localX = 0.55f;
337 asset.bones.push_back(left);
338 asset.bones.push_back(right);
340 edge.boneA = 0;
341 edge.boneB = 1;
342 edge.strainThreshold = strainThreshold;
343 asset.edges.push_back(edge);
344 auto valid = asset.validate();
346 return eve::Result<GeometryCollectionAsset>::success(std::move(asset));
347}
348
350 if (!std::isfinite(interClusterStrain) || interClusterStrain <= 0.f)
352 eve::DiagnosticCode::InvalidArgument, "interClusterStrain must be finite and positive",
353 "interClusterStrain"));
355 asset.bones.reserve(6);
356 for (int cluster = 0; cluster < 2; ++cluster) {
357 for (int level = 0; level < 3; ++level) {
359 bone.halfExtentX = 0.45f;
360 bone.halfExtentY = 0.45f;
361 bone.halfExtentZ = 0.45f;
362 bone.localX = cluster == 0 ? -0.55f : 0.55f;
363 bone.localY = 0.5f + static_cast<float>(level) * 0.95f;
364 bone.localZ = 0.f;
365 bone.mass = 1.5f;
366 bone.density = 1.f;
367 bone.anchoredDefault = level == 0;
368 bone.clusterId = cluster;
369 bone.fractureLevel = 0;
370 asset.bones.push_back(bone);
371 }
372 }
373 auto addEdge = [&](int a, int b, float threshold) {
375 edge.boneA = a;
376 edge.boneB = b;
377 edge.strainThreshold = threshold;
378 asset.edges.push_back(edge);
379 };
380 // Vertical edges inside each cluster (stronger).
381 addEdge(0, 1, interClusterStrain * 2.f);
382 addEdge(1, 2, interClusterStrain * 2.f);
383 addEdge(3, 4, interClusterStrain * 2.f);
384 addEdge(4, 5, interClusterStrain * 2.f);
385 // Horizontal welds between clusters (weaker — tend to break first).
386 addEdge(0, 3, interClusterStrain);
387 addEdge(1, 4, interClusterStrain);
388 addEdge(2, 5, interClusterStrain);
389 auto valid = asset.validate();
391 return eve::Result<GeometryCollectionAsset>::success(std::move(asset));
392}
393
394} // namespace eve::physics
double value
HexVec3 left
HexVec3 right
std::uint32_t bone
bool required
std::string name
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int level
const RoadEdge * edge
double number
bool found
bool boolean
std::string string
double restitution
Json object
float size
Definition TreeMesh.cpp:156
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
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
std::map< std::string, Value > Object
Definition Value.h:34
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Type type() const
Return the active value type.
static eve::Result< SchemaRegistrationStatus > registerVersioned(const SchemaDefinition &definition)
Registers a new exact (id, version) entry without replacing it.
static const SchemaDefinition * resolve(const std::string &schemaId, int schemaVersion)
Resolves one exact (schemaId, schemaVersion) pair, or nullptr.
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
bool decodeEdge(const EditorValue &value, std::uint32_t &id, std::vector< procgen::road::RoadControlPoint > &points)
const EditorValue * field(const EditorValue &value, const char *name)
EditorValue encodeEdge(std::uint32_t id, const std::vector< procgen::road::RoadControlPoint > &points)
ValueType
JSON-compatible value kinds understood by a schema field.
Definition SchemaTypes.h:16
Immutable cooked/authored geometry-collection asset.
eve::Result< eve::Value > toValue() const
Encode the complete canonical version-2 value.
static eve::Result< void > ensureSchemaRegistered()
Idempotently register version 2 in the process schema registry.
static constexpr std::uint32_t SchemaVersion
std::vector< GeometryCollectionEdge > edges
static eve::Result< GeometryCollectionAsset > fromValue(const eve::Value &value)
Decode and validate version 2, or migrate version 1 (cluster fields = 0).
std::vector< GeometryCollectionBone > bones
static constexpr std::string_view SchemaId
static constexpr std::uint32_t SchemaVersionV1
static eve::Result< GeometryCollectionAsset > makeWeldedBoxesFixture(float strainThreshold=1.f)
Build a two-box welded fixture used by tests and the smoke example.
static eve::Result< GeometryCollectionAsset > makeClusterPillarFixture(float interClusterStrain=0.8f)
Build a 2×3 anchored pillar with two cook clusters for demos.
static eve::schema::SchemaDefinition schemaDefinition()
Build the tooling schema that mirrors validate().
eve::Result< void > validate() const
Validate bone/edge topology and numeric ranges.
One pre-authored leaf bone (box collision proxy) inside a geometry collection.
One undirected structural edge with a strain break threshold.
Metadata and validation constraints for one object member.
Definition SchemaTypes.h:77
Versioned runtime schema for a JSON object.
Definition SchemaTypes.h:93
std::vector< FieldDefinition > fields
Definition SchemaTypes.h:99