|
|
@@ -15,14 +15,8 @@ namespace bs
|
|
|
{
|
|
|
AnimationManager::AnimationManager()
|
|
|
: mNextId(1), mUpdateRate(1.0f / 60.0f), mAnimationTime(0.0f), mLastAnimationUpdateTime(0.0f)
|
|
|
- , mNextAnimationUpdateTime(0.0f), mPaused(false), mWorkerStarted(false), mPoseReadBufferIdx(1)
|
|
|
- , mPoseWriteBufferIdx(0), mDataReady(false)
|
|
|
+ , mNextAnimationUpdateTime(0.0f), mPaused(false), mPoseReadBufferIdx(1), mPoseWriteBufferIdx(0)
|
|
|
{
|
|
|
- mAnimationWorker = Task::create("Animation", std::bind(&AnimationManager::evaluateAnimation, this));
|
|
|
-
|
|
|
- mDataReadyCount.store(0, std::memory_order_release);
|
|
|
- mWorkerState.store(WorkerState::Inactive, std::memory_order_release);
|
|
|
-
|
|
|
mBlendShapeVertexDesc = VertexDataDesc::create();
|
|
|
mBlendShapeVertexDesc->addVertElem(VET_FLOAT3, VES_POSITION, 1, 1);
|
|
|
mBlendShapeVertexDesc->addVertElem(VET_UBYTE4_NORM, VES_NORMAL, 1, 1);
|
|
|
@@ -41,41 +35,48 @@ namespace bs
|
|
|
mUpdateRate = 1.0f / fps;
|
|
|
}
|
|
|
|
|
|
- void AnimationManager::preUpdate()
|
|
|
+ const EvaluatedAnimationData* AnimationManager::update(bool async)
|
|
|
{
|
|
|
- if (mPaused || !mWorkerStarted)
|
|
|
- return;
|
|
|
+ // Wait for any workers to complete
|
|
|
+ {
|
|
|
+ Lock lock(mMutex);
|
|
|
|
|
|
- mAnimationWorker->wait();
|
|
|
-
|
|
|
- WorkerState state = mWorkerState.load(std::memory_order_acquire);
|
|
|
- assert(state == WorkerState::DataReady);
|
|
|
+ while (mNumActiveWorkers > 0)
|
|
|
+ mWorkerDoneSignal.wait(lock);
|
|
|
|
|
|
- // Trigger events
|
|
|
- for (auto& anim : mAnimations)
|
|
|
- {
|
|
|
- anim.second->updateFromProxy();
|
|
|
- anim.second->triggerEvents(mAnimationTime, gTime().getFrameDelta());
|
|
|
+ // Advance the buffers (last write buffer becomes read buffer)
|
|
|
+ if(mSwapBuffers)
|
|
|
+ {
|
|
|
+ mPoseReadBufferIdx = (mPoseReadBufferIdx + 1) % (CoreThread::NUM_SYNC_BUFFERS + 1);
|
|
|
+ mPoseWriteBufferIdx = (mPoseWriteBufferIdx + 1) % (CoreThread::NUM_SYNC_BUFFERS + 1);
|
|
|
+
|
|
|
+ mSwapBuffers = false;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- void AnimationManager::postUpdate()
|
|
|
- {
|
|
|
- if (mPaused)
|
|
|
- return;
|
|
|
+ if(mPaused)
|
|
|
+ return &mAnimData[mPoseReadBufferIdx];
|
|
|
|
|
|
mAnimationTime += gTime().getFrameDelta();
|
|
|
if (mAnimationTime < mNextAnimationUpdateTime)
|
|
|
- return;
|
|
|
+ return &mAnimData[mPoseReadBufferIdx];
|
|
|
|
|
|
mNextAnimationUpdateTime = Math::floor(mAnimationTime / mUpdateRate) * mUpdateRate + mUpdateRate;
|
|
|
|
|
|
float timeDelta = mAnimationTime - mLastAnimationUpdateTime;
|
|
|
mLastAnimationUpdateTime = mAnimationTime;
|
|
|
|
|
|
- // Update poses in the currently active buffer. Multi-buffering allows the core thread to safely read the
|
|
|
- // poses without worrying about them being overwritten by another call to postUpdate, as long as the simulation
|
|
|
- // thread doesn't go more than (CoreThread::NUM_SYNC_BUFFERS - 1) frames ahead.
|
|
|
+ // Trigger events and update attachments (for the data from the last frame)
|
|
|
+ if(async)
|
|
|
+ {
|
|
|
+ for (auto& anim : mAnimations)
|
|
|
+ {
|
|
|
+ anim.second->updateFromProxy();
|
|
|
+ anim.second->triggerEvents(mAnimationTime, gTime().getFrameDelta());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update animation proxies from the latest data
|
|
|
mProxies.clear();
|
|
|
for (auto& anim : mAnimations)
|
|
|
{
|
|
|
@@ -83,6 +84,7 @@ namespace bs
|
|
|
mProxies.push_back(anim.second->mAnimProxy);
|
|
|
}
|
|
|
|
|
|
+ // Build frustums for culling
|
|
|
mCullFrustums.clear();
|
|
|
|
|
|
auto& allCameras = gSceneManager().getAllCameras();
|
|
|
@@ -97,26 +99,7 @@ namespace bs
|
|
|
mCullFrustums.push_back(entry.second->getWorldFrustum());
|
|
|
}
|
|
|
|
|
|
- // Make sure thread finishes writing all changes to the anim proxies as they will be read by the animation thread
|
|
|
- mWorkerStarted = true;
|
|
|
- mWorkerState.store(WorkerState::Started, std::memory_order_release);
|
|
|
-
|
|
|
- // Note: Animation thread will trigger about the same time as the core thread. The core thread will need to wait
|
|
|
- // until animation thread finishes, which might end up blocking it (and losing the multi-threading performance).
|
|
|
- // Consider delaying displayed animation for a single frame or pre-calculating animations (by advancing time the
|
|
|
- // previous frame) for non-dirty animations.
|
|
|
- TaskScheduler::instance().addTask(mAnimationWorker);
|
|
|
- }
|
|
|
-
|
|
|
- void AnimationManager::evaluateAnimation()
|
|
|
- {
|
|
|
- // Make sure we don't load obsolete anim proxy data written by the simulation thread
|
|
|
- WorkerState state = mWorkerState.load(std::memory_order_acquire);
|
|
|
- assert(state == WorkerState::Started);
|
|
|
-
|
|
|
- // No need for locking, as we are sure that only postUpdate() writes to the proxy buffer, and increments the write
|
|
|
- // buffer index. And it's called sequentially ensuring previous call to evaluate finishes.
|
|
|
-
|
|
|
+ // Prepare the write buffer
|
|
|
UINT32 totalNumBones = 0;
|
|
|
for (auto& anim : mProxies)
|
|
|
{
|
|
|
@@ -124,192 +107,272 @@ namespace bs
|
|
|
totalNumBones += anim->skeleton->getNumBones();
|
|
|
}
|
|
|
|
|
|
- RendererAnimationData& renderData = mAnimData[mPoseWriteBufferIdx];
|
|
|
-
|
|
|
- UINT32 prevPoseBufferIdx = (mPoseWriteBufferIdx + CoreThread::NUM_SYNC_BUFFERS - 1) % CoreThread::NUM_SYNC_BUFFERS;
|
|
|
- RendererAnimationData& prevRenderData = mAnimData[prevPoseBufferIdx];
|
|
|
-
|
|
|
- mPoseWriteBufferIdx = (mPoseWriteBufferIdx + 1) % CoreThread::NUM_SYNC_BUFFERS;
|
|
|
-
|
|
|
+ // Prepare the write buffer
|
|
|
+ EvaluatedAnimationData& renderData = mAnimData[mPoseWriteBufferIdx];
|
|
|
renderData.transforms.resize(totalNumBones);
|
|
|
renderData.infos.clear();
|
|
|
|
|
|
+ // Queue animation evaluation tasks
|
|
|
+ {
|
|
|
+ Lock lock(mMutex);
|
|
|
+ mNumActiveWorkers = (UINT32)mProxies.size();
|
|
|
+ }
|
|
|
+
|
|
|
UINT32 curBoneIdx = 0;
|
|
|
- for(auto& anim : mProxies)
|
|
|
+ for (auto& anim : mProxies)
|
|
|
{
|
|
|
- if(anim->mCullEnabled)
|
|
|
+ auto evaluateAnimWorker = [this, anim, curBoneIdx]()
|
|
|
{
|
|
|
- bool isVisible = false;
|
|
|
- for(auto& frustum : mCullFrustums)
|
|
|
+ UINT32 boneIdx = curBoneIdx;
|
|
|
+ evaluateAnimation(anim.get(), boneIdx);
|
|
|
+
|
|
|
+ Lock lock(mMutex);
|
|
|
{
|
|
|
- if(frustum.intersects(anim->mBounds))
|
|
|
- {
|
|
|
- isVisible = true;
|
|
|
- break;
|
|
|
- }
|
|
|
+ assert(mNumActiveWorkers > 0);
|
|
|
+ mNumActiveWorkers--;
|
|
|
}
|
|
|
|
|
|
- if (!isVisible)
|
|
|
- continue;
|
|
|
- }
|
|
|
+ mWorkerDoneSignal.notify_one();
|
|
|
+ };
|
|
|
|
|
|
- RendererAnimationData::AnimInfo animInfo;
|
|
|
- bool hasAnimInfo = false;
|
|
|
+ SPtr<Task> task = Task::create("AnimWorker", evaluateAnimWorker);
|
|
|
+ TaskScheduler::instance().addTask(task);
|
|
|
|
|
|
- // Evaluate skeletal animation
|
|
|
if (anim->skeleton != nullptr)
|
|
|
+ curBoneIdx += anim->skeleton->getNumBones();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Wait for tasks to complete
|
|
|
+ if(!async)
|
|
|
+ {
|
|
|
{
|
|
|
- UINT32 numBones = anim->skeleton->getNumBones();
|
|
|
+ Lock lock(mMutex);
|
|
|
|
|
|
- RendererAnimationData::PoseInfo& poseInfo = animInfo.poseInfo;
|
|
|
- poseInfo.animId = anim->id;
|
|
|
- poseInfo.startIdx = curBoneIdx;
|
|
|
- poseInfo.numBones = numBones;
|
|
|
+ while (mNumActiveWorkers > 0)
|
|
|
+ mWorkerDoneSignal.wait(lock);
|
|
|
+ }
|
|
|
|
|
|
- memset(anim->skeletonPose.hasOverride, 0, sizeof(bool) * anim->skeletonPose.numBones);
|
|
|
- Matrix4* boneDst = renderData.transforms.data() + curBoneIdx;
|
|
|
+ // Trigger events and update attachments (for the data we just evaluated)
|
|
|
+ for (auto& anim : mAnimations)
|
|
|
+ {
|
|
|
+ anim.second->updateFromProxy();
|
|
|
+ anim.second->triggerEvents(mAnimationTime, gTime().getFrameDelta());
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // Copy transforms from mapped scene objects
|
|
|
- UINT32 boneTfrmIdx = 0;
|
|
|
- for(UINT32 i = 0; i < anim->numSceneObjects; i++)
|
|
|
- {
|
|
|
- const AnimatedSceneObjectInfo& soInfo = anim->sceneObjectInfos[i];
|
|
|
+ mSwapBuffers = true;
|
|
|
|
|
|
- if (soInfo.boneIdx == -1)
|
|
|
- continue;
|
|
|
+ return &mAnimData[mPoseReadBufferIdx];
|
|
|
+ }
|
|
|
|
|
|
- boneDst[soInfo.boneIdx] = anim->sceneObjectTransforms[boneTfrmIdx];
|
|
|
- anim->skeletonPose.hasOverride[soInfo.boneIdx] = true;
|
|
|
- boneTfrmIdx++;
|
|
|
+ void AnimationManager::evaluateAnimation(AnimationProxy* anim, UINT32& curBoneIdx)
|
|
|
+ {
|
|
|
+ if (anim->mCullEnabled)
|
|
|
+ {
|
|
|
+ bool isVisible = false;
|
|
|
+ for (auto& frustum : mCullFrustums)
|
|
|
+ {
|
|
|
+ if (frustum.intersects(anim->mBounds))
|
|
|
+ {
|
|
|
+ isVisible = true;
|
|
|
+ break;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Animate bones
|
|
|
- anim->skeleton->getPose(boneDst, anim->skeletonPose, anim->skeletonMask, anim->layers, anim->numLayers);
|
|
|
+ if (!isVisible)
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- curBoneIdx += numBones;
|
|
|
- hasAnimInfo = true;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- RendererAnimationData::PoseInfo& poseInfo = animInfo.poseInfo;
|
|
|
- poseInfo.animId = anim->id;
|
|
|
- poseInfo.startIdx = 0;
|
|
|
- poseInfo.numBones = 0;
|
|
|
- }
|
|
|
+ EvaluatedAnimationData& renderData = mAnimData[mPoseWriteBufferIdx];
|
|
|
+
|
|
|
+ UINT32 prevPoseBufferIdx = (mPoseWriteBufferIdx + CoreThread::NUM_SYNC_BUFFERS) % (CoreThread::NUM_SYNC_BUFFERS + 1);
|
|
|
+ EvaluatedAnimationData& prevRenderData = mAnimData[prevPoseBufferIdx];
|
|
|
|
|
|
- // Reset mapped SO transform
|
|
|
- for (UINT32 i = 0; i < anim->sceneObjectPose.numBones; i++)
|
|
|
- {
|
|
|
- anim->sceneObjectPose.positions[i] = Vector3::ZERO;
|
|
|
- anim->sceneObjectPose.rotations[i] = Quaternion::IDENTITY;
|
|
|
- anim->sceneObjectPose.scales[i] = Vector3::ONE;
|
|
|
- }
|
|
|
+ EvaluatedAnimationData::AnimInfo animInfo;
|
|
|
+ bool hasAnimInfo = false;
|
|
|
+
|
|
|
+ // Evaluate skeletal animation
|
|
|
+ if (anim->skeleton != nullptr)
|
|
|
+ {
|
|
|
+ UINT32 numBones = anim->skeleton->getNumBones();
|
|
|
|
|
|
- // Update mapped scene objects
|
|
|
- memset(anim->sceneObjectPose.hasOverride, 1, sizeof(bool) * anim->numSceneObjects);
|
|
|
+ EvaluatedAnimationData::PoseInfo& poseInfo = animInfo.poseInfo;
|
|
|
+ poseInfo.animId = anim->id;
|
|
|
+ poseInfo.startIdx = curBoneIdx;
|
|
|
+ poseInfo.numBones = numBones;
|
|
|
|
|
|
- // Update scene object transforms
|
|
|
- for(UINT32 i = 0; i < anim->numSceneObjects; i++)
|
|
|
+ memset(anim->skeletonPose.hasOverride, 0, sizeof(bool) * anim->skeletonPose.numBones);
|
|
|
+ Matrix4* boneDst = renderData.transforms.data() + curBoneIdx;
|
|
|
+
|
|
|
+ // Copy transforms from mapped scene objects
|
|
|
+ UINT32 boneTfrmIdx = 0;
|
|
|
+ for (UINT32 i = 0; i < anim->numSceneObjects; i++)
|
|
|
{
|
|
|
const AnimatedSceneObjectInfo& soInfo = anim->sceneObjectInfos[i];
|
|
|
|
|
|
- // We already evaluated bones
|
|
|
- if (soInfo.boneIdx != -1)
|
|
|
+ if (soInfo.boneIdx == -1)
|
|
|
continue;
|
|
|
|
|
|
- if (soInfo.layerIdx == -1 || soInfo.stateIdx == -1)
|
|
|
- continue;
|
|
|
+ boneDst[soInfo.boneIdx] = anim->sceneObjectTransforms[boneTfrmIdx];
|
|
|
+ anim->skeletonPose.hasOverride[soInfo.boneIdx] = true;
|
|
|
+ boneTfrmIdx++;
|
|
|
+ }
|
|
|
|
|
|
- const AnimationState& state = anim->layers[soInfo.layerIdx].states[soInfo.stateIdx];
|
|
|
- if (state.disabled)
|
|
|
- continue;
|
|
|
+ // Animate bones
|
|
|
+ anim->skeleton->getPose(boneDst, anim->skeletonPose, anim->skeletonMask, anim->layers, anim->numLayers);
|
|
|
+
|
|
|
+ curBoneIdx += numBones;
|
|
|
+ hasAnimInfo = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ EvaluatedAnimationData::PoseInfo& poseInfo = animInfo.poseInfo;
|
|
|
+ poseInfo.animId = anim->id;
|
|
|
+ poseInfo.startIdx = 0;
|
|
|
+ poseInfo.numBones = 0;
|
|
|
+ }
|
|
|
|
|
|
+ // Reset mapped SO transform
|
|
|
+ for (UINT32 i = 0; i < anim->sceneObjectPose.numBones; i++)
|
|
|
+ {
|
|
|
+ anim->sceneObjectPose.positions[i] = Vector3::ZERO;
|
|
|
+ anim->sceneObjectPose.rotations[i] = Quaternion::IDENTITY;
|
|
|
+ anim->sceneObjectPose.scales[i] = Vector3::ONE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update mapped scene objects
|
|
|
+ memset(anim->sceneObjectPose.hasOverride, 1, sizeof(bool) * anim->numSceneObjects);
|
|
|
+
|
|
|
+ // Update scene object transforms
|
|
|
+ for (UINT32 i = 0; i < anim->numSceneObjects; i++)
|
|
|
+ {
|
|
|
+ const AnimatedSceneObjectInfo& soInfo = anim->sceneObjectInfos[i];
|
|
|
+
|
|
|
+ // We already evaluated bones
|
|
|
+ if (soInfo.boneIdx != -1)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (soInfo.layerIdx == -1 || soInfo.stateIdx == -1)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ const AnimationState& state = anim->layers[soInfo.layerIdx].states[soInfo.stateIdx];
|
|
|
+ if (state.disabled)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ {
|
|
|
+ UINT32 curveIdx = soInfo.curveIndices.position;
|
|
|
+ if (curveIdx != (UINT32)-1)
|
|
|
{
|
|
|
- UINT32 curveIdx = soInfo.curveIndices.position;
|
|
|
- if (curveIdx != (UINT32)-1)
|
|
|
- {
|
|
|
- const TAnimationCurve<Vector3>& curve = state.curves->position[curveIdx].curve;
|
|
|
- anim->sceneObjectPose.positions[curveIdx] = curve.evaluate(state.time, state.positionCaches[curveIdx], state.loop);
|
|
|
- anim->sceneObjectPose.hasOverride[curveIdx] = false;
|
|
|
- }
|
|
|
+ const TAnimationCurve<Vector3>& curve = state.curves->position[curveIdx].curve;
|
|
|
+ anim->sceneObjectPose.positions[curveIdx] = curve.evaluate(state.time, state.positionCaches[curveIdx], state.loop);
|
|
|
+ anim->sceneObjectPose.hasOverride[curveIdx] = false;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ {
|
|
|
+ UINT32 curveIdx = soInfo.curveIndices.rotation;
|
|
|
+ if (curveIdx != (UINT32)-1)
|
|
|
{
|
|
|
- UINT32 curveIdx = soInfo.curveIndices.rotation;
|
|
|
- if (curveIdx != (UINT32)-1)
|
|
|
- {
|
|
|
- const TAnimationCurve<Quaternion>& curve = state.curves->rotation[curveIdx].curve;
|
|
|
- anim->sceneObjectPose.rotations[curveIdx] = curve.evaluate(state.time, state.rotationCaches[curveIdx], state.loop);
|
|
|
- anim->sceneObjectPose.rotations[curveIdx].normalize();
|
|
|
- anim->sceneObjectPose.hasOverride[curveIdx] = false;
|
|
|
- }
|
|
|
+ const TAnimationCurve<Quaternion>& curve = state.curves->rotation[curveIdx].curve;
|
|
|
+ anim->sceneObjectPose.rotations[curveIdx] = curve.evaluate(state.time, state.rotationCaches[curveIdx], state.loop);
|
|
|
+ anim->sceneObjectPose.rotations[curveIdx].normalize();
|
|
|
+ anim->sceneObjectPose.hasOverride[curveIdx] = false;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ {
|
|
|
+ UINT32 curveIdx = soInfo.curveIndices.scale;
|
|
|
+ if (curveIdx != (UINT32)-1)
|
|
|
{
|
|
|
- UINT32 curveIdx = soInfo.curveIndices.scale;
|
|
|
- if (curveIdx != (UINT32)-1)
|
|
|
- {
|
|
|
- const TAnimationCurve<Vector3>& curve = state.curves->scale[curveIdx].curve;
|
|
|
- anim->sceneObjectPose.scales[curveIdx] = curve.evaluate(state.time, state.scaleCaches[curveIdx], state.loop);
|
|
|
- anim->sceneObjectPose.hasOverride[curveIdx] = false;
|
|
|
- }
|
|
|
+ const TAnimationCurve<Vector3>& curve = state.curves->scale[curveIdx].curve;
|
|
|
+ anim->sceneObjectPose.scales[curveIdx] = curve.evaluate(state.time, state.scaleCaches[curveIdx], state.loop);
|
|
|
+ anim->sceneObjectPose.hasOverride[curveIdx] = false;
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Update generic curves
|
|
|
- // Note: No blending for generic animations, just use first animation
|
|
|
- if (anim->numLayers > 0 && anim->layers[0].numStates > 0)
|
|
|
+ // Update generic curves
|
|
|
+ // Note: No blending for generic animations, just use first animation
|
|
|
+ if (anim->numLayers > 0 && anim->layers[0].numStates > 0)
|
|
|
+ {
|
|
|
+ const AnimationState& state = anim->layers[0].states[0];
|
|
|
+ if (!state.disabled)
|
|
|
{
|
|
|
- const AnimationState& state = anim->layers[0].states[0];
|
|
|
- if (!state.disabled)
|
|
|
+ UINT32 numCurves = (UINT32)state.curves->generic.size();
|
|
|
+ for (UINT32 i = 0; i < numCurves; i++)
|
|
|
{
|
|
|
- UINT32 numCurves = (UINT32)state.curves->generic.size();
|
|
|
- for (UINT32 i = 0; i < numCurves; i++)
|
|
|
- {
|
|
|
- const TAnimationCurve<float>& curve = state.curves->generic[i].curve;
|
|
|
- anim->genericCurveOutputs[i] = curve.evaluate(state.time, state.genericCaches[i], state.loop);
|
|
|
- }
|
|
|
+ const TAnimationCurve<float>& curve = state.curves->generic[i].curve;
|
|
|
+ anim->genericCurveOutputs[i] = curve.evaluate(state.time, state.genericCaches[i], state.loop);
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Update morph shapes
|
|
|
- if(anim->numMorphShapes > 0)
|
|
|
+ // Update morph shapes
|
|
|
+ if (anim->numMorphShapes > 0)
|
|
|
+ {
|
|
|
+ auto iterFind = prevRenderData.infos.find(anim->id);
|
|
|
+ if (iterFind != prevRenderData.infos.end())
|
|
|
+ animInfo.morphShapeInfo = iterFind->second.morphShapeInfo;
|
|
|
+ else
|
|
|
+ animInfo.morphShapeInfo.version = 1; // 0 is considered invalid version
|
|
|
+
|
|
|
+ // Recalculate weights if curves are present
|
|
|
+ bool hasMorphCurves = false;
|
|
|
+ for (UINT32 i = 0; i < anim->numMorphChannels; i++)
|
|
|
{
|
|
|
- auto iterFind = prevRenderData.infos.find(anim->id);
|
|
|
- if (iterFind != prevRenderData.infos.end())
|
|
|
- animInfo.morphShapeInfo = iterFind->second.morphShapeInfo;
|
|
|
+ MorphChannelInfo& channelInfo = anim->morphChannelInfos[i];
|
|
|
+ if (channelInfo.weightCurveIdx != (UINT32)-1)
|
|
|
+ {
|
|
|
+ channelInfo.weight = Math::clamp01(anim->genericCurveOutputs[channelInfo.weightCurveIdx]);
|
|
|
+ hasMorphCurves = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ float frameWeight;
|
|
|
+ if (channelInfo.frameCurveIdx != (UINT32)-1)
|
|
|
+ {
|
|
|
+ frameWeight = Math::clamp01(anim->genericCurveOutputs[channelInfo.frameCurveIdx]);
|
|
|
+ hasMorphCurves = true;
|
|
|
+ }
|
|
|
else
|
|
|
- animInfo.morphShapeInfo.version = 1; // 0 is considered invalid version
|
|
|
+ frameWeight = 0.0f;
|
|
|
|
|
|
- // Recalculate weights if curves are present
|
|
|
- bool hasMorphCurves = false;
|
|
|
- for(UINT32 i = 0; i < anim->numMorphChannels; i++)
|
|
|
+ if (channelInfo.shapeCount == 1)
|
|
|
{
|
|
|
- MorphChannelInfo& channelInfo = anim->morphChannelInfos[i];
|
|
|
- if(channelInfo.weightCurveIdx != (UINT32)-1)
|
|
|
- {
|
|
|
- channelInfo.weight = Math::clamp01(anim->genericCurveOutputs[channelInfo.weightCurveIdx]);
|
|
|
- hasMorphCurves = true;
|
|
|
- }
|
|
|
+ MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart];
|
|
|
|
|
|
- float frameWeight;
|
|
|
- if (channelInfo.frameCurveIdx != (UINT32)-1)
|
|
|
+ // Blend between base shape and the only available frame
|
|
|
+ float relative = frameWeight - shapeInfo.frameWeight;
|
|
|
+ if (relative <= 0.0f)
|
|
|
{
|
|
|
- frameWeight = Math::clamp01(anim->genericCurveOutputs[channelInfo.frameCurveIdx]);
|
|
|
- hasMorphCurves = true;
|
|
|
+ float diff = shapeInfo.frameWeight;
|
|
|
+ if (diff > 0.0f)
|
|
|
+ {
|
|
|
+ float t = -relative / diff;
|
|
|
+ shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ shapeInfo.finalWeight = 1.0f;
|
|
|
}
|
|
|
- else
|
|
|
- frameWeight = 0.0f;
|
|
|
-
|
|
|
- if(channelInfo.shapeCount == 1)
|
|
|
+ else // If past the final frame we clamp
|
|
|
+ shapeInfo.finalWeight = 1.0f;
|
|
|
+ }
|
|
|
+ else if (channelInfo.shapeCount > 1)
|
|
|
+ {
|
|
|
+ for (UINT32 j = 0; j < channelInfo.shapeCount - 1; j++)
|
|
|
{
|
|
|
- MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart];
|
|
|
+ float prevShapeWeight;
|
|
|
+ if (j > 0)
|
|
|
+ prevShapeWeight = anim->morphShapeInfos[j - 1].frameWeight;
|
|
|
+ else
|
|
|
+ prevShapeWeight = 0.0f; // Base shape, blend between it and the first frame
|
|
|
+
|
|
|
+ float nextShapeWeight = anim->morphShapeInfos[j + 1].frameWeight;
|
|
|
+ MorphShapeInfo& shapeInfo = anim->morphShapeInfos[j];
|
|
|
|
|
|
- // Blend between base shape and the only available frame
|
|
|
float relative = frameWeight - shapeInfo.frameWeight;
|
|
|
if (relative <= 0.0f)
|
|
|
{
|
|
|
- float diff = shapeInfo.frameWeight;
|
|
|
+ float diff = shapeInfo.frameWeight - prevShapeWeight;
|
|
|
if (diff > 0.0f)
|
|
|
{
|
|
|
float t = -relative / diff;
|
|
|
@@ -318,189 +381,127 @@ namespace bs
|
|
|
else
|
|
|
shapeInfo.finalWeight = 1.0f;
|
|
|
}
|
|
|
- else // If past the final frame we clamp
|
|
|
- shapeInfo.finalWeight = 1.0f;
|
|
|
- }
|
|
|
- else if(channelInfo.shapeCount > 1)
|
|
|
- {
|
|
|
- for(UINT32 j = 0; j < channelInfo.shapeCount - 1; j++)
|
|
|
+ else
|
|
|
{
|
|
|
- float prevShapeWeight;
|
|
|
- if (j > 0)
|
|
|
- prevShapeWeight = anim->morphShapeInfos[j - 1].frameWeight;
|
|
|
- else
|
|
|
- prevShapeWeight = 0.0f; // Base shape, blend between it and the first frame
|
|
|
-
|
|
|
- float nextShapeWeight = anim->morphShapeInfos[j + 1].frameWeight;
|
|
|
- MorphShapeInfo& shapeInfo = anim->morphShapeInfos[j];
|
|
|
-
|
|
|
- float relative = frameWeight - shapeInfo.frameWeight;
|
|
|
- if (relative <= 0.0f)
|
|
|
+ float diff = nextShapeWeight - shapeInfo.frameWeight;
|
|
|
+ if (diff > 0.0f)
|
|
|
{
|
|
|
- float diff = shapeInfo.frameWeight - prevShapeWeight;
|
|
|
- if (diff > 0.0f)
|
|
|
- {
|
|
|
- float t = -relative / diff;
|
|
|
- shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
|
|
|
- }
|
|
|
- else
|
|
|
- shapeInfo.finalWeight = 1.0f;
|
|
|
+ float t = relative / diff;
|
|
|
+ shapeInfo.finalWeight = std::min(t, 1.0f);
|
|
|
}
|
|
|
else
|
|
|
- {
|
|
|
- float diff = nextShapeWeight - shapeInfo.frameWeight;
|
|
|
- if (diff > 0.0f)
|
|
|
- {
|
|
|
- float t = relative / diff;
|
|
|
- shapeInfo.finalWeight = std::min(t, 1.0f);
|
|
|
- }
|
|
|
- else
|
|
|
- shapeInfo.finalWeight = 0.0f;
|
|
|
- }
|
|
|
+ shapeInfo.finalWeight = 0.0f;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Last frame
|
|
|
- {
|
|
|
- UINT32 lastFrame = channelInfo.shapeStart + channelInfo.shapeCount - 1;
|
|
|
- MorphShapeInfo& prevShapeInfo = anim->morphShapeInfos[lastFrame - 1];
|
|
|
- MorphShapeInfo& shapeInfo = anim->morphShapeInfos[lastFrame];
|
|
|
+ // Last frame
|
|
|
+ {
|
|
|
+ UINT32 lastFrame = channelInfo.shapeStart + channelInfo.shapeCount - 1;
|
|
|
+ MorphShapeInfo& prevShapeInfo = anim->morphShapeInfos[lastFrame - 1];
|
|
|
+ MorphShapeInfo& shapeInfo = anim->morphShapeInfos[lastFrame];
|
|
|
|
|
|
- float relative = frameWeight - shapeInfo.frameWeight;
|
|
|
- if (relative <= 0.0f)
|
|
|
+ float relative = frameWeight - shapeInfo.frameWeight;
|
|
|
+ if (relative <= 0.0f)
|
|
|
+ {
|
|
|
+ float diff = shapeInfo.frameWeight - prevShapeInfo.frameWeight;
|
|
|
+ if (diff > 0.0f)
|
|
|
{
|
|
|
- float diff = shapeInfo.frameWeight - prevShapeInfo.frameWeight;
|
|
|
- if (diff > 0.0f)
|
|
|
- {
|
|
|
- float t = -relative / diff;
|
|
|
- shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
|
|
|
- }
|
|
|
- else
|
|
|
- shapeInfo.finalWeight = 1.0f;
|
|
|
+ float t = -relative / diff;
|
|
|
+ shapeInfo.finalWeight = 1.0f - std::min(t, 1.0f);
|
|
|
}
|
|
|
- else // If past the final frame we clamp
|
|
|
+ else
|
|
|
shapeInfo.finalWeight = 1.0f;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- for(UINT32 j = 0; j < channelInfo.shapeCount; j++)
|
|
|
- {
|
|
|
- MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart + j];
|
|
|
- shapeInfo.finalWeight *= channelInfo.weight;
|
|
|
+ else // If past the final frame we clamp
|
|
|
+ shapeInfo.finalWeight = 1.0f;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Generate morph shape vertices
|
|
|
- if(anim->morphChannelWeightsDirty || hasMorphCurves)
|
|
|
+ for (UINT32 j = 0; j < channelInfo.shapeCount; j++)
|
|
|
{
|
|
|
- SPtr<MeshData> meshData = bs_shared_ptr_new<MeshData>(anim->numMorphVertices, 0, mBlendShapeVertexDesc);
|
|
|
-
|
|
|
- UINT8* bufferData = meshData->getData();
|
|
|
- memset(bufferData, 0, meshData->getSize());
|
|
|
-
|
|
|
- UINT32 tempDataSize = (sizeof(Vector3) + sizeof(float)) * anim->numMorphVertices;
|
|
|
- UINT8* tempData = (UINT8*)bs_stack_alloc(tempDataSize);
|
|
|
- memset(tempData, 0, tempDataSize);
|
|
|
+ MorphShapeInfo& shapeInfo = anim->morphShapeInfos[channelInfo.shapeStart + j];
|
|
|
+ shapeInfo.finalWeight *= channelInfo.weight;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Vector3* tempNormals = (Vector3*)tempData;
|
|
|
- float* accumulatedWeight = (float*)(tempData + sizeof(Vector3) * anim->numMorphVertices);
|
|
|
+ // Generate morph shape vertices
|
|
|
+ if (anim->morphChannelWeightsDirty || hasMorphCurves)
|
|
|
+ {
|
|
|
+ SPtr<MeshData> meshData = bs_shared_ptr_new<MeshData>(anim->numMorphVertices, 0, mBlendShapeVertexDesc);
|
|
|
|
|
|
- UINT8* positions = meshData->getElementData(VES_POSITION, 1, 1);
|
|
|
- UINT8* normals = meshData->getElementData(VES_NORMAL, 1, 1);
|
|
|
+ UINT8* bufferData = meshData->getData();
|
|
|
+ memset(bufferData, 0, meshData->getSize());
|
|
|
|
|
|
- UINT32 stride = mBlendShapeVertexDesc->getVertexStride(1);
|
|
|
+ UINT32 tempDataSize = (sizeof(Vector3) + sizeof(float)) * anim->numMorphVertices;
|
|
|
+ UINT8* tempData = (UINT8*)bs_stack_alloc(tempDataSize);
|
|
|
+ memset(tempData, 0, tempDataSize);
|
|
|
|
|
|
- for(UINT32 i = 0; i < anim->numMorphShapes; i++)
|
|
|
- {
|
|
|
- const MorphShapeInfo& info = anim->morphShapeInfos[i];
|
|
|
- float absWeight = Math::abs(info.finalWeight);
|
|
|
+ Vector3* tempNormals = (Vector3*)tempData;
|
|
|
+ float* accumulatedWeight = (float*)(tempData + sizeof(Vector3) * anim->numMorphVertices);
|
|
|
|
|
|
- if (absWeight < 0.0001f)
|
|
|
- continue;
|
|
|
+ UINT8* positions = meshData->getElementData(VES_POSITION, 1, 1);
|
|
|
+ UINT8* normals = meshData->getElementData(VES_NORMAL, 1, 1);
|
|
|
|
|
|
- const Vector<MorphVertex>& morphVertices = info.shape->getVertices();
|
|
|
- UINT32 numVertices = (UINT32)morphVertices.size();
|
|
|
- for(UINT32 j = 0; j < numVertices; j++)
|
|
|
- {
|
|
|
- const MorphVertex& vertex = morphVertices[j];
|
|
|
+ UINT32 stride = mBlendShapeVertexDesc->getVertexStride(1);
|
|
|
|
|
|
- Vector3* destPos = (Vector3*)(positions + vertex.sourceIdx * stride);
|
|
|
- *destPos += vertex.deltaPosition * info.finalWeight;
|
|
|
+ for (UINT32 i = 0; i < anim->numMorphShapes; i++)
|
|
|
+ {
|
|
|
+ const MorphShapeInfo& info = anim->morphShapeInfos[i];
|
|
|
+ float absWeight = Math::abs(info.finalWeight);
|
|
|
|
|
|
- tempNormals[vertex.sourceIdx] += vertex.deltaNormal * info.finalWeight;
|
|
|
- accumulatedWeight[vertex.sourceIdx] += absWeight;
|
|
|
- }
|
|
|
- }
|
|
|
+ if (absWeight < 0.0001f)
|
|
|
+ continue;
|
|
|
|
|
|
- for(UINT32 i = 0; i < anim->numMorphVertices; i++)
|
|
|
+ const Vector<MorphVertex>& morphVertices = info.shape->getVertices();
|
|
|
+ UINT32 numVertices = (UINT32)morphVertices.size();
|
|
|
+ for (UINT32 j = 0; j < numVertices; j++)
|
|
|
{
|
|
|
- PackedNormal* destNrm = (PackedNormal*)(normals + i * stride);
|
|
|
+ const MorphVertex& vertex = morphVertices[j];
|
|
|
|
|
|
- if (accumulatedWeight[i] > 0.0001f)
|
|
|
- {
|
|
|
- Vector3 normal = tempNormals[i] / accumulatedWeight[i];
|
|
|
- normal /= 2.0f; // Accumulated normal is in range [-2, 2] but our normal packing method assumes [-1, 1] range
|
|
|
+ Vector3* destPos = (Vector3*)(positions + vertex.sourceIdx * stride);
|
|
|
+ *destPos += vertex.deltaPosition * info.finalWeight;
|
|
|
|
|
|
- MeshUtility::packNormals(&normal, (UINT8*)destNrm, 1, sizeof(Vector3), stride);
|
|
|
- destNrm->w = (UINT8)(std::min(1.0f, accumulatedWeight[i]) * 255.999f);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- *destNrm = {{ 127, 127, 127, 0 }};
|
|
|
- }
|
|
|
+ tempNormals[vertex.sourceIdx] += vertex.deltaNormal * info.finalWeight;
|
|
|
+ accumulatedWeight[vertex.sourceIdx] += absWeight;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- bs_stack_free(tempData);
|
|
|
+ for (UINT32 i = 0; i < anim->numMorphVertices; i++)
|
|
|
+ {
|
|
|
+ PackedNormal* destNrm = (PackedNormal*)(normals + i * stride);
|
|
|
|
|
|
- animInfo.morphShapeInfo.meshData = meshData;
|
|
|
+ if (accumulatedWeight[i] > 0.0001f)
|
|
|
+ {
|
|
|
+ Vector3 normal = tempNormals[i] / accumulatedWeight[i];
|
|
|
+ normal /= 2.0f; // Accumulated normal is in range [-2, 2] but our normal packing method assumes [-1, 1] range
|
|
|
|
|
|
- animInfo.morphShapeInfo.version++;
|
|
|
- anim->morphChannelWeightsDirty = false;
|
|
|
+ MeshUtility::packNormals(&normal, (UINT8*)destNrm, 1, sizeof(Vector3), stride);
|
|
|
+ destNrm->w = (UINT8)(std::min(1.0f, accumulatedWeight[i]) * 255.999f);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ *destNrm = { { 127, 127, 127, 0 } };
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- hasAnimInfo = true;
|
|
|
- }
|
|
|
- else
|
|
|
- animInfo.morphShapeInfo.version = 1;
|
|
|
-
|
|
|
- if (hasAnimInfo)
|
|
|
- renderData.infos[anim->id] = animInfo;
|
|
|
- }
|
|
|
-
|
|
|
- // Increments counter and ensures all writes are recorded
|
|
|
- mWorkerState.store(WorkerState::DataReady, std::memory_order_release);
|
|
|
- mDataReadyCount.fetch_add(1, std::memory_order_acq_rel);
|
|
|
- }
|
|
|
+ bs_stack_free(tempData);
|
|
|
|
|
|
- void AnimationManager::waitUntilComplete()
|
|
|
- {
|
|
|
- mAnimationWorker->wait();
|
|
|
+ animInfo.morphShapeInfo.meshData = meshData;
|
|
|
|
|
|
- // Read counter, and ensure all reads are done after writes on anim thread complete
|
|
|
- INT32 dataReadyCount = mDataReadyCount.load(std::memory_order_acquire);
|
|
|
+ animInfo.morphShapeInfo.version++;
|
|
|
+ anim->morphChannelWeightsDirty = false;
|
|
|
+ }
|
|
|
|
|
|
- if (dataReadyCount > CoreThread::NUM_SYNC_BUFFERS)
|
|
|
- {
|
|
|
- LOGERR("Animation manager threading issue. Too many entries in queue: " + toString(dataReadyCount));
|
|
|
- assert(dataReadyCount <= CoreThread::NUM_SYNC_BUFFERS);
|
|
|
+ hasAnimInfo = true;
|
|
|
}
|
|
|
+ else
|
|
|
+ animInfo.morphShapeInfo.version = 1;
|
|
|
|
|
|
- mDataReady = dataReadyCount > 0;
|
|
|
- if (!mDataReady)
|
|
|
- return;
|
|
|
-
|
|
|
- mDataReadyCount.fetch_sub(1, std::memory_order_release);
|
|
|
- mPoseReadBufferIdx = (mPoseReadBufferIdx + 1) % CoreThread::NUM_SYNC_BUFFERS;
|
|
|
- }
|
|
|
-
|
|
|
- const RendererAnimationData& AnimationManager::getRendererData()
|
|
|
- {
|
|
|
- if (!mDataReady)
|
|
|
+ if (hasAnimInfo)
|
|
|
{
|
|
|
- static RendererAnimationData dummy;
|
|
|
- return dummy;
|
|
|
+ Lock lock(mMutex);
|
|
|
+ renderData.infos[anim->id] = animInfo;
|
|
|
}
|
|
|
-
|
|
|
- return mAnimData[mPoseReadBufferIdx];
|
|
|
}
|
|
|
|
|
|
UINT64 AnimationManager::registerAnimation(Animation* anim)
|