Reputation: 11
I made today a skinned mesh, and i tried out my Blockbench model with a animation and saw that the keyframes of this model that set to 0 not set into the model, so my question is now how can i reproduce the empty positions, roations, scale for my model.
For Blender everything works fine because they even set the default values in the model, but with Blockbench i need to reproduce this values.
This is my Code:
using Assimp;
using Bliss.CSharp.Geometry.Animations.Bones;
using Bliss.CSharp.Geometry.Conversions;
using Bliss.CSharp.Logging;
using AMatrix4x4 = Assimp.Matrix4x4;
using Matrix4x4 = System.Numerics.Matrix4x4;
using AQuaternion = Assimp.Quaternion;
namespace Bliss.CSharp.Geometry.Animations;
public class MeshAmateurBuilder {
private Node _rootNode;
private ModelAnimation[] _animations;
private Dictionary<uint, Bone> _bonesByName;
private Matrix4x4[] _boneTransformations;
/// <summary>
/// Initializes a new instance of the <see cref="MeshAmateurBuilder"/> class with a root node and animations.
/// </summary>
/// <param name="rootNode">The root <see cref="Node"/> representing the hierarchical structure of the mesh.</param>
/// <param name="animations">An array of <see cref="ModelAnimation"/> objects defining animations for the mesh.</param>
public MeshAmateurBuilder(Node rootNode, ModelAnimation[] animations) {
this._rootNode = rootNode;
this._animations = animations;
}
/// <summary>
/// Constructs a dictionary containing bone information mapped by animation name and frame index.
/// </summary>
/// <param name="bonesByName">A dictionary mapping bone identifiers to bone objects.</param>
/// <returns>A dictionary where keys are animation names, and values are dictionaries that map frame indices to arrays of bone information.</returns>
public Dictionary<string, Dictionary<int, BoneInfo[]>> Build(Dictionary<uint, Bone> bonesByName) {
this._bonesByName = bonesByName;
this._boneTransformations = new Matrix4x4[bonesByName.Count];
Dictionary<string, Dictionary<int, BoneInfo[]>> boneInfos = new Dictionary<string, Dictionary<int, BoneInfo[]>>();
foreach (ModelAnimation animation in this._animations) {
boneInfos.Add(animation.Name, this.SetupBoneInfos(animation));
}
return boneInfos;
}
/// <summary>
/// Generates a dictionary that maps frame indices to arrays of <see cref="BoneInfo"/> for a given animation.
/// </summary>
/// <param name="animation">The animation for which bone information is being set up.</param>
/// <returns>A dictionary with frame indices as keys and arrays of <see cref="BoneInfo"/> as values.</returns>
private Dictionary<int, BoneInfo[]> SetupBoneInfos(ModelAnimation animation) {
Dictionary<int, BoneInfo[]> bones = new Dictionary<int, BoneInfo[]>();
for (int i = 0; i < animation.FrameCount; i++) {
List<BoneInfo> boneInfos = new List<BoneInfo>();
this.UpdateChannel(this._rootNode, animation, i, AMatrix4x4.Identity);
for (uint boneId = 0; boneId < this._boneTransformations.Length; boneId++) {
BoneInfo boneInfo = new BoneInfo(this._bonesByName[boneId].Name, boneId, this._boneTransformations[boneId]);
boneInfos.Add(boneInfo);
}
bones.Add(i, boneInfos.ToArray());
}
return bones;
}
/// <summary>
/// Updates the transformation channel of a specified node based on the animation data and current frame index.
/// </summary>
/// <param name="node">The node whose transformation channel is to be updated.</param>
/// <param name="animation">The animation containing the transformation data.</param>
/// <param name="frame">The current frame index being processed.</param>
/// <param name="parentTransform">The transformation matrix of the parent node.</param>
private void UpdateChannel(Node node, ModelAnimation animation, int frame, AMatrix4x4 parentTransform) {
AMatrix4x4 nodeTransformation = AMatrix4x4.Identity;
if (this.GetChannel(node, animation, out NodeAnimationChannel? channel)) {
AMatrix4x4 scale = this.InterpolateScale(channel!, animation, frame);
AMatrix4x4 rotation = this.InterpolateRotation(channel!, animation, frame);
AMatrix4x4 translation = this.InterpolateTranslation(channel!, animation, frame);
nodeTransformation = scale * rotation * translation;
}
foreach (uint boneId in this._bonesByName.Keys) {
Bone bone = this._bonesByName[boneId];
if (node.Name == bone.Name) {
AMatrix4x4 rootInverseTransform = this._rootNode.Transform;
rootInverseTransform.Inverse();
AMatrix4x4 transformation = bone.OffsetMatrix * nodeTransformation * parentTransform * rootInverseTransform;
this._boneTransformations[boneId] = Matrix4x4.Transpose(ModelConversion.FromAMatrix4X4(transformation));
}
}
foreach (Node childNode in node.Children) {
this.UpdateChannel(childNode, animation, frame, nodeTransformation * parentTransform);
}
}
/// <summary>
/// Interpolates the translation transformation at a specific frame using the provided animation channel and animation data.
/// </summary>
/// <param name="channel">The <see cref="NodeAnimationChannel"/> containing position keys for the node.</param>
/// <param name="animation">The <see cref="ModelAnimation"/> containing the animation data, including timing information.</param>
/// <param name="frame">The current frame for which the translation is being interpolated.</param>
/// <returns>An <see cref="Assimp.Matrix4x4"/> representing the interpolated translation transformation for the given frame.</returns>
private AMatrix4x4 InterpolateTranslation(NodeAnimationChannel channel, ModelAnimation animation, int frame) {
double frameTime = frame / 60.0F * animation.TicksPerSecond;
Vector3D position;
if (channel.PositionKeyCount == 1) {
position = channel.PositionKeys[0].Value;
}
else {
uint frameIndex = 0;
for (uint i = 0; i < channel.PositionKeyCount - 1; i++) {
if (frameTime < channel.PositionKeys[(int) (i + 1)].Time) {
frameIndex = i;
break;
}
}
VectorKey currentFrame = channel.PositionKeys[(int) frameIndex];
VectorKey nextFrame = channel.PositionKeys[(int) ((frameIndex + 1) % channel.PositionKeyCount)];
double delta = (frameTime - currentFrame.Time) / (nextFrame.Time - currentFrame.Time);
Vector3D start = currentFrame.Value;
Vector3D end = nextFrame.Value;
position = start + (float) delta * (end - start);
}
return AMatrix4x4.FromTranslation(position);
}
/// <summary>
/// Interpolates the rotation transformation for a given node animation channel at a specified frame.
/// </summary>
/// <param name="channel">The <see cref="NodeAnimationChannel"/> containing the rotation keyframes for the node.</param>
/// <param name="animation">The <see cref="ModelAnimation"/> object defining the overall animation data.</param>
/// <param name="frame">The current frame for which the rotation needs to be interpolated.</param>
/// <returns>An <see cref="AMatrix4x4"/> representing the interpolated rotation transformation matrix for the specified frame.</returns>
private AMatrix4x4 InterpolateRotation(NodeAnimationChannel channel, ModelAnimation animation, int frame) {
double frameTime = frame / 60.0F * animation.TicksPerSecond;
AQuaternion rotation;
if (channel.RotationKeyCount == 1) {
rotation = channel.RotationKeys[0].Value;
}
else {
uint frameIndex = 0;
for (uint i = 0; i < channel.RotationKeyCount - 1; i++) {
if (frameTime < channel.RotationKeys[(int) (i + 1)].Time) {
frameIndex = i;
break;
}
}
QuaternionKey currentFrame = channel.RotationKeys[(int) frameIndex];
QuaternionKey nextFrame = channel.RotationKeys[(int) ((frameIndex + 1) % channel.RotationKeyCount)];
double delta = (frameTime - currentFrame.Time) / (nextFrame.Time - currentFrame.Time);
AQuaternion start = currentFrame.Value;
AQuaternion end = nextFrame.Value;
rotation = AQuaternion.Slerp(start, end, (float) delta);
rotation.Normalize();
}
return rotation.GetMatrix();
}
/// <summary>
/// Computes the interpolated scale transformation for a given animation channel at a specific frame.
/// </summary>
/// <param name="channel">The <see cref="NodeAnimationChannel"/> containing scaling keyframes.</param>
/// <param name="animation">The <see cref="ModelAnimation"/> associated with the animation data.</param>
/// <param name="frame">The current frame of the animation for which the scale transformation is calculated.</param>
/// <returns>An <see cref="AMatrix4x4"/> representing the interpolated scale transformation at the specified frame.</returns>
private AMatrix4x4 InterpolateScale(NodeAnimationChannel channel, ModelAnimation animation, int frame) {
double frameTime = frame / 60.0F * animation.TicksPerSecond;
Vector3D scale;
if (channel.ScalingKeyCount == 1) {
scale = channel.ScalingKeys[0].Value;
}
else {
uint frameIndex = 0;
for (uint i = 0; i < channel.ScalingKeyCount - 1; i++) {
if (frameTime < channel.ScalingKeys[(int) (i + 1)].Time) {
frameIndex = i;
break;
}
}
VectorKey currentFrame = channel.ScalingKeys[(int)frameIndex];
VectorKey nextFrame = channel.ScalingKeys[(int)((frameIndex + 1) % channel.ScalingKeyCount)];
double delta = (frameTime - currentFrame.Time) / (nextFrame.Time - currentFrame.Time);
Vector3D start = currentFrame.Value;
Vector3D end = nextFrame.Value;
scale = start + (float) delta * (end - start);
}
return AMatrix4x4.FromScaling(scale);
}
/// <summary>
/// Retrieves the animation channel for a given node from the specified animation.
/// </summary>
/// <param name="node">The node for which the animation channel should be retrieved.</param>
/// <param name="animation">The animation containing the channels to check.</param>
/// <param name="channel">The output parameter that will hold the retrieved node animation channel if found; otherwise, null.</param>
/// <returns>True if the channel for the specified node is found in the animation; otherwise, false.</returns>
private bool GetChannel(Node node, ModelAnimation animation, out NodeAnimationChannel? channel) {
foreach (NodeAnimationChannel nodeChannel in animation.AnimationChannels) {
if (nodeChannel.NodeName == node.Name) {
channel = nodeChannel;
return true;
}
}
channel = null;
return false;
}
}
Upvotes: 1
Views: 18