载入中...
搜索中...
未找到
FractureRecipe.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, "fracture-recipe 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, "fracture-recipe 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, "fracture-recipe number must be a finite float", std::string(name)));
44 return eve::Result<float>::success(static_cast<float>(parsed));
45}
46
47eve::Result<std::string> stringField(const eve::Value::Object& object, std::string_view name) {
48 const eve::Value* value = field(object, name);
49 if (!value || !value->isString())
51 eve::DiagnosticCode::InvalidArgument, "fracture-recipe field must be a string", std::string(name)));
52 return eve::Result<std::string>::success(value->asString());
53}
54
55eve::Result<std::uint64_t> u64Field(const eve::Value::Object& object, std::string_view name) {
56 auto parsed = integer64(object, name);
57 if (!parsed) return eve::Result<std::uint64_t>::failure(parsed.status());
58 if (parsed.value() < 0)
60 eve::DiagnosticCode::InvalidArgument, "fracture-recipe seed must be non-negative", std::string(name)));
61 return eve::Result<std::uint64_t>::success(static_cast<std::uint64_t>(parsed.value()));
62}
63
64eve::Result<std::vector<float>> floatArray(const eve::Value::Object& object, std::string_view name) {
65 const eve::Value* value = field(object, name);
66 const auto* array = value ? value->getIf<eve::Value::Array>() : nullptr;
67 if (!array)
69 eve::DiagnosticCode::InvalidArgument, "fracture-recipe field must be an array", std::string(name)));
70 std::vector<float> out;
71 out.reserve(array->size());
72 for (const auto& item : *array) {
73 if (!item.isNumeric())
75 eve::DiagnosticCode::InvalidArgument, "fracture-recipe array entries must be numeric",
76 std::string(name)));
77 const double parsed = item.isDouble() ? item.asDouble() : static_cast<double>(item.asInt());
78 if (!std::isfinite(parsed))
79 return eve::Result<std::vector<float>>::failure(eve::Diagnostic::error(
80 eve::DiagnosticCode::InvalidArgument, "fracture-recipe array entries must be finite",
81 std::string(name)));
82 out.push_back(static_cast<float>(parsed));
83 }
84 return eve::Result<std::vector<float>>::success(std::move(out));
85}
86
87const char* modeName(FractureMode mode) {
88 switch (mode) {
89 case FractureMode::UniformVoronoi: return "UniformVoronoi";
90 case FractureMode::ClusteredVoronoi: return "ClusteredVoronoi";
91 case FractureMode::Radial: return "Radial";
92 case FractureMode::Planar: return "Planar";
93 }
94 return "UniformVoronoi";
95}
96
97eve::Result<FractureMode> parseMode(const std::string& name) {
103 eve::DiagnosticCode::InvalidArgument, "unknown fracture mode", "mode"));
104}
105
106} // namespace
107
109 if (siteCountMin < 1 || siteCountMax < siteCountMin || siteCountMax > 4096)
111 eve::DiagnosticCode::InvalidArgument, "siteCountMin/Max must satisfy 1 <= min <= max <= 4096",
112 "siteCount"));
113 if (clusterCount < 1 || clusterCount > 256)
115 eve::DiagnosticCode::InvalidArgument, "clusterCount must be in [1, 256]", "clusterCount"));
116 if (!std::isfinite(clusterRadius) || clusterRadius <= 0.f)
118 eve::DiagnosticCode::InvalidArgument, "clusterRadius must be finite and positive", "clusterRadius"));
119 if (radialPlanes < 0 || radialPlanes > 32 || radialSpokes < 1 || radialSpokes > 64)
121 eve::DiagnosticCode::InvalidArgument, "radialPlanes/Spokes are outside supported ranges", "radial"));
122 if (planeNormals.size() % 3 != 0)
124 eve::DiagnosticCode::InvalidArgument, "planeNormals must contain xyz triples", "planeNormals"));
125 if (planeOffsets.size() != planeNormals.size() / 3)
127 eve::DiagnosticCode::InvalidArgument, "planeOffsets must match planeNormals triple count", "planeOffsets"));
128 for (float value : planeNormals) {
129 if (!std::isfinite(value))
131 eve::DiagnosticCode::InvalidArgument, "planeNormals must be finite", "planeNormals"));
132 }
133 for (float value : planeOffsets) {
134 if (!std::isfinite(value))
136 eve::DiagnosticCode::InvalidArgument, "planeOffsets must be finite", "planeOffsets"));
137 }
138 if (mode == FractureMode::Planar && planeOffsets.empty())
140 eve::DiagnosticCode::InvalidArgument, "Planar mode requires at least one plane", "planeOffsets"));
141 if (randomStreamName.empty() || randomStreamName.size() > 128)
143 eve::DiagnosticCode::InvalidArgument, "randomStreamName must be a non-empty string up to 128 chars",
144 "randomStreamName"));
145 if (!std::isfinite(defaultStrainThreshold) || defaultStrainThreshold <= 0.f)
147 eve::DiagnosticCode::InvalidArgument, "defaultStrainThreshold must be finite and positive",
148 "defaultStrainThreshold"));
149 if (!std::isfinite(defaultDensity) || defaultDensity <= 0.f || !std::isfinite(defaultFriction) ||
150 defaultFriction < 0.f || defaultFriction > 1.f || !std::isfinite(defaultRestitution) ||
153 eve::DiagnosticCode::InvalidArgument, "default material fields are outside supported ranges",
154 "material"));
155 if (!std::isfinite(minimumThickness) || minimumThickness <= 0.f)
157 eve::DiagnosticCode::InvalidArgument, "minimumThickness must be finite and positive",
158 "minimumThickness"));
159 if (maximumBones < 1 || maximumBones > 4096)
161 eve::DiagnosticCode::InvalidArgument, "maximumBones must be in [1, 4096]", "maximumBones"));
162 if (interiorMaterialLogicalId.size() > 256)
164 eve::DiagnosticCode::InvalidArgument, "interiorMaterialLogicalId exceeds 256 chars",
165 "interiorMaterialLogicalId"));
167}
168
170 auto valid = validate();
171 if (!valid) return eve::Result<eve::Value>::failure(valid.status());
173 normals.reserve(planeNormals.size());
174 for (float value : planeNormals) normals.push_back(static_cast<double>(value));
175 eve::Value::Array offsets;
176 offsets.reserve(planeOffsets.size());
177 for (float value : planeOffsets) offsets.push_back(static_cast<double>(value));
179 object["schema"] = std::string(SchemaId);
180 object["schemaVersion"] = static_cast<std::int64_t>(SchemaVersion);
181 object["mode"] = std::string(modeName(mode));
182 object["siteCountMin"] = static_cast<std::int64_t>(siteCountMin);
183 object["siteCountMax"] = static_cast<std::int64_t>(siteCountMax);
184 object["clusterCount"] = static_cast<std::int64_t>(clusterCount);
185 object["clusterRadius"] = static_cast<double>(clusterRadius);
186 object["radialPlanes"] = static_cast<std::int64_t>(radialPlanes);
187 object["radialSpokes"] = static_cast<std::int64_t>(radialSpokes);
188 object["planeNormals"] = eve::Value(std::move(normals));
189 object["planeOffsets"] = eve::Value(std::move(offsets));
190 object["interiorMaterialLogicalId"] = interiorMaterialLogicalId;
191 object["randomStreamName"] = randomStreamName;
192 object["seed"] = static_cast<std::int64_t>(seed);
193 object["defaultStrainThreshold"] = static_cast<double>(defaultStrainThreshold);
194 object["defaultDensity"] = static_cast<double>(defaultDensity);
195 object["defaultFriction"] = static_cast<double>(defaultFriction);
196 object["defaultRestitution"] = static_cast<double>(defaultRestitution);
197 object["minimumThickness"] = static_cast<double>(minimumThickness);
198 object["maximumBones"] = static_cast<std::int64_t>(maximumBones);
199 return eve::Result<eve::Value>::success(eve::Value(std::move(object)));
200}
201
205 schema.id = std::string(SchemaId);
206 schema.version = static_cast<int>(SchemaVersion);
207 schema.title = "Fracture Recipe";
208 schema.description = "Deterministic cook recipe for geometry-collection pre-fracture.";
209 schema.additionalProperties = false;
210 auto makeField = [](std::string name, ValueType type, bool required) {
212 field.name = std::move(name);
213 field.type = type;
214 field.required = required;
215 return field;
216 };
217 schema.fields = {
218 makeField("schema", ValueType::String, true),
219 makeField("schemaVersion", ValueType::Integer, true),
220 makeField("mode", ValueType::String, true),
221 makeField("siteCountMin", ValueType::Integer, true),
222 makeField("siteCountMax", ValueType::Integer, true),
223 makeField("clusterCount", ValueType::Integer, true),
224 makeField("clusterRadius", ValueType::Number, true),
225 makeField("radialPlanes", ValueType::Integer, true),
226 makeField("radialSpokes", ValueType::Integer, true),
227 makeField("planeNormals", ValueType::Array, true),
228 makeField("planeOffsets", ValueType::Array, true),
229 makeField("interiorMaterialLogicalId", ValueType::String, true),
230 makeField("randomStreamName", ValueType::String, true),
231 makeField("seed", ValueType::Integer, true),
232 makeField("defaultStrainThreshold", ValueType::Number, true),
233 makeField("defaultDensity", ValueType::Number, true),
234 makeField("defaultFriction", ValueType::Number, true),
235 makeField("defaultRestitution", ValueType::Number, true),
236 makeField("minimumThickness", ValueType::Number, true),
237 makeField("maximumBones", ValueType::Integer, true),
238 };
239 return schema;
240}
241
243 if (eve::schema::SchemaRegistry::resolve(std::string(SchemaId), static_cast<int>(SchemaVersion)))
246 if (!registration.ok()) return eve::Result<void>::failure(registration.status());
248}
249
251 const auto* object = value.getIf<eve::Value::Object>();
252 if (!object)
254 eve::DiagnosticCode::InvalidArgument, "fracture-recipe must be an object", "recipe"));
255 if (!hasExactFields(*object,
256 {"schema", "schemaVersion", "mode", "siteCountMin", "siteCountMax", "clusterCount",
257 "clusterRadius", "radialPlanes", "radialSpokes", "planeNormals", "planeOffsets",
258 "interiorMaterialLogicalId", "randomStreamName", "seed", "defaultStrainThreshold",
259 "defaultDensity", "defaultFriction", "defaultRestitution", "minimumThickness",
260 "maximumBones"}))
262 eve::DiagnosticCode::InvalidArgument, "fracture-recipe has unknown or missing fields", "recipe"));
263 const eve::Value* schemaField = field(*object, "schema");
264 if (!schemaField || !schemaField->isString() || schemaField->asString() != SchemaId)
266 eve::DiagnosticCode::InvalidArgument, "fracture-recipe schema id mismatch", "schema"));
267 auto version = integer64(*object, "schemaVersion");
268 if (!version) return eve::Result<FractureRecipe>::failure(version.status());
269 if (version.value() != static_cast<std::int64_t>(SchemaVersion))
271 eve::DiagnosticCode::Unsupported, "fracture-recipe schema version is unsupported", "schemaVersion"));
272 auto modeString = stringField(*object, "mode");
273 if (!modeString) return eve::Result<FractureRecipe>::failure(modeString.status());
274 auto mode = parseMode(modeString.value());
275 if (!mode) return eve::Result<FractureRecipe>::failure(mode.status());
276
277 FractureRecipe recipe;
278 recipe.mode = mode.value();
279 auto siteMin = integer64(*object, "siteCountMin");
280 if (!siteMin) return eve::Result<FractureRecipe>::failure(siteMin.status());
281 auto siteMax = integer64(*object, "siteCountMax");
282 if (!siteMax) return eve::Result<FractureRecipe>::failure(siteMax.status());
283 auto clusters = integer64(*object, "clusterCount");
284 if (!clusters) return eve::Result<FractureRecipe>::failure(clusters.status());
285 auto clusterRadius = number(*object, "clusterRadius");
287 auto radialPlanes = integer64(*object, "radialPlanes");
289 auto radialSpokes = integer64(*object, "radialSpokes");
291 auto normals = floatArray(*object, "planeNormals");
293 auto offsets = floatArray(*object, "planeOffsets");
294 if (!offsets) return eve::Result<FractureRecipe>::failure(offsets.status());
295 auto interior = stringField(*object, "interiorMaterialLogicalId");
296 if (!interior) return eve::Result<FractureRecipe>::failure(interior.status());
297 auto stream = stringField(*object, "randomStreamName");
298 if (!stream) return eve::Result<FractureRecipe>::failure(stream.status());
299 auto seed = u64Field(*object, "seed");
300 if (!seed) return eve::Result<FractureRecipe>::failure(seed.status());
301 auto strain = number(*object, "defaultStrainThreshold");
302 if (!strain) return eve::Result<FractureRecipe>::failure(strain.status());
303 auto density = number(*object, "defaultDensity");
304 if (!density) return eve::Result<FractureRecipe>::failure(density.status());
305 auto friction = number(*object, "defaultFriction");
306 if (!friction) return eve::Result<FractureRecipe>::failure(friction.status());
307 auto restitution = number(*object, "defaultRestitution");
309 auto thickness = number(*object, "minimumThickness");
311 auto maxBones = integer64(*object, "maximumBones");
312 if (!maxBones) return eve::Result<FractureRecipe>::failure(maxBones.status());
313
314 auto clampInt = [](std::int64_t value) -> eve::Result<int> {
315 if (value < std::numeric_limits<int>::min() || value > std::numeric_limits<int>::max())
317 eve::DiagnosticCode::InvalidArgument, "integer field exceeds int range", "recipe"));
318 return eve::Result<int>::success(static_cast<int>(value));
319 };
320 auto siteMinI = clampInt(siteMin.value());
321 if (!siteMinI) return eve::Result<FractureRecipe>::failure(siteMinI.status());
322 auto siteMaxI = clampInt(siteMax.value());
323 if (!siteMaxI) return eve::Result<FractureRecipe>::failure(siteMaxI.status());
324 auto clustersI = clampInt(clusters.value());
325 if (!clustersI) return eve::Result<FractureRecipe>::failure(clustersI.status());
326 auto radialPlanesI = clampInt(radialPlanes.value());
327 if (!radialPlanesI) return eve::Result<FractureRecipe>::failure(radialPlanesI.status());
328 auto radialSpokesI = clampInt(radialSpokes.value());
329 if (!radialSpokesI) return eve::Result<FractureRecipe>::failure(radialSpokesI.status());
330 auto maxBonesI = clampInt(maxBones.value());
331 if (!maxBonesI) return eve::Result<FractureRecipe>::failure(maxBonesI.status());
332
333 recipe.siteCountMin = siteMinI.value();
334 recipe.siteCountMax = siteMaxI.value();
335 recipe.clusterCount = clustersI.value();
336 recipe.clusterRadius = clusterRadius.value();
337 recipe.radialPlanes = radialPlanesI.value();
338 recipe.radialSpokes = radialSpokesI.value();
339 recipe.planeNormals = std::move(normals.value());
340 recipe.planeOffsets = std::move(offsets.value());
341 recipe.interiorMaterialLogicalId = std::move(interior.value());
342 recipe.randomStreamName = std::move(stream.value());
343 recipe.seed = seed.value();
344 recipe.defaultStrainThreshold = strain.value();
345 recipe.defaultDensity = density.value();
346 recipe.defaultFriction = friction.value();
347 recipe.defaultRestitution = restitution.value();
348 recipe.minimumThickness = thickness.value();
349 recipe.maximumBones = maxBonesI.value();
350
351 auto valid = recipe.validate();
352 if (!valid) return eve::Result<FractureRecipe>::failure(valid.status());
353 auto registered = ensureSchemaRegistered();
354 if (!registered) return eve::Result<FractureRecipe>::failure(registered.status());
355 return eve::Result<FractureRecipe>::success(std::move(recipe));
356}
357
358} // namespace eve::physics
double value
float thickness
Definition CaveMesh.cpp:83
std::vector< float > normals
bool required
std::string name
bool valid
double number
bool found
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
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
FractureMode
Fracture pattern used when cooking a geometry collection.
const EditorValue * field(const EditorValue &value, const char *name)
ValueType
JSON-compatible value kinds understood by a schema field.
Definition SchemaTypes.h:16
Versioned cook recipe for Chaos-style pre-fracture (schema v1).
static eve::Result< void > ensureSchemaRegistered()
Ensure schema registered.
static constexpr std::uint32_t SchemaVersion
int maximumBones
Soft cap on produced leaf bones.
float minimumThickness
Minimum AABB axis length in metres; thinner meshes are rejected.
eve::Result< eve::Value > toValue() const
To value.
static constexpr std::string_view SchemaId
static eve::Result< FractureRecipe > fromValue(const eve::Value &value)
From value.
static eve::schema::SchemaDefinition schemaDefinition()
Schema definition.
eve::Result< void > validate() const
Validate.
std::vector< float > planeOffsets
std::vector< float > planeNormals
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