载入中...
搜索中...
未找到
ProceduralBoneConfig.cpp
浏览该文件的文档.
2
6#include "common/Diagnostic.h"
7
8#include <cmath>
9#include <set>
10
11namespace eve::animation {
12namespace {
13const Value* field(const Value& value,const char* name){return value.find(name);}
14bool number(const Value& object,const char* name,float& out){const Value* value=field(object,name);if(!value||(!value->isInt64()&&!value->isDouble()))return false;out=static_cast<float>(value->isInt64()?value->asInt():value->asDouble());return std::isfinite(out);}
15bool integer(const Value& object,const char* name,int& out){const Value* value=field(object,name);if(!value||!value->isInt64())return false;out=static_cast<int>(value->asInt());return true;}
16bool boolean(const Value& object,const char* name,bool& out){const Value* value=field(object,name);if(!value||!value->isBool())return false;out=value->asBool();return true;}
17bool string(const Value& object,const char* name,std::string& out){const Value* value=field(object,name);if(!value||!value->isString())return false;out=value->asString();return true;}
18Value particleValue(const DynamicBoneParticleConfig& p){Value::Object o;o["stiffness"]=p.stiffness;o["damping"]=p.damping;o["inertia"]=p.inertia;o["gravityScale"]=p.gravityScale;o["radius"]=p.radius;return Value(std::move(o));}
19}
20
22 auto unit=[](float v){return std::isfinite(v)&&v>=0.f&&v<=1.f;};
23 if(!unit(weight)||!unit(objectMoveResponse)||!std::isfinite(updateRate)||updateRate<0.f||!std::isfinite(teleportThreshold)||teleportThreshold<0.f||!std::isfinite(distanceLimit)||distanceLimit<0.f)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid global dynamic-bone parameter","dynamicBone"));
24 std::set<std::pair<std::string,std::string>> roots;
25 for(size_t i=0;i<chains.size();++i){const auto& c=chains[i];if(c.rootBone.empty()||c.endBone.empty()||!roots.emplace(c.rootBone,c.endBone).second||!unit(c.stiffness)||!unit(c.damping)||!unit(c.inertia)||c.radius<0.f||c.iterations<1||c.freezeAxis<0||c.freezeAxis>3||c.endMode<0||c.endMode>2)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid or duplicate chain","chains["+std::to_string(i)+"]"));for(const auto& p:c.particles)if(!unit(p.stiffness)||!unit(p.damping)||!unit(p.inertia)||p.radius<0.f)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid particle profile","chains.particles"));}
26 for(size_t i=0;i<colliders.size();++i)if(!std::isfinite(colliders[i].radius)||colliders[i].radius<=0.f)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid collider radius","colliders["+std::to_string(i)+"].radius"));
28 return Result<void>::success();
29}
30
32 auto valid=validate();if(!valid)return Result<Value>::failure(valid.status());
33 Value::Object root=unknownFields;root["schema"]=std::string(SchemaId);root["schemaVersion"]=static_cast<std::int64_t>(SchemaVersion);
34 root["gravityX"]=gravityX;root["gravityY"]=gravityY;root["gravityZ"]=gravityZ;root["externalX"]=externalX;root["externalY"]=externalY;root["externalZ"]=externalZ;root["weight"]=weight;root["updateRate"]=updateRate;root["teleportThreshold"]=teleportThreshold;root["objectMoveResponse"]=objectMoveResponse;root["distanceLimit"]=distanceLimit;
35 Value::Array chainValues;for(const auto& c:chains){Value::Object o;o["rootBone"]=c.rootBone;o["endBone"]=c.endBone;o["stiffness"]=c.stiffness;o["damping"]=c.damping;o["inertia"]=c.inertia;o["gravityScale"]=c.gravityScale;o["radius"]=c.radius;o["iterations"]=static_cast<std::int64_t>(c.iterations);o["freezeAxis"]=static_cast<std::int64_t>(c.freezeAxis);o["selfCollision"]=c.selfCollision;o["endMode"]=static_cast<std::int64_t>(c.endMode);o["endLength"]=c.endLength;o["endX"]=c.endX;o["endY"]=c.endY;o["endZ"]=c.endZ;Value::Array particles;for(const auto& p:c.particles)particles.push_back(particleValue(p));o["particles"]=Value(std::move(particles));chainValues.emplace_back(std::move(o));}root["chains"]=Value(std::move(chainValues));
36 Value::Array colliderValues;for(const auto& c:colliders){Value::Object o;o["bone"]=c.bone;o["startX"]=c.startX;o["startY"]=c.startY;o["startZ"]=c.startZ;o["endX"]=c.endX;o["endY"]=c.endY;o["endZ"]=c.endZ;o["radius"]=c.radius;o["capsule"]=c.capsule;o["inside"]=c.inside;o["enabled"]=c.enabled;colliderValues.emplace_back(std::move(o));}root["colliders"]=Value(std::move(colliderValues));
37 auto legValue=[](const FootIKLegConfig& leg){Value::Object o;o["hip"]=leg.hip;o["knee"]=leg.knee;o["foot"]=leg.foot;o["toe"]=leg.toe;o["soleOffset"]=leg.soleOffset;o["toeSoleOffset"]=leg.toeSoleOffset;return Value(std::move(o));};Value::Object foot;foot["enabled"]=footIK.enabled;foot["footLockEnabled"]=footIK.footLockEnabled;foot["pelvisBone"]=footIK.pelvisBone;foot["left"]=legValue(footIK.left);foot["right"]=legValue(footIK.right);foot["maxPelvisOffset"]=footIK.maxPelvisOffset;foot["minGroundNormalY"]=footIK.minGroundNormalY;foot["positionResponse"]=footIK.positionResponse;foot["rotationResponse"]=footIK.rotationResponse;foot["groundStartHeight"]=footIK.groundStartHeight;foot["groundQueryDistance"]=footIK.groundQueryDistance;foot["contactGraceTime"]=footIK.contactGraceTime;foot["lockEnterWeight"]=footIK.lockEnterWeight;foot["lockExitWeight"]=footIK.lockExitWeight;root["footIK"]=Value(std::move(foot));
38 return Result<Value>::success(Value(std::move(root)));
39}
40
42 if(!value.isObject())return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"expected object","dynamicBone"));
44 std::string schema;int version=0;if(!string(value,"schema",schema)||schema!=SchemaId)return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"unexpected schema","schema"));if(!integer(value,"schemaVersion",version)||version!=static_cast<int>(SchemaVersion))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"unsupported schema version","schemaVersion"));
45 if(!number(value,"gravityX",out.gravityX)||!number(value,"gravityY",out.gravityY)||!number(value,"gravityZ",out.gravityZ)||!number(value,"externalX",out.externalX)||!number(value,"externalY",out.externalY)||!number(value,"externalZ",out.externalZ)||!number(value,"weight",out.weight)||!number(value,"updateRate",out.updateRate)||!number(value,"teleportThreshold",out.teleportThreshold)||!number(value,"objectMoveResponse",out.objectMoveResponse)||!number(value,"distanceLimit",out.distanceLimit))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"missing numeric global field","dynamicBone"));
46 const Value* chainArray=field(value,"chains");const Value* colliderArray=field(value,"colliders");const Value* foot=field(value,"footIK");if(!chainArray||!chainArray->isArray()||!colliderArray||!colliderArray->isArray()||!foot||!foot->isObject())return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"expected chain, collider and Foot IK values","dynamicBone"));
47 for(const auto& item:*chainArray->getIf<Value::Array>()){if(!item.isObject())return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"expected chain object","chains"));DynamicBoneChainConfig c;if(!string(item,"rootBone",c.rootBone)||!string(item,"endBone",c.endBone)||!number(item,"stiffness",c.stiffness)||!number(item,"damping",c.damping)||!number(item,"inertia",c.inertia)||!number(item,"gravityScale",c.gravityScale)||!number(item,"radius",c.radius)||!integer(item,"iterations",c.iterations)||!integer(item,"freezeAxis",c.freezeAxis)||!boolean(item,"selfCollision",c.selfCollision)||!integer(item,"endMode",c.endMode)||!number(item,"endLength",c.endLength)||!number(item,"endX",c.endX)||!number(item,"endY",c.endY)||!number(item,"endZ",c.endZ))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid chain field","chains"));const Value* profiles=field(item,"particles");if(!profiles||!profiles->isArray())return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"expected particle array","chains.particles"));for(const auto& profile:*profiles->getIf<Value::Array>()){DynamicBoneParticleConfig p;if(!profile.isObject()||!number(profile,"stiffness",p.stiffness)||!number(profile,"damping",p.damping)||!number(profile,"inertia",p.inertia)||!number(profile,"gravityScale",p.gravityScale)||!number(profile,"radius",p.radius))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid particle","chains.particles"));c.particles.push_back(p);}out.chains.push_back(std::move(c));}
48 for(const auto& item:*colliderArray->getIf<Value::Array>()){if(!item.isObject())return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"expected collider object","colliders"));DynamicBoneColliderConfig c;if(!string(item,"bone",c.bone)||!number(item,"startX",c.startX)||!number(item,"startY",c.startY)||!number(item,"startZ",c.startZ)||!number(item,"endX",c.endX)||!number(item,"endY",c.endY)||!number(item,"endZ",c.endZ)||!number(item,"radius",c.radius)||!boolean(item,"capsule",c.capsule)||!boolean(item,"inside",c.inside)||!boolean(item,"enabled",c.enabled))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid collider field","colliders"));out.colliders.push_back(std::move(c));}
49 if(!boolean(*foot,"enabled",out.footIK.enabled)||!boolean(*foot,"footLockEnabled",out.footIK.footLockEnabled)||!string(*foot,"pelvisBone",out.footIK.pelvisBone)||!number(*foot,"maxPelvisOffset",out.footIK.maxPelvisOffset)||!number(*foot,"minGroundNormalY",out.footIK.minGroundNormalY)||!number(*foot,"positionResponse",out.footIK.positionResponse)||!number(*foot,"rotationResponse",out.footIK.rotationResponse)||!number(*foot,"groundStartHeight",out.footIK.groundStartHeight)||!number(*foot,"groundQueryDistance",out.footIK.groundQueryDistance)||!number(*foot,"contactGraceTime",out.footIK.contactGraceTime)||!number(*foot,"lockEnterWeight",out.footIK.lockEnterWeight)||!number(*foot,"lockExitWeight",out.footIK.lockExitWeight))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid Foot IK field","footIK"));
50 auto decodeLeg=[&](const char* name,FootIKLegConfig& leg){const Value* item=field(*foot,name);return item&&item->isObject()&&string(*item,"hip",leg.hip)&&string(*item,"knee",leg.knee)&&string(*item,"foot",leg.foot)&&string(*item,"toe",leg.toe)&&number(*item,"soleOffset",leg.soleOffset)&&number(*item,"toeSoleOffset",leg.toeSoleOffset);};if(!decodeLeg("left",out.footIK.left)||!decodeLeg("right",out.footIK.right))return Result<DynamicBoneConfig>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"invalid Foot IK leg","footIK"));
51 out.unknownFields=*value.getIf<Value::Object>();for(const char* key:{"schema","schemaVersion","gravityX","gravityY","gravityZ","externalX","externalY","externalZ","weight","updateRate","teleportThreshold","objectMoveResponse","distanceLimit","chains","colliders","footIK"})out.unknownFields.erase(key);
52 auto valid=out.validate();if(!valid)return Result<DynamicBoneConfig>::failure(valid.status());return Result<DynamicBoneConfig>::success(std::move(out));
53}
54
57 for(const auto& c:chains){const int chain=candidate.addChainByName(c.rootBone,c.endBone,c.stiffness,c.damping,c.inertia,c.gravityScale,c.radius,c.iterations);if(chain<0)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"chain bone path cannot be resolved",c.rootBone+"->"+c.endBone));candidate.setChainFreezeAxis(chain,c.freezeAxis);candidate.setChainSelfCollision(chain,c.selfCollision);if(c.endMode==1)candidate.setChainEndLength(chain,c.endLength);else if(c.endMode==2)candidate.setChainEndOffset(chain,c.endX,c.endY,c.endZ);for(size_t i=0;i<c.particles.size();++i){const auto& p=c.particles[i];candidate.setChainParticleParameters(chain,static_cast<int>(i),p.stiffness,p.damping,p.inertia,p.gravityScale,p.radius);}}
58 for(const auto& c:colliders){const int bone=c.bone.empty()?-1:skeleton.findBone(c.bone);if(!c.bone.empty()&&bone<0)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"collider bone cannot be resolved",c.bone));if(c.capsule){if(bone<0)candidate.addColliderCapsule(c.startX,c.startY,c.startZ,c.endX,c.endY,c.endZ,c.radius);else candidate.addBoneColliderCapsule(bone,c.startX,c.startY,c.startZ,c.endX,c.endY,c.endZ,c.radius);}else{if(bone<0)candidate.addColliderSphere(c.startX,c.startY,c.startZ,c.radius);else candidate.addBoneColliderSphere(bone,c.startX,c.startY,c.startZ,c.radius);}const int index=candidate.getColliderCount()-1;candidate.setColliderInside(index,c.inside);candidate.setColliderEnabled(index,c.enabled);}
59 solver=candidate;return Result<void>::success();
60}
61
63 DynamicBoneSolver dynamicCandidate(&skeleton);auto dynamicResult=apply(skeleton,dynamicCandidate);if(!dynamicResult)return dynamicResult;
64 FootIKSolver footCandidate(&skeleton);if(footIK.enabled){const int pelvis=skeleton.findBone(footIK.pelvisBone);const int lh=skeleton.findBone(footIK.left.hip),lk=skeleton.findBone(footIK.left.knee),lf=skeleton.findBone(footIK.left.foot);if(pelvis<0||lh<0||lk<0||lf<0)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"Foot IK bone cannot be resolved","footIK.left"));footCandidate.setPelvisBone(pelvis);footCandidate.configureLeftLeg(lh,lk,lf,footIK.left.soleOffset);if(!footIK.left.toe.empty()){const int toe=skeleton.findBone(footIK.left.toe);if(toe<0)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"left toe cannot be resolved","footIK.left.toe"));footCandidate.configureLeftToe(toe,footIK.left.toeSoleOffset);}if(!footIK.right.hip.empty()||!footIK.right.knee.empty()||!footIK.right.foot.empty()){const int rh=skeleton.findBone(footIK.right.hip),rk=skeleton.findBone(footIK.right.knee),rf=skeleton.findBone(footIK.right.foot);if(rh<0||rk<0||rf<0)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"right leg cannot be resolved","footIK.right"));footCandidate.configureRightLeg(rh,rk,rf,footIK.right.soleOffset);if(!footIK.right.toe.empty()){const int toe=skeleton.findBone(footIK.right.toe);if(toe<0)return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"right toe cannot be resolved","footIK.right.toe"));footCandidate.configureRightToe(toe,footIK.right.toeSoleOffset);}}footCandidate.setMaxPelvisOffset(footIK.maxPelvisOffset);footCandidate.setMinGroundNormalY(footIK.minGroundNormalY);footCandidate.setPositionResponse(footIK.positionResponse);footCandidate.setRotationResponse(footIK.rotationResponse);footCandidate.setGroundQuery(footIK.groundStartHeight,footIK.groundQueryDistance);footCandidate.setContactGraceTime(footIK.contactGraceTime);footCandidate.setFootLockThresholds(footIK.lockEnterWeight,footIK.lockExitWeight);footCandidate.setFootLockEnabled(footIK.footLockEnabled);}
65 dynamicSolver=dynamicCandidate;footSolver=footCandidate;return Result<void>::success();
66}
67}
double value
int root
Definition AnimSmr.cpp:119
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
std::uint32_t key
float v
std::int32_t c
std::uint32_t bone
std::string name
bool valid
float radius
double number
bool boolean
std::string string
TacticalUnit * unit
uint32_t index
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
std::map< std::string, Value > Object
Definition Value.h:34
bool isArray() const noexcept
Return true when this value is an array.
Definition Value.h:97
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
bool isObject() const noexcept
Return true when this value is an object.
Definition Value.h:99
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
int findBone(const std::string &name) const
Finds bone.
World-space spring-chain solver that writes results as local bone rotations.
void setObjectMoveResponse(float value)
Set how much simulated particles follow root translation, from zero to one.
void setColliderInside(int index, bool inside)
Select inside containment or outside exclusion for a collider.
void setChainParticleParameters(int chainIndex, int particleIndex, float stiffness, float damping, float inertia, float gravityScale, float radius)
Override simulation parameters for one chain particle, where zero is the root.
void setDistanceLimit(float value)
Set sleep distance; zero disables distance sleeping.
void setWeight(float weight)
Set final rotation blend weight in [0, 1].
void addBoneColliderCapsule(int boneIndex, float startX, float startY, float startZ, float endX, float endY, float endZ, float radius)
Add a capsule whose endpoints follow bone-local offsets.
void addColliderSphere(float centerX, float centerY, float centerZ, float radius)
Add a fixed model-space sphere collider; non-positive radii are ignored.
void addColliderCapsule(float startX, float startY, float startZ, float endX, float endY, float endZ, float radius)
Add a fixed model-space capsule collider.
void setTeleportThreshold(float distance)
Set root movement distance that triggers a simulation reset.
void setUpdateRate(float updatesPerSecond)
Set preferred simulation frequency; zero means one step per update.
void setChainEndOffset(int chainIndex, float x, float y, float z)
Add a virtual particle at a last-bone-local offset; this replaces end length mode.
void setColliderEnabled(int index, bool enabled)
Enable or disable a collider without changing its index.
void setExternalForce(float x, float y, float z)
Set additional model-space acceleration such as wind.
void setGlobalGravity(float x, float y, float z)
Set model-space gravity acceleration.
void setChainSelfCollision(int chainIndex, bool enabled)
Enable or disable non-adjacent particle self-collision for a chain.
void setChainEndLength(int chainIndex, float length)
Add a virtual particle after the last bone along the animated terminal direction.
void addBoneColliderSphere(int boneIndex, float offsetX, float offsetY, float offsetZ, float radius)
Add a sphere collider following a bone-local offset.
int getColliderCount() const
Return collider count.
int addChainByName(const std::string &rootBone, const std::string &endBone, float stiffness, float damping, float inertia, float gravityScale=1.f, float radius=0.f, int iterations=4)
Name-based addChain variant; returns -1 for invalid input.
void setChainFreezeAxis(int chainIndex, int axis)
Restrict a chain to its animated model-space plane.
Paired-foot IK with contact smoothing and pelvis compensation.
void setMinGroundNormalY(float value)
Set minimum accepted upward normal cosine in [0, 1].
void setMaxPelvisOffset(float distance)
Set maximum pelvis compensation distance.
void setGroundQuery(float startHeight, float distance)
Set ray origin height and downward query distance.
void configureLeftToe(int toe, float soleOffset=0.f)
Configure an optional left toe child and its sole offset.
void configureRightLeg(int hip, int knee, int foot, float soleOffset=0.f)
Configure the right hip-knee-foot chain and sole offset.
void setFootLockEnabled(bool enabled)
Enable or disable world-space foot locking.
void setPelvisBone(int bone)
Configure the pelvis bone.
void setPositionResponse(float value)
Set contact interpolation response in inverse seconds.
void setRotationResponse(float value)
Set foot-normal interpolation response in inverse seconds.
void setContactGraceTime(float seconds)
Set how long a missing automatic contact retains its prior target.
void setFootLockThresholds(float enterWeight, float exitWeight)
Set lock enter and release contact-weight thresholds.
void configureLeftLeg(int hip, int knee, int foot, float soleOffset=0.f)
Configure the left hip-knee-foot chain and sole offset.
void configureRightToe(int toe, float soleOffset=0.f)
Configure an optional right toe child and its sole offset.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
const EditorValue * field(const EditorValue &value, const char *name)
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
Persisted name-based dynamic-bone chain configuration.
Persisted fixed or bone-following sphere/capsule configuration.
Versioned, forward-compatible dynamic-bone asset definition.
static Result< DynamicBoneConfig > fromValue(const Value &value)
Decode schema v1 transactionally; future versions are rejected.
Result< void > validate() const
Validate ranges and structural resource invariants.
Result< Value > toValue() const
Encode schema v1 while preserving unknown root fields.
Result< void > apply(AnimSkeleton &skeleton, DynamicBoneSolver &solver) const
Resolve bone names and atomically replace a solver configuration.
std::vector< DynamicBoneColliderConfig > colliders
static constexpr std::string_view SchemaId
std::vector< DynamicBoneChainConfig > chains
static constexpr std::uint32_t SchemaVersion
Persisted particle profile for one dynamic-bone chain particle.
Persisted name-based leg and optional toe mapping.