载入中...
搜索中...
未找到
SkeletonOverlay.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <map>
6#include <set>
7
9namespace {
10
11bool finite(const SkeletonOverlayBone& bone) {
12 for (double value : bone.position)
13 if (!std::isfinite(value)) return false;
14 for (double value : bone.rotation)
15 if (!std::isfinite(value)) return false;
16 return std::isfinite(bone.maskWeight) && bone.maskWeight >= 0.0 && bone.maskWeight <= 1.0;
17}
18
19std::array<double, 3> axis(const std::array<double, 4>& source, int index) {
20 double x = source[0], y = source[1], z = source[2], w = source[3];
21 const double length = std::sqrt(x * x + y * y + z * z + w * w);
22 if (length <= 1e-12) return index == 0 ? std::array<double, 3>{1.0, 0.0, 0.0}
23 : index == 1 ? std::array<double, 3>{0.0, 1.0, 0.0}
24 : std::array<double, 3>{0.0, 0.0, 1.0};
25 x /= length; y /= length; z /= length; w /= length;
26 if (index == 0) return {1.0 - 2.0 * (y * y + z * z), 2.0 * (x * y + w * z),
27 2.0 * (x * z - w * y)};
28 if (index == 1) return {2.0 * (x * y - w * z), 1.0 - 2.0 * (x * x + z * z),
29 2.0 * (y * z + w * x)};
30 return {2.0 * (x * z + w * y), 2.0 * (y * z - w * x),
31 1.0 - 2.0 * (x * x + y * y)};
32}
33
34std::array<double, 4> color(const SkeletonOverlayBone& bone) {
35 std::array<double, 4> result{0.35, 0.7, 1.0, 1.0};
36 if (bone.selected) result = {1.0, 0.78, 0.15, 1.0};
37 else if (bone.retarget == SkeletonRetargetState::Unmatched) result = {1.0, 0.2, 0.2, 1.0};
38 else if (bone.retarget == SkeletonRetargetState::Ambiguous) result = {1.0, 0.45, 0.1, 1.0};
39 else if (bone.retarget == SkeletonRetargetState::Matched) result = {0.25, 0.9, 0.55, 1.0};
40 const double intensity = 0.25 + 0.75 * bone.maskWeight;
41 for (size_t index = 0; index < 3; ++index) result[index] *= intensity;
42 return result;
43}
44
45editing::GizmoSnapshot failure(std::string target, Revision revision, const char* rule,
46 std::string message) {
47 editing::GizmoSnapshot result;
48 result.target = std::move(target);
49 result.targetRevision = revision;
50 result.diagnostics.push_back(eve::editing::ruleDiagnostic(
51 eve::DiagnosticCode::PreconditionViolation, RuleId(rule), DiagnosticSeverity::Error, std::move(message)));
52 return result;
53}
54
55} // namespace
56
58 std::string target, Revision revision, const std::vector<SkeletonOverlayBone>& bones,
59 const SkeletonOverlayOptions& options) const {
60 if (target.empty() || bones.empty())
61 return failure(std::move(target), revision, "editor.skeleton.overlay-empty",
62 "Skeleton overlay requires a target and at least one bone");
63 if (options.maximumBones == 0 || bones.size() > options.maximumBones ||
64 !std::isfinite(options.jointRadius) || options.jointRadius <= 0.0 ||
65 !std::isfinite(options.axisLength) || options.axisLength <= 0.0)
66 return failure(std::move(target), revision, "editor.skeleton.overlay-budget",
67 "Skeleton overlay exceeds its bone or geometry budget");
68 std::map<StableId, const SkeletonOverlayBone*> indexed;
69 for (const SkeletonOverlayBone& bone : bones) {
70 if (bone.id.empty() || !finite(bone) || !indexed.emplace(bone.id, &bone).second)
71 return failure(std::move(target), revision, "editor.skeleton.overlay-bone",
72 "Skeleton overlay contains duplicate, empty or non-finite bone data");
73 if (bone.constrained &&
74 (bone.minimumAngles[0] > bone.maximumAngles[0] ||
75 bone.minimumAngles[1] > bone.maximumAngles[1]))
76 return failure(std::move(target), revision, "editor.skeleton.overlay-constraint",
77 "Skeleton constraint minimum exceeds maximum");
78 }
79 for (const SkeletonOverlayBone& bone : bones) {
80 if (!bone.parent.empty() && !indexed.contains(bone.parent))
81 return failure(std::move(target), revision, "editor.skeleton.overlay-parent",
82 "Skeleton bone references a missing parent: " + bone.id.value());
83 std::set<StableId> ancestors;
85 while (current && !current->parent.empty()) {
86 if (!ancestors.insert(current->id).second)
87 return failure(std::move(target), revision, "editor.skeleton.overlay-cycle",
88 "Skeleton hierarchy contains a cycle");
89 current = indexed.at(current->parent);
90 }
91 }
92
94 result.target = std::move(target);
95 result.targetRevision = revision;
96 for (const SkeletonOverlayBone& bone : bones) {
97 const auto rgba = color(bone);
98 result.primitives.push_back({bone.id.value() + ":joint", "joint", bone.position, {}, {},
99 rgba, options.jointRadius, 0.0,
101 if (!bone.parent.empty()) {
102 const SkeletonOverlayBone& parent = *indexed.at(bone.parent);
103 std::array<double, 3> direction;
104 double lengthSquared = 0.0;
105 for (size_t index = 0; index < 3; ++index) {
106 direction[index] = bone.position[index] - parent.position[index];
107 lengthSquared += direction[index] * direction[index];
108 }
109 const double length = std::sqrt(lengthSquared);
110 if (length > 1e-12) {
111 for (double& value : direction) value /= length;
112 result.primitives.push_back({bone.id.value() + ":bone", "bone-line",
113 parent.position, {}, direction, rgba, 0.0, length,
115 } else {
117 eve::DiagnosticCode::PreconditionViolation, RuleId("editor.skeleton.zero-length"),
118 DiagnosticSeverity::Warning, "Bone has the same world position as its parent: " + bone.name));
119 }
120 }
121 if (options.showAxes && (!options.axesForSelectedOnly || bone.selected)) {
122 static const std::array<std::array<double, 4>, 3> colors{{
123 {1.0, 0.2, 0.2, 1.0}, {0.2, 1.0, 0.2, 1.0}, {0.2, 0.45, 1.0, 1.0}}};
124 static const char* names[] = {"x", "y", "z"};
125 for (int index = 0; index < 3; ++index)
126 result.primitives.push_back({bone.id.value() + ":axis-" + names[index], "axis",
127 bone.position, {}, axis(bone.rotation, index), colors[index],
128 0.0, options.axisLength, false});
129 }
130 if (options.showConstraints && bone.constrained) {
131 result.primitives.push_back({bone.id.value() + ":constraint-yaw", "constraint-arc",
132 bone.position,
133 {bone.minimumAngles[0], bone.maximumAngles[0], 0.0},
134 axis(bone.rotation, 1), {1.0, 0.45, 0.1, 1.0},
135 options.axisLength, 0.0, true});
136 result.primitives.push_back({bone.id.value() + ":constraint-pitch", "constraint-arc",
137 bone.position,
138 {bone.minimumAngles[1], bone.maximumAngles[1], 0.0},
139 axis(bone.rotation, 0), {0.8, 0.3, 1.0, 1.0},
140 options.axisLength, 0.0, true});
141 }
142 }
143 result.status = EditorStatus::Applied;
144 return result;
145}
146
147} // namespace eve::animation_editing
LogicalId target
double value
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float length
Definition CaveMesh.cpp:94
std::string message
std::vector< Bone > bones
std::array< float, 4 > rotation
std::array< float, 3 > position
std::uint32_t bone
std::int32_t parent
bool finite
std::uint64_t revision
std::vector< float > colors
RoadLaneDirection direction
double current
const SquirrelValueOptions & options
uint32_t index
const UnitySourceAsset & source
editing::GizmoSnapshot build(std::string target, Revision revision, const std::vector< SkeletonOverlayBone > &bones, const SkeletonOverlayOptions &options={}) const
Build a deterministic overlay or structured hierarchy/value diagnostics.
editing::Revision Revision
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
int axis(int64_t a, size_t rank)
Axis.
Immutable renderer-neutral world-space bone snapshot.
Overlay density, feature toggles and hard safety budgets.
Immutable revision-tagged overlay snapshot consumed by any viewport renderer.
std::vector< GizmoPrimitive > primitives
std::vector< Diagnostic > diagnostics
glm::vec4 color