| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #include "Animation/BsSkeleton.h" |
| 4 | #include "Animation/BsAnimationClip.h" |
| 5 | #include "Animation/BsSkeletonMask.h" |
| 6 | #include "Private/RTTI/BsSkeletonRTTI.h" |
| 7 | |
| 8 | namespace bs |
| 9 | { |
| 10 | LocalSkeletonPose::LocalSkeletonPose(UINT32 numBones, bool individualOverride) |
| 11 | : numBones(numBones) |
| 12 | { |
| 13 | const UINT32 overridesPerBone = individualOverride ? 3 : 1; |
| 14 | |
| 15 | UINT32 elementSize = sizeof(Vector3) * 2 + sizeof(Quaternion) + sizeof(bool) * overridesPerBone; |
| 16 | UINT8* buffer = (UINT8*)bs_alloc(elementSize * numBones); |
| 17 | |
| 18 | positions = (Vector3*)buffer; |
| 19 | buffer += sizeof(Vector3) * numBones; |
| 20 | |
| 21 | rotations = (Quaternion*)buffer; |
| 22 | buffer += sizeof(Quaternion) * numBones; |
| 23 | |
| 24 | scales = (Vector3*)buffer; |
| 25 | buffer += sizeof(Vector3) * numBones; |
| 26 | |
| 27 | hasOverride = (bool*)buffer; |
| 28 | } |
| 29 | |
| 30 | LocalSkeletonPose::LocalSkeletonPose(UINT32 numPos, UINT32 numRot, UINT32 numScale) |
| 31 | { |
| 32 | UINT32 bufferSize = sizeof(Vector3) * numPos + sizeof(Quaternion) * numRot + sizeof(Vector3) * numScale; |
| 33 | UINT8* buffer = (UINT8*)bs_alloc(bufferSize); |
| 34 | |
| 35 | positions = (Vector3*)buffer; |
| 36 | buffer += sizeof(Vector3) * numPos; |
| 37 | |
| 38 | rotations = (Quaternion*)buffer; |
| 39 | buffer += sizeof(Quaternion) * numRot; |
| 40 | |
| 41 | scales = (Vector3*)buffer; |
| 42 | } |
| 43 | |
| 44 | LocalSkeletonPose::LocalSkeletonPose(LocalSkeletonPose&& other) |
| 45 | : positions{std::exchange(other.positions, nullptr)} |
| 46 | , rotations{std::exchange(other.rotations, nullptr)} |
| 47 | , scales{std::exchange(other.scales, nullptr)} |
| 48 | , hasOverride{std::exchange(other.hasOverride, nullptr)} |
| 49 | , numBones(std::exchange(other.numBones, 0)) |
| 50 | { } |
| 51 | |
| 52 | LocalSkeletonPose::~LocalSkeletonPose() |
| 53 | { |
| 54 | if (positions != nullptr) |
| 55 | bs_free(positions); |
| 56 | } |
| 57 | |
| 58 | LocalSkeletonPose& LocalSkeletonPose::operator=(LocalSkeletonPose&& other) |
| 59 | { |
| 60 | if (this != &other) |
| 61 | { |
| 62 | if (positions != nullptr) |
| 63 | bs_free(positions); |
| 64 | |
| 65 | positions = std::exchange(other.positions, nullptr); |
| 66 | rotations = std::exchange(other.rotations, nullptr); |
| 67 | scales = std::exchange(other.scales, nullptr); |
| 68 | hasOverride = std::exchange(other.hasOverride, nullptr); |
| 69 | numBones = std::exchange(other.numBones, 0); |
| 70 | } |
| 71 | |
| 72 | return *this; |
| 73 | } |
| 74 | |
| 75 | Skeleton::Skeleton(BONE_DESC* bones, UINT32 numBones) |
| 76 | : mNumBones(numBones), mBoneTransforms(bs_newN<Transform>(numBones)), mInvBindPoses(bs_newN<Matrix4>(numBones)) |
| 77 | , mBoneInfo(bs_newN<SkeletonBoneInfo>(numBones)) |
| 78 | { |
| 79 | for(UINT32 i = 0; i < numBones; i++) |
| 80 | { |
| 81 | mBoneTransforms[i] = bones[i].localTfrm; |
| 82 | mInvBindPoses[i] = bones[i].invBindPose; |
| 83 | mBoneInfo[i].name = bones[i].name; |
| 84 | mBoneInfo[i].parent = bones[i].parent; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | Skeleton::~Skeleton() |
| 89 | { |
| 90 | if(mBoneTransforms != nullptr) |
| 91 | bs_deleteN(mBoneTransforms, mNumBones); |
| 92 | |
| 93 | if(mInvBindPoses != nullptr) |
| 94 | bs_deleteN(mInvBindPoses, mNumBones); |
| 95 | |
| 96 | if (mBoneInfo != nullptr) |
| 97 | bs_deleteN(mBoneInfo, mNumBones); |
| 98 | } |
| 99 | |
| 100 | SPtr<Skeleton> Skeleton::create(BONE_DESC* bones, UINT32 numBones) |
| 101 | { |
| 102 | Skeleton* rawPtr = new (bs_alloc<Skeleton>()) Skeleton(bones, numBones); |
| 103 | |
| 104 | return bs_shared_ptr<Skeleton>(rawPtr); |
| 105 | } |
| 106 | |
| 107 | void Skeleton::getPose(Matrix4* pose, LocalSkeletonPose& localPose, const SkeletonMask& mask, |
| 108 | const AnimationClip& clip, float time, bool loop) |
| 109 | { |
| 110 | bs_frame_mark(); |
| 111 | { |
| 112 | FrameVector<AnimationCurveMapping> boneToCurveMapping(mNumBones); |
| 113 | |
| 114 | AnimationState state; |
| 115 | state.curves = clip.getCurves(); |
| 116 | state.boneToCurveMapping = boneToCurveMapping.data(); |
| 117 | state.loop = loop; |
| 118 | state.weight = 1.0f; |
| 119 | state.time = time; |
| 120 | |
| 121 | FrameVector<TCurveCache<Vector3>> positionCache(state.curves->position.size()); |
| 122 | FrameVector<TCurveCache<Quaternion>> rotationCache(state.curves->rotation.size()); |
| 123 | FrameVector<TCurveCache<Vector3>> scaleCache(state.curves->scale.size()); |
| 124 | |
| 125 | state.positionCaches = positionCache.data(); |
| 126 | state.rotationCaches = rotationCache.data(); |
| 127 | state.scaleCaches = scaleCache.data(); |
| 128 | state.genericCaches = nullptr; |
| 129 | state.disabled = false; |
| 130 | |
| 131 | AnimationStateLayer layer; |
| 132 | layer.index = 0; |
| 133 | layer.additive = false; |
| 134 | layer.states = &state; |
| 135 | layer.numStates = 1; |
| 136 | |
| 137 | clip.getBoneMapping(*this, state.boneToCurveMapping); |
| 138 | |
| 139 | getPose(pose, localPose, mask, &layer, 1); |
| 140 | } |
| 141 | bs_frame_clear(); |
| 142 | } |
| 143 | |
| 144 | void Skeleton::getPose(Matrix4* pose, LocalSkeletonPose& localPose, const SkeletonMask& mask, |
| 145 | const AnimationStateLayer* layers, UINT32 numLayers) |
| 146 | { |
| 147 | // Note: If more performance is required this method could be optimized with vector instructions |
| 148 | |
| 149 | assert(localPose.numBones == mNumBones); |
| 150 | |
| 151 | for(UINT32 i = 0; i < mNumBones; i++) |
| 152 | { |
| 153 | localPose.positions[i] = Vector3::ZERO; |
| 154 | localPose.rotations[i] = Quaternion::ZERO; |
| 155 | localPose.scales[i] = Vector3::ONE; |
| 156 | } |
| 157 | |
| 158 | bool* hasAnimCurve = bs_stack_alloc<bool>(mNumBones); |
| 159 | bs_zero_out(hasAnimCurve, mNumBones); |
| 160 | |
| 161 | // Note: For a possible performance improvement consider keeping an array of only active (non-disabled) bones and |
| 162 | // just iterate over them without mask checks. Possibly also a list of active curve mappings to avoid those checks |
| 163 | // as well. |
| 164 | for(UINT32 i = 0; i < numLayers; i++) |
| 165 | { |
| 166 | const AnimationStateLayer& layer = layers[i]; |
| 167 | |
| 168 | float invLayerWeight; |
| 169 | if (layer.additive) |
| 170 | { |
| 171 | float weightSum = 0.0f; |
| 172 | for (UINT32 j = 0; j < layer.numStates; j++) |
| 173 | weightSum += layer.states[j].weight; |
| 174 | |
| 175 | invLayerWeight = 1.0f / weightSum; |
| 176 | } |
| 177 | else |
| 178 | invLayerWeight = 1.0f; |
| 179 | |
| 180 | for (UINT32 j = 0; j < layer.numStates; j++) |
| 181 | { |
| 182 | const AnimationState& state = layer.states[j]; |
| 183 | if (state.disabled) |
| 184 | continue; |
| 185 | |
| 186 | float normWeight = state.weight * invLayerWeight; |
| 187 | |
| 188 | // Early exit for clips that don't contribute (which there could be plenty especially for sequential blends) |
| 189 | if (Math::approxEquals(normWeight, 0.0f)) |
| 190 | continue; |
| 191 | |
| 192 | for (UINT32 k = 0; k < mNumBones; k++) |
| 193 | { |
| 194 | if (!mask.isEnabled(k)) |
| 195 | continue; |
| 196 | |
| 197 | const AnimationCurveMapping& mapping = state.boneToCurveMapping[k]; |
| 198 | UINT32 curveIdx = mapping.position; |
| 199 | if (curveIdx != (UINT32)-1) |
| 200 | { |
| 201 | const TAnimationCurve<Vector3>& curve = state.curves->position[curveIdx].curve; |
| 202 | localPose.positions[k] += curve.evaluate(state.time, state.positionCaches[curveIdx], state.loop) * normWeight; |
| 203 | |
| 204 | localPose.hasOverride[k] = false; |
| 205 | hasAnimCurve[k] = true; |
| 206 | } |
| 207 | |
| 208 | curveIdx = mapping.scale; |
| 209 | if (curveIdx != (UINT32)-1) |
| 210 | { |
| 211 | const TAnimationCurve<Vector3>& curve = state.curves->scale[curveIdx].curve; |
| 212 | localPose.scales[k] *= curve.evaluate(state.time, state.scaleCaches[curveIdx], state.loop) * normWeight; |
| 213 | |
| 214 | localPose.hasOverride[k] = false; |
| 215 | hasAnimCurve[k] = true; |
| 216 | } |
| 217 | |
| 218 | if (layer.additive) |
| 219 | { |
| 220 | curveIdx = mapping.rotation; |
| 221 | if (curveIdx != (UINT32)-1) |
| 222 | { |
| 223 | bool isAssigned = localPose.rotations[k].w != 0.0f; |
| 224 | if (!isAssigned) |
| 225 | localPose.rotations[k] = Quaternion::IDENTITY; |
| 226 | |
| 227 | const TAnimationCurve<Quaternion>& curve = state.curves->rotation[curveIdx].curve; |
| 228 | |
| 229 | Quaternion value = curve.evaluate(state.time, state.rotationCaches[curveIdx], state.loop); |
| 230 | value = Quaternion::lerp(normWeight, Quaternion::IDENTITY, value); |
| 231 | |
| 232 | localPose.rotations[k] *= value; |
| 233 | localPose.hasOverride[k] = false; |
| 234 | hasAnimCurve[k] = true; |
| 235 | } |
| 236 | } |
| 237 | else |
| 238 | { |
| 239 | curveIdx = mapping.rotation; |
| 240 | if (curveIdx != (UINT32)-1) |
| 241 | { |
| 242 | const TAnimationCurve<Quaternion>& curve = state.curves->rotation[curveIdx].curve; |
| 243 | Quaternion value = curve.evaluate(state.time, state.rotationCaches[curveIdx], state.loop) * normWeight; |
| 244 | |
| 245 | if (value.dot(localPose.rotations[k]) < 0.0f) |
| 246 | value = -value; |
| 247 | |
| 248 | localPose.rotations[k] += value; |
| 249 | localPose.hasOverride[k] = false; |
| 250 | hasAnimCurve[k] = true; |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | // Apply default local tranform to non-animated bones (so that any potential child bones are transformed properly) |
| 258 | for(UINT32 i = 0; i < mNumBones; i++) |
| 259 | { |
| 260 | if(hasAnimCurve[i]) |
| 261 | continue; |
| 262 | |
| 263 | localPose.positions[i] = mBoneTransforms[i].getPosition(); |
| 264 | localPose.rotations[i] = mBoneTransforms[i].getRotation(); |
| 265 | localPose.scales[i] = mBoneTransforms[i].getScale(); |
| 266 | } |
| 267 | |
| 268 | // Calculate local pose matrices |
| 269 | UINT32 isGlobalBytes = sizeof(bool) * mNumBones; |
| 270 | bool* isGlobal = (bool*)bs_stack_alloc(isGlobalBytes); |
| 271 | memset(isGlobal, 0, isGlobalBytes); |
| 272 | |
| 273 | for(UINT32 i = 0; i < mNumBones; i++) |
| 274 | { |
| 275 | bool isAssigned = localPose.rotations[i].w != 0.0f; |
| 276 | if (!isAssigned) |
| 277 | localPose.rotations[i] = Quaternion::IDENTITY; |
| 278 | else |
| 279 | localPose.rotations[i].normalize(); |
| 280 | |
| 281 | if (localPose.hasOverride[i]) |
| 282 | { |
| 283 | isGlobal[i] = true; |
| 284 | continue; |
| 285 | } |
| 286 | |
| 287 | pose[i] = Matrix4::TRS(localPose.positions[i], localPose.rotations[i], localPose.scales[i]); |
| 288 | } |
| 289 | |
| 290 | // Calculate global poses |
| 291 | // Note: For a possible performance improvement consider sorting bones in such order so that parents (and overrides) |
| 292 | // always come before children, we no isGlobal check is needed. |
| 293 | std::function<void(UINT32)> calcGlobal = [&](UINT32 boneIdx) |
| 294 | { |
| 295 | UINT32 parentBoneIdx = mBoneInfo[boneIdx].parent; |
| 296 | if (parentBoneIdx == (UINT32)-1) |
| 297 | { |
| 298 | isGlobal[boneIdx] = true; |
| 299 | return; |
| 300 | } |
| 301 | |
| 302 | if (!isGlobal[parentBoneIdx]) |
| 303 | calcGlobal(parentBoneIdx); |
| 304 | |
| 305 | pose[boneIdx] = pose[parentBoneIdx] * pose[boneIdx]; |
| 306 | isGlobal[boneIdx] = true; |
| 307 | }; |
| 308 | |
| 309 | for (UINT32 i = 0; i < mNumBones; i++) |
| 310 | { |
| 311 | if (!isGlobal[i]) |
| 312 | calcGlobal(i); |
| 313 | } |
| 314 | |
| 315 | for (UINT32 i = 0; i < mNumBones; i++) |
| 316 | pose[i] = pose[i] * mInvBindPoses[i]; |
| 317 | |
| 318 | bs_stack_free(isGlobal); |
| 319 | bs_stack_free(hasAnimCurve); |
| 320 | } |
| 321 | |
| 322 | Transform Skeleton::calcBoneTransform(UINT32 idx) const |
| 323 | { |
| 324 | if(idx >= mNumBones) |
| 325 | return Transform::IDENTITY; |
| 326 | |
| 327 | Transform output = mBoneTransforms[idx]; |
| 328 | |
| 329 | UINT32 parentIdx = mBoneInfo[idx].parent; |
| 330 | while(parentIdx != (UINT32)-1) |
| 331 | { |
| 332 | output.makeWorld(mBoneTransforms[parentIdx]); |
| 333 | |
| 334 | parentIdx = mBoneInfo[parentIdx].parent; |
| 335 | } |
| 336 | |
| 337 | return output; |
| 338 | } |
| 339 | |
| 340 | UINT32 Skeleton::getRootBoneIndex() const |
| 341 | { |
| 342 | for (UINT32 i = 0; i < mNumBones; i++) |
| 343 | { |
| 344 | if (mBoneInfo[i].parent == (UINT32)-1) |
| 345 | return i; |
| 346 | } |
| 347 | |
| 348 | return (UINT32)-1; |
| 349 | } |
| 350 | |
| 351 | SPtr<Skeleton> Skeleton::createEmpty() |
| 352 | { |
| 353 | Skeleton* rawPtr = new (bs_alloc<Skeleton>()) Skeleton(); |
| 354 | |
| 355 | SPtr<Skeleton> newSkeleton = bs_shared_ptr<Skeleton>(rawPtr); |
| 356 | return newSkeleton; |
| 357 | } |
| 358 | |
| 359 | RTTITypeBase* Skeleton::getRTTIStatic() |
| 360 | { |
| 361 | return SkeletonRTTI::instance(); |
| 362 | } |
| 363 | |
| 364 | RTTITypeBase* Skeleton::getRTTI() const |
| 365 | { |
| 366 | return getRTTIStatic(); |
| 367 | } |
| 368 | } |