载入中...
搜索中...
未找到
AnimSkin.cpp
浏览该文件的文档.
4
5#include "common/Exception.h"
6#include "graphics/Graphics.h"
7#include "graphics/Mesh.h"
8
9
10#include <algorithm>
11#include <cmath>
12#include <cstring>
13#include <unordered_map>
14#include <vector>
15
16namespace eve::animation {
17
18namespace {
19
25void normalMatrixCofactor(const Mat4& m, float out[9]) {
26 // Row-major 3x3 linear part of the skin matrix.
27 const float r0x = m.m[0], r0y = m.m[4], r0z = m.m[8];
28 const float r1x = m.m[1], r1y = m.m[5], r1z = m.m[9];
29 const float r2x = m.m[2], r2y = m.m[6], r2z = m.m[10];
30 out[0] = r1y * r2z - r1z * r2y;
31 out[1] = r1z * r2x - r1x * r2z;
32 out[2] = r1x * r2y - r1y * r2x;
33 out[3] = r2y * r0z - r2z * r0y;
34 out[4] = r2z * r0x - r2x * r0z;
35 out[5] = r2x * r0y - r2y * r0x;
36 out[6] = r0y * r1z - r0z * r1y;
37 out[7] = r0z * r1x - r0x * r1z;
38 out[8] = r0x * r1y - r0y * r1x;
39}
40
41} // namespace
42
43void AnimSkin::requireVertex(int vertexIndex) const {
44 if (vertexIndex < 0 || vertexIndex >= vertexCount_) {
45 throw Exception("AnimSkin: invalid vertex index %d", vertexIndex);
46 }
47}
48
49void AnimSkin::requireSkinBone(int skinBoneIndex) const {
50 if (skinBoneIndex < 0 || skinBoneIndex >= getBoneCount()) {
51 throw Exception("AnimSkin: invalid skin bone index %d", skinBoneIndex);
52 }
53}
54
56 auto fail = [](const char* message) {
58 DiagnosticCode::InvalidArgument, message, "animation.skin.streams"));
59 };
60 const std::size_t vertices = streams.vertexCount > 0 ? static_cast<std::size_t>(streams.vertexCount) : 0U;
61 const std::size_t joints = streams.skeletonBones.size();
62 if (vertices == 0 || vertices > 8U * 1024U * 1024U ||
63 streams.bindPositions.size() != vertices * 3U ||
64 (!streams.bindNormals.empty() && streams.bindNormals.size() != vertices * 3U) ||
65 joints == 0 || joints > 65535U || streams.boneNames.size() != joints ||
66 streams.inverseBindMatrices.size() != joints ||
67 streams.vertexSkinJoints.size() != vertices * kMaxInfluences ||
68 streams.vertexWeights.size() != vertices * kMaxInfluences)
69 return fail("AnimSkin native stream sizes are invalid");
70 for (float value : streams.bindPositions) if (!std::isfinite(value)) return fail("AnimSkin position is non-finite");
71 for (float value : streams.bindNormals) if (!std::isfinite(value)) return fail("AnimSkin normal is non-finite");
72 for (std::size_t joint = 0; joint < joints; ++joint) {
73 if (streams.skeletonBones[joint] < 0 || streams.boneNames[joint].empty())
74 return fail("AnimSkin joint identity is invalid");
75 for (float value : streams.inverseBindMatrices[joint])
76 if (!std::isfinite(value)) return fail("AnimSkin inverse bind is non-finite");
77 }
78 for (std::size_t index = 0; index < streams.vertexWeights.size(); ++index) {
79 const int joint = streams.vertexSkinJoints[index];
80 const float weight = streams.vertexWeights[index];
81 if (!std::isfinite(weight) || weight < 0.F ||
82 (weight > 0.F && (joint < 0 || static_cast<std::size_t>(joint) >= joints)) ||
83 (weight == 0.F && joint >= static_cast<int>(joints)))
84 return fail("AnimSkin influence is invalid");
85 }
86 auto skin = std::make_unique<AnimSkin>();
87 skin->vertexCount_ = streams.vertexCount;
88 skin->bindPos_ = std::move(streams.bindPositions);
89 skin->bindNrm_ = std::move(streams.bindNormals);
90 skin->skeletonBone_ = std::move(streams.skeletonBones);
91 skin->skinBoneNames_ = std::move(streams.boneNames);
92 skin->inverseBind_.resize(joints);
93 for (std::size_t joint = 0; joint < joints; ++joint)
94 std::copy(streams.inverseBindMatrices[joint].begin(), streams.inverseBindMatrices[joint].end(),
95 skin->inverseBind_[joint].m);
96 skin->influences_.resize(vertices * kMaxInfluences);
97 for (std::size_t index = 0; index < skin->influences_.size(); ++index) {
98 skin->influences_[index].bone = streams.vertexWeights[index] > 0.F ? streams.vertexSkinJoints[index] : -1;
99 skin->influences_[index].weight = streams.vertexWeights[index];
100 }
101 return Result<std::unique_ptr<AnimSkin>>::success(std::move(skin));
102}
103
104int AnimSkin::getSkeletonBone(int skinBoneIndex) const {
105 requireSkinBone(skinBoneIndex);
106 return skeletonBone_[static_cast<size_t>(skinBoneIndex)];
107}
108
109std::string AnimSkin::getSkinBoneName(int skinBoneIndex) const {
110 requireSkinBone(skinBoneIndex);
111 return skinBoneNames_[static_cast<size_t>(skinBoneIndex)];
112}
113
114float AnimSkin::getInverseBindElement(int skinBoneIndex, int elementIndex) const {
115 requireSkinBone(skinBoneIndex);
116 if (elementIndex < 0 || elementIndex > 15) {
117 throw Exception("AnimSkin.getInverseBindElement: elementIndex must be 0..15");
118 }
119 return inverseBind_[static_cast<size_t>(skinBoneIndex)].m[elementIndex];
120}
121
123 matrixPaletteValid_ = false;
124 if (!pose) return false;
125 skinMatrices_.resize(inverseBind_.size());
126 for (size_t j = 0; j < inverseBind_.size(); ++j) {
127 const int skelBone = skeletonBone_[j];
128 const Mat4 world = Mat4::fromTRS(pose->world(skelBone));
129 skinMatrices_[j] = Mat4::mul(world, inverseBind_[j]);
130 }
131 matrixPaletteValid_ = true;
132 return true;
133}
134
135float AnimSkin::getMatrixPaletteElement(int skinBoneIndex, int elementIndex) const {
136 requireSkinBone(skinBoneIndex);
137 if (!matrixPaletteValid_) throw Exception("AnimSkin.getMatrixPaletteElement: call updateMatrixPalette first");
138 if (elementIndex < 0 || elementIndex >= 16) {
139 throw Exception("AnimSkin.getMatrixPaletteElement: elementIndex must be 0..15");
140 }
141 return skinMatrices_[static_cast<size_t>(skinBoneIndex)].m[elementIndex];
142}
143
145 if (!gfx || !mesh || mesh->getVertexCount() != vertexCount_) return false;
146 // The uploaded joint stream uses unsigned 16-bit indices.
147 if (getBoneCount() > 65536) return false;
148 std::vector<uint16_t> joints(static_cast<size_t>(vertexCount_) * kMaxInfluences, 0);
149 std::vector<float> weights(static_cast<size_t>(vertexCount_) * kMaxInfluences, 0.f);
150 for (int v = 0; v < vertexCount_; ++v) {
151 for (int i = 0; i < kMaxInfluences; ++i) {
152 const size_t idx = static_cast<size_t>(v) * kMaxInfluences + i;
153 const auto& inf = influences_[idx];
154 if (inf.bone >= 0) {
155 joints[idx] = static_cast<uint16_t>(inf.bone);
156 weights[idx] = inf.weight;
157 }
158 }
159 }
160 return gfx->setMeshSkinningData(mesh, joints.data(), weights.data(), vertexCount_);
161}
162
164 if (!mesh || !mesh->hasGpuSkinning() || !updateMatrixPalette(pose) || skinMatrices_.empty()) return false;
165 return mesh->setSkinPalette(skinMatrices_.front().m, static_cast<int>(skinMatrices_.size())).ok();
166}
167
168float AnimSkin::getBindPositionX(int vertexIndex) const {
169 requireVertex(vertexIndex);
170 return bindPos_[static_cast<size_t>(vertexIndex) * 3u + 0];
171}
172float AnimSkin::getBindPositionY(int vertexIndex) const {
173 requireVertex(vertexIndex);
174 return bindPos_[static_cast<size_t>(vertexIndex) * 3u + 1];
175}
176float AnimSkin::getBindPositionZ(int vertexIndex) const {
177 requireVertex(vertexIndex);
178 return bindPos_[static_cast<size_t>(vertexIndex) * 3u + 2];
179}
180
181int AnimSkin::getVertexBone(int vertexIndex, int influenceIndex) const {
182 requireVertex(vertexIndex);
183 if (influenceIndex < 0 || influenceIndex >= kMaxInfluences) {
184 throw Exception("AnimSkin.getVertexBone: influenceIndex must be 0..%d", kMaxInfluences - 1);
185 }
186 const Influence& inf = influences_[static_cast<size_t>(vertexIndex) * kMaxInfluences + influenceIndex];
187 if (inf.bone < 0) return -1;
188 return skeletonBone_[static_cast<size_t>(inf.bone)];
189}
190
191int AnimSkin::getVertexSkinJoint(int vertexIndex, int influenceIndex) const {
192 requireVertex(vertexIndex);
193 if (influenceIndex < 0 || influenceIndex >= kMaxInfluences)
194 throw Exception("AnimSkin.getVertexSkinJoint: influenceIndex must be 0..%d", kMaxInfluences - 1);
195 return influences_[static_cast<size_t>(vertexIndex) * kMaxInfluences + influenceIndex].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", kMaxInfluences - 1);
202 }
203 return influences_[static_cast<size_t>(vertexIndex) * kMaxInfluences + influenceIndex].weight;
204}
205
206void AnimSkin::skinPositions(const AnimPose* pose, float* outPosXYZ) const {
207 if (!pose) throw Exception("AnimSkin.skinPositions: pose is null");
208 if (!outPosXYZ) throw Exception("AnimSkin.skinPositions: outPosXYZ is null");
209 if (vertexCount_ <= 0) return;
210
212
213 for (int v = 0; v < vertexCount_; ++v) {
214 const float bx = bindPos_[static_cast<size_t>(v) * 3u + 0];
215 const float by = bindPos_[static_cast<size_t>(v) * 3u + 1];
216 const float bz = bindPos_[static_cast<size_t>(v) * 3u + 2];
217 float ox = 0.f, oy = 0.f, oz = 0.f;
218 float wsum = 0.f;
219 for (int i = 0; i < kMaxInfluences; ++i) {
220 const Influence& inf = influences_[static_cast<size_t>(v) * kMaxInfluences + i];
221 if (inf.bone < 0 || inf.weight <= 0.f) continue;
222 float px, py, pz;
223 skinMatrices_[static_cast<size_t>(inf.bone)].transformPoint(bx, by, bz, px, py, pz);
224 ox += px * inf.weight;
225 oy += py * inf.weight;
226 oz += pz * inf.weight;
227 wsum += inf.weight;
228 }
229 if (wsum <= 1e-8f) {
230 ox = bx;
231 oy = by;
232 oz = bz;
233 }
234 outPosXYZ[static_cast<size_t>(v) * 3u + 0] = ox;
235 outPosXYZ[static_cast<size_t>(v) * 3u + 1] = oy;
236 outPosXYZ[static_cast<size_t>(v) * 3u + 2] = oz;
237 }
238}
239
240bool AnimSkin::skinPositionsTo(const AnimPose* pose, std::vector<float>& outPosXYZ) const {
241 if (!pose || vertexCount_ <= 0) return false;
242 outPosXYZ.resize(static_cast<size_t>(vertexCount_) * 3u);
243 skinPositions(pose, outPosXYZ.data());
244 return true;
245}
246
248 if (!pose || vertexCount_ <= 0) {
249 skinnedValid_ = false;
250 return false;
251 }
252 skinnedPos_.resize(static_cast<size_t>(vertexCount_) * 3u);
253 skinPositions(pose, skinnedPos_.data());
254 skinnedValid_ = true;
255 return true;
256}
257
258float AnimSkin::getSkinnedPositionX(int vertexIndex) const {
259 requireVertex(vertexIndex);
260 if (!skinnedValid_) throw Exception("AnimSkin.getSkinnedPositionX: call updateSkinnedPositions first");
261 return skinnedPos_[static_cast<size_t>(vertexIndex) * 3u + 0];
262}
263float AnimSkin::getSkinnedPositionY(int vertexIndex) const {
264 requireVertex(vertexIndex);
265 if (!skinnedValid_) throw Exception("AnimSkin.getSkinnedPositionY: call updateSkinnedPositions first");
266 return skinnedPos_[static_cast<size_t>(vertexIndex) * 3u + 1];
267}
268float AnimSkin::getSkinnedPositionZ(int vertexIndex) const {
269 requireVertex(vertexIndex);
270 if (!skinnedValid_) throw Exception("AnimSkin.getSkinnedPositionZ: call updateSkinnedPositions first");
271 return skinnedPos_[static_cast<size_t>(vertexIndex) * 3u + 2];
272}
273
274std::vector<float> AnimSkin::getSkinnedPositions() const {
275 if (!skinnedValid_) return {};
276 return skinnedPos_;
277}
278
280 skinnedNrmValid_ = false;
281 if (!pose || bindNrm_.empty() || vertexCount_ <= 0) return false;
282
283 // n' = sum_i w_i * (M_i^{-1})^T * n, M_i = boneWorld * inverseBind.
284 normalMatrices_.resize(inverseBind_.size() * 9u);
285 for (size_t j = 0; j < inverseBind_.size(); ++j) {
286 const int skelBone = skeletonBone_[j];
287 const Mat4 world = Mat4::fromTRS(pose->world(skelBone));
288 const Mat4 skinMat = Mat4::mul(world, inverseBind_[j]);
289 normalMatrixCofactor(skinMat, normalMatrices_.data() + j * 9u);
290 }
291
292 skinnedNrm_.resize(static_cast<size_t>(vertexCount_) * 3u);
293 for (int v = 0; v < vertexCount_; ++v) {
294 const size_t base = static_cast<size_t>(v) * 3u;
295 const float bx = bindNrm_[base + 0];
296 const float by = bindNrm_[base + 1];
297 const float bz = bindNrm_[base + 2];
298 float nx = 0.f, ny = 0.f, nz = 0.f;
299 float wsum = 0.f;
300 for (int i = 0; i < kMaxInfluences; ++i) {
301 const Influence& inf = influences_[static_cast<size_t>(v) * kMaxInfluences + i];
302 if (inf.bone < 0 || inf.weight <= 0.f) continue;
303 const float* c = normalMatrices_.data() + static_cast<size_t>(inf.bone) * 9u;
304 const float tx = c[0] * bx + c[1] * by + c[2] * bz;
305 const float ty = c[3] * bx + c[4] * by + c[5] * bz;
306 const float tz = c[6] * bx + c[7] * by + c[8] * bz;
307 nx += tx * inf.weight;
308 ny += ty * inf.weight;
309 nz += tz * inf.weight;
310 wsum += inf.weight;
311 }
312 if (wsum <= 1e-8f) {
313 nx = bx;
314 ny = by;
315 nz = bz;
316 } else {
317 const float len = std::sqrt(nx * nx + ny * ny + nz * nz);
318 if (len > 1e-8f) {
319 nx /= len;
320 ny /= len;
321 nz /= len;
322 } else {
323 nx = bx;
324 ny = by;
325 nz = bz;
326 }
327 }
328 skinnedNrm_[base + 0] = nx;
329 skinnedNrm_[base + 1] = ny;
330 skinnedNrm_[base + 2] = nz;
331 }
332 skinnedNrmValid_ = true;
333 return true;
334}
335
336std::vector<float> AnimSkin::getSkinnedNormals() const {
337 if (!skinnedNrmValid_) return {};
338 return skinnedNrm_;
339}
340
342 if (!gfx || !mesh || !mesh->gpuHandle || !pose || vertexCount_ <= 0) return false;
343 if (mesh->getVertexCount() != vertexCount_) return false;
344
345 if (!updateSkinnedPositions(pose)) return false;
347 const auto& importedUv = mesh->importedUv(0);
348 const float* uv = importedUv.size() == static_cast<size_t>(vertexCount_) * 2u
349 ? importedUv.data()
350 : nullptr;
351 return gfx->updateMeshVertices(mesh, skinnedPos_.data(),
352 skinnedNrmValid_ ? skinnedNrm_.data() : nullptr, uv,
353 vertexCount_, nullptr, 0);
354}
355
356} // namespace eve::animation
double value
eve::EntitySpatialPose pose
float uv
int bz
Definition CaveMesh.cpp:114
int bx
Definition CaveMesh.cpp:114
int by
Definition CaveMesh.cpp:114
float py
float nx
float nz
float ny
float pz
std::string message
float v
std::int32_t c
std::vector< Colorf > px
std::uint32_t bone
int idx
World3D * world
std::vector< Point > vertices
Mesh * mesh
float weights[3]
uint32_t index
double oy
double ox
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
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Move-only operation result carrying either a value or Status.
Definition Result.h:155
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
int getSkeletonBone(int skinBoneIndex) const
Skeleton bone index used by skin joint i (-1 if unused).
Definition AnimSkin.cpp:104
bool applyToMesh(graphics::Graphics *gfx, graphics::Mesh *mesh, const AnimPose *pose)
Skin positions (and normals when available) and write the result back to a GPU mesh in one call....
Definition AnimSkin.cpp:341
float getInverseBindElement(int skinBoneIndex, int elementIndex) const
Inverse-bind matrix element (column-major, 0..15) for skin joint i.
Definition AnimSkin.cpp:114
int getBoneCount() const
Returns the bone count.
Definition AnimSkin.h:76
std::vector< float > getSkinnedPositions() const
Packed skinned positions (xyz, vertexCount*3) as a copy. Empty when updateSkinnedPositions() has not ...
Definition AnimSkin.cpp:274
float getSkinnedPositionX(int vertexIndex) const
Cached skinned position component (requires updateSkinnedPositions).
Definition AnimSkin.cpp:258
bool updateMatrixPalette(const AnimPose *pose) const
Build and cache the column-major skinning matrix palette for a pose. The palette contains boneWorld *...
Definition AnimSkin.cpp:122
float getBindPositionX(int vertexIndex) const
Bind-pose (unskinned) position component for vertex v (0..vertexCount-1).
Definition AnimSkin.cpp:168
int getVertexBone(int vertexIndex, int influenceIndex) const
Influence slot i (0..3) for vertex: skeleton bone index or -1.
Definition AnimSkin.cpp:181
bool updateSkinnedPositions(const AnimPose *pose)
Skin into an internal cache readable via getSkinnedPosition*. Returns false if pose is null or there ...
Definition AnimSkin.cpp:247
float getMatrixPaletteElement(int skinBoneIndex, int elementIndex) const
Cached palette element for one skin bone (elementIndex 0..15).
Definition AnimSkin.cpp:135
bool updateSkinnedNormals(const AnimPose *pose)
Skin the bind-pose normals captured at fromModel with the pose. pose must already have computeWorld(s...
Definition AnimSkin.cpp:279
bool skinPositionsTo(const AnimPose *pose, std::vector< float > &outPosXYZ) const
Convenience: skin into a vector sized vertexCount*3. Returns false if pose is null or bone count mism...
Definition AnimSkin.cpp:240
float getVertexWeight(int vertexIndex, int influenceIndex) const
Returns the vertex weight.
Definition AnimSkin.cpp:198
std::string getSkinBoneName(int skinBoneIndex) const
Returns the skin bone name.
Definition AnimSkin.cpp:109
bool bindGpuMesh(graphics::Graphics *gfx, graphics::Mesh *mesh)
Upload this skin's joint/weight vertex stream to an existing GPU mesh.
Definition AnimSkin.cpp:144
std::vector< float > getSkinnedNormals() const
Packed skinned normals (xyz, vertexCount*3) as a copy.
Definition AnimSkin.cpp:336
float getBindPositionZ(int vertexIndex) const
Returns the bind position z.
Definition AnimSkin.cpp:176
bool updateGpuMesh(graphics::Mesh *mesh, const AnimPose *pose) const
Update a bound GPU mesh's matrix palette from a world-computed pose.
Definition AnimSkin.cpp:163
static Result< std::unique_ptr< AnimSkin > > fromStreams(AnimSkinStreamData streams)
Construct a caller-owned skin from validated native streams.
Definition AnimSkin.cpp:55
int getVertexSkinJoint(int vertexIndex, int influenceIndex) const
Influence slot i (0..3) for vertex: skin-local joint index or -1.
Definition AnimSkin.cpp:191
float getSkinnedPositionZ(int vertexIndex) const
Returns the skinned position z.
Definition AnimSkin.cpp:268
float getSkinnedPositionY(int vertexIndex) const
Returns the skinned position y.
Definition AnimSkin.cpp:263
static constexpr int kMaxInfluences
Definition AnimSkin.h:49
float getBindPositionY(int vertexIndex) const
Returns the bind position y.
Definition AnimSkin.cpp:172
void skinPositions(const AnimPose *pose, float *outPosXYZ) const
Linear-blend skin bind-pose positions into outPosXYZ (vertexCount*3 floats). pose must already have c...
Definition AnimSkin.cpp:206
virtual bool updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
In-place update of a mesh's vertex/index data (CPU -> host-visible VBO). Mirrors bakeMeshMorph: the u...
virtual bool setMeshSkinningData(Mesh *mesh, const uint16_t *joints4, const float *weights4, int vertexCount)
Upload four joint indices and weights per vertex for built-in GPU skinning.
Definition Graphics.h:882
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
Owned native streams for constructing a skin outside the model importer.
Definition AnimSkin.h:28
std::vector< std::array< float, 16 > > inverseBindMatrices
Definition AnimSkin.h:34
std::vector< int > vertexSkinJoints
Definition AnimSkin.h:35
std::vector< std::string > boneNames
Definition AnimSkin.h:33
std::vector< int > skeletonBones
Definition AnimSkin.h:32
std::vector< float > bindPositions
Definition AnimSkin.h:30
std::vector< float > bindNormals
Definition AnimSkin.h:31
std::vector< float > vertexWeights
Definition AnimSkin.h:36
Column-major 4x4 matrix (OpenGL / glTF / Assimp-compatible layout). Elements: m[col * 4 + row].
Definition AnimMath.h:156
static Mat4 fromTRS(const TransformTRS &t)
From trs.
Definition AnimMath.h:163
static Mat4 mul(const Mat4 &a, const Mat4 &b)
Mul.
Definition AnimMath.h:191