载入中...
搜索中...
未找到
AnimSkin.cpp
浏览该文件的文档.
4
5#include "common/Exception.h"
6#include "model3d/ModelData.h"
7
8#include <assimp/matrix4x4.h>
9#include <assimp/mesh.h>
10#include <assimp/scene.h>
11
12#include <algorithm>
13#include <cmath>
14#include <cstring>
15#include <unordered_map>
16#include <vector>
17
18namespace eve::animation {
19
20namespace {
21
22Mat4 fromAiMatrix(const aiMatrix4x4 &m) {
23 // Assimp is row-major; convert to column-major Mat4.
24 Mat4 out;
25 out.m[0] = m.a1;
26 out.m[1] = m.b1;
27 out.m[2] = m.c1;
28 out.m[3] = m.d1;
29 out.m[4] = m.a2;
30 out.m[5] = m.b2;
31 out.m[6] = m.c2;
32 out.m[7] = m.d2;
33 out.m[8] = m.a3;
34 out.m[9] = m.b3;
35 out.m[10] = m.c3;
36 out.m[11] = m.d3;
37 out.m[12] = m.a4;
38 out.m[13] = m.b4;
39 out.m[14] = m.c4;
40 out.m[15] = m.d4;
41 return out;
42}
43
44} // namespace
45
46void AnimSkin::requireVertex(int vertexIndex) const {
47 if (vertexIndex < 0 || vertexIndex >= vertexCount_) {
48 throw Exception("AnimSkin: invalid vertex index %d", vertexIndex);
49 }
50}
51
52void AnimSkin::requireSkinBone(int skinBoneIndex) const {
53 if (skinBoneIndex < 0 || skinBoneIndex >= getBoneCount()) {
54 throw Exception("AnimSkin: invalid skin bone index %d", skinBoneIndex);
55 }
56}
57
59 const AnimSkeleton *skeleton) {
60 if (!model || !skeleton) {
61 throw Exception("AnimSkin.fromModel: null model/skeleton");
62 }
63 const aiMesh *mesh = model->getMesh(meshIndex);
64 if (!mesh) {
65 throw Exception("AnimSkin.fromModel: invalid mesh index %d", meshIndex);
66 }
67 if (!mesh->HasBones() || mesh->mNumBones == 0) {
68 throw Exception("AnimSkin.fromModel: mesh %d has no bones", meshIndex);
69 }
70 if (mesh->mNumVertices == 0 || !mesh->mVertices) {
71 throw Exception("AnimSkin.fromModel: mesh %d has no vertices", meshIndex);
72 }
73
74 auto *skin = new AnimSkin();
75 skin->vertexCount_ = static_cast<int>(mesh->mNumVertices);
76 skin->bindPos_.resize(static_cast<size_t>(skin->vertexCount_) * 3u);
77 for (int v = 0; v < skin->vertexCount_; ++v) {
78 const aiVector3D &p = mesh->mVertices[v];
79 skin->bindPos_[static_cast<size_t>(v) * 3u + 0] = p.x;
80 skin->bindPos_[static_cast<size_t>(v) * 3u + 1] = p.y;
81 skin->bindPos_[static_cast<size_t>(v) * 3u + 2] = p.z;
82 }
83
84 // Gather skin joints that map onto the skeleton by name.
85 std::vector<int> meshBoneToSkin(mesh->mNumBones, -1);
86 int matched = 0;
87 for (unsigned b = 0; b < mesh->mNumBones; ++b) {
88 const aiBone *bone = mesh->mBones[b];
89 if (!bone) continue;
90 const std::string name = bone->mName.C_Str();
91 const int skBone = skeleton->findBone(name);
92 if (skBone < 0) continue;
93 meshBoneToSkin[b] = static_cast<int>(skin->skeletonBone_.size());
94 skin->skeletonBone_.push_back(skBone);
95 skin->skinBoneNames_.push_back(name);
96 skin->inverseBind_.push_back(fromAiMatrix(bone->mOffsetMatrix));
97 ++matched;
98 }
99 if (matched == 0) {
100 delete skin;
101 throw Exception("AnimSkin.fromModel: no mesh bones matched skeleton names");
102 }
103
104 // Accumulate all influences then keep the top-k per vertex (renormalize).
105 struct Acc {
106 int bone = -1;
107 float weight = 0.f;
108 };
109 std::vector<std::vector<Acc>> acc(static_cast<size_t>(skin->vertexCount_));
110
111 for (unsigned b = 0; b < mesh->mNumBones; ++b) {
112 const int skinBone = meshBoneToSkin[b];
113 if (skinBone < 0) continue;
114 const aiBone *bone = mesh->mBones[b];
115 if (!bone) continue;
116 for (unsigned w = 0; w < bone->mNumWeights; ++w) {
117 const aiVertexWeight &vw = bone->mWeights[w];
118 if (vw.mVertexId >= mesh->mNumVertices) continue;
119 if (vw.mWeight <= 0.f) continue;
120 acc[vw.mVertexId].push_back(Acc{skinBone, vw.mWeight});
121 }
122 }
123
124 skin->influences_.assign(static_cast<size_t>(skin->vertexCount_) * kMaxInfluences, Influence{});
125 for (int v = 0; v < skin->vertexCount_; ++v) {
126 auto &list = acc[static_cast<size_t>(v)];
127 std::sort(list.begin(), list.end(),
128 [](const Acc &a, const Acc &b) { return a.weight > b.weight; });
129 // Merge duplicate bone entries.
130 std::unordered_map<int, float> merged;
131 for (const Acc &a : list) {
132 if (a.bone < 0) continue;
133 merged[a.bone] += a.weight;
134 }
135 list.clear();
136 for (const auto &kv : merged) list.push_back(Acc{kv.first, kv.second});
137 std::sort(list.begin(), list.end(),
138 [](const Acc &a, const Acc &b) { return a.weight > b.weight; });
139
140 float sum = 0.f;
141 const int take = std::min(kMaxInfluences, static_cast<int>(list.size()));
142 for (int i = 0; i < take; ++i) sum += list[static_cast<size_t>(i)].weight;
143 const float inv = (sum > 1e-8f) ? (1.f / sum) : 0.f;
144 for (int i = 0; i < take; ++i) {
145 Influence &dst = skin->influences_[static_cast<size_t>(v) * kMaxInfluences + i];
146 dst.bone = list[static_cast<size_t>(i)].bone;
147 dst.weight = list[static_cast<size_t>(i)].weight * inv;
148 }
149 // Vertices with no weights stay at bind (weight sum 0 → identity path in skin).
150 }
151
152 return skin;
153}
154
155int AnimSkin::getSkeletonBone(int skinBoneIndex) const {
156 requireSkinBone(skinBoneIndex);
157 return skeletonBone_[static_cast<size_t>(skinBoneIndex)];
158}
159
160std::string AnimSkin::getSkinBoneName(int skinBoneIndex) const {
161 requireSkinBone(skinBoneIndex);
162 return skinBoneNames_[static_cast<size_t>(skinBoneIndex)];
163}
164
165float AnimSkin::getInverseBindElement(int skinBoneIndex, int elementIndex) const {
166 requireSkinBone(skinBoneIndex);
167 if (elementIndex < 0 || elementIndex > 15) {
168 throw Exception("AnimSkin.getInverseBindElement: elementIndex must be 0..15");
169 }
170 return inverseBind_[static_cast<size_t>(skinBoneIndex)].m[elementIndex];
171}
172
173float AnimSkin::getBindPositionX(int vertexIndex) const {
174 requireVertex(vertexIndex);
175 return bindPos_[static_cast<size_t>(vertexIndex) * 3u + 0];
176}
177float AnimSkin::getBindPositionY(int vertexIndex) const {
178 requireVertex(vertexIndex);
179 return bindPos_[static_cast<size_t>(vertexIndex) * 3u + 1];
180}
181float AnimSkin::getBindPositionZ(int vertexIndex) const {
182 requireVertex(vertexIndex);
183 return bindPos_[static_cast<size_t>(vertexIndex) * 3u + 2];
184}
185
186int AnimSkin::getVertexBone(int vertexIndex, int influenceIndex) const {
187 requireVertex(vertexIndex);
188 if (influenceIndex < 0 || influenceIndex >= kMaxInfluences) {
189 throw Exception("AnimSkin.getVertexBone: influenceIndex must be 0..%d",
190 kMaxInfluences - 1);
191 }
192 const Influence &inf =
193 influences_[static_cast<size_t>(vertexIndex) * kMaxInfluences + influenceIndex];
194 if (inf.bone < 0) return -1;
195 return skeletonBone_[static_cast<size_t>(inf.bone)];
196}
197
198float AnimSkin::getVertexWeight(int vertexIndex, int influenceIndex) const {
199 requireVertex(vertexIndex);
200 if (influenceIndex < 0 || influenceIndex >= kMaxInfluences) {
201 throw Exception("AnimSkin.getVertexWeight: influenceIndex must be 0..%d",
202 kMaxInfluences - 1);
203 }
204 return influences_[static_cast<size_t>(vertexIndex) * kMaxInfluences + influenceIndex].weight;
205}
206
207void AnimSkin::skinPositions(const AnimPose *pose, float *outPosXYZ) const {
208 if (!pose) throw Exception("AnimSkin.skinPositions: pose is null");
209 if (!outPosXYZ) throw Exception("AnimSkin.skinPositions: outPosXYZ is null");
210 if (vertexCount_ <= 0) return;
211
212 // Precompute skinMatrix[j] = boneWorld[skel] * inverseBind[j]
213 std::vector<Mat4> skinMats(inverseBind_.size());
214 for (size_t j = 0; j < inverseBind_.size(); ++j) {
215 const int skelBone = skeletonBone_[j];
216 const Mat4 world = Mat4::fromTRS(pose->world(skelBone));
217 skinMats[j] = Mat4::mul(world, inverseBind_[j]);
218 }
219
220 for (int v = 0; v < vertexCount_; ++v) {
221 const float bx = bindPos_[static_cast<size_t>(v) * 3u + 0];
222 const float by = bindPos_[static_cast<size_t>(v) * 3u + 1];
223 const float bz = bindPos_[static_cast<size_t>(v) * 3u + 2];
224 float ox = 0.f, oy = 0.f, oz = 0.f;
225 float wsum = 0.f;
226 for (int i = 0; i < kMaxInfluences; ++i) {
227 const Influence &inf =
228 influences_[static_cast<size_t>(v) * kMaxInfluences + i];
229 if (inf.bone < 0 || inf.weight <= 0.f) continue;
230 float px, py, pz;
231 skinMats[static_cast<size_t>(inf.bone)].transformPoint(bx, by, bz, px, py, pz);
232 ox += px * inf.weight;
233 oy += py * inf.weight;
234 oz += pz * inf.weight;
235 wsum += inf.weight;
236 }
237 if (wsum <= 1e-8f) {
238 ox = bx;
239 oy = by;
240 oz = bz;
241 }
242 outPosXYZ[static_cast<size_t>(v) * 3u + 0] = ox;
243 outPosXYZ[static_cast<size_t>(v) * 3u + 1] = oy;
244 outPosXYZ[static_cast<size_t>(v) * 3u + 2] = oz;
245 }
246}
247
248bool AnimSkin::skinPositionsTo(const AnimPose *pose, std::vector<float> &outPosXYZ) const {
249 if (!pose || vertexCount_ <= 0) return false;
250 outPosXYZ.resize(static_cast<size_t>(vertexCount_) * 3u);
251 skinPositions(pose, outPosXYZ.data());
252 return true;
253}
254
255bool AnimSkin::updateSkinnedPositions(const AnimPose *pose) {
256 if (!pose || vertexCount_ <= 0) {
257 skinnedValid_ = false;
258 return false;
259 }
260 skinnedPos_.resize(static_cast<size_t>(vertexCount_) * 3u);
261 skinPositions(pose, skinnedPos_.data());
262 skinnedValid_ = true;
263 return true;
264}
265
266float AnimSkin::getSkinnedPositionX(int vertexIndex) const {
267 requireVertex(vertexIndex);
268 if (!skinnedValid_) throw Exception("AnimSkin.getSkinnedPositionX: call updateSkinnedPositions first");
269 return skinnedPos_[static_cast<size_t>(vertexIndex) * 3u + 0];
270}
271float AnimSkin::getSkinnedPositionY(int vertexIndex) const {
272 requireVertex(vertexIndex);
273 if (!skinnedValid_) throw Exception("AnimSkin.getSkinnedPositionY: call updateSkinnedPositions first");
274 return skinnedPos_[static_cast<size_t>(vertexIndex) * 3u + 1];
275}
276float AnimSkin::getSkinnedPositionZ(int vertexIndex) const {
277 requireVertex(vertexIndex);
278 if (!skinnedValid_) throw Exception("AnimSkin.getSkinnedPositionZ: call updateSkinnedPositions first");
279 return skinnedPos_[static_cast<size_t>(vertexIndex) * 3u + 2];
280}
281
282} // namespace eve::animation
int w
std::vector< Colorf > px
uint32_t a
uint32_t b
glm::vec4 p[6]
Mesh * mesh
glm::mat4 model
const char * name
Definition RockMesh.cpp:21
int v
float m[16]
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:15
const TransformTRS & world(int boneIndex) const
Definition AnimPose.cpp:197
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
int findBone(const std::string &name) const
CPU linear-blend skinning binding for one mesh against an AnimSkeleton.
Definition AnimSkin.h:25
int getBoneCount() const
Definition AnimSkin.h:44
static AnimSkin * fromModel(const model3d::ModelData *model, int meshIndex, const AnimSkeleton *skeleton)
Build skin binding for meshIndex on model, mapping bone names onto skeleton. Throws if the mesh has n...
Definition AnimSkin.cpp:58
static constexpr int kMaxInfluences
Definition AnimSkin.h:27
CPU-side decoded 3D model (Assimp scene owned via medialoader::ModelScene). Does not upload to GPU — ...
Definition ModelData.h:23
Column-major 4x4 matrix (OpenGL / glTF / Assimp-compatible layout). Elements: m[col * 4 + row].
Definition AnimMath.h:97