载入中...
搜索中...
未找到
CameraTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <set>
6#include <utility>
7
8namespace eve::camera_editing {
9namespace {
10const EditorValue* field(const EditorValue& value,const char* key){const auto* object=value.getIf<EditorValue::Object>();if(!object)return nullptr;const auto it=object->find(key);return it==object->end()?nullptr:&it->second;}
11EditorValue vec2(glm::vec2 v){return EditorValue::Array{v.x,v.y};}
12EditorValue vec3(glm::vec3 v){return EditorValue::Array{v.x,v.y,v.z};}
13bool readVec(const EditorValue* value,float* out,std::size_t count){const auto* array=value?value->getIf<EditorValue::Array>():nullptr;if(!array||array->size()!=count)return false;for(std::size_t i=0;i<count;++i){const auto* number=(*array)[i].getIf<double>();if(!number||!std::isfinite(*number))return false;out[i]=static_cast<float>(*number);}return true;}
14bool finite(float value){return std::isfinite(value);}
15bool modeValid(const std::string& value){return value=="follow"||value=="orbit"||value=="topdown"||value=="firstperson"||value=="cinematic";}
16bool propertyValid(const std::string& value){return value=="fov"||value=="radius"||value=="smooth"||value=="compositionX"||value=="compositionY";}
17EditorValue rigValue(const CameraRigValue& r){return EditorValue::Object{{"id",r.id.value()},{"name",r.name},{"mode",r.mode},{"priority",int64_t{r.priority}},{"enabled",r.enabled},{"target",vec3(r.target)},{"offset",vec3(r.offset)},{"lookAhead",vec3(r.lookAhead)},{"composition",vec2(r.composition)},{"radius",r.radius},{"azimuth",r.azimuth},{"elevation",r.elevation},{"yaw",r.yaw},{"pitch",r.pitch},{"fov",r.fov},{"smooth",r.smooth},{"maxSpeed",r.maxSpeed}};}
18EditorValue keyValue(const CameraTimelineKeyValue& k){return EditorValue::Object{{"id",k.id.value()},{"kind",k.kind},{"time",k.time},{"rig",k.rig.value()},{"blend",k.blend},{"property",k.property},{"value",k.value},{"name",k.name},{"data",k.data}};}
19Result<CameraRigValue> parseRig(const EditorValue& value){CameraRigValue r;const auto* id=field(value,"id"),*name=field(value,"name"),*mode=field(value,"mode"),*priority=field(value,"priority"),*enabled=field(value,"enabled");const auto* ids=id?id->getIf<std::string>():nullptr;const auto* names=name?name->getIf<std::string>():nullptr;const auto* modes=mode?mode->getIf<std::string>():nullptr;const auto* priorities=priority?priority->getIf<int64_t>():nullptr;const auto* enableds=enabled?enabled->getIf<bool>():nullptr;if(!ids||ids->empty()||!names||names->empty()||!modes||!priorities||!enableds)return eve::editing::failed<CameraRigValue>(EditorStatus::Rejected, RuleId("editor.camera.rig"), "Camera rig identity fields are invalid");r.id=ObjectId(*ids);r.name=*names;r.mode=*modes;r.priority=static_cast<int>(*priorities);r.enabled=*enableds;if(!readVec(field(value,"target"),&r.target.x,3)||!readVec(field(value,"offset"),&r.offset.x,3)||!readVec(field(value,"lookAhead"),&r.lookAhead.x,3)||!readVec(field(value,"composition"),&r.composition.x,2))return eve::editing::failed<CameraRigValue>(EditorStatus::Rejected, RuleId("editor.camera.vector"), "Camera rig vectors are invalid");float* values[]={&r.radius,&r.azimuth,&r.elevation,&r.yaw,&r.pitch,&r.fov,&r.smooth,&r.maxSpeed};const char* names2[]={"radius","azimuth","elevation","yaw","pitch","fov","smooth","maxSpeed"};for(int i=0;i<8;++i){const auto* v=field(value,names2[i]);const auto* n=v?v->getIf<double>():nullptr;if(!n||!std::isfinite(*n))return eve::editing::failed<CameraRigValue>(EditorStatus::Rejected, RuleId("editor.camera.number"), "Camera rig scalar is invalid");*values[i]=static_cast<float>(*n);}return eve::editing::applied<CameraRigValue>(std::move(r));}
20Result<CameraTimelineKeyValue> parseKey(const EditorValue& value){CameraTimelineKeyValue k;const auto* id=field(value,"id"),*kind=field(value,"kind"),*rig=field(value,"rig"),*property=field(value,"property"),*name=field(value,"name"),*data=field(value,"data");const auto* ids=id?id->getIf<std::string>():nullptr;const auto* kinds=kind?kind->getIf<std::string>():nullptr;const auto* rigs=rig?rig->getIf<std::string>():nullptr;const auto* properties=property?property->getIf<std::string>():nullptr;const auto* names=name?name->getIf<std::string>():nullptr;const auto* datas=data?data->getIf<std::string>():nullptr;if(!ids||ids->empty()||!kinds||!rigs||!properties||!names||!datas)return eve::editing::failed<CameraTimelineKeyValue>(EditorStatus::Rejected, RuleId("editor.camera.key"), "Camera timeline key fields are invalid");k.id=ObjectId(*ids);k.kind=*kinds;k.rig=ObjectId(*rigs);k.property=*properties;k.name=*names;k.data=*datas;float* values[]={&k.time,&k.blend,&k.value};const char* fields[]={"time","blend","value"};for(int i=0;i<3;++i){const auto* v=field(value,fields[i]);const auto* n=v?v->getIf<double>():nullptr;if(!n||!std::isfinite(*n))return eve::editing::failed<CameraTimelineKeyValue>(EditorStatus::Rejected, RuleId("editor.camera.key-number"), "Camera timeline scalar is invalid");*values[i]=static_cast<float>(*n);}return eve::editing::applied<CameraTimelineKeyValue>(std::move(k));}
21PropertyDescriptor descriptor(std::string path,PropertyType type,EditorValue value,double minimum=-1e9,double maximum=1e9){PropertyDescriptor d;d.path=PropertyPath(path);d.displayNameKey="editor.camera."+path;d.category="camera-rig";d.type=type;d.defaultValue=std::move(value);d.flags=PropertyFlag::Runtime;d.numeric.minimum=minimum;d.numeric.maximum=maximum;return d;}
22bool errors(const std::vector<EditorDiagnostic>& values){return std::any_of(values.begin(),values.end(),[](const auto& d){return d.severity()==DiagnosticSeverity::Error;});}
23}
24
27 return {
28 TargetId(id_), "camera-asset", revisionValue(), false, {CapabilityId("eve.editor.target.camera-properties")}};
29}
30void* CameraDocumentTarget::queryCapability(const CapabilityId& capability){return capability==CapabilityId("eve.editor.target.camera-properties")?static_cast<IPropertyProvider*>(this):nullptr;}
31bool CameraDocumentTarget::matches(const SelectionSnapshot& selection)const{if(selection.items.empty())return false;for(const auto& item:selection.items)if(item.target!=TargetId(id_)||std::none_of(rigs_.begin(),rigs_.end(),[&](const auto& r){return r.id.value()==item.item.value();}))return false;return true;}
38PropertySchema CameraDocumentTarget::schema(const SelectionSnapshot&)const{PropertySchema s;s.typeId="camera.rig";s.properties={descriptor("rig.enabled",PropertyType::Bool,true),descriptor("rig.priority",PropertyType::Int,int64_t{0},-100000,100000),descriptor("rig.mode",PropertyType::Enum,"follow"),descriptor("rig.target",PropertyType::Vec3,vec3({0,0,0})),descriptor("rig.offset",PropertyType::Vec3,vec3({0,2,6})),descriptor("rig.lookAhead",PropertyType::Vec3,vec3({0,0,0})),descriptor("rig.composition",PropertyType::Vec2,vec2({0,0}),-0.9,0.9),descriptor("rig.radius",PropertyType::Float,10.0,0.01,100000),descriptor("rig.azimuth",PropertyType::Float,45.0),descriptor("rig.elevation",PropertyType::Float,30.0,-89,89),descriptor("rig.yaw",PropertyType::Float,0.0),descriptor("rig.pitch",PropertyType::Float,0.0,-89,89),descriptor("rig.fov",PropertyType::Float,60.0,1,179),descriptor("rig.smooth",PropertyType::Float,6.0,0,10000),descriptor("rig.maxSpeed",PropertyType::Float,0.0,0,100000)};s.properties[2].enumItems={"follow","orbit","topdown","firstperson","cinematic"};return s;}
39PropertyReadResult CameraDocumentTarget::read(const SelectionSnapshot& selection,const PropertyPath& path)const{if(!matches(selection)||!schema(selection).find(path))return{};std::optional<EditorValue> common;for(const auto& item:selection.items){const auto& r=*std::find_if(rigs_.begin(),rigs_.end(),[&](const auto& v){return v.id.value()==item.item.value();});EditorValue value;if(path==PropertyPath("rig.enabled"))value=r.enabled;else if(path==PropertyPath("rig.priority"))value=int64_t{r.priority};else if(path==PropertyPath("rig.mode"))value=r.mode;else if(path==PropertyPath("rig.target"))value=vec3(r.target);else if(path==PropertyPath("rig.offset"))value=vec3(r.offset);else if(path==PropertyPath("rig.lookAhead"))value=vec3(r.lookAhead);else if(path==PropertyPath("rig.composition"))value=vec2(r.composition);else if(path==PropertyPath("rig.radius"))value=r.radius;else if(path==PropertyPath("rig.azimuth"))value=r.azimuth;else if(path==PropertyPath("rig.elevation"))value=r.elevation;else if(path==PropertyPath("rig.yaw"))value=r.yaw;else if(path==PropertyPath("rig.pitch"))value=r.pitch;else if(path==PropertyPath("rig.fov"))value=r.fov;else if(path==PropertyPath("rig.smooth"))value=r.smooth;else value=r.maxSpeed;if(!common)common=value;else if(*common!=value)return{PropertyReadState::Mixed,{},{}};}return{PropertyReadState::Value,*common,{}};}
40EditorValue CameraDocumentTarget::contentValue()const{EditorValue::Array rigs,keys;for(const auto& r:rigs_)rigs.push_back(rigValue(r));for(const auto& k:keys_)keys.push_back(keyValue(k));return EditorValue::Object{{"rigs",std::move(rigs)},{"timeline",std::move(keys)}};}
41Result<DomainOperation> CameraDocumentTarget::replacement(EditorValue content,std::string property)const{DomainOperation op;op.type="camera.document.replace.v1";op.inverseType=op.type;op.target=TargetId(id_);op.payload=std::move(content);op.inverse=contentValue();op.hasInverse=true;if(!property.empty())op.affectedProperties.push_back(property);op.mergeKey="camera:"+id_+":"+(property.empty()?"structure":property);return eve::editing::applied<DomainOperation>(std::move(op));}
42Result<DomainOperation> CameraDocumentTarget::makeSet(const SelectionSnapshot& selection,const PropertyPath& path,const EditorValue& value,PropertySetMode mode)const{if(mode==PropertySetMode::Reset)return makeReset(selection,path);auto d=schema(selection).find(path);if(!matches(selection)||!d||mode!=PropertySetMode::Absolute||!validatePropertyValue(*d,value).ok())return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.camera.set"), "Camera property edit is invalid");auto candidate=*this;for(const auto& item:selection.items){auto& r=*std::find_if(candidate.rigs_.begin(),candidate.rigs_.end(),[&](const auto& v){return v.id.value()==item.item.value();});if(path==PropertyPath("rig.enabled"))r.enabled=*value.getIf<bool>();else if(path==PropertyPath("rig.priority"))r.priority=static_cast<int>(*value.getIf<int64_t>());else if(path==PropertyPath("rig.mode"))r.mode=*value.getIf<std::string>();else if(path==PropertyPath("rig.target"))readVec(&value,&r.target.x,3);else if(path==PropertyPath("rig.offset"))readVec(&value,&r.offset.x,3);else if(path==PropertyPath("rig.lookAhead"))readVec(&value,&r.lookAhead.x,3);else if(path==PropertyPath("rig.composition"))readVec(&value,&r.composition.x,2);else{const float number=static_cast<float>(*value.getIf<double>());if(path==PropertyPath("rig.radius"))r.radius=number;else if(path==PropertyPath("rig.azimuth"))r.azimuth=number;else if(path==PropertyPath("rig.elevation"))r.elevation=number;else if(path==PropertyPath("rig.yaw"))r.yaw=number;else if(path==PropertyPath("rig.pitch"))r.pitch=number;else if(path==PropertyPath("rig.fov"))r.fov=number;else if(path==PropertyPath("rig.smooth"))r.smooth=number;else r.maxSpeed=number;}}if(errors(candidate.validate()))return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.camera.invalid"), "Camera edit produces an invalid document");return replacement(candidate.contentValue(),path.value());}
43Result<DomainOperation> CameraDocumentTarget::makeReset(const SelectionSnapshot& selection,const PropertyPath& path)const{auto d=schema(selection).find(path);if(!d)return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.camera.property"), "Unknown camera property");return makeSet(selection,path,d->defaultValue,PropertySetMode::Absolute);}
44Result<DomainOperation> CameraDocumentTarget::makeCreateRig(const CameraRigValue& rig)const{if(rig.id.empty()||std::any_of(rigs_.begin(),rigs_.end(),[&](const auto& v){return v.id==rig.id||v.name==rig.name;}))return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.camera.rig-id"), "Camera rig ID and name must be unique");auto candidate=*this;candidate.rigs_.push_back(rig);if(errors(candidate.validate()))return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.camera.rig"), "Camera rig is invalid");return replacement(candidate.contentValue());}
45Result<DomainOperation> CameraDocumentTarget::makeDeleteRig(const ObjectId& id)const{if(std::none_of(rigs_.begin(),rigs_.end(),[&](const auto& r){return r.id==id;}))return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.camera.rig"), "Camera rig does not exist");if(std::any_of(keys_.begin(),keys_.end(),[&](const auto& k){return k.kind=="cut"&&k.rig==id;}))return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.camera.rig-reference"), "Timeline cuts still reference this rig");auto candidate=*this;std::erase_if(candidate.rigs_,[&](const auto& r){return r.id==id;});return replacement(candidate.contentValue());}
46Result<DomainOperation> CameraDocumentTarget::makeCreateKey(const CameraTimelineKeyValue& key)const{if(key.id.empty()||std::any_of(keys_.begin(),keys_.end(),[&](const auto& v){return v.id==key.id;}))return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.camera.key-id"), "Camera key ID must be unique");auto candidate=*this;candidate.keys_.push_back(key);if(errors(candidate.validate()))return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.camera.key"), "Camera timeline key is invalid");return replacement(candidate.contentValue());}
47Result<DomainOperation> CameraDocumentTarget::makeDeleteKey(const ObjectId& id)const{if(std::none_of(keys_.begin(),keys_.end(),[&](const auto& k){return k.id==id;}))return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.camera.key"), "Camera key does not exist");auto candidate=*this;std::erase_if(candidate.keys_,[&](const auto& k){return k.id==id;});return replacement(candidate.contentValue());}
48std::vector<CameraTimelineKeyValue> CameraDocumentTarget::timeline()const{auto out=keys_;std::stable_sort(out.begin(),out.end(),[](const auto& a,const auto& b){return a.time==b.time?a.id.value()<b.id.value():a.time<b.time;});return out;}
49std::vector<EditorDiagnostic> CameraDocumentTarget::validate()const{std::vector<EditorDiagnostic> out;if(rigs_.size()>64)out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.rig-budget"),DiagnosticSeverity::Error,"Camera asset exceeds 64 rigs"));if(keys_.size()>4096)out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.key-budget"),DiagnosticSeverity::Error,"Camera asset exceeds 4096 timeline keys"));std::set<std::string> ids,names,keyIds;bool enabled=false;for(const auto& r:rigs_){enabled|=r.enabled;if(r.id.empty()||r.name.empty()||!ids.insert(r.id.value()).second||!names.insert(r.name).second)out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.rig-identity"),DiagnosticSeverity::Error,"Camera rig IDs and names must be non-empty and unique"));if(!modeValid(r.mode)||r.priority < -100000||r.priority>100000||!finite(r.radius)||r.radius<.01f||!finite(r.elevation)||std::abs(r.elevation)>89.f||!finite(r.pitch)||std::abs(r.pitch)>89.f||!finite(r.fov)||r.fov<1.f||r.fov>179.f||!finite(r.smooth)||r.smooth<0||!finite(r.maxSpeed)||r.maxSpeed<0||glm::any(glm::greaterThan(glm::abs(r.composition),glm::vec2(.9f))))out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.rig-range"),DiagnosticSeverity::Error,"Camera rig contains an invalid mode or out-of-range value"));}for(const auto& k:keys_){if(k.id.empty()||!keyIds.insert(k.id.value()).second||!finite(k.time)||k.time<0||k.time>86400||!finite(k.blend)||k.blend<0||!finite(k.value))out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.key-range"),DiagnosticSeverity::Error,"Camera timeline key is invalid or outside the 24-hour budget"));else if(k.kind=="cut"&&!ids.contains(k.rig.value()))out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.cut-rig"),DiagnosticSeverity::Error,"Camera cut references a missing rig"));else if(k.kind=="float"&&!propertyValid(k.property))out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.float-property"),DiagnosticSeverity::Error,"Camera automation property is unsupported"));else if(k.kind=="event"&&k.name.empty())out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.event-name"),DiagnosticSeverity::Error,"Camera event name must not be empty"));else if(k.kind!="cut"&&k.kind!="float"&&k.kind!="event")out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.key-kind"),DiagnosticSeverity::Error,"Camera timeline key kind is unsupported"));}if(rigs_.empty())out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.empty"),DiagnosticSeverity::Warning,"Camera asset has no rigs"));else if(!enabled)out.push_back(eve::editing::ruleDiagnostic(eve::DiagnosticCode::InvalidArgument,RuleId("editor.camera.no-enabled-rig"),DiagnosticSeverity::Error,"Camera asset must retain at least one enabled rig"));return out;}
51 if (operation.target != TargetId(id_) || operation.type != "camera.document.replace.v1")
52 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.camera.operation"), "Camera operation mismatch");
53 const auto *rigsValue = field(operation.payload, "rigs"), *keysValue = field(operation.payload, "timeline");
54 const auto* rigs = rigsValue ? rigsValue->getIf<EditorValue::Array>() : nullptr;
55 const auto* keys = keysValue ? keysValue->getIf<EditorValue::Array>() : nullptr;
56 if (!rigs || !keys || rigs->size() > 64 || keys->size() > 4096 ||
57 !operation.payload.isWithinLimits(8, 100000, 4 * 1024 * 1024))
58 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.camera.payload"), "Camera document payload exceeds limits");
59 CameraDocumentTarget candidate(id_);
60 for (const auto& value : *rigs) {
61 auto parsed = parseRig(value);
62 if (!parsed.ok())
63 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.camera.rig"), "Camera document contains an invalid rig");
64 candidate.rigs_.push_back(parsed.value());
65 }
66 for (const auto& value : *keys) {
67 auto parsed = parseKey(value);
68 if (!parsed.ok())
69 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.camera.key"), "Camera document contains an invalid key");
70 candidate.keys_.push_back(parsed.value());
71 }
72 if (errors(candidate.validate()))
73 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.camera.invalid"), "Camera document validation failed");
74 rigs_ = std::move(candidate.rigs_);
75 keys_ = std::move(candidate.keys_);
77 widenDirty(0, 0);
78 return eve::editing::applied<void>();
79}
80std::unique_ptr<IDomainOperationTarget> CameraDocumentTarget::cloneDomainState()const{return std::make_unique<CameraDocumentTarget>(*this);}
81Result<void> CameraDocumentTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> candidate){auto* typed=dynamic_cast<CameraDocumentTarget*>(candidate.get());if(!typed||typed->id_!=id_)return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.camera.candidate"), "Camera candidate mismatch");*this=*typed;return eve::editing::applied<void>();}
82EditorValue CameraDocumentTarget::snapshotValue()const{return EditorValue::Object{{"schemaVersion",int64_t{1}},{"content",contentValue()}};}
84 const auto *versionValue = field(snapshot, "schemaVersion"), *content = field(snapshot, "content");
85 const auto* version = versionValue ? versionValue->getIf<int64_t>() : nullptr;
86 if (!version || *version != 1 || !content)
87 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.camera.snapshot"), "Unsupported camera snapshot");
90 operation.type = "camera.document.replace.v1";
91 operation.payload = *content;
92 auto result = applyDomainOperation(operation);
93 if (result.ok()) clearDirtyRegion();
94 return result;
95}
96
97Result<CameraPreviewPose> CameraPreview::evaluate(const CameraDocumentTarget& document,float time)const{if(!finite(time)||time<0||time>86400)return eve::editing::failed<CameraPreviewPose>(EditorStatus::Rejected, RuleId("editor.camera.preview-time"), "Camera preview time is outside the 24-hour budget");const auto diagnostics=document.validate();if(errors(diagnostics))return eve::editing::failed<CameraPreviewPose>(EditorStatus::Rejected, RuleId("editor.camera.preview-invalid"), "Camera document is invalid");if(document.rigs().empty())return eve::editing::failed<CameraPreviewPose>(EditorStatus::NotFound, RuleId("editor.camera.preview-rig"), "Camera preview has no rig");const auto keys=document.timeline();const CameraRigValue* rig=nullptr;for(const auto& key:keys)if(key.time<=time&&key.kind=="cut"){const auto found=std::find_if(document.rigs().begin(),document.rigs().end(),[&](const auto& r){return r.id==key.rig&&r.enabled;});if(found!=document.rigs().end())rig=&*found;}if(!rig){const auto found=std::max_element(document.rigs().begin(),document.rigs().end(),[](const auto& a,const auto& b){if(a.enabled!=b.enabled)return !a.enabled;return a.priority<b.priority;});rig=&*found;}auto sample=[&](const std::string& property,float fallback){const CameraTimelineKeyValue* before=nullptr,*after=nullptr;for(const auto& key:keys){if(key.kind!="float"||key.property!=property)continue;if(key.time<=time)before=&key;else{after=&key;break;}}if(!before)return after?after->value:fallback;if(!after||after->time<=before->time)return before->value;const float alpha=(time-before->time)/(after->time-before->time);return glm::mix(before->value,after->value,alpha);};CameraPreviewPose pose;pose.rig=rig->id;pose.target=rig->target+rig->lookAhead;const float radius=std::max(.01f,sample("radius",rig->radius));const float az=glm::radians(rig->azimuth),el=glm::radians(rig->elevation),yaw=glm::radians(rig->yaw),pitch=glm::radians(rig->pitch);if(rig->mode=="follow")pose.eye=rig->target+rig->offset;else if(rig->mode=="orbit")pose.eye=rig->target+glm::vec3(std::cos(el)*std::sin(az),std::sin(el),std::cos(el)*std::cos(az))*radius;else if(rig->mode=="topdown")pose.eye=rig->target+glm::vec3(0,radius,0);else if(rig->mode=="firstperson"){pose.eye=rig->target;pose.target=pose.eye+glm::vec3(std::cos(pitch)*std::sin(yaw),std::sin(pitch),-std::cos(pitch)*std::cos(yaw));}else pose.eye=rig->target+rig->offset;pose.fov=glm::clamp(sample("fov",rig->fov),1.f,179.f);return eve::editing::applied<CameraPreviewPose>(pose);}
98Result<EditorGizmoSnapshot> CameraPreview::gizmo(const CameraDocumentTarget& document,float time)const{auto sampled=evaluate(document,time);if(!sampled.ok()) return Result<EditorGizmoSnapshot>::failure(sampled.status());const auto& pose=sampled.value();const glm::vec3 delta=pose.target-pose.eye;const float length=glm::length(delta);const glm::vec3 direction=length>1e-6f?delta/length:glm::vec3(0,0,-1);EditorGizmoSnapshot snapshot;snapshot.status=EditorStatus::Applied;snapshot.target=document.targetId().value();snapshot.targetRevision=document.revision();EditorGizmoPrimitive camera;camera.id="camera";camera.kind="camera";camera.position={pose.eye.x,pose.eye.y,pose.eye.z};camera.direction={direction.x,direction.y,direction.z};camera.length=length;camera.color={0.2,0.8,1,1};snapshot.primitives.push_back(camera);EditorGizmoPrimitive target;target.id="target";target.kind="point";target.position={pose.target.x,pose.target.y,pose.target.z};target.radius=.15;target.color={1,.7,.1,1};snapshot.primitives.push_back(target);EditorGizmoPrimitive frustum;frustum.id="frustum";frustum.kind="frustum";frustum.position=camera.position;frustum.direction=camera.direction;frustum.length=1;frustum.radius=std::tan(glm::radians(pose.fov*.5f));frustum.color={.2,.8,1,.6};snapshot.primitives.push_back(frustum);return eve::editing::applied<EditorGizmoSnapshot>(std::move(snapshot));}
99
100} // namespace eve::camera_editing
LogicalId target
ActionParameterOperation operation
double value
std::string descriptor
eve::EntitySpatialPose pose
const std::string & s
int priority
float length
Definition CaveMesh.cpp:94
int az
Definition CaveMesh.cpp:113
std::map< std::string, Var > values
float maximum[3]
float minimum[3]
float camera[2]
std::uint32_t key
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
double r
float v
TokenKind kind
std::map< std::string, PropertyRule > properties
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
bool finite
float radius
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
float begin
float d
FrustumPlanes frustum
RoadLaneDirection direction
double number
bool found
std::uint32_t count
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
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
bool empty() const noexcept
True when no compatibility spelling has been assigned.
Definition Identity.h:374
Revisioned camera-rig and director-timeline editing document.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< DomainOperation > makeDeleteKey(const ObjectId &id) const
Remove a timeline key.
Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode) const override
Make set.
const std::vector< CameraRigValue > & rigs() const
Return authored rigs in stable document order.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically replace the document from a snapshot.
std::vector< EditorDiagnostic > validate() const
Validate references, ranges, names and document budgets.
Result< DomainOperation > makeDeleteRig(const ObjectId &id) const
Delete a rig only when no timeline cut references it.
Result< DomainOperation > makeCreateRig(const CameraRigValue &rig) const
Add a rig with a stable identity.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const override
Current revision.
CameraDocumentTarget(std::string id)
Camera document target.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
Result< DomainOperation > makeCreateKey(const CameraTimelineKeyValue &key) const
Add a stable timeline key.
Result< DomainOperation > makeReset(const SelectionSnapshot &selection, const PropertyPath &path) const override
Make reset.
EditorValue snapshotValue() const
Capture a versioned atomic persistence value.
std::vector< CameraTimelineKeyValue > timeline() const
Return timeline keys sorted by time then stable identity.
PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const override
Reads .
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
PropertySchema schema(const SelectionSnapshot &selection) const override
Schema.
TargetId targetId() const override
Target id.
TargetDescriptor describe() const override
Describe.
Result< EditorGizmoSnapshot > gizmo(const CameraDocumentTarget &document, float time) const
Build camera position, look line, target and near-plane overlay primitives.
Result< CameraPreviewPose > evaluate(const CameraDocumentTarget &document, float time) const
Evaluate the most recent cut and scalar keys at a bounded time.
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
std::uint64_t revision() const override
Return the current revision.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
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.
editing::CapabilityId CapabilityId
editing::PropertyType PropertyType
editing::PropertyPath PropertyPath
editing::DomainOperation DomainOperation
editing::ObjectId ObjectId
editing::Value EditorValue
editing::PropertyDescriptor PropertyDescriptor
editing::TargetId TargetId
editing::RuleId RuleId
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)
bool enabled
Renderer-neutral camera pose sampled from an authored timeline.
Stable authored camera rig independent of a live scene camera.
Stable camera timeline key (cut, event, or scalar automation).
DomainOperation public API.
Renderer-neutral wire primitive for authoring viewport overlays.
Immutable revision-tagged overlay snapshot consumed by any viewport renderer.
std::vector< GizmoPrimitive > primitives
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
std::vector< SelectionItem > items
Stable discovery metadata exposed by every editable target.