载入中...
搜索中...
未找到
DecalTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <map>
6#include <string>
7#include <utility>
8#include <vector>
9
10namespace eve::decal_editing {
11namespace {
12PropertyDescriptor prop(const char*path,PropertyType type,EditorValue value,const char*category,double min=0,double max=0,bool ranged=false,std::vector<std::string> enumItems={}){PropertyDescriptor d;d.path=PropertyPath(path);d.displayNameKey=std::string("editor.decal.")+path;d.category=category;d.type=type;d.flags=PropertyFlag::Runtime;d.defaultValue=std::move(value);if(type==PropertyType::Enum)d.enumItems=enumItems.empty()?std::vector<std::string>{"over","add"}:std::move(enumItems);if(ranged){d.numeric.minimum=min;d.numeric.maximum=max;}return d;}
13const EditorValue* field(const EditorValue&v,const std::string&k){const auto*o=v.getIf<EditorValue::Object>();if(!o)return nullptr;auto i=o->find(k);return i==o->end()?nullptr:&i->second;}
14bool hasError(const std::vector<EditorDiagnostic>&d){return std::any_of(d.begin(),d.end(),[](const auto&v){return v.severity()==DiagnosticSeverity::Error;});}
15EditorDiagnostic diagnostic(const char* rule, DiagnosticSeverity severity, std::string message) {
17 std::move(message));
18}
19}
20
21PropertySchema DecalDocumentTarget::decalSchema(){PropertySchema s;s.typeId="decal.instance";s.properties={
22 prop("transform.position",PropertyType::Vec3,EditorValue::Array{0.0,0.0,0.0},"transform"),prop("transform.normal",PropertyType::Vec3,EditorValue::Array{0.0,1.0,0.0},"transform"),prop("transform.yaw",PropertyType::Float,0.0,"transform"),
23 prop("projection.size",PropertyType::Float,0.5,"projection",0.0001,100000,true),prop("projection.depth",PropertyType::Float,0.15,"projection",0.0001,100000,true),prop("projection.mode",PropertyType::Enum,"planar","projection",0,0,false,{"planar","triplanar","spherical","world"}),prop("projection.blendSharpness",PropertyType::Float,4.0,"projection",0.1,64,true),prop("projection.parallaxScale",PropertyType::Float,0.0,"projection",0,1,true),prop("projection.parallaxMinLayers",PropertyType::Float,8.0,"projection",1,64,true),prop("projection.parallaxMaxLayers",PropertyType::Float,24.0,"projection",1,64,true),prop("projection.edgeFadeWidth",PropertyType::Float,0.06,"projection",0,0.49,true),prop("decal.kind",PropertyType::String,"default","decal"),
24 prop("texture.albedo",PropertyType::AssetRef,"","textures"),prop("texture.normal",PropertyType::AssetRef,"","textures"),prop("texture.params",PropertyType::AssetRef,"","textures"),prop("texture.uvRect",PropertyType::Vec4,EditorValue::Array{0.0,0.0,1.0,1.0},"textures"),
25 prop("channel.normal",PropertyType::Float,0.0,"channels",0,1,true),prop("channel.roughness",PropertyType::Float,0.0,"channels",-1,1,true),prop("channel.metal",PropertyType::Float,0.0,"channels",-1,1,true),prop("channel.emissive",PropertyType::Float,0.0,"channels",0,1000,true),prop("blend.mode",PropertyType::Enum,"over","channels",0,0,false,{"over","add"}),
26 prop("lifetime.seconds",PropertyType::Float,0.0,"lifetime",0,1000000,true),prop("lifetime.fadeIn",PropertyType::Float,0.0,"lifetime",0,1000000,true),prop("lifetime.fadeOut",PropertyType::Float,0.0,"lifetime",0,1000000,true)};return s;}
27std::map<std::string,EditorValue> DecalDocumentTarget::defaults(){std::map<std::string,EditorValue>v;for(const auto&p:decalSchema().properties)v.emplace(p.path.value(),p.defaultValue);return v;}
28DecalDocumentTarget::DecalDocumentTarget(std::string id):id_(std::move(id)),values_(defaults()){}
30 return {TargetId(id_), "decal", revisionValue(), false, {CapabilityId("eve.editor.target.decal-properties")}};
31}
32void*DecalDocumentTarget::queryCapability(const CapabilityId&c){return c==CapabilityId("eve.editor.target.decal-properties")?static_cast<IPropertyProvider*>(this):nullptr;}
33bool DecalDocumentTarget::matches(const SelectionSnapshot&s)const{return s.items.size()==1&&s.items.front().target==TargetId(id_);}
41PropertyReadResult DecalDocumentTarget::read(const SelectionSnapshot&s,const PropertyPath&p)const{if(!matches(s))return{};auto i=values_.find(p.value());return i==values_.end()?PropertyReadResult{}:PropertyReadResult{PropertyReadState::Value,i->second,{}};}
42const EditorValue*DecalDocumentTarget::value(const std::string&p)const{auto i=values_.find(p);return i==values_.end()?nullptr:&i->second;}
43Result<DomainOperation>DecalDocumentTarget::makeSet(const SelectionSnapshot&s,const PropertyPath&p,const EditorValue&v,PropertySetMode m)const{if(m==PropertySetMode::Reset)return makeReset(s,p);auto d=decalSchema().find(p);if(!matches(s)||!d||m!=PropertySetMode::Absolute)return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.decal.set"), "Decal property requires a matching absolute edit");auto checked=validatePropertyValue(*d,v);if(!checked.ok())return Result<DomainOperation>::failure(checked.status());auto candidate=*this;candidate.values_[p.value()]=v;if(hasError(candidate.validate()))return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.decal.invalid"), "Decal property conflicts with projection rules");DomainOperation op;op.type="decal.properties.replace.v1";op.inverseType=op.type;op.target=TargetId(id_);op.payload=EditorValue::Object{{"values",EditorValue::Object(candidate.values_.begin(),candidate.values_.end())}};op.inverse=EditorValue::Object{{"values",EditorValue::Object(values_.begin(),values_.end())}};op.hasInverse=true;op.affectedProperties.push_back(p.value());op.mergeKey="decal:"+id_+":"+p.value();return eve::editing::applied<DomainOperation>(std::move(op));}
44Result<DomainOperation>DecalDocumentTarget::makeReset(const SelectionSnapshot&s,const PropertyPath&p)const{auto d=decalSchema().find(p);if(!d)return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.decal.property"), "Unknown decal property");return makeSet(s,p,d->defaultValue,PropertySetMode::Absolute);}
46 if (op.target != TargetId(id_) || op.type != "decal.properties.replace.v1")
47 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.decal.operation"), "Decal operation mismatch");
48 const auto* v = field(op.payload, "values");
49 const auto* o = v ? v->getIf<EditorValue::Object>() : nullptr;
50 if (!o) return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.decal.payload"), "Decal operation payload is invalid");
51 auto candidate = *this;
52 candidate.values_.clear();
53 for (const auto& [k, val] : *o) {
54 auto d = decalSchema().find(PropertyPath(k));
55 if (!d)
56 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.decal.unknown"), "Decal payload contains an unknown property");
57 if (!validatePropertyValue(*d, val).ok())
58 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.decal.value"), "Decal payload contains an invalid property value");
59 candidate.values_[k] = val;
60 }
61 if (candidate.values_.size() != decalSchema().properties.size() || hasError(candidate.validate()))
62 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.decal.invalid"), "Decal payload is incomplete or invalid");
63 values_ = std::move(candidate.values_);
65 widenDirty(0, 0);
66 return eve::editing::applied<void>();
67}
68std::unique_ptr<IDomainOperationTarget>DecalDocumentTarget::cloneDomainState()const{return std::make_unique<DecalDocumentTarget>(*this);}
69Result<void>DecalDocumentTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget>candidate){auto*t=dynamic_cast<DecalDocumentTarget*>(candidate.get());if(!t||t->id_!=id_)return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.decal.candidate"), "Decal candidate mismatch");*this=*t;return eve::editing::applied<void>();}
70std::vector<EditorDiagnostic>DecalDocumentTarget::validate()const{std::vector<EditorDiagnostic>d;const auto&normal=*value("transform.normal")->getIf<EditorValue::Array>();double length2=0;for(const auto&v:normal){const auto*n=v.getIf<double>();if(!n||!std::isfinite(*n)){d.push_back(diagnostic("editor.decal.normal",DiagnosticSeverity::Error,"Decal normal must be finite Vec3"));return d;}length2+=*n**n;}if(length2<1e-12)d.push_back(diagnostic("editor.decal.zero-normal",DiagnosticSeverity::Error,"Decal projection normal must be non-zero"));const auto&uv=*value("texture.uvRect")->getIf<EditorValue::Array>();if(uv.size()!=4)d.push_back(diagnostic("editor.decal.uv",DiagnosticSeverity::Error,"Decal UV rect must have four components"));else{double n[4];for(int i=0;i<4;++i){const auto*v=uv[i].getIf<double>();if(!v||!std::isfinite(*v)){d.push_back(diagnostic("editor.decal.uv",DiagnosticSeverity::Error,"Decal UV rect must be finite"));return d;}n[i]=*v;}if(n[0]<0||n[1]<0||n[2]<=0||n[3]<=0||n[0]+n[2]>1||n[1]+n[3]>1)d.push_back(diagnostic("editor.decal.uv-range",DiagnosticSeverity::Error,"Decal UV rect must stay within normalized atlas bounds"));}const auto&blend=*value("blend.mode")->getIf<std::string>();if(blend!="over"&&blend!="add")d.push_back(diagnostic("editor.decal.blend",DiagnosticSeverity::Error,"Decal blend mode must be over or add"));const auto&proj=*value("projection.mode")->getIf<std::string>();if(proj!="planar"&&proj!="triplanar"&&proj!="spherical"&&proj!="world")d.push_back(diagnostic("editor.decal.projection",DiagnosticSeverity::Error,"Decal projection mode must be planar, triplanar, spherical, or world"));const double sharpness=*value("projection.blendSharpness")->getIf<double>();if(!std::isfinite(sharpness)||sharpness<=0.0)d.push_back(diagnostic("editor.decal.sharpness",DiagnosticSeverity::Error,"Decal blend sharpness must be finite and positive"));const double minLayers=*value("projection.parallaxMinLayers")->getIf<double>();const double maxLayers=*value("projection.parallaxMaxLayers")->getIf<double>();if(minLayers>maxLayers)d.push_back(diagnostic("editor.decal.parallax-layers",DiagnosticSeverity::Error,"Decal POM minimum layers must not exceed maximum layers"));if(value("decal.kind")->getIf<std::string>()->empty())d.push_back(diagnostic("editor.decal.kind",DiagnosticSeverity::Error,"Decal kind must not be empty"));if(*value("texture.albedo")->getIf<std::string>()=="")d.push_back(diagnostic("editor.decal.albedo",DiagnosticSeverity::Warning,"Decal has no albedo texture"));if(blend=="add"&&*value("channel.emissive")->getIf<double>()<=0)d.push_back(diagnostic("editor.decal.additive-emissive",DiagnosticSeverity::Warning,"Additive decal has zero emissive strength"));if(*value("lifetime.seconds")->getIf<double>()==0.0&&(*value("lifetime.fadeIn")->getIf<double>()>0.0||*value("lifetime.fadeOut")->getIf<double>()>0.0))d.push_back(diagnostic("editor.decal.persistent-fade",DiagnosticSeverity::Warning,"Persistent decal fade settings have no expiry endpoint"));return d;}
71EditorValue DecalDocumentTarget::snapshotValue()const{return EditorValue::Object{{"schemaVersion",int64_t{4}},{"values",EditorValue::Object(values_.begin(),values_.end())}};}
72Result<void>DecalDocumentTarget::loadSnapshot(const EditorValue&s){const auto*v=field(s,"schemaVersion"),*values=field(s,"values");const auto*version=v?v->getIf<int64_t>():nullptr;const auto*object=values?values->getIf<EditorValue::Object>():nullptr;if(!version||(*version!=1&&*version!=2&&*version!=3&&*version!=4)||!object)return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.decal.snapshot"), "Unsupported decal snapshot");EditorValue::Object migrated=*object;const auto baseline=defaults();if(*version<4){for(const char*key:{"projection.parallaxScale","projection.parallaxMinLayers","projection.parallaxMaxLayers","projection.edgeFadeWidth"})if(!migrated.contains(key))migrated.emplace(key,baseline.at(key));}DomainOperation op;op.target=TargetId(id_);op.type="decal.properties.replace.v1";op.payload=EditorValue::Object{{"values",std::move(migrated)}};auto result=applyDomainOperation(op);if(result.ok())clearDirtyRegion();return result;}
73
74} // namespace eve::decal_editing
double value
const std::string & s
float uv
glm::vec4 p[6]
std::map< std::string, Var > values
std::string message
std::uint32_t key
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t c
float blend
std::map< std::string, PropertyRule > properties
Texture * normal
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float d
float t
glm::mat4 proj
Json object
const char * category
float m[16]
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
Stable, serializable authoring document for one projected decal.
Definition DecalTarget.h:33
const EditorValue * value(const std::string &path) const
Read a renderer-neutral property by stable path.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
DecalDocumentTarget(std::string id)
Decal document target.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
TargetDescriptor describe() const override
Describe.
EditorValue snapshotValue() const
Capture schema-version-two decal content.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load v4 content or migrate v1-v3 content with compatible projection defaults.
std::vector< EditorDiagnostic > validate() const
Validate projection geometry, fades, UVs and texture-channel rules.
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
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
const char * defaultValue
editing::PropertySchema PropertySchema
editing::PropertyDescriptor PropertyDescriptor
editing::PropertyPath PropertyPath
editing::Value EditorValue
Definition DecalTarget.h:25
editing::Diagnostic EditorDiagnostic
Definition DecalTarget.h:25
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)
DomainOperation public API.
std::vector< std::string > affectedProperties
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.