2021SC@SDUSC
Class Animation
Animation.h
API_CLASS(NoSpawn) class FLAXENGINE_API Animation : public BinaryAsset
{
本质是一个继承的二进制流对象。
/// Length of the animation in seconds.
/// </summary>
API_FIELD() float Length;
/// <summary>
/// Amount of animation frames (some curve tracks may use less keyframes).
/// </summary>
API_FIELD() int32 FramesCount;
/// <summary>
/// Amount of animation channel tracks.
/// </summary>
API_FIELD() int32 ChannelsCount;
乱七八糟一堆属性。
看文档。
构造器Animation()
Properties
Duration get动画的持续时间,以帧为单位 类型是single
FramesperSecond 返回动画的帧率 single类型
Info 返回动画片段的信息 类型是InfoData
Length get动画长度,以秒为单位 single
Methods
LoadTimeLine() 获取作为序列化时间线数据的动画。用于在编辑器中显示它。
SaveTimeline(Byte[]) 保存时间轴
参数就是时间线data的字节流,返回一个表示操作成功与否的布尔值
然后是Animation.cpp
ScopeLock lock(Locker);
InfoData info;
info.MemoryUsage = sizeof(Animation);
if (IsLoaded())
{
info.Length = Data.GetLength();
info.FramesCount = (int32)Data.Duration;
info.ChannelsCount = Data.Channels.Count();
info.KeyframesCount = Data.GetKeyframesCount();
info.MemoryUsage += Data.Channels.Capacity() * sizeof(NodeAnimationData);
for (auto& e : Data.Channels)
{
info.MemoryUsage += (e.NodeName.Length() + 1) * sizeof(Char);
info.MemoryUsage += e.Position.GetKeyframes().Capacity() * sizeof(LinearCurveKeyframe<Vector3>);
info.MemoryUsage += e.Rotation.GetKeyframes().Capacity() * sizeof(LinearCurveKeyframe<Quaternion>);
info.MemoryUsage += e.Scale.GetKeyframes().Capacity() * sizeof(LinearCurveKeyframe<Vector3>);
}
}
加载目标Animation的信息。如果还没有加载,全部置零。
void Animation::ClearCache()
{
ScopeLock lock(Locker);
// Unlink events
for (auto i = MappingCache.Begin(); i.IsNotEnd(); ++i)
{
i->Key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
i->Key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
}
// Free memory
MappingCache.Clear();
MappingCache.Cleanup();
}
动画清缓存;分为两步,第一步清理事件,第二步清理内存。
Asset::LoadResult Animation::load()
加载动画。
const auto dataChunk = GetChunk(0);
if (dataChunk == nullptr)
return LoadResult::MissingDataChunk;
MemoryReadStream stream(dataChunk->Get(), dataChunk->Size());
拿到动画的数据流。
stream.ReadInt32(&headerVersion);
stream.ReadDouble(&Data.Duration);
stream.ReadDouble(&Data.FramesPerSecond);
Data.EnableRootMotion = stream.ReadBool();
stream.ReadString(&Data.RootNodeName, 13);
读动画的信息流。
if (Data.Duration < ZeroTolerance || Data.FramesPerSecond < ZeroTolerance)
{
LOG(Warning, "Invalid animation info");
return LoadResult::Failed;
}
读数据的时候,如果你的信息不合法,返回失败。
int32 animationsCount;
stream.ReadInt32(&animationsCount);
Data.Channels.Resize(animationsCount, false);
for (int32 i = 0; i < animationsCount; i++)
{
auto& anim = Data.Channels[i];
stream.ReadString(&anim.NodeName, 172);
bool failed = Serialization::Deserialize(stream, anim.Position);
failed |= Serialization::Deserialize(stream, anim.Rotation);
failed |= Serialization::Deserialize(stream, anim.Scale);
if (failed)
{
LOG(Warning, "Failed to deserialize the animation curve data.");
return LoadResult::Failed;
}
}
装载动画。对应报错。
void Animation::unload(bool isReloading)
{
ClearCache();
Data.Dispose();
}
卸载。
然后上面提到了Animation的infoData,跳过去看一下吧。
结构体:Animation.InfoData
Fields:
ChannelsCount
通道数,类型Int32
FramesCount
帧数,int32
KeyframesCount
关键帧计数,int32.这个关键帧在视频编码技术中常用。比如一个大容量的视频文件,如果直接剪辑切开,势必需要二次编码;而采用关键帧分割,就可以避免二次编码。
Length 长度。按秒计数。Single
MemoryUsage
总内存占用,int32
然后看一下文档:
Class AnimationGraph
动画图像,用于评估当前帧的动画模型的最终姿态。这个翻译挺费解的。后面再看看能不能搞清楚它。
构造器:AnimationGraph()
Properties:
BaseModel Gets the base model asset used for the animation preview and the skeleton layout source.
获取用于动画预览和骨架布局的基础模型。类型是SkinnedModel。
Class AnimationGraphFunction
动画的函数,包含动画中可重用的部分。
Methods
GetSignature(out String[], out String[])
获取 Visject Surface 编辑器的函数签名。两个参数,第一个是类型,第二个是名字。
LoadSurface()
加载图像
SaveSurface(Byte[])
更新表面(保存新图表,放弃缓存的数据,重新加载)
Asset::LoadResult AnimationGraphFunction::load()
整个的用来加载图像。
const auto surfaceChunk = GetChunk(0);
if (!surfaceChunk || !surfaceChunk->IsLoaded())
return LoadResult::MissingDataChunk;
GraphData.Swap(surfaceChunk->Data);
从chunk加载图,如果丢失进入对应的事件。
MemoryReadStream stream(GraphData.Get(), GraphData.Length());
AnimGraph graph(this, true);
if (graph.Load(&stream, false))
return LoadResult::Failed;
加载图。可以看到对应的方法和错误处理。
if (Inputs.Count() >= 16 || Outputs.Count() >= 16)
{
LOG(Error, "Too many function inputs/outputs in '{0}'. The limit is max 16 inputs and max 16 outputs.", ToString());
}
输入控制。
void AnimationGraphFunction::unload(bool isReloading)
{
GraphData.Release();
Inputs.Clear();
Outputs.Clear();
}
卸载。
Class SkinnedModel
蒙皮模型。包含网格构成的模型,可以使用骨骼蒙皮渲染。
构造器:SkinnedModel()
Fields
MaxBones 最大骨骼支架可允许数量 默认256 Int32
Properties
BlendShapes
获取蒙皮模型网格所使用的混合形状名称。返回一个字符串数组。
Bones 获取骨骼层级结构。直接返回一个骨骼类型数组。
跳过去看一下这个SkeletonBone。
结构体:SkeletonBone
描述单个骨骼数据。由运行时使用。也就是骨骼节点集合的子集。
Fields
LocalTransform
骨骼相对于父骨在绑定姿势上的局部转变。
类型是一个Transform。我们再跳进Transform看一下。
结构体:Transform
描述三维空间中的变形。
构造器就不看了。直接看Fields。
Fields:
Identity 一个默认的恒等变换。
Orientation 旋转。类型是Quaternion。再看Quaternion。
结构体:Quaternion
表示扭转的结构。按照俯仰,横向摆动,滚转的顺序(x,y,z)。
结束父类之后我们再细说这个Quaternion。先往回递归。
Scale 变换的比例向量。类型是vector3.
Sizeinbytes 大小,没什么好说的。类型int32
Translation translation的向量。这个翻译很模糊,如果不实际在编辑器里试一下,我就搞不懂它的作用了。Vector3类型。
Properties:
Backward 向后的向量。
Down 向下。
Forward 向前。
Isidentity get一个布尔值,看这个变换是不是恒等的。
Left 向左
Right 向右。
Up 向上。
上面这些全都是方向向量vector3
Methods:
Add(Transform, Transform)
把两个变形叠加。输入两个transform类型,输出一个transform。
Equals(transform)
给一个布尔值,判断两个变换是否恒等。
然后还有两个判断相等的函数,参数不同。
Gethashcode
拿到变换的哈希码。重写。
GetRotation 拿到旋转的矩阵(这个矩阵是从Orientation中导出的。)
GetWorld 拿到一个全局的四维矩阵,等价于这一变换。
Lerp(transform,transform,Single)
在两个transform之间执行一次线性插值。
LocalToWorld(Transform)
在局部空间中执行给定变换的变换。
然后下面是一大堆局部和全局、vector、矩阵、transform之间相互变换的函数。
NearEqual(Transform, Transform, Single)
得到一个布尔值,判断两个变换是否非严格地相似。
SetRotation(Matrix)
设置旋转。参数给一个矩阵。
Subtract(transform,transform)
两个变换相减。
之后下面是重写基本的几个函数,toString,然后是重载操作符。
NodeIndex
骨骼节点的索引。Int32
OffsetMatrix
绑定姿势中从网格空间变换到骨骼空间的矩阵。
ParentIndex
父骨骼的节点。根骨骼默认是-1.
LoadedLODS
获取获取加载的模型LOD。这个LoD是什么呢?我们往下看。
LODS
模型细节的等级。所以这个lod就是level of details的缩写。
那表示精细程度总得有个结构吧。
往下一看确实有。
Class SkinnedModelLOD
表示蒙皮模型的细节级别。
构造器:SkinnedModelLoD()
Properties:
Box
获取此模型 LOD 中所有网格的包围盒组合。类型是个BoundingBox。
进去看。
结构体:BoundingBox
表示三维空间中轴对齐的边界框。
构造器。BoundingBox(vector3,vector3)用两个向量构造。两个向量表示顶点的最大值、最小值。
后面的太多了,之后一起单独拿出来看。
Meshes
网格阵列。类型是Class SkinnedMesh。这个也后面拿出来看(因为相当于是另一个类了,太长了递归容易回不来)
ScreenSize
呈现此 LOD 的模型屏幕大小的下限。Single类型。
看一下SkinnedModel.cpp
if (buffer->IsValidFor(this) == false) \
{ \
LOG(Warning, "Invalid Skinned Model Instance Buffer size {0} for Skinned Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), ToString(), MaterialSlots.Count()); \
buffer->Setup(this); \
}
判断是否有效。
BytesContainer data;
model->GetLODData(_lodIndex, data);
if (data.IsInvalid())
{
LOG(Warning, "Missing data chunk");
return true;
}
MemoryReadStream stream(data.Get(), data.Length());
// Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
// Load model LOD (initialize vertex and index buffers)
if (model->LODs[_lodIndex].Load(stream))
{
LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex);
return true;
}
同样的错误判断。
Array<String> SkinnedModel::GetBlendShapes()
{
Array<String> result;
if (LODs.HasItems())
{
for (auto& mesh : LODs[0].Meshes)
{
for (auto& blendShape : mesh.BlendShapes)
{
if (!result.Contains(blendShape.Name))
result.Add(blendShape.Name);
}
}
}
return result;
}
上面看到过的。
BoundingBox SkinnedModel::GetBox(int32 lodIndex) const
{
return LODs[lodIndex].GetBox();
}
拿边界箱。可以看到都是从LOD里面拿。