summaryrefslogtreecommitdiff
path: root/Runtime/Filters/Deformation
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Filters/Deformation')
-rw-r--r--Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp141
-rw-r--r--Runtime/Filters/Deformation/BlendShapeAnimationBinding.h4
-rw-r--r--Runtime/Filters/Deformation/SkinnedMeshFilter.cpp1535
-rw-r--r--Runtime/Filters/Deformation/SkinnedMeshFilter.h213
4 files changed, 1893 insertions, 0 deletions
diff --git a/Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp
new file mode 100644
index 0000000..484297f
--- /dev/null
+++ b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.cpp
@@ -0,0 +1,141 @@
+#include "UnityPrefix.h"
+#include "Runtime/Animation/GenericAnimationBindingCache.h"
+#include "Runtime/Animation/BoundCurve.h"
+#include "SkinnedMeshFilter.h"
+#include "Runtime/Filters/Mesh/MeshBlendShape.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Interfaces/IAnimationBinding.h"
+#include "Runtime/mecanim/generic/crc32.h"
+
+const char* kBlendShapePrefix = "blendShape.";
+const char* kBlendShapeSerializedPropertyPath = "m_BlendShapeWeights.Array.data[";
+
+static int BlendShapeNameToIndex (SkinnedMeshRenderer& renderer, BindingHash hash)
+{
+ const Mesh* mesh = renderer.GetMesh();
+ if (mesh == NULL)
+ return -1;
+
+ const BlendShapeData& blendShapes = mesh->GetBlendShapeData();
+ int index = GetChannelIndex (blendShapes, hash);
+
+ return index;
+}
+
+static std::string BlendShapeIndexToName (Object& targetObject, int index)
+{
+ SkinnedMeshRenderer* renderer = dynamic_pptr_cast<SkinnedMeshRenderer*> (&targetObject);
+
+ if (renderer == NULL)
+ return std::string();
+ const Mesh* mesh = renderer->GetMesh();
+ if (mesh == NULL)
+ return std::string();
+
+ const BlendShapeData& blendShapeData = mesh->GetBlendShapeData();
+ if (index < mesh->GetBlendShapeChannelCount ())
+ return std::string(kBlendShapePrefix) + GetChannelName (blendShapeData, index);
+ else
+ return std::string();
+}
+
+static bool CalculateBlendShapeHash (const char* name, BindingHash& hash)
+{
+ // Special case support for blendshape weights. We have to call a function.
+ const char* shapeName = ParsePrefixedName (name, kBlendShapePrefix);
+ if (shapeName == NULL)
+ return false;
+
+ hash = mecanim::processCRC32(shapeName);
+ return true;
+}
+
+class BlendshapePropertyBinding : public IAnimationBinding
+{
+#if UNITY_EDITOR
+ virtual void GetAllAnimatableProperties (Object& targetObject, std::vector<EditorCurveBinding>& outProperties) const
+ {
+ SkinnedMeshRenderer& renderer = static_cast<SkinnedMeshRenderer&> (targetObject);
+ Mesh* mesh = renderer.GetMesh();
+ if (mesh == NULL)
+ return;
+
+ for (int i = 0; i < mesh->GetBlendShapeChannelCount (); ++i)
+ AddBinding (outProperties, ClassID(SkinnedMeshRenderer), BlendShapeIndexToName (targetObject, i));
+ }
+#endif
+
+ virtual float GetFloatValue (const UnityEngine::Animation::BoundCurve& bind) const
+ {
+ SkinnedMeshRenderer* renderer = reinterpret_cast<SkinnedMeshRenderer*>(bind.targetObject);
+ const int shapeIndex = reinterpret_cast<int> (bind.targetPtr);
+ return renderer->GetBlendShapeWeight(shapeIndex);
+ }
+
+ virtual void SetFloatValue (const UnityEngine::Animation::BoundCurve& bind, float value) const
+ {
+ SkinnedMeshRenderer* renderer = reinterpret_cast<SkinnedMeshRenderer*>(bind.targetObject);
+
+ const int shapeIndex = reinterpret_cast<int> (bind.targetPtr);
+ renderer->SetBlendShapeWeight(shapeIndex, value);
+ }
+
+ virtual void SetPPtrValue (const UnityEngine::Animation::BoundCurve& bound, SInt32 value) const { }
+
+ virtual SInt32 GetPPtrValue (const UnityEngine::Animation::BoundCurve& bound) const { return 0;}
+
+ virtual bool GenerateBinding (const UnityStr& attribute, bool pptrCurve, UnityEngine::Animation::GenericBinding& outputBinding) const
+ {
+ BindingHash hash;
+ if (pptrCurve || !CalculateBlendShapeHash (attribute.c_str(), hash))
+ return false;
+
+ outputBinding.attribute = hash;
+ return true;
+ }
+
+ virtual ClassIDType BindValue (Object& target, const UnityEngine::Animation::GenericBinding& inputBinding, UnityEngine::Animation::BoundCurve& bound) const
+ {
+ int index = BlendShapeNameToIndex (static_cast<SkinnedMeshRenderer&> (target), inputBinding.attribute);
+ if (index == -1)
+ return ClassID(Undefined);
+
+ bound.targetPtr = (void*)index;
+ return ClassID(float);
+ }
+
+ virtual std::string SerializedPropertyPathToCurveAttribute (Object& target, const char* propertyPath) const
+ {
+ Assert (target.GetClassID () == ClassID(SkinnedMeshRenderer));
+
+ if (BeginsWith (propertyPath, kBlendShapeSerializedPropertyPath))
+ {
+ int index = StringToInt (propertyPath + strlen(kBlendShapeSerializedPropertyPath));
+ return BlendShapeIndexToName (target, index);
+ }
+ else
+ return string();
+ }
+
+ virtual std::string CurveAttributeToSerializedPath (const UnityEngine::Animation::BoundCurve& bound) const
+ {
+ const int shapeIndex = reinterpret_cast<int> (bound.targetPtr);
+ Assert (shapeIndex != -1);
+ return kBlendShapeSerializedPropertyPath + IntToString(shapeIndex) + ']';
+ }
+
+};
+
+static BlendshapePropertyBinding* gBinding = NULL;
+
+void InitializeBlendShapeAnimationBindingInterface ()
+{
+ gBinding = UNITY_NEW (BlendshapePropertyBinding, kMemAnimation);
+ UnityEngine::Animation::GetGenericAnimationBindingCache ().RegisterIAnimationBinding (ClassID(SkinnedMeshRenderer), UnityEngine::Animation::kBlendShapeWeightBinding, gBinding);
+}
+
+void CleanupBlendShapeAnimationBindingInterface ()
+{
+ UNITY_DELETE (gBinding, kMemAnimation);
+}
+
diff --git a/Runtime/Filters/Deformation/BlendShapeAnimationBinding.h b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.h
new file mode 100644
index 0000000..7857b28
--- /dev/null
+++ b/Runtime/Filters/Deformation/BlendShapeAnimationBinding.h
@@ -0,0 +1,4 @@
+#pragma once
+
+void InitializeBlendShapeAnimationBindingInterface ();
+void CleanupBlendShapeAnimationBindingInterface (); \ No newline at end of file
diff --git a/Runtime/Filters/Deformation/SkinnedMeshFilter.cpp b/Runtime/Filters/Deformation/SkinnedMeshFilter.cpp
new file mode 100644
index 0000000..a6ad95e
--- /dev/null
+++ b/Runtime/Filters/Deformation/SkinnedMeshFilter.cpp
@@ -0,0 +1,1535 @@
+#include "UnityPrefix.h"
+#include "SkinnedMeshFilter.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "Runtime/Filters/Mesh/MeshSkinning.h"
+#include "Runtime/Animation/Animation.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Filters/Mesh/MeshRenderer.h"
+#include "Runtime/Filters/Mesh/MeshBlendShaping.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Filters/Mesh/MeshUtility.h"
+#include "Runtime/Graphics/DrawUtil.h"
+#include "Runtime/GameCode/CallDelayed.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/BaseClasses/SupportedMessageOptimization.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "Runtime/Dynamics/SkinnedCloth.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/BaseClasses/CleanupManager.h"
+#include "Runtime/BaseClasses/EventIDs.h"
+#include "Runtime/Misc/PlayerSettings.h"
+#include "Runtime/Interfaces/IPhysics.h"
+#include "Runtime/Interfaces/IAnimation.h"
+#include "BlendShapeAnimationBinding.h"
+#if UNITY_PS3
+#include "Runtime/GfxDevice/ps3/GfxGCMVBO.h"
+#include "Runtime/Filters/Mesh/MeshPartitioner.h"
+#endif
+
+PROFILER_INFORMATION(gMeshSkinningUpdate, "MeshSkinning.Update", kProfilerRender)
+PROFILER_INFORMATION(gMeshSkinningUpdateImmediate, "MeshSkinning.UpdateImmediate", kProfilerRender)
+PROFILER_INFORMATION(gMeshSkinningPrepare, "MeshSkinning.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gMeshSkinningRender, "MeshSkinning.Render", kProfilerRender)
+PROFILER_INFORMATION(gMeshSkinningWait, "MeshSkinning.WaitForSkinThreads", kProfilerRender)
+PROFILER_INFORMATION(gMeshSkinningSkinGPU, "MeshSkinning.SkinOnGPU", kProfilerRender)
+
+
+#if UNITY_EDITOR
+#define SET_CACHED_SURFACE_AREA_DIRTY() m_CachedSurfaceArea = -1.0f;
+#else
+#define SET_CACHED_SURFACE_AREA_DIRTY() // do nothing
+#endif
+
+typedef List< ListNode<SkinnedMeshRenderer> > SkinnedMeshList;
+static SkinnedMeshList gActiveSkinnedMeshes;
+
+/*
+JOE:
+ * TODO: Do we really need this. -> Test cloth
+
+MIRCEA:
+
+ * PS3 version for fallback skinning ( so mesh particle emitters & skinned cloth still works )
+
+*/
+
+SkinnedMeshRenderer::SkinnedMeshRenderer (MemLabelId label, ObjectCreationMode mode)
+: Super(kRendererSkinnedMesh, label, mode)
+, m_BlendShapeWeights(0, label)
+, m_CachedAnimator(NULL)
+, m_CachedBlendShapeCount (0)
+, m_SkinNode(this)
+, m_MeshNode(this)
+, m_AABB(Vector3f::zero, Vector3f::zero)
+, m_MemExportInfo(0)
+{
+ m_Visible = false;
+ m_UpdateBeforeRendering = false;
+ m_SourceMeshDirty = false;
+ m_DirtyAABB = true;
+ m_CachedMesh = NULL;
+ m_ChannelsInVBO = 0;
+ m_Cloth = NULL;
+ m_VBO = NULL;
+ SET_CACHED_SURFACE_AREA_DIRTY();
+}
+
+SkinnedMeshRenderer::~SkinnedMeshRenderer ()
+{
+ Assert(m_CachedAnimator == NULL);
+
+ if(m_MemExportInfo)
+ GetGfxDevice().DeleteGPUSkinningInfo(m_MemExportInfo);
+
+ if (m_VBO)
+ {
+ GetGfxDevice().DeleteVBO(m_VBO);
+ m_VBO = NULL;
+ }
+}
+
+void SkinnedMeshRenderer::Setup (Mesh* mesh, const dynamic_array<PPtr<Transform> >& state)
+{
+ m_Bones = state;
+ m_Mesh = mesh;
+ UpdateCachedMesh ();
+ SetDirty();
+}
+
+void SkinnedMeshRenderer::SetMesh (Mesh* mesh)
+{
+ m_Mesh = mesh;
+ UpdateCachedMesh ();
+ SetDirty();
+}
+
+Mesh* SkinnedMeshRenderer::GetMesh ()
+{
+ return m_Mesh;
+}
+
+void SkinnedMeshRenderer::SetBones (const dynamic_array<PPtr<Transform> >& bones)
+{
+ m_Bones = bones;
+ SetDirty();
+ if (!bones.empty())
+ ClearCachedAnimatorBinding(); // switch to non-optimized mode, no binding is needed anymore
+}
+
+void SkinnedMeshRenderer::Reset()
+{
+ Super::Reset();
+ m_Quality = 0;
+ m_UpdateWhenOffscreen = false;
+ m_AABB = AABB(Vector3f::zero, Vector3f::zero);
+}
+
+
+bool SkinnedMeshRenderer::DoesQualifyForMemExport() const
+{
+ bool qualifies = (m_Cloth == 0) && (m_MemExportInfo);
+ qualifies = qualifies && GetPlayerSettings().GetGPUSkinning();
+
+ return qualifies;
+}
+
+bool SkinnedMeshRenderer::CalculateBoneBasedBounds (const Matrix4x4f* poseMatrices, size_t size, MinMaxAABB& output)
+{
+ if (m_CachedMesh == NULL)
+ return false;
+
+ if (m_CachedMesh->GetMaxBoneIndex() >= size)
+ return false;
+
+ const Mesh::AABBContainer& bounds = m_CachedMesh->GetCachedBonesBounds ();
+ if (size > bounds.size())
+ return false;
+
+ MinMaxAABB minMaxAABB;
+ for(int i=0;i<size;i++)
+ {
+ AABB result;
+
+ ///@TODO: OPTIMIZATION: DO this precomputed.
+ if (!bounds[i].IsValid())
+ continue;
+
+ AABB aabb = bounds[i];
+
+ TransformAABB(aabb, poseMatrices[i], result);
+ // TransformAABBSlow (aabb, animatedPose[i], result);
+
+ minMaxAABB.Encapsulate(result.GetMin());
+ minMaxAABB.Encapsulate(result.GetMax());
+ }
+
+ output = minMaxAABB;
+ return true;
+}
+
+#if UNITY_EDITOR
+bool SkinnedMeshRenderer::CalculateVertexBasedBounds (const Matrix4x4f* poseMatrices, MinMaxAABB& output)
+{
+ if (m_CachedMesh == NULL)
+ return false;
+
+ int boneCount = m_CachedMesh->GetBindposeCount ();
+
+ Matrix4x4f* fullMatrices = NULL;
+ ALLOC_TEMP(fullMatrices, Matrix4x4f, boneCount);
+
+ const Matrix4x4f* bindPoses = m_CachedMesh->GetBindposes();
+ for (int i=0;i<boneCount;i++)
+ {
+ MultiplyMatrices4x4(&poseMatrices[i], &bindPoses[i], &fullMatrices[i]);
+ }
+
+ MinMaxAABB minMaxAABB;
+
+ // could actually use only the vertices which are affected by bones which are actually animated
+ StrideIterator<Vector3f> vertices = m_CachedMesh->GetVertexBegin();
+ const BoneInfluence* boneInfluences = m_CachedMesh->GetBoneWeights ();
+ int count = m_CachedMesh->GetVertexCount();
+ if (count == 0)
+ minMaxAABB.Encapsulate(Vector3f::zero);
+ for (int i = 0; i < count; ++i, ++vertices)
+ {
+ const BoneInfluence& boneInfluence = boneInfluences[i];
+ Vector3f v(Vector3f::zero);
+
+ float w = 0;
+
+ // This always calculates bounding box as if it was 4 bone skinning
+ // 2 bone or 1 bone skinning could produce slightly different results
+ for (int j = 0; j < 4; ++j)
+ {
+ const int boneIndex = boneInfluence.boneIndex[j];
+ Assert(boneIndex < boneCount);
+
+ v += fullMatrices[boneIndex].MultiplyPoint3(*vertices) * boneInfluence.weight[j];
+
+ w += boneInfluence.weight[j];
+ }
+
+ Assert(w > 0.99f && w < 1.01f);
+
+ minMaxAABB.Encapsulate(v);
+ }
+
+ output = minMaxAABB;
+
+ return true;
+}
+#endif
+
+bool SkinnedMeshRenderer::CalculateAnimatedPosesWithRoot (const Matrix4x4f& rootMatrix, Matrix4x4f* poses, size_t size)
+{
+ if (!CalculateAnimatedPoses(poses, size))
+ return false;
+
+ for (int i=0;i<size;i++)
+ {
+ Matrix4x4f temp;
+ MultiplyMatrices4x4(&rootMatrix, poses + i, &temp);
+ poses[i] = temp;
+ }
+ return true;
+}
+
+bool SkinnedMeshRenderer::CalculateSkinningMatrices (const Matrix4x4f& rootPose, Matrix4x4f* skinnedPoses, size_t size)
+{
+ Assert(m_CachedMesh != NULL);
+ DebugAssert (size > 0);
+
+ if (!CalculateAnimatedPoses(skinnedPoses, size))
+ return false;
+
+ MultiplyMatrixArrayWithBase4x4 (&rootPose, skinnedPoses, m_CachedMesh->GetBindposes(), skinnedPoses, size);
+
+ return true;
+}
+
+bool SkinnedMeshRenderer::CalculateAnimatedPoses (Matrix4x4f* poses, size_t size)
+{
+ if (IsOptimized())
+ {
+ const dynamic_array<UInt16>& skeletonIndices = GetSkeletonIndices();
+ if (!skeletonIndices.empty()) // implies: GetAnimator() && GetAnimationInterface()
+ return GetAnimationInterface()->CalculateWorldSpaceMatricesMainThread(*GetAnimator(), skeletonIndices.begin(), size, poses);
+ else
+ return false;
+ }
+ else
+ {
+ if (size > m_Bones.size())
+ return false;
+
+ bool hasAnyBones = false;
+ for (int i=0;i<size;i++)
+ {
+ Transform* transform = m_Bones[i];
+ if (transform)
+ {
+ poses[i] = transform->GetLocalToWorldMatrix ();
+ hasAnyBones = true;
+ }
+ else
+ poses[i].SetIdentity ();
+ }
+
+ return hasAnyBones;
+ }
+}
+
+const dynamic_array<UInt16>& SkinnedMeshRenderer::GetSkeletonIndices()
+{
+ Assert(IsOptimized());
+
+ if (m_SkeletonIndices.empty())
+ CreateCachedAnimatorBinding ();
+
+
+ return m_SkeletonIndices;
+}
+
+int SkinnedMeshRenderer::GetBindposeCount () const
+{
+ return m_CachedMesh ? m_CachedMesh->GetBindposeCount() : 0;
+}
+
+bool SkinnedMeshRenderer::PrepareVBO(bool hasSkin, bool hasBlendshape, bool doMemExport, int flags)
+{
+ if (!hasSkin && !hasBlendshape)
+ return false;
+
+ // Right now, it can be false for 2 cases:
+ // 1. GetSkinnedVerticesAndNormal
+ // In this case, SF_ReadbackBack will also be set.
+ // SkinMeshInfo.outVertices will be allocated in SkinnedMeshRenderer.m_SkinnedVertices.
+ // 2. Bake mesh
+ // In this case, SkinMeshInfo.outVertices will be the vertex data of the output Mesh.
+ bool doNeedVBO = !(flags & SF_NoUpdateVBO);
+ bool newVBO = false;
+
+ if (hasBlendshape)
+ {
+ // Handle cases:
+ // hasSkin==true, hasBlendshape==true or
+ // hasSkin==false, hasBlendshape==true
+ //
+ // Hardware skinning is not applicable
+ if (!m_VBO && doNeedVBO)
+ {
+ m_VBO = GetGfxDevice().CreateVBO();
+ m_VBO->SetVertexStreamMode(0, VBO::kStreamModeDynamic);
+ m_VBO->SetIndicesDynamic(false);
+ newVBO = true;
+ }
+ }
+ else
+ {
+ // Handle case: hasSkin==ture, hasBlendshape==false
+ bool canMapVBO = m_VBO && (m_VBO->GetVertexStreamMode(0) != VBO::kStreamModeNoAccess);
+
+ // Check if mem-export qualification changes. See SkinnedMeshRenderer::PrepareSkinXenon
+ // Re-create VBO if it changes.
+ if (m_VBO && (doMemExport == canMapVBO))
+ {
+ GetGfxDevice().DeleteVBO(m_VBO);
+ m_VBO = NULL;
+ if(m_MemExportInfo)
+ m_MemExportInfo->SetDestVBO(NULL);
+ }
+
+ if (!m_VBO && doNeedVBO)
+ {
+ m_VBO = GetGfxDevice().CreateVBO();
+ if (!doMemExport)
+ {
+ m_VBO->SetVertexStreamMode(0, VBO::kStreamModeDynamic);
+ m_VBO->SetIndicesDynamic(false);
+ }
+ else
+ m_VBO->UseAsStreamOutput();
+ newVBO = true;
+ }
+ }
+
+ if (doMemExport)
+ {
+ // Rebuild the VBO in case mem-export qualification is gained. This could use cleaning up.
+ // OR if source is dirty.
+ if (newVBO || (doNeedVBO && m_SourceMeshDirty))
+ m_CachedMesh->CopyToVBO(m_ChannelsInVBO, *m_VBO);
+ return true;
+ }
+
+ if (newVBO || (doNeedVBO && (m_SourceMeshDirty || m_VBO->IsVertexBufferLost())))
+ {
+ // fill the VBO
+ m_VBO->SetMappedFromRenderThread(!m_Cloth);
+ m_CachedMesh->CopyToVBO(m_ChannelsInVBO, *m_VBO);
+ m_SourceMeshDirty = false;
+ }
+
+ return true;
+}
+
+
+bool SkinnedMeshRenderer::PrepareSkinCommon(UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask)
+{
+ m_UpdateBeforeRendering = false;
+ if (!m_CachedMesh || (m_CachedMesh->GetSubMeshCount () == 0))
+ return false;
+ DebugAssertIf(m_CachedMesh != m_Mesh);
+
+ int bindposeCount = GetBindposeCount();
+ int blendShapeCount = GetValidBlendShapeWeightCount ();
+
+ if (bindposeCount > 0 && m_CachedMesh->GetMaxBoneIndex() >= bindposeCount)
+ {
+ ErrorStringObject("Bone influences do not match bones.", this);
+ return false;
+ }
+
+ // We are not monitoring whether the bones have changed
+ // so we always have to make cached surface area dirty,
+ // as it could have changed.
+ SET_CACHED_SURFACE_AREA_DIRTY();
+
+ bool hasSkin = (bindposeCount > 0) && !m_CachedMesh->GetSkin().empty();
+
+ // For skinned meshes we only care about active blend shapes.
+ // For non-skinned meshes we need to take the blend shape code path
+ // even if all the shapes are zero weight. There is no optimized path
+ // to render the undeformed source mesh (case 557165).
+ // TODO: Write an optimized path for no skin, no active blend shapes.
+ bool hasBlendshape = false;
+ if (hasSkin)
+ hasBlendshape = blendShapeCount > 0;
+ else
+ hasBlendshape = m_CachedBlendShapeCount > 0;
+
+ bool doMemExport = DoesQualifyForMemExport() &&
+ (flags & SF_AllowMemExport) && hasSkin && !hasBlendshape;
+
+ m_CachedMesh->InitVertexBufferData(requiredChannels);
+ m_ChannelsInVBO = m_CachedMesh->GetAvailableChannels();
+
+ if (!PrepareVBO(hasSkin, hasBlendshape, doMemExport, flags))
+ return false;
+
+ // Fill SkinMeshInfo
+ skin.boneCount = bindposeCount;
+ skin.blendshapeCount = blendShapeCount;
+ skin.vertexCount = m_CachedMesh->GetVertexCount();
+ skin.memExport = doMemExport;
+ skin.Allocate();
+
+ if (hasSkin)
+ {
+ skin.bonesPerVertex = GetBonesPerVertexCount();
+ skin.compactSkin = m_CachedMesh->GetSkinInfluence(skin.bonesPerVertex);
+
+ Matrix4x4f rootPose;
+ if (!(flags & SF_ClothPlaying))
+ rootPose = GetActualRootBone().GetWorldToLocalMatrixNoScale ();
+ else
+ // clothed skins are simulated using world space rotation, so rotating the character will affect the cloth simulation.
+ // translation is applied using forces in the cloth, which is smoother.
+ rootPose.SetTranslate (-GetActualRootBone().GetPosition());
+
+ bool canCalcSkinMatricesInMT = false;
+ if (calcSkinMatricesTask && IsOptimized())
+ {
+ const dynamic_array<UInt16>& skeletonIndices = GetSkeletonIndices();
+ if (skeletonIndices.empty())
+ return false;
+ else // implies GetAnimationInterface() && GetAnimator()
+ {
+ const void* skeletonPose = GetAnimationInterface()->GetGlobalSpaceSkeletonPose(*GetAnimator());
+ if (skeletonPose)
+ {
+ canCalcSkinMatricesInMT = true;
+
+ calcSkinMatricesTask->skeletonPose = skeletonPose;
+ calcSkinMatricesTask->skeletonIndices = skeletonIndices.begin();
+ calcSkinMatricesTask->rootPose = rootPose;
+ calcSkinMatricesTask->bindPoseCount = bindposeCount;
+ calcSkinMatricesTask->bindPose = m_CachedMesh->GetBindposes();
+
+ calcSkinMatricesTask->outPose = skin.cachedPose;
+ }
+ }
+ }
+
+ if (!canCalcSkinMatricesInMT)
+ {
+ // slow code path
+ if (!CalculateSkinningMatrices(rootPose, skin.cachedPose, bindposeCount))
+ return false;
+ }
+ }
+ else
+ {
+ skin.cachedPose = NULL;
+ skin.compactSkin = NULL;
+ }
+
+ if (hasBlendshape)
+ {
+ Assert (skin.blendshapeCount <= m_CachedMesh->GetBlendShapeChannelCount());
+ memcpy (skin.blendshapeWeights, m_BlendShapeWeights.begin(), skin.blendshapeCount * sizeof(float));
+ skin.blendshapes = &m_CachedMesh->GetBlendShapeData();
+ }
+
+#if UNITY_PS3
+ if ((NULL == m_Cloth) && (!m_CachedMesh->m_PartitionInfos.empty()))
+ {
+ m_ChannelsInVBO = m_CachedMesh->GetAvailableChannels();
+ return true;
+ }
+#endif
+
+ const VertexData& vertexData = m_CachedMesh->GetVertexData();
+ const StreamInfo streamInfo = vertexData.GetStream(0);
+ skin.inVertices = vertexData.GetDataPtr() + streamInfo.offset;
+ skin.inStride = streamInfo.stride;
+ skin.outStride = streamInfo.stride;
+#if !UNITY_FLASH
+ if (streamInfo.channelMask & ~VERTEX_FORMAT3(Vertex, Normal, Tangent))
+ ErrorString(Format("Skinned mesh stream should contain only positions, normals and tangents. channelMask was %lx", streamInfo.channelMask));
+#endif
+
+ if (skin.memExport)
+ return true;
+
+ // Skin vertices into vbo
+ const ChannelInfo& normalInfo = vertexData.GetChannel(kShaderChannelNormal);
+ const ChannelInfo& tangentInfo = vertexData.GetChannel(kShaderChannelTangent);
+
+#if !UNITY_PS3 // Stream layout for skinned models is different on PS3
+ DebugAssert(!normalInfo.IsValid() || (normalInfo.stream == 0 && normalInfo.format == kChannelFormatFloat && normalInfo.dimension == 3));
+ DebugAssert(!tangentInfo.IsValid() || (tangentInfo.stream == 0 && tangentInfo.format == kChannelFormatFloat && tangentInfo.dimension == 4));
+#endif
+
+ skin.skinNormals = normalInfo.IsValid();
+ skin.normalOffset = normalInfo.offset;
+ skin.skinTangents = tangentInfo.IsValid();
+ skin.tangentOffset = tangentInfo.offset;
+
+ if (flags & SF_ReadbackBuffer)
+ {
+ // Allocate a temporary buffer to read back from
+ m_SkinnedVertices.resize_uninitialized(skin.outStride * skin.vertexCount);
+ skin.outVertices = &m_SkinnedVertices[0];
+ }
+ return true;
+}
+
+#if UNITY_PS3
+bool SkinnedMeshRenderer::PrepareSkinPS3( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask )
+{
+ if (!m_CachedMesh || m_CachedMesh->GetSkin().empty ())
+ return false;
+
+ skin.vertexData = NULL;
+
+ if (!PrepareSkinCommon( requiredChannels, flags, skin, calcSkinMatricesTask ))
+ return false;
+
+ skin.vertexData = &m_CachedMesh->GetVertexData();
+
+ if(m_CachedMesh->m_PartitionInfos.empty() || m_Cloth)
+ return true;
+
+ if(m_SourceMeshDirty)
+ {
+ VertexBufferData vertexBuffer;
+ IndexBufferData indexBuffer;
+
+ m_CachedMesh->GetVertexBufferData( vertexBuffer, m_ChannelsInVBO );
+ m_CachedMesh->GetIndexBufferData( indexBuffer );
+
+ vertexBuffer.inflPerVertex = skin.bonesPerVertex;
+ vertexBuffer.numInfluences = m_CachedMesh->GetSkin().size();
+ vertexBuffer.influences = skin.compactSkin;
+ vertexBuffer.numBones = skin.boneCount;
+ vertexBuffer.bones = skin.cachedPose;
+
+ m_VBO->UpdateVertexData( vertexBuffer );
+ m_VBO->UpdateIndexData( indexBuffer );
+
+ m_SourceMeshDirty = false;
+ }
+ else
+ ((GfxGCMVBO*)m_VBO)->UpdateBones(skin.boneCount, skin.cachedPose);
+
+ skin.Release();
+ return false;
+}
+#endif
+
+bool SkinnedMeshRenderer::PrepareSkinGPU( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask )
+{
+ if (!PrepareSkinCommon( requiredChannels, flags, skin, calcSkinMatricesTask ))
+ return false;
+
+ if (skin.memExport)
+ {
+ Assert(m_ChannelsInVBO != 0);
+ Assert(skin.inStride == skin.outStride);
+
+
+ // TODO: Are there any situations where this might change on the fly? In the editor?
+ if(!m_MemExportInfo->GetDestVBO() || m_SourceMeshDirty)
+ {
+ VertexBufferData vertexBuffer;
+ const StreamInfo& skinStream = vertexBuffer.streams[0];
+
+ m_CachedMesh->GetVertexBufferData(vertexBuffer, m_ChannelsInVBO);
+
+ // Source data
+ const size_t dataBufferSize = skinStream.stride * skin.vertexCount;
+ const void* dataBufferPtr = vertexBuffer.buffer + skinStream.offset;
+
+ // Skin
+ Assert(sizeof(BoneInfluence) == 32);
+ const int skinBufferSize = m_CachedMesh->GetVertexCount() * sizeof(BoneInfluence);
+ // const BoneInfluence* skinBufferPtr = m_CachedMesh->GetBoneWeights();
+ void *skinBufferPtr = m_CachedMesh->GetSkinInfluence(skin.bonesPerVertex);
+
+ m_MemExportInfo->SetVertexCount(skin.vertexCount);
+ m_MemExportInfo->SetChannelMap(skinStream.channelMask);
+ m_MemExportInfo->SetStride(skin.outStride);
+ m_MemExportInfo->SetDestVBO(m_VBO);
+ m_MemExportInfo->SetBonesPerVertex(skin.bonesPerVertex);
+
+ GetGfxDevice().UpdateSkinSourceData(m_MemExportInfo, dataBufferPtr, (const BoneInfluence *)skinBufferPtr, m_SourceMeshDirty);
+ }
+
+
+ // Bones (uses 4 weights, ignores quality settings)
+ Assert(skin.boneCount > 0);
+
+ skin.mei = m_MemExportInfo;
+ m_SourceMeshDirty = false;
+ }
+ else
+ {
+ Assert(m_SourceMeshDirty == false);
+ }
+
+ return true;
+}
+
+bool SkinnedMeshRenderer::PrepareSkin( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask )
+{
+#if UNITY_PS3
+ return PrepareSkinPS3(requiredChannels, flags, skin, calcSkinMatricesTask);
+#else
+ return PrepareSkinGPU(requiredChannels, flags, skin, calcSkinMatricesTask);
+#endif
+}
+
+#if UNITY_EDITOR
+void SkinnedMeshRenderer::UpdateClothDataForEditing(const SkinMeshInfo& skin)
+{
+ // update cloth vertices in edit mode for the cloth vertex editor
+ if (m_Cloth != NULL && !IsWorldPlaying())
+ {
+ dynamic_array<Vector3f> &vertices = m_Cloth->GetVertices();
+ dynamic_array<Vector3f> &normals = m_Cloth->GetNormals();
+ GetSkinnedVerticesAndNormals (&vertices, &normals);
+ }
+}
+#endif
+
+bool SkinnedMeshRenderer::SkinMesh( SkinMeshInfo& skin, bool lastMemExportThisFrame, UInt32 cpuFence, int flags )
+{
+ GfxDevice& device = GetGfxDevice();
+ if (skin.memExport)
+ {
+ GetGfxDevice().UpdateSkinBonePoses(m_MemExportInfo, skin.boneCount, skin.cachedPose);
+ skin.Release();
+
+ // Issue GPU skinning requests in sync. Cloth is done on CPU in parallel.
+ PROFILER_AUTO(gMeshSkinningSkinGPU, this)
+ device.SkinOnGPU(m_MemExportInfo, lastMemExportThisFrame);
+ device.GetFrameStats().AddDrawCall (skin.vertexCount, skin.vertexCount);
+ GPU_TIMESTAMP();
+ return true;
+ }
+ else
+ {
+#if ENABLE_MULTITHREADED_CODE
+ m_CachedMesh->SetCurrentCPUFence(cpuFence);
+#endif
+ return GetGfxDevice().SkinMesh(skin, (flags & SF_ReadbackBuffer) ? NULL : m_VBO);
+ }
+}
+
+bool SkinnedMeshRenderer::SkinMeshImmediate( UInt32 requiredChannels )
+{
+ GfxDevice& device = GetGfxDevice();
+ // Double check there are no fences inserted during skinning
+ UInt32 expectedFence = device.GetNextCPUFence();
+ device.BeginSkinning(1);
+ SkinMeshInfo skin;
+ int flags = SF_AllowMemExport;
+ bool success = PrepareSkin(requiredChannels, flags, skin);
+ if (success)
+ {
+ SkinMesh(skin, true, expectedFence, flags);
+#if UNITY_EDITOR
+ UpdateClothDataForEditing(skin);
+#endif
+ }
+ device.EndSkinning();
+ // Insert fence after all skinning is complete
+ UInt32 fence = device.InsertCPUFence();
+
+ return success;
+}
+
+#if UNITY_EDITOR
+float SkinnedMeshRenderer::GetCachedSurfaceArea ()
+{
+ if (m_CachedSurfaceArea >= 0.0f)
+ return m_CachedSurfaceArea;
+
+ Mesh* mesh = m_CachedMesh;
+ if (!mesh)
+ {
+ m_CachedSurfaceArea = 1.0f;
+ return m_CachedSurfaceArea;
+ }
+
+ Matrix4x4f objectToWorld = GetTransformInfo ().worldMatrix;
+
+ Mesh::TemporaryIndexContainer triangles;
+ mesh->GetTriangles (triangles);
+
+ dynamic_array<Vector3f> vertices;
+ if (GetSkinnedVerticesAndNormals (&vertices, NULL)) // this may fail and return empty array as is the case in repro for bug 505751.
+ {
+ m_CachedSurfaceArea = CalculateSurfaceArea (objectToWorld, triangles, vertices);
+ }
+ else
+ {
+ m_CachedSurfaceArea = 1.0f; // to avoid repeat invocation of failing GetCachedSurfaceArea
+ }
+ return m_CachedSurfaceArea;
+}
+
+bool SkinnedMeshRenderer::GetSkinnedVerticesAndNormals (dynamic_array<Vector3f>* vertices, dynamic_array<Vector3f>* normals)
+{
+ UInt32 requiredChannels = (1 << kShaderChannelVertex);
+ if (normals)
+ requiredChannels |= (1 << kShaderChannelNormal);
+
+ SkinMeshInfo skin;
+ if (!PrepareSkinCommon(requiredChannels, SF_ReadbackBuffer | SF_NoUpdateVBO, skin))
+ return false;
+
+ DeformSkinnedMesh(skin);
+ skin.Release();
+
+ bool hasVertices = m_ChannelsInVBO & (1 << kShaderChannelVertex);
+ bool hasNormals = skin.skinNormals;
+
+ if (vertices && hasVertices)
+ {
+ vertices->resize_uninitialized (skin.vertexCount);
+
+ for (int i = 0; i < skin.vertexCount; i++)
+ {
+ char* vertex = ((char*)skin.outVertices + i * skin.outStride);
+ (*vertices)[i] = *((Vector3f*)vertex);
+ }
+ }
+
+ if (normals && hasNormals)
+ {
+ normals->resize_uninitialized (skin.vertexCount);
+
+ for (int i = 0; i < skin.vertexCount; i++)
+ {
+ char* normal = ((char*)skin.outVertices + i * skin.outStride + skin.normalOffset);
+ (*normals)[i] = *((Vector3f*)normal);
+ }
+ }
+ return true;
+}
+#endif
+
+
+int SkinnedMeshRenderer::GetBonesPerVertexCount ()
+{
+ if (m_Quality == 0)
+ return GetQualitySettings().GetCurrent().blendWeights;
+ else
+ return m_Quality;
+}
+
+template<class TransferFunction> inline
+void SkinnedMeshRenderer::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ transfer.SetVersion(2);
+
+ TRANSFER_SIMPLE (m_Quality);
+ TRANSFER_SIMPLE (m_UpdateWhenOffscreen);
+ transfer.Align();
+
+ transfer.Transfer (m_Mesh, "m_Mesh");
+
+ transfer.Transfer (m_Bones, "m_Bones", kHideInEditorMask);
+ transfer.Align();
+
+ transfer.Transfer(m_BlendShapeWeights, "m_BlendShapeWeights");
+
+ transfer.Transfer(m_RootBone, "m_RootBone");
+
+ transfer.Transfer(m_AABB, "m_AABB");
+ transfer.Transfer(m_DirtyAABB, "m_DirtyAABB", kHideInEditorMask);
+ transfer.Align();
+}
+
+IMPLEMENT_CLASS_HAS_INIT (SkinnedMeshRenderer)
+IMPLEMENT_OBJECT_SERIALIZE (SkinnedMeshRenderer)
+
+void SkinnedMeshRenderer::InitializeClass ()
+{
+ REGISTER_MESSAGE_VOID (SkinnedMeshRenderer, kBecameVisible, BecameVisible);
+ REGISTER_MESSAGE_VOID (SkinnedMeshRenderer, kBecameInvisible, BecameInvisible);
+
+ REGISTER_MESSAGE_VOID(SkinnedMeshRenderer, kDidDeleteMesh, DidDeleteMesh);
+ REGISTER_MESSAGE_VOID(SkinnedMeshRenderer, kDidModifyMesh, DidModifyMesh);
+ RegisterAllowNameConversion (SkinnedMeshRenderer::GetClassStringStatic(), "m_LodMesh", "m_Mesh");
+ RegisterAllowNameConversion (SkinnedMeshRenderer::GetClassStringStatic(), "m_Animation", "m_DisableAnimationWhenOffscreen");
+
+
+ InitializeBlendShapeAnimationBindingInterface ();
+
+}
+
+void SkinnedMeshRenderer::CleanupClass ()
+{
+ CleanupBlendShapeAnimationBindingInterface ();
+}
+
+void SkinnedMeshRenderer::DidDeleteMesh ()
+{
+ m_CachedMesh = NULL;
+ m_CachedBlendShapeCount = 0;
+}
+
+void SkinnedMeshRenderer::DidModifyMesh ()
+{
+ m_SourceMeshDirty = true;
+}
+
+void SkinnedMeshRenderer::UpdateCachedMesh ()
+{
+ Mesh* mesh = m_Mesh;
+ if (mesh != m_CachedMesh)
+ {
+ m_SourceMeshDirty = true;
+
+ m_CachedMesh = mesh;
+ BoundsChanged();
+ m_TransformDirty = true;
+ SET_CACHED_SURFACE_AREA_DIRTY();
+
+ m_MeshNode.RemoveFromList();
+
+ if (m_CachedMesh)
+ m_CachedMesh->AddObjectUser( m_MeshNode );
+ }
+
+ if (m_CachedMesh != NULL)
+ m_CachedBlendShapeCount = GetBlendShapeChannelCount(m_CachedMesh->GetBlendShapeData());
+ else
+ m_CachedBlendShapeCount = 0;
+
+ ClearCachedAnimatorBinding();
+}
+
+
+
+void SkinnedMeshRenderer::UpdateRenderer()
+{
+ if (GetEnabled() && IsActive())
+ {
+ // Force update bounding volumes when we have a root bone or we have to
+ bool recalculateBoundingVolumeEveryFrame = ShouldRecalculateBoundingVolumeEveryFrame();
+ if (recalculateBoundingVolumeEveryFrame)
+ {
+ // Make we continously get this callback every frame so that we can make sure that skinned meshes are put in the gActiveSkinnedMeshes queue
+ // depending on their visibility
+ UpdateManagerState(true);
+
+ // The root transform can move every frame and there is no way for us to track it.
+ TransformChanged (Transform::kPositionChanged | Transform::kRotationChanged | Transform::kScaleChanged);
+ }
+ }
+
+ UpdateVisibleSkinnedMeshQueue(IsActive());
+
+ Super::UpdateRenderer();
+}
+
+void SkinnedMeshRenderer::UpdateVisibleSkinnedMeshQueue (bool active)
+{
+ bool needsUpdate = m_Visible && (GetEnabled() && active);
+ if (needsUpdate == m_SkinNode.IsInList())
+ return;
+
+ if (needsUpdate)
+ gActiveSkinnedMeshes.push_back(m_SkinNode);
+ else
+ m_SkinNode.RemoveFromList();
+}
+
+void SkinnedMeshRenderer::Deactivate (DeactivateOperation operation)
+{
+ Super::Deactivate(operation);
+ UpdateVisibleSkinnedMeshQueue(false);
+ ClearCachedAnimatorBinding();
+}
+
+void SkinnedMeshRenderer::SetUpdateWhenOffscreen (bool onlyIfVisible)
+{
+ m_UpdateWhenOffscreen = onlyIfVisible;
+
+ // We might have to start firing UpdateRenderer events every frame...
+ UpdateManagerState (IsActive());
+ BoundsChanged ();
+
+ SetDirty();
+}
+
+void SkinnedMeshRenderer::BecameVisible ()
+{
+ m_Visible = true;
+
+ // When using LOD we might have a skinned mesh, the animation component might sample during OnBecameVisible since it wasn't visible before.
+ // In that case the root bone might change, thus we force a transformDirty in on became visible as well as every single frame.
+ if (ShouldRecalculateBoundingVolumeEveryFrame())
+ m_TransformDirty = true;
+
+ UpdateVisibleSkinnedMeshQueue (IsActive());
+
+ m_UpdateBeforeRendering = true;
+}
+
+void SkinnedMeshRenderer::BecameInvisible ()
+{
+ m_Visible = false;
+ UpdateVisibleSkinnedMeshQueue (IsActive());
+}
+
+
+static GPUSkinningInfo* CreateGPUSkinningIfAvailable()
+{
+ if (!GetBuildSettings().hasAdvancedVersion)
+ return NULL;
+
+ return GetGfxDevice().CreateGPUSkinningInfo();
+}
+
+
+void SkinnedMeshRenderer::AwakeFromLoad(AwakeFromLoadMode awakeMode)
+{
+ if (!m_MemExportInfo)
+ m_MemExportInfo = CreateGPUSkinningIfAvailable();
+
+ #if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG
+ HandleOldSkinnedFilter ();
+ #endif
+
+ Super::AwakeFromLoad(awakeMode);
+
+ UpdateCachedMesh ();
+
+ // Make sure we are added to the visibile queue if we the renderer is active
+ UpdateVisibleSkinnedMeshQueue (IsActive());
+
+ // The root transform pptr might have changed and thus worldTransform needs to be updated
+ TransformChanged(Transform::kPositionChanged | Transform::kRotationChanged | Transform::kScaleChanged | Transform::kParentingChanged);
+
+ m_BlendShapeWeights.resize_initialized(m_CachedBlendShapeCount, 0.0F);
+}
+
+void SkinnedMeshRenderer::SetQuality (int quality)
+{
+ m_Quality = quality; SetDirty();
+}
+
+void SkinnedMeshRenderer::Render (int subsetIndex, const ChannelAssigns& channels)
+{
+ PROFILER_AUTO(gMeshSkinningRender, this)
+
+ if (m_CachedMesh)
+ {
+ if (m_CachedMesh->GetSkin().empty() && m_CachedBlendShapeCount == 0)
+ {
+ ErrorStringObject("SkinnedMeshRenderer requires a mesh with skinning or blendshape information.", this);
+ return;
+ }
+
+ UInt32 requiredChannels = channels.GetSourceMap();
+ // Skinned cloth prefers to have normals, even if shader does not need them
+ if (m_Cloth)
+ requiredChannels |= (1<<kShaderChannelNormal);
+
+ // Just in time update
+ if (m_UpdateBeforeRendering || m_SourceMeshDirty || !m_VBO || m_VBO->IsVertexBufferLost())
+ {
+ PROFILER_BEGIN(gMeshSkinningUpdateImmediate, this)
+ bool success = SkinMeshImmediate(requiredChannels);
+ PROFILER_END
+
+ // Mesh skinning can fail (Bone indices out of bounds, bone transforms missing etc)
+ if (!success)
+ return;
+ }
+
+ if (m_CustomProperties)
+ GetGfxDevice().SetMaterialProperties (*m_CustomProperties);
+ DrawUtil::DrawVBOMeshRaw (*m_VBO, *m_CachedMesh, channels, subsetIndex, m_ChannelsInVBO);
+ }
+
+ GPU_TIMESTAMP();
+}
+
+void SkinnedMeshRenderer::SetLocalAABB(const AABB& bounds)
+{
+ m_AABB = bounds;
+ m_DirtyAABB = false;
+ SetDirty();
+}
+
+void SkinnedMeshRenderer::ReadSkinningDataForCloth(const SkinMeshInfo& skin)
+{
+#if ENABLE_CLOTH
+ void *normalPointer = (char*)skin.outVertices + skin.normalOffset;
+ if (!skin.skinNormals)
+ normalPointer = NULL;
+ void *tangentPointer = (char*)skin.outVertices + skin.tangentOffset;
+ if (!skin.skinTangents)
+ tangentPointer = NULL;
+
+ GetIPhysics()->SetUpSkinnedBuffersOnSkinnedCloth(*m_Cloth, skin.outVertices, normalPointer, tangentPointer, skin.outStride);
+#endif
+}
+
+void SkinnedMeshRenderer::UpdateAllSkinnedMeshes(UpdateType updateType, dynamic_array<SkinnedMeshRenderer*>* outMeshes)
+{
+ PROFILER_AUTO(gMeshSkinningUpdate, NULL)
+
+ // TODO: we should submit skinning jobs, go do something else (e.g. update particles, movies, whatnot),
+ // and wait/reintegrate jobs after that is done.
+
+ int flags = SF_None;
+ if (updateType == kUpdateCloth)
+ {
+ flags |= SF_ReadbackBuffer;
+ if (IsWorldPlaying())
+ flags |= SF_ClothPlaying;
+ }
+ else
+ {
+ flags = SF_AllowMemExport;
+ }
+
+ size_t skinCount = 0;
+ size_t maxCount = gActiveSkinnedMeshes.size_slow();
+ dynamic_array<SkinnedMeshRenderer*> skinMeshes(maxCount, kMemTempAlloc);
+ dynamic_array<SkinMeshInfo> skinInfos(maxCount, kMemTempAlloc);
+ dynamic_array<CalculateSkinMatricesTask> calculateSkinMatricesTasks(maxCount, kMemTempAlloc);
+
+ PROFILER_BEGIN(gMeshSkinningPrepare, NULL);
+ // Find out which renderers to skin this frame
+ const SkinMeshInfo* lastMemExport = 0;
+ SkinnedMeshList::iterator next;
+ int skinMatrixTaskCount = 0;
+ for (SkinnedMeshList::iterator i=gActiveSkinnedMeshes.begin();i != gActiveSkinnedMeshes.end();i=next)
+ {
+ SkinnedMeshRenderer& skin = **i;
+ next = i;
+ next++;
+ UpdateType type = (skin.m_Cloth && IsWorldPlaying())? kUpdateCloth : kUpdateNonCloth;
+ if (type != updateType)
+ continue;
+ SkinMeshInfo& info = skinInfos[skinCount];
+ memset(&info, 0, sizeof(SkinMeshInfo));
+
+ calculateSkinMatricesTasks[skinMatrixTaskCount].skeletonPose = NULL; // mark invalid task
+ if (skin.PrepareSkin(skin.m_ChannelsInVBO, flags, info, &calculateSkinMatricesTasks[skinMatrixTaskCount]))
+ {
+ if (calculateSkinMatricesTasks[skinMatrixTaskCount].skeletonPose != NULL)
+ skinMatrixTaskCount++; // valid task
+ skinMeshes[skinCount] = &skin;
+ skinCount++;
+ if (info.memExport)
+ lastMemExport = &info;
+ }
+ }
+ PROFILER_END;
+
+ if (skinMatrixTaskCount)
+ {
+#if ENABLE_MULTITHREADED_CODE
+ JobScheduler& scheduler = GetJobScheduler();
+ JobScheduler::JobGroupID jobGroup;
+#define CALC_SKIN_MATRICES_LOOP(x,list,size) \
+ { \
+ size_t jobCount = size; \
+ jobGroup = scheduler.BeginGroup(jobCount); \
+ for (size_t i = 0; i < jobCount; ++i) \
+ { CalculateSkinMatricesTask& task = list[i]; scheduler.SubmitJob (jobGroup, x, &task, NULL); } \
+ scheduler.WaitForGroup (jobGroup); \
+ }
+#else
+#define CALC_SKIN_MATRICES_LOOP(x,list,size) \
+ for (size_t i=0;i<size;i++) \
+ { CalculateSkinMatricesTask& task = list[i]; x (&task); }
+#endif
+
+ // Do it in multiple threads
+ if (GetAnimationInterface())
+ {
+ /// @TODO: Simplify this. No need for having an interface that returns a callback... Doh
+ CalculateAnimatorSkinMatricesFunc calculateAnimatorSkinMatricesFunc =
+ GetAnimationInterface()->GetCalculateAnimatorSkinMatricesFunc();
+ if (calculateAnimatorSkinMatricesFunc)
+ CALC_SKIN_MATRICES_LOOP(calculateAnimatorSkinMatricesFunc, calculateSkinMatricesTasks, skinMatrixTaskCount)
+ }
+ }
+
+ if (skinCount == 0)
+ return;
+
+ skinMeshes.resize_uninitialized(skinCount);
+ skinInfos.resize_uninitialized(skinCount);
+
+ // Double check there are no fences inserted during skinning
+ GfxDevice& device = GetGfxDevice();
+ UInt32 expectedFence = device.GetNextCPUFence();
+
+ // Now we know exactly which renderers to skin and which one is last
+ device.BeginSkinning(skinCount);
+ for (int i = 0; i < skinCount; i++)
+ {
+ SkinMeshInfo& info = skinInfos[i];
+ SkinnedMeshRenderer& skin = *skinMeshes[i];
+ bool lastMemExportThisFrame = (&info == lastMemExport);
+ skin.SkinMesh(info, lastMemExportThisFrame, expectedFence, flags);
+ }
+
+ PROFILER_BEGIN(gMeshSkinningWait, NULL)
+ device.EndSkinning();
+ PROFILER_END;
+
+ // Insert fence after all skinning is complete
+ UInt32 fence = device.InsertCPUFence();
+ Assert(fence == expectedFence);
+
+ // Read back vertices for cloth
+ // It's fine to do this after EndSkinning() since we own the buffer (m_SkinnedVertices)
+ if (updateType == kUpdateCloth)
+ {
+ for (int i = 0; i < skinCount; i++)
+ {
+ SkinnedMeshRenderer& skin = *skinMeshes[i];
+ skin.ReadSkinningDataForCloth(skinInfos[i]);
+ }
+ }
+
+ if (outMeshes)
+ outMeshes->assign(skinMeshes.begin(), skinMeshes.end());
+}
+
+void SkinnedMeshRenderer::UploadSkinnedClothes(const dynamic_array<SkinnedMeshRenderer*>& skinnedMeshes)
+{
+ int skinCount = skinnedMeshes.size();
+ for (int i = 0; i < skinCount; i++)
+ {
+ SkinnedMeshRenderer& skin = *skinnedMeshes[i];
+
+ if (skin.m_SkinnedVertices.empty())
+ continue;
+
+ VertexStreamData mappedVSD;
+ if (skin.m_VBO->MapVertexStream(mappedVSD, 0))
+ {
+ memcpy(mappedVSD.buffer, skin.m_SkinnedVertices.data(), skin.m_SkinnedVertices.size());
+ skin.m_VBO->UnmapVertexStream(0);
+ }
+ }
+}
+
+Transform& SkinnedMeshRenderer::GetActualRootBone ()
+{
+ Transform* rootBone = m_RootBone;
+ if (rootBone != NULL)
+ // Not optimized mode && m_RootBone != NULL
+ return *rootBone;
+ else
+ // Optimized mode
+ // Not optimized mode && m_RootBone == NULL
+ return GetTransform();
+}
+
+
+bool SkinnedMeshRenderer::CalculateRootLocalSpaceBounds (MinMaxAABB& minMaxAAbb)
+{
+ Matrix4x4f* poses;
+ int poseCount = GetBindposeCount();
+ ALLOC_TEMP(poses, Matrix4x4f, poseCount);
+
+ Transform& rootBone = GetActualRootBone();
+
+ if (CalculateAnimatedPosesWithRoot(rootBone.GetWorldToLocalMatrix(), poses, poseCount) && CalculateBoneBasedBounds(poses, poseCount, minMaxAAbb))
+ return true;
+ else
+ return false;
+}
+
+void SkinnedMeshRenderer::UpdateTransformInfo ()
+{
+ Transform& rootBone = GetActualRootBone();
+
+ Vector3f pos;
+ Quaternionf rot;
+ TransformType transformType = rootBone.GetPositionAndRotationWithTransformType (pos, rot);
+
+ const bool hasSkin = !m_CachedMesh ? true : !m_CachedMesh->GetSkin().empty();
+ if (hasSkin || IsNoScaleTransform(transformType))
+ {
+ m_TransformInfo.transformType = transformType & kOddNegativeScaleTransform;
+ m_TransformInfo.worldMatrix.SetTR (pos, rot);
+ m_TransformInfo.invScale = 1.0f;
+ }
+ // This codepath only exists for blendshapes. Skinned meshes will always use only position & rotation (no scale)
+ // For blendshapes the skinning code does no bone or matrix deformation thus there is no good way to plug scale in there.
+ else
+ {
+ float uniformScale = 1.0f;
+ m_TransformInfo.worldMatrix = rootBone.GetLocalToWorldMatrix();
+ m_TransformInfo.transformType = transformType = ComputeTransformType(m_TransformInfo.worldMatrix, uniformScale);
+ m_TransformInfo.invScale = 1.0f / uniformScale;
+ }
+
+#if UNITY_SUPPORTS_VFP || (UNITY_SUPPORTS_NEON && !UNITY_DISABLE_NEON_SKINNING)
+ // NOTE: optimized VFP routines do not do any normalization
+ // instead we rely on GPU to do that
+ if (GetBonesPerVertexCount () != 1)
+ m_TransformInfo.transformType |= kNonUniformScaleTransform;
+#endif
+
+ // Compute world space bounding volume from each bone to get a very accurate bounding volume
+ if (m_UpdateWhenOffscreen && hasSkin)
+ {
+ Matrix4x4f* poses;
+ int poseCount = GetBindposeCount();
+ ALLOC_TEMP(poses, Matrix4x4f, poseCount);
+
+ MinMaxAABB accurateMinMax;
+ if (CalculateAnimatedPoses(poses, poseCount) && CalculateBoneBasedBounds(poses, poseCount, accurateMinMax))
+ {
+ m_TransformInfo.worldAABB = accurateMinMax;
+ InverseTransformAABB(m_TransformInfo.worldAABB, pos, rot, m_TransformInfo.localAABB);
+ return;
+ }
+ }
+
+ // Calculate m_AABB from the current pose and undirty the AABB
+ if (m_DirtyAABB)
+ {
+ MinMaxAABB accurateMinMax;
+ if (!hasSkin && m_CachedMesh)
+ {
+ accurateMinMax = m_CachedMesh->GetLocalAABB();
+ SetLocalAABB(accurateMinMax);
+ }
+ else if (CalculateRootLocalSpaceBounds(accurateMinMax))
+ {
+ SetLocalAABB(accurateMinMax);
+ }
+ else
+ {
+ ////@TOOD: figure out something logicalll....
+ m_AABB = AABB(Vector3f::zero, Vector3f::zero);
+ }
+ }
+
+ MinMaxAABB localAABB = m_AABB;
+
+ if (IsNoScaleTransform(transformType))
+ {
+ m_TransformInfo.localAABB = localAABB;
+ TransformAABB (localAABB, pos, rot, m_TransformInfo.worldAABB);
+ }
+ else
+ {
+ // Calculate world space bounding volume (Transform from root space to world space)
+ Matrix4x4f rootToWorldMatrix;
+ rootBone.CalculateTransformMatrix (rootToWorldMatrix);
+ TransformAABB (localAABB, rootToWorldMatrix, m_TransformInfo.worldAABB);
+
+
+ // Calculate local space bounding volume (Transform from root space to SkinnedMeshRenderer space)
+ // When we have scaled objects we have to
+ // The m_TransformInfo.localAABB is the local space bounding volume relative to the root position and rotation, excluding scale.
+ // Thus the following code simply exists to add scale to the stored m_AABB.
+ Matrix4x4f inverseWorldMatrix;
+ inverseWorldMatrix.SetTRInverse (pos, rot);
+
+ Matrix4x4f rootToLocal;
+ MultiplyMatrices4x4(&inverseWorldMatrix, &rootToWorldMatrix, &rootToLocal);
+
+ TransformAABB (localAABB, rootToLocal, m_TransformInfo.localAABB);
+ }
+}
+
+
+void SkinnedMeshRenderer::GetSkinnedMeshLocalAABB (AABB& bounds)
+{
+ // Make sure the localAABB & m_LocalAABB is up to date
+ const TransformInfo& info = GetTransformInfo();
+
+ // Extract from world space bounding volume
+ if (m_UpdateWhenOffscreen)
+ bounds = info.localAABB;
+ // Return precomputed local bounding volume
+ else
+ bounds = m_AABB;
+
+}
+
+void SkinnedMeshRenderer::BakeMesh (Mesh& mesh)
+{
+ if (m_CachedMesh == NULL)
+ return;
+
+ // Making the mesh relative to the root bone is very unintutive when using it as a SkinnedMesh.
+ // So don't do that when exporting the mesh.
+ PPtr<Transform> oldRootBone = m_RootBone;
+ m_RootBone = NULL;
+
+ SkinMeshInfo skin;
+#if UNITY_PS3
+ if (false == m_CachedMesh->m_Partitions.empty())
+ {
+ WarningString(Format("Optimized skinned meshes cannot be baked (%s).", m_CachedMesh->GetName()));
+ return;
+ }
+
+ skin.vertexData = &m_CachedMesh->GetVertexData();
+#endif
+
+ if (PrepareSkinCommon(m_CachedMesh->GetAvailableChannels(), SF_NoUpdateVBO, skin))
+ {
+ mesh.WaitOnRenderThreadUse();
+
+ mesh.SetBoneWeights(NULL, 0);
+
+ const VertexData& skinVertexData = m_CachedMesh->GetVertexData();
+ const VertexStreamsLayout& skinStreamsLayout = skinVertexData.GetStreamsLayout();
+ const VertexChannelsLayout& skinChannelsLayout = skinVertexData.GetChannelsLayout();
+ mesh.ResizeVertices (skin.vertexCount, m_ChannelsInVBO, skinStreamsLayout, skinChannelsLayout);
+
+ skin.outVertices = mesh.GetVertexDataPointer();
+
+ DeformSkinnedMesh(skin);
+ skin.Release();
+
+ // Skinning only updates vertex data in "hot" stream (zero). Have to copy
+ // all the other data like UVs.
+ const UInt32 channelsToCopy = m_ChannelsInVBO & (~skinStreamsLayout.channelMasks[0]);
+ CopyVertexDataChannels (skin.vertexCount, channelsToCopy, skinVertexData, mesh.GetVertexData());
+
+ mesh.GetIndexBuffer() = m_CachedMesh->GetIndexBuffer();
+ mesh.GetSubMeshes() = m_CachedMesh->GetSubMeshes();
+ mesh.SetVertexColorsSwizzled(m_CachedMesh->GetVertexColorsSwizzled());
+
+ mesh.SetChannelsDirty(true, true);
+
+ // Calculate bounding volume
+ Matrix4x4f rootPose = GetActualRootBone().GetWorldToLocalMatrixNoScale ();
+ MinMaxAABB accurateMinMax;
+ Matrix4x4f* poses;
+ int poseCount = GetBindposeCount();
+ ALLOC_TEMP(poses, Matrix4x4f, poseCount);
+ if (CalculateAnimatedPosesWithRoot(rootPose, poses, poseCount) && CalculateBoneBasedBounds(poses, poseCount, accurateMinMax))
+ mesh.SetLocalAABB(accurateMinMax);
+ }
+
+ m_RootBone = oldRootBone;
+}
+
+////@TODO: Write integration test for this!
+#if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG
+
+void SkinnedMeshRenderer::HandleOldSkinnedFilter ()
+{
+ /// Backwards compatibility for old school skinned mesh filters
+ /// - Remove mesh renderer
+ /// - copy over materials from renderer
+ Renderer* meshRenderer = NULL;
+ if (GetGameObjectPtr())
+ meshRenderer = QueryComponent(MeshRenderer);
+
+ if (meshRenderer)
+ {
+ #if UNITY_EDITOR
+ GetCleanupManager ().MarkForDeletion (meshRenderer, "Obsolete");
+ #endif
+ SetMaterialArray(meshRenderer->GetMaterialArray(), meshRenderer->GetSubsetIndices());
+ }
+}
+
+#endif
+
+
+#if ENABLE_PROFILER
+int SkinnedMeshRenderer::GetVisibleSkinnedMeshRendererCount ()
+{
+ return gActiveSkinnedMeshes.size_slow();
+}
+#endif
+
+size_t SkinnedMeshRenderer::GetValidBlendShapeWeightCount () const
+{
+ size_t size = std::min<UInt32>(m_CachedBlendShapeCount, m_BlendShapeWeights.size());
+
+ for (int i=size-1;i >= 0;i--)
+ {
+ if (HasValidWeight(m_BlendShapeWeights[i]))
+ return i + 1;
+ }
+ return 0;
+}
+
+float SkinnedMeshRenderer::GetBlendShapeWeight(UInt32 index) const
+{
+ size_t size = std::min<UInt32>(m_CachedBlendShapeCount, m_BlendShapeWeights.size());
+ if (index >= size)
+ return 0.0F;
+ else
+ return m_BlendShapeWeights[index];
+}
+
+void SkinnedMeshRenderer::SetBlendShapeWeight(UInt32 index, float weight)
+{
+ if (index >= m_CachedBlendShapeCount)
+ {
+ ErrorStringMsg("Array index (%d) is out of bounds (size=%d)", (int)index, (int)m_BlendShapeWeights.size());
+ return;
+ }
+
+ // The m_BlendShapeWeights array is stored seperately from the mesh.
+ // Thus it can go out of sync.
+ // It is bad to fix dependent serialized data in Awake (eg. SkinnedMeshRenderer in scenes could change based on changes in an asset)
+ // Thus we resize to the correct size when the user sets a blendshape weight.
+ // We can never assume that m_BlendShapeWeights.size() already matches the amount of blendshapes in the used mesh when this function is called.
+ if (index >= m_BlendShapeWeights.size())
+ m_BlendShapeWeights.resize_initialized(m_CachedBlendShapeCount, 0.0F);
+
+ m_BlendShapeWeights[index] = weight;
+ SetDirty();
+}
+
+/*
+ * Make sure m_BlendShapeWeights is not accessed directly anywhere.. It is not the size of the number of blendshapes..
+ * Wrong: bool ShouldRecalculateBoundingVolumeEveryFrame () { return m_UpdateWhenOffscreen || m_RootBone.GetInstanceID() != 0 || !m_BlendShapeWeights.empty (); }
+ * Wrong: BlendShape bounding volume calculation needs serious review
+ * Extract & Store blendshape default values in skinned mesh renderer from fbx file
+ * Review code for multithreaded rendering integration when changing blendshape weights. Probably should also hook into mesh changed for recalculating blendshape count...
+ * Fix animation window editing being abysmal slow
+*/
+void SkinnedMeshRenderer::UnloadVBOFromGfxDevice()
+{
+ if (m_VBO)
+ {
+ GetGfxDevice().DeleteVBO (m_VBO);
+ m_VBO = NULL;
+ }
+ if (m_MemExportInfo)
+ {
+ GetGfxDevice().DeleteGPUSkinningInfo(m_MemExportInfo);
+ m_MemExportInfo = NULL;
+ }
+
+ m_SourceMeshDirty = true;
+}
+
+void SkinnedMeshRenderer::ReloadVBOToGfxDevice()
+{
+ if (!m_MemExportInfo)
+ m_MemExportInfo = CreateGPUSkinningIfAvailable();
+}
+
+Unity::Component* SkinnedMeshRenderer::GetAnimator()
+{
+ if (m_CachedAnimator == NULL)
+ CreateCachedAnimatorBinding ();
+
+ return m_CachedAnimator;
+}
+
+void SkinnedMeshRenderer::CreateCachedAnimatorBinding()
+{
+ ClearCachedAnimatorBinding();
+
+ if (!m_CachedMesh)
+ return;
+
+ const dynamic_array<BindingHash>& bonePathHashes = m_CachedMesh->GetBonePathHashes();
+ if (bonePathHashes.size() != GetBindposeCount())
+ {
+ ErrorStringObject("Bones do not match bindpose.", m_CachedMesh);
+ return;
+ }
+
+ m_CachedAnimator = FindAncestorComponentExactTypeImpl(GetGameObject(), ClassID(Animator));
+ if (m_CachedAnimator == NULL || GetAnimationInterface() == NULL)
+ return;
+
+ m_SkeletonIndices.resize_uninitialized(bonePathHashes.size());
+ if (!GetAnimationInterface()->PathHashesToIndices(*m_CachedAnimator, bonePathHashes.begin(), bonePathHashes.size(), m_SkeletonIndices.begin()))
+ m_SkeletonIndices.clear();
+
+ Assert(!m_CachedAnimator->HasEvent(AnimatorModifiedCallback, this));
+ m_CachedAnimator->AddEvent(AnimatorModifiedCallback, this);
+}
+
+void SkinnedMeshRenderer::ClearCachedAnimatorBinding()
+{
+ if (m_CachedAnimator == NULL)
+ return;
+
+ m_CachedAnimator->RemoveEvent(AnimatorModifiedCallback, this);
+ m_SkeletonIndices.clear();
+ m_CachedAnimator = NULL;
+}
+
+void SkinnedMeshRenderer::AnimatorModifiedCallback(void* userData, void* sender, int eventID)
+{
+ SkinnedMeshRenderer& skinnedMeshRenderer = *reinterpret_cast<SkinnedMeshRenderer*>(userData);
+
+ if (eventID == kAnimatorClearEvent)
+ skinnedMeshRenderer.ClearCachedAnimatorBinding();
+}
+
diff --git a/Runtime/Filters/Deformation/SkinnedMeshFilter.h b/Runtime/Filters/Deformation/SkinnedMeshFilter.h
new file mode 100644
index 0000000..d20d304
--- /dev/null
+++ b/Runtime/Filters/Deformation/SkinnedMeshFilter.h
@@ -0,0 +1,213 @@
+#ifndef SKINNEDMESHFILTER_H
+#define SKINNEDMESHFILTER_H
+
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Filters/Mesh/Mesh.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/GfxDevice/GPUSkinningInfo.h"
+#include "Runtime/Modules/ExportModules.h"
+#include "Runtime/BaseClasses/NamedObject.h"
+
+class Mesh;
+class Animation;
+class VBO;
+namespace Unity { class SkinnedCloth; }
+struct SkinMeshInfo;
+struct CalculateSkinMatricesTask;
+
+class EXPORT_COREMODULE SkinnedMeshRenderer : public Renderer
+{
+ PPtr<Mesh> m_Mesh;
+ Mesh* m_CachedMesh;
+
+ // Bones for non-optimized mode
+ dynamic_array<PPtr<Transform> > m_Bones;
+ PPtr<Transform> m_RootBone;
+ // Bones for optimized mode
+ dynamic_array<UInt16> m_SkeletonIndices;
+
+ Unity::Component* m_CachedAnimator;
+
+ UInt32 m_CachedBlendShapeCount;
+ dynamic_array<float> m_BlendShapeWeights;
+
+ AABB m_AABB;
+ int m_Quality; ///< enum { Auto = 0, 1 Bone = 1, 2 Bones = 2, 4 Bones = 4 } Number of bones to use per vertex during skinning.
+ bool m_UpdateWhenOffscreen;///< Unity will calculate an accurate bounding volume representation every frame.
+ bool m_DirtyAABB;
+ bool m_Visible;
+
+ VBO* m_VBO;
+ dynamic_array<UInt8> m_SkinnedVertices;
+ UInt32 m_ChannelsInVBO;
+ bool m_SourceMeshDirty;
+ bool m_UpdateBeforeRendering;
+ SkinnedCloth* m_Cloth;
+
+ GPUSkinningInfo* m_MemExportInfo;
+
+ ListNode<Object> m_MeshNode;
+
+#if UNITY_EDITOR
+ float m_CachedSurfaceArea;
+#endif
+
+#if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG
+ PPtr<Animation> m_DeprecatedDisableAnimationWhenOffscreen;
+#endif
+
+ ListNode<SkinnedMeshRenderer> m_SkinNode;
+ void DirtyAndClearCache ();
+
+public:
+
+ int GetBonesPerVertexCount ();
+
+ REGISTER_DERIVED_CLASS (SkinnedMeshRenderer, Renderer)
+ DECLARE_OBJECT_SERIALIZE (SkinnedMeshRenderer)
+
+ SkinnedMeshRenderer (MemLabelId label, ObjectCreationMode mode);
+ // ~SkinnedMeshRenderer (); declared-by-macro
+
+ virtual void Reset ();
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+ virtual void Deactivate (DeactivateOperation operation);
+
+ void Setup (Mesh* lodmesh, const dynamic_array<PPtr<Transform> >& state);
+
+ enum SkinningFlags
+ {
+ SF_None = 0,
+ SF_NoUpdateVBO = 1 << 0,
+ SF_ReadbackBuffer = 1 << 1,
+ SF_ClothPlaying = 1 << 2,
+ SF_AllowMemExport = 1 << 3,
+ };
+
+ enum UpdateType
+ {
+ kUpdateCloth,
+ kUpdateNonCloth
+ };
+
+ bool PrepareSkinCommon( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL );
+ bool PrepareSkinPS3( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL );
+ bool PrepareSkinGPU( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL );
+ bool PrepareSkin( UInt32 requiredChannels, int flags, SkinMeshInfo& skin, CalculateSkinMatricesTask* calcSkinMatricesTask=NULL );
+
+ bool SkinMesh( SkinMeshInfo& skin, bool lastMemExportThisFrame, UInt32 cpuFence, int flags );
+ bool SkinMeshImmediate( UInt32 requiredChannels );
+
+ bool CalculateAnimatedPoses (Matrix4x4f* poses, size_t size);
+
+#if UNITY_EDITOR
+ float GetCachedSurfaceArea ();
+ bool GetSkinnedVerticesAndNormals (dynamic_array<Vector3f>* vertices, dynamic_array<Vector3f>* normals);
+ bool CalculateVertexBasedBounds (const Matrix4x4f* poseMatrices, MinMaxAABB& output);
+#endif
+
+ void SetUpdateWhenOffscreen (bool onlyIfVisible);
+ bool GetUpdateWhenOffscreen () { return m_UpdateWhenOffscreen; }
+
+ void SetDisableAnimationWhenOffscreen (Animation* animation);
+ Animation* GetDisableAnimationWhenOffscreen ();
+
+ const dynamic_array<PPtr<Transform> >& GetBones () const { return m_Bones; }
+ void SetBones (const dynamic_array<PPtr<Transform> >& bones);
+
+ bool IsOptimized () { return m_Bones.size()==0 && GetBindposeCount()>0; }
+ // Before invoking this function, please make sure the SkinnedMeshRenderer is in optimized mode.
+ const dynamic_array<UInt16>& GetSkeletonIndices();
+
+ int GetBindposeCount () const;
+ int GetBoneCount () const { return m_Bones.size(); }
+
+ void SetQuality (int quality);
+ int GetQuality () { return m_Quality; }
+
+ void SetMesh (Mesh* mesh);
+ Mesh* GetMesh ();
+
+ void SetCloth (SkinnedCloth *value) { m_Cloth = value; }
+ void SetRootBone (Transform* rootBone) { m_RootBone = rootBone; }
+ Transform* GetRootBone () { return m_RootBone; }
+
+ Transform& GetActualRootBone ();
+
+ static void UpdateAllSkinnedMeshes(UpdateType updateType, dynamic_array<SkinnedMeshRenderer*>* outMeshes = NULL);
+ static void UploadSkinnedClothes(const dynamic_array<SkinnedMeshRenderer*>& skinnedMeshes);
+ void ReadSkinningDataForCloth(const SkinMeshInfo& skin);
+#if UNITY_EDITOR
+ void UpdateClothDataForEditing(const SkinMeshInfo& skin);
+#endif
+
+ /// Handlers so we only skin when somebody sees us.
+ virtual void BecameVisible ();
+ virtual void BecameInvisible ();
+
+ #if UNITY_EDITOR || SUPPORT_REPRODUCE_LOG
+ void HandleOldSkinnedFilter ();
+ #endif
+
+ virtual void UpdateTransformInfo();
+
+ void SetLocalAABB(const AABB& bounds);
+ void GetSkinnedMeshLocalAABB(AABB& bounds);
+
+ void DidDeleteMesh ();
+ void DidModifyMesh ();
+ void UpdateCachedMesh ();
+
+ void BakeMesh (Mesh& mesh);
+
+ virtual void Render (int materialIndex, const ChannelAssigns& channels);
+
+ float GetBlendShapeWeight(UInt32 index) const;
+ void SetBlendShapeWeight(UInt32 index, float weight);
+
+#if ENABLE_PROFILER
+ static int GetVisibleSkinnedMeshRendererCount ();
+#endif
+
+ bool DoesQualifyForMemExport() const;
+
+ bool CalculateRootLocalSpaceBounds (MinMaxAABB& minMaxAAbb);
+
+ void UnloadVBOFromGfxDevice();
+ void ReloadVBOToGfxDevice();
+
+protected:
+
+ size_t GetValidBlendShapeWeightCount () const;
+
+ bool ShouldRecalculateBoundingVolumeEveryFrame () { return m_UpdateWhenOffscreen || m_RootBone.GetInstanceID() != 0; }
+
+
+ // Puts the renderer into the queue of all visible skinned meshes depending on if it is currently visible.
+ void UpdateVisibleSkinnedMeshQueue (bool active);
+
+ bool CalculateAnimatedPosesWithRoot (const Matrix4x4f& rootMatrix, Matrix4x4f* poses, size_t size);
+ // Main thread only.
+ bool CalculateSkinningMatrices (const Matrix4x4f& rootPose, Matrix4x4f* poses, size_t size);
+
+ bool PrepareVBO (bool hasSkin, bool hasBlendshape, bool doMemExport, int flags);
+
+ virtual void UpdateRenderer();
+
+ Unity::Component* GetAnimator ();
+ void CreateCachedAnimatorBinding ();
+ void ClearCachedAnimatorBinding ();
+ bool CalculateBoneBasedBounds (const Matrix4x4f* animatedPoses, size_t size, MinMaxAABB& output);
+
+ static void AnimatorModifiedCallback(void* userData, void* sender, int eventID);
+};
+
+#endif