载入中...
搜索中...
未找到
PhysicsJointTarget.cpp
浏览该文件的文档.
2
3#include <cmath>
4#include <tuple>
5#include <utility>
6
7namespace eve::physics_editing {
8namespace {
9
10const EditorValue* field(const EditorValue& value, const char* key) {
11 const auto* object = value.getIf<EditorValue::Object>();
12 if (!object) return nullptr;
13 const auto found = object->find(key);
14 return found == object->end() ? nullptr : &found->second;
15}
16
17PropertyDescriptor jointProperty(const char* path, const char* label, const char* category, PropertyType type,
19 PropertyDescriptor result;
20 result.path = PropertyPath(path);
21 result.displayNameKey = label;
22 result.category = category;
23 result.type = type;
24 result.flags = PropertyFlag::Runtime;
25 result.defaultValue = std::move(value);
26 return result;
27}
28
29} // namespace
30
31PhysicsJointTarget::PhysicsJointTarget(std::string id) : id_(std::move(id)), values_(defaults()) {}
32
34 return {
35 TargetId(id_), "physics-joint-3d", revisionValue(), false, {CapabilityId("eve.editor.target.physics-joint")}};
36}
37
38void* PhysicsJointTarget::queryCapability(const CapabilityId& capability) {
39 return capability == CapabilityId("eve.editor.target.physics-joint") ? static_cast<IPropertyProvider*>(this)
40 : nullptr;
41}
42
44 if (operation.target != TargetId(id_) || operation.type != "physics.joint.property.set.v1")
45 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.physics.joint-operation"),
46 "Operation is not valid for this physics joint");
47 const EditorValue* pathValue = field(operation.payload, "path");
48 const EditorValue* value = field(operation.payload, "value");
49 const auto* path = pathValue ? pathValue->getIf<std::string>() : nullptr;
50 if (!path || !value)
51 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.physics.joint-payload"),
52 "Joint operation requires path and value");
53 auto descriptor = jointSchema().find(PropertyPath(*path));
54 if (!descriptor)
55 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.physics.joint-property"),
56 "Unknown joint property: " + *path);
57 auto valid = validatePropertyValue(*descriptor, *value);
58 if (!valid.ok()) return valid;
59 values_[*path] = *value;
61 widenDirty(0, 0);
62 return eve::editing::applied<void>();
63}
64
65std::unique_ptr<IDomainOperationTarget> PhysicsJointTarget::cloneDomainState() const {
66 return std::make_unique<PhysicsJointTarget>(*this);
67}
68
69Result<void> PhysicsJointTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> candidate) {
70 auto* typed = dynamic_cast<PhysicsJointTarget*>(candidate.get());
71 if (!typed || typed->id_ != id_)
72 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.physics.joint-candidate-mismatch"),
73 "Joint candidate belongs to another target");
74 *this = *typed;
75 return eve::editing::applied<void>();
76}
77
79 if (!selectionMatches(selection))
81 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "Selection does not belong to this joint",
82 "editor.physics.joint-selection", {}, "editor.PhysicsJointTarget"));
84}
85
86PropertySchema PhysicsJointTarget::schema(const SelectionSnapshot&) const { return jointSchema(); }
87
89 if (!selectionMatches(selection)) return {};
90 const auto found = values_.find(path.value());
91 return found == values_.end() ? PropertyReadResult{}
92 : PropertyReadResult{PropertyReadState::Value, found->second, {}};
93}
94
96 const EditorValue& value, PropertySetMode mode) const {
97 if (!selectionMatches(selection) || mode != PropertySetMode::Absolute)
98 return eve::editing::failed<DomainOperation>(
99 EditorStatus::Rejected, RuleId("editor.physics.joint-set"),
100 "Joint property requires its own selection and absolute assignment");
101 auto descriptor = jointSchema().find(path);
102 if (!descriptor)
103 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.physics.joint-property"),
104 "Unknown joint property: " + path.value());
105 auto valid = validatePropertyValue(*descriptor, value);
106 if (!valid.ok()) {
108 }
109 auto payload = [&](const EditorValue& assigned) {
111 object["path"] = path.value();
112 object["value"] = assigned;
113 return EditorValue(std::move(object));
114 };
116 operation.type = "physics.joint.property.set.v1";
117 operation.target = TargetId(id_);
118 operation.payload = payload(value);
119 operation.inverse = payload(values_.at(path.value()));
120 operation.hasInverse = true;
121 operation.affectedProperties.push_back(path.value());
122 operation.mergeKey = "physics.joint:" + id_ + ":" + path.value();
123 return eve::editing::applied<DomainOperation>(std::move(operation));
124}
125
127 const PropertyPath& path) const {
128 auto descriptor = jointSchema().find(path);
129 if (!descriptor)
130 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.physics.joint-property"),
131 "Unknown joint property: " + path.value());
132 return makeSet(selection, path, descriptor->defaultValue, PropertySetMode::Absolute);
133}
134
137 for (const auto& [path, value] : values_) properties[path] = value;
139 root["schemaVersion"] = 1;
140 root["properties"] = EditorValue(std::move(properties));
141 return EditorValue(std::move(root));
142}
143
144std::vector<EditorDiagnostic> PhysicsJointTarget::validate() const {
145 std::vector<EditorDiagnostic> diagnostics;
146 const auto text = [&](const char* path) { return *values_.at(path).getIf<std::string>(); };
147 const auto number = [&](const char* path) { return *values_.at(path).getIf<double>(); };
148 if (text("body.a").empty() || text("body.b").empty() || text("body.a") == text("body.b"))
149 diagnostics.push_back(eve::editing::ruleDiagnostic(
150 eve::DiagnosticCode::InvariantViolation, RuleId("editor.physics.joint-bodies"),
151 DiagnosticSeverity::Error, "Joint requires two distinct body references"));
152 const std::string kind = text("joint.kind");
153 if (kind == "prismatic" || kind == "wheel" || kind == "parallel") {
154 const auto& axis = *values_.at("joint.axis").getIf<EditorValue::Array>();
155 double lengthSquared = 0.0;
156 for (const EditorValue& component : axis) {
157 const double value = *component.getIf<double>();
158 lengthSquared += value * value;
159 }
160 if (lengthSquared < 1e-12)
161 diagnostics.push_back(eve::editing::ruleDiagnostic(
162 eve::DiagnosticCode::InvariantViolation, RuleId("editor.physics.joint-axis"),
163 DiagnosticSeverity::Error, "Prismatic and wheel joints require a non-zero axis"));
164 }
165 if (*values_.at("limit.enabled").getIf<bool>() && number("limit.minimum") > number("limit.maximum"))
166 diagnostics.push_back(eve::editing::ruleDiagnostic(
167 eve::DiagnosticCode::InvariantViolation, RuleId("editor.physics.joint-limit"),
168 DiagnosticSeverity::Error, "Joint limit minimum exceeds maximum"));
169 return diagnostics;
170}
171
172PropertySchema PhysicsJointTarget::jointSchema() {
173 PropertySchema result;
174 result.typeId = "physics.joint3d";
175 auto kind = jointProperty("joint.kind", "editor.physics.joint-kind", "joint", PropertyType::Enum, "distance");
176 kind.enumItems = {"distance", "revolute", "prismatic", "spherical", "wheel", "weld", "motor",
177 "parallel", "filter"};
178 result.properties.push_back(std::move(kind));
179 result.properties.push_back(jointProperty("body.a", "editor.physics.body-a", "joint", PropertyType::ObjectRef, ""));
180 result.properties.push_back(jointProperty("body.b", "editor.physics.body-b", "joint", PropertyType::ObjectRef, ""));
181 for (const auto& [path, label, value] :
182 {std::tuple{"anchor.a", "editor.physics.anchor-a", EditorValue::Array{0.0, 0.0, 0.0}},
183 {"anchor.b", "editor.physics.anchor-b", EditorValue::Array{0.0, 0.0, 0.0}},
184 {"joint.axis", "editor.physics.axis", EditorValue::Array{1.0, 0.0, 0.0}}})
185 result.properties.push_back(jointProperty(path, label, "joint", PropertyType::Vec3, value));
186 result.properties.push_back(
187 jointProperty("limit.enabled", "editor.physics.limit-enabled", "limit", PropertyType::Bool, false));
188 result.properties.push_back(
189 jointProperty("motor.enabled", "editor.physics.motor-enabled", "motor", PropertyType::Bool, false));
190 result.properties.push_back(
191 jointProperty("collision.connected", "editor.physics.collide-connected", "joint", PropertyType::Bool, false));
192 auto numeric = [&](const char* path, const char* label, const char* category, double value, double minimum,
193 double maximum) {
194 auto descriptor = jointProperty(path, label, category, PropertyType::Float, value);
195 descriptor.numeric.minimum = minimum;
196 descriptor.numeric.maximum = maximum;
197 descriptor.numeric.step = 0.01;
198 result.properties.push_back(std::move(descriptor));
199 };
200 numeric("limit.minimum", "editor.physics.limit-minimum", "limit", 0.0, -100000.0, 100000.0);
201 numeric("limit.maximum", "editor.physics.limit-maximum", "limit", 0.0, -100000.0, 100000.0);
202 numeric("motor.speed", "editor.physics.motor-speed", "motor", 0.0, -100000.0, 100000.0);
203 numeric("motor.force", "editor.physics.motor-force", "motor", 0.0, 0.0, 1000000000.0);
204 numeric("break.force", "editor.physics.break-force", "break", 0.0, 0.0, 1000000000.0);
205 numeric("break.torque", "editor.physics.break-torque", "break", 0.0, 0.0, 1000000000.0);
206 return result;
207}
208
209std::map<std::string, EditorValue> PhysicsJointTarget::defaults() {
210 std::map<std::string, EditorValue> result;
211 for (const PropertyDescriptor& descriptor : jointSchema().properties)
212 result.emplace(descriptor.path.value(), descriptor.defaultValue);
213 return result;
214}
215
216bool PhysicsJointTarget::selectionMatches(const SelectionSnapshot& selection) const {
217 return selection.items.size() == 1 && selection.items.front().target == TargetId(id_);
218}
219
220} // namespace eve::physics_editing
ActionParameterOperation operation
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
std::string descriptor
std::string label
float maximum[3]
float minimum[3]
std::uint32_t key
std::string text
TokenKind kind
std::map< std::string, PropertyRule > properties
bool valid
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
double number
bool found
Json object
const char * category
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
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
UI-independent property access for an immutable selection snapshot.
Dotted property path (layout.size) compared and persisted as text.
Definition EditingIds.h:78
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Serializable joint authoring target using stable body references.
EditorValue snapshotValue() const
Capture deterministic joint content.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
TargetDescriptor describe() const override
Describe.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
std::vector< EditorDiagnostic > validate() const
Report missing bodies, invalid axes and limit ranges.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
PhysicsJointTarget(std::string id)
Physics joint target.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
const char * defaultValue
editing::PropertyDescriptor PropertyDescriptor
editing::PropertyPath PropertyPath
editing::TargetId TargetId
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
editing::Value EditorValue
const EditorValue * field(const EditorValue &value, const char *name)
DomainOperation public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
std::vector< PropertyDescriptor > properties
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.