载入中...
搜索中...
未找到
VoxelPaletteTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <set>
5#include <utility>
6
7namespace eve::voxel_editing {
8namespace {
9const EditorValue* field(const EditorValue& v, const char* k) {
10 const auto* o = v.getIf<EditorValue::Object>();
11 if (!o) return nullptr;
12 const auto i = o->find(k);
13 return i == o->end() ? nullptr : &i->second;
14}
15EditorValue faces(const std::uint8_t* v) {
17 for (int i = 0; i < 6; ++i) a.push_back(int64_t{v[i]});
18 return a;
19}
20EditorValue entryValue(const VoxelPaletteEntryValue& v) {
21 return EditorValue::Object{{"id", v.id.value()},
22 {"name", v.type.name},
23 {"faces", faces(v.type.faceTex)},
24 {"directional", v.type.directional},
25 {"composeGroup", v.type.composeGroup},
26 {"connects", v.type.connects}};
27}
28Result<VoxelPaletteEntryValue> parse(const EditorValue& v) {
29 VoxelPaletteEntryValue out;
30 const auto * id = field(v, "id"), *name = field(v, "name"), *directional = field(v, "directional"),
31 *group = field(v, "composeGroup"), *connects = field(v, "connects"), *faceValue = field(v, "faces");
32 const auto* ids = id ? id->getIf<std::string>() : nullptr;
33 const auto* names = name ? name->getIf<std::string>() : nullptr;
34 const auto* dirs = directional ? directional->getIf<bool>() : nullptr;
35 const auto* groups = group ? group->getIf<std::string>() : nullptr;
36 const auto* conns = connects ? connects->getIf<bool>() : nullptr;
37 const auto* fa = faceValue ? faceValue->getIf<EditorValue::Array>() : nullptr;
38 if (!ids || ids->empty() || !names || !dirs || !groups || !conns || !fa || fa->size() != 6)
39 return eve::editing::failed<VoxelPaletteEntryValue>(
40 EditorStatus::Rejected, RuleId("editor.voxel-palette.entry"), "Voxel palette entry is invalid");
41 out.id = ObjectId(*ids);
42 out.type.name = *names;
43 out.type.directional = *dirs;
44 out.type.composeGroup = *groups;
45 out.type.connects = *conns;
46 for (int i = 0; i < 6; ++i) {
47 const auto* n = (*fa)[i].getIf<int64_t>();
48 if (!n || *n < 0 || *n > 255)
49 return eve::editing::failed<VoxelPaletteEntryValue>(
50 EditorStatus::Rejected, RuleId("editor.voxel-palette.face"), "Voxel face texture must be 0..255");
51 out.type.faceTex[i] = static_cast<std::uint8_t>(*n);
52 }
53 return eve::editing::applied<VoxelPaletteEntryValue>(std::move(out));
54}
58 d.displayNameKey = "editor.voxel-palette." + path;
59 d.category = "cube-type";
60 d.type = type;
61 d.defaultValue = std::move(value);
62 d.flags = PropertyFlag::Runtime;
63 if (type == PropertyType::Array) d.presenterHint = "face-texture-six";
64 return d;
65}
66bool errors(const std::vector<EditorDiagnostic>& d) {
67 return std::any_of(d.begin(), d.end(), [](const auto& v) { return v.severity() == DiagnosticSeverity::Error; });
68}
69} // namespace
70VoxelPaletteTarget::VoxelPaletteTarget(std::string id) : id_(std::move(id)) {}
72 return {TargetId(id_),
73 "voxel-palette",
75 false,
76 {CapabilityId("eve.editor.target.voxel-palette-properties")}};
77}
79 return c == CapabilityId("eve.editor.target.voxel-palette-properties") ? static_cast<IPropertyProvider*>(this)
80 : nullptr;
81}
82bool VoxelPaletteTarget::matches(const SelectionSnapshot& s) const {
83 if (s.items.empty()) return false;
84 for (const auto& i : s.items)
85 if (i.target != TargetId(id_) || std::none_of(entries_.begin(), entries_.end(),
86 [&](const auto& v) { return v.id.value() == i.item.value(); }))
87 return false;
88 return true;
89}
99 s.typeId = "voxel.cube-type";
100 s.properties = {prop("cube.faces", PropertyType::Array,
101 EditorValue::Array{int64_t{0}, int64_t{0}, int64_t{0}, int64_t{0}, int64_t{0}, int64_t{0}}),
102 prop("cube.directional", PropertyType::Bool, false),
103 prop("cube.composeGroup", PropertyType::String, ""),
104 prop("cube.connects", PropertyType::Bool, false)};
105 return s;
106}
108 if (!matches(s) || !schema(s).find(p)) return {};
109 std::optional<EditorValue> common;
110 for (const auto& i : s.items) {
111 const auto& v = std::find_if(entries_.begin(), entries_.end(), [&](const auto& x) {
112 return x.id.value() == i.item.value();
113 })->type;
115 if (p == PropertyPath("cube.faces"))
116 value = faces(v.faceTex);
117 else if (p == PropertyPath("cube.directional"))
118 value = v.directional;
119 else if (p == PropertyPath("cube.composeGroup"))
120 value = v.composeGroup;
121 else
122 value = v.connects;
123 if (!common)
124 common = value;
125 else if (*common != value)
126 return {PropertyReadState::Mixed, {}, {}};
127 }
128 return {PropertyReadState::Value, *common, {}};
129}
130EditorValue VoxelPaletteTarget::contentValue() const {
132 for (const auto& v : entries_) a.push_back(entryValue(v));
133 return EditorValue::Object{{"entries", std::move(a)}};
134}
135Result<DomainOperation> VoxelPaletteTarget::replacement(EditorValue content,
136 std::string property) const {
138 op.type = "voxel.palette.replace.v1";
139 op.inverseType = op.type;
140 op.target = TargetId(id_);
141 op.payload = std::move(content);
142 op.inverse = contentValue();
143 op.hasInverse = true;
144 if (!property.empty()) op.affectedProperties.push_back(property);
145 op.mergeKey = "voxel-palette:" + id_ + ":" + (property.empty() ? "structure" : property);
146 return eve::editing::applied<DomainOperation>(std::move(op));
147}
149 const EditorValue& value, PropertySetMode mode) const {
150 if (mode == PropertySetMode::Reset) return makeReset(s, p);
151 if (!matches(s) || !schema(s).find(p) || mode != PropertySetMode::Absolute)
152 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.voxel-palette.set"),
153 "Voxel palette property edit is invalid");
154 auto c = *this;
155 for (const auto& i : s.items) {
156 auto& v = std::find_if(c.entries_.begin(), c.entries_.end(), [&](const auto& x) {
157 return x.id.value() == i.item.value();
158 })->type;
159 if (p == PropertyPath("cube.faces")) {
160 const auto* a = value.getIf<EditorValue::Array>();
161 if (!a || a->size() != 6)
162 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected,
163 RuleId("editor.voxel-palette.faces"),
164 "Voxel faces require six texture IDs");
165 for (int face = 0; face < 6; ++face) {
166 const auto* n = (*a)[face].getIf<int64_t>();
167 if (!n || *n < 0 || *n > 255)
168 return eve::editing::failed<DomainOperation>(
169 EditorStatus::Rejected, RuleId("editor.voxel-palette.face"), "Voxel texture ID must be 0..255");
170 v.faceTex[face] = static_cast<std::uint8_t>(*n);
171 }
172 } else if (p == PropertyPath("cube.directional"))
173 v.directional = *value.getIf<bool>();
174 else if (p == PropertyPath("cube.composeGroup"))
175 v.composeGroup = *value.getIf<std::string>();
176 else
177 v.connects = *value.getIf<bool>();
178 }
179 if (errors(c.validate()))
180 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.voxel-palette.capacity"),
181 "Voxel palette edit exceeds the 255 variant capacity");
182 return replacement(c.contentValue(), p.value());
183}
185 auto d = schema(s).find(p);
186 if (!d)
187 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.voxel-palette.property"),
188 "Unknown Voxel palette property");
189 return makeSet(s, p, d->defaultValue, PropertySetMode::Absolute);
190}
192 if (v.id.empty() || v.type.name.empty() || std::any_of(entries_.begin(), entries_.end(), [&](const auto& x) {
193 return x.id == v.id || x.type.name == v.type.name;
194 }))
195 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.voxel-palette.identity"),
196 "Voxel palette ID and name must be unique");
197 auto c = *this;
198 c.entries_.push_back(v);
199 if (errors(c.validate()))
200 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.voxel-palette.capacity"),
201 "Voxel palette exceeds the 255 variant capacity");
202 return replacement(c.contentValue());
203}
205 if (std::none_of(entries_.begin(), entries_.end(), [&](const auto& v) { return v.id == id; }))
206 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.voxel-palette.entry"),
207 "Voxel palette entry does not exist");
208 auto c = *this;
209 std::erase_if(c.entries_, [&](const auto& v) { return v.id == id; });
210 return replacement(c.contentValue());
211}
212std::vector<EditorDiagnostic> VoxelPaletteTarget::validate() const {
213 std::vector<EditorDiagnostic> d;
214 std::set<std::string> ids, names;
215 int variants = 0;
216 for (const auto& v : entries_) {
217 variants += v.type.directional ? 4 : 1;
218 if (v.id.empty() || v.type.name.empty() || !ids.insert(v.id.value()).second ||
219 !names.insert(v.type.name).second)
221 RuleId("editor.voxel-palette.identity"), DiagnosticSeverity::Error,
222 "Voxel palette IDs and names must be non-empty and unique"));
223 if (v.type.connects && v.type.composeGroup.empty())
225 RuleId("editor.voxel-palette.connect-group"), DiagnosticSeverity::Warning,
226 "Connecting cube type has no compose group"));
227 }
228 if (variants > 255)
230 RuleId("editor.voxel-palette.variant-capacity"), DiagnosticSeverity::Error,
231 "Voxel palette exceeds 255 runtime variants"));
232 if (entries_.empty())
234 RuleId("editor.voxel-palette.empty"), DiagnosticSeverity::Warning, "Voxel palette has no cube types"));
235 return d;
236}
238 if (op.target != TargetId(id_) || op.type != "voxel.palette.replace.v1")
239 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.voxel-palette.operation"),
240 "Voxel palette operation mismatch");
241 const auto* values = field(op.payload, "entries");
242 const auto* a = values ? values->getIf<EditorValue::Array>() : nullptr;
243 if (!a || a->size() > 255 || !op.payload.isWithinLimits(6, 10000, 1024 * 1024))
244 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.voxel-palette.payload"),
245 "Voxel palette payload exceeds limits");
247 for (const auto& v : *a) {
248 auto parsed = parse(v);
249 if (!parsed.ok())
250 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.voxel-palette.entry"),
251 "Voxel palette entry cannot be parsed");
252 c.entries_.push_back(parsed.value());
253 }
254 if (errors(c.validate()))
255 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.voxel-palette.invalid"),
256 "Voxel palette validation failed");
257 entries_ = std::move(c.entries_);
258 bumpRevision();
259 widenDirty(0, 0);
260 return eve::editing::applied<void>();
261}
262std::unique_ptr<IDomainOperationTarget> VoxelPaletteTarget::cloneDomainState() const {
263 return std::make_unique<VoxelPaletteTarget>(*this);
264}
265Result<void> VoxelPaletteTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> c) {
266 auto* t = dynamic_cast<VoxelPaletteTarget*>(c.get());
267 if (!t || t->id_ != id_)
268 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.voxel-palette.candidate"),
269 "Voxel palette candidate mismatch");
270 *this = *t;
271 return eve::editing::applied<void>();
272}
274 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"content", contentValue()}};
275}
277 const auto *v = field(s, "schemaVersion"), *content = field(s, "content");
278 const auto* version = v ? v->getIf<int64_t>() : nullptr;
279 if (!version || *version != 1 || !content)
280 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.voxel-palette.snapshot"),
281 "Unsupported Voxel palette snapshot");
283 op.target = TargetId(id_);
284 op.type = "voxel.palette.replace.v1";
285 op.payload = *content;
286 auto result = applyDomainOperation(op);
287 if (result.ok()) clearDirtyRegion();
288 return result;
289}
290} // namespace eve::voxel_editing
LogicalId target
double value
float x
Definition AnimClip.cpp:738
const std::string & s
building::EdgeCurveGroup group
glm::vec4 p[6]
std::map< std::string, Var > values
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t c
std::string name
MeleePoint3 a
Definition MeleeHit.cpp:40
uint32_t groups
Definition OnnxGpgpu.cpp:39
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float begin
float d
float t
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
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
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.
virtual void clearDirtyRegion()=0
Clears dirty region.
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
bool isWithinLimits(size_t maxDepth, size_t maxElements, size_t maxStringBytes) const
Validate nesting, element count and approximate string byte limits.
std::map< std::string, Value > Object
Type type() const
Return the active value type.
const T * getIf() const
Return an immediately borrowed typed value.
Revisioned Voxel CubeType palette with face-material Inspector.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget >) override
Commits domain state.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &) const override
Current revision.
PropertyReadResult read(const SelectionSnapshot &, const PropertyPath &) const override
Reads .
VoxelPaletteTarget(std::string id)
Voxel palette target.
Result< DomainOperation > makeReset(const SelectionSnapshot &, const PropertyPath &) const override
Make reset.
std::vector< EditorDiagnostic > validate() const
Validate.
void * queryCapability(const CapabilityId &) override
Query an optional stable editing capability.
Result< void > loadSnapshot(const EditorValue &)
Loads snapshot.
Result< DomainOperation > makeCreate(const VoxelPaletteEntryValue &) const
Add one unique named cube type.
Result< DomainOperation > makeDelete(const ObjectId &) const
Delete one cube type by stable editor ID.
EditorValue snapshotValue() const
Snapshot value.
Result< DomainOperation > makeSet(const SelectionSnapshot &, const PropertyPath &, const EditorValue &, PropertySetMode) const override
Make set.
PropertySchema schema(const SelectionSnapshot &) const override
Schema.
TargetDescriptor describe() const override
Describe.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
Result< void > applyDomainOperation(const DomainOperation &) override
Applies domain operation.
PropertySetMode
PropertySetMode public API.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
const EditorValue * field(const EditorValue &value, const char *name)
ModelData parse(std::span< const uint8_t > bytes)
Parse.
editing::CapabilityId CapabilityId
editing::ObjectId ObjectId
editing::DomainOperation DomainOperation
editing::TargetId TargetId
editing::Value EditorValue
editing::PropertyDescriptor PropertyDescriptor
editing::RuleId RuleId
editing::PropertyPath PropertyPath
editing::PropertyType PropertyType
DomainOperation public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.
Stable authored entry in a Voxel cube palette.