Closed Soreepeong closed 1 year ago
I haven't looked at animations before but did dig into the collada docs for it a while back. Blender's implementation of Collada is generally pretty good, but it can get weird some times.
One thing you might want to try is to create a simple model with a couple of bones, add an animation, and then export that file to Collada. It should create the library_animation
for you, and then you can compare it to what your animation output looks like to see if there are any differences.
I've been working on adding USD support to the project, which seems like the way forward too. It appears to be the de facto standard. It's been challenging to get it to compile. And I really need to compile it with the /clr
option so that I can use the dlls directly in the project (or ideally, make a Nuget package so it's available to everyone). I don't suppose you have any experience compiling c++ projects into the common language runtime? :)
I was trying to integrate FBX SDK with it but lost interest for the day when designing structures for interops, referring to https://github.com/lmcintyre/fbxInterop and https://github.com/TexTools/TT_FBX_Reader . Easiest way would be just dllexporting the functions.
Blender couldn't import the collada file exported from Blender correctly for a complicated one, so I'm starting with this instead: https://gist.github.com/sbarthelemy/610049/a4ee9e6c33af901001aecf1ba61b08a82d4ea40f
It kind of works, but seems that a lot of rotational quats are getting dealt w.r.t. wrong axis. Judging from the connecting part between head and "hair" have distinct border between them, maybe something about model exporting has to be fixed?
https://user-images.githubusercontent.com/3614868/203582998-5653b703-2e43-4dd0-aa77-186cabb48ebb.mp4
I have run the following code, then imported from Blender with another script below.
var animFile = Model.FromFile(@"...\knuckles.dba");
var animChunk = (ChunkController_905)animFile.ChunkMap.Values.First();
using var ms = new FileStream(@"...\knuckles.bin", FileMode.Create, FileAccess.Write);
// using var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
bw.Write(animChunk.Animations.Count);
foreach (var anim in animChunk.Animations)
{
bw.Write(Encoding.UTF8.GetBytes(anim.Name).Length);
bw.Write(Encoding.UTF8.GetBytes(anim.Name));
bw.Write(anim.MotionParams.Start);
bw.Write(anim.MotionParams.End);
bw.Write(anim.MotionParams.SecsPerTick * anim.MotionParams.TicksPerFrame);
bw.Write(anim.Controllers.Count);
foreach (var con in anim.Controllers)
{
var boneName = Bones!.BoneList.Single(x => x.ControllerID == con.ControllerID).boneName;
bw.Write(Encoding.UTF8.GetBytes(boneName).Length);
bw.Write(Encoding.UTF8.GetBytes(boneName));
if (con.PosTrack != 0xffffffffu)
{
var kt = animChunk.KeyTimes[(int)con.PosKeyTimeTrack];
var kp = animChunk.KeyPositions[(int)con.PosTrack];
var count = kt.Count;
bw.Write(count);
for (var i = 0; i < count; i++)
{
bw.Write(kt[i]);
bw.Write(kp[i].X);
bw.Write(kp[i].Y);
bw.Write(kp[i].Z);
}
} else
bw.Write(0);
if (con.RotTrack != 0xffffffffu)
{
var rt = animChunk.KeyTimes[(int)con.RotKeyTimeTrack];
var rp = animChunk.KeyRotations[(int)con.RotTrack];
var count = rt.Count;
bw.Write(count);
for (var i = 0; i < count; i++)
{
bw.Write(rt[i]);
bw.Write(rp[i].X);
bw.Write(rp[i].Y);
bw.Write(rp[i].Z);
bw.Write(rp[i].W);
}
} else
bw.Write(0);
}
}
import dataclasses
import struct
import typing
import pickle
import bpy
import mathutils
@dataclasses.dataclass
class MyVector:
x: float
y: float
z: float
@classmethod
def from_io(cls, fp: typing.IO[bytes]):
return cls(*struct.unpack("<3f", fp.read(12)))
@property
def vector(self):
return mathutils.Vector((self.x, self.y, self.z))
@dataclasses.dataclass
class MyQuaternion:
x: float
y: float
z: float
w: float
@classmethod
def from_io(cls, fp: typing.IO[bytes]):
return cls(*struct.unpack("<4f", fp.read(16)))
@property
def quaternion(self):
x,y,z,w=self.x,self.y,self.z,self.w
return mathutils.Quaternion((w,x,y,z))
@dataclasses.dataclass
class BoneAnimation:
name: str
translation: list[tuple[float, MyVector]]
rotation: list[tuple[float, MyQuaternion]]
@classmethod
def from_io(cls, fp: typing.IO[bytes]):
return cls(
name=fp.read(int.from_bytes(fp.read(4), "little")).decode("utf-8"),
translation=[(*struct.unpack("<f", fp.read(4)), MyVector.from_io(fp))
for _ in range(int.from_bytes(fp.read(4), "little"))],
rotation=[(*struct.unpack("<f", fp.read(4)), MyQuaternion.from_io(fp))
for _ in range(int.from_bytes(fp.read(4), "little"))]
)
@dataclasses.dataclass
class Animation:
name: str
start: int
end: int
secs_per_frame: float
bones: list[BoneAnimation]
@classmethod
def from_io(cls, fp: typing.IO[bytes]):
return cls(
name=fp.read(int.from_bytes(fp.read(4), "little")).decode("utf-8"),
start=int.from_bytes(fp.read(4), "little"),
end=int.from_bytes(fp.read(4), "little"),
secs_per_frame=struct.unpack("<f", fp.read(4))[0],
bones=[BoneAnimation.from_io(fp) for _ in range(int.from_bytes(fp.read(4), "little"))],
)
@dataclasses.dataclass
class BinaryFile:
animations: list[Animation]
@classmethod
def from_io(cls, fp: typing.IO[bytes]):
return cls(animations=[Animation.from_io(fp) for _ in range(int.from_bytes(fp.read(4), "little"))])
fpath = r"...\knuckles.bin"
anims = BinaryFile.from_io(open(fpath, "rb"))
pickle.dump(anims.animations[128], open(fpath + ".pkl", "wb"))
anims = pickle.load(open(fpath + ".pkl", "rb"))
print(anims.name)
max_keyframe = anims.end - anims.start + 1
bpy.data.scenes[0].frame_start = 1
bpy.data.scenes[0].frame_end = max_keyframe
for action in bpy.data.actions:
bpy.data.actions.remove(action)
for boneanim in anims.bones:
bone = bpy.data.objects["Armature"].pose.bones[boneanim.name]
bone.location = mathutils.Vector()
bone.rotation_quaternion = mathutils.Quaternion()
# continue
translations: list[MyVector | None] = [None] * (max_keyframe + 1)
rotations: list[MyQuaternion | None] = [None] * (max_keyframe + 1)
for t, q in boneanim.rotation:
rotations[int(t) - anims.start] = q
for t, tr in boneanim.translation:
translations[int(t) - anims.start] = tr
bone.location = mathutils.Vector()
bone.rotation_quaternion = mathutils.Quaternion()
for i, (tr, ro) in enumerate(zip(translations, rotations)):
if tr and False: # later
bone.location = tr.vector
bone.keyframe_insert("location", frame=i, group=f"{boneanim.name}")
if ro:
bone.rotation_quaternion = ro.quaternion
bone.keyframe_insert("rotation_quaternion", frame=i, group=f"{boneanim.name}")
I am fairly confident that cgf-converter doesn't handle rotations correctly. It just happens to get it wrong in just the right way where it works most of the time, but I think I'm still missing something. :(
I'm going to poke around in this space too after the holidays and see if I can help you with the animations. That would be a huge win! Thank you for doing so much ground work on this.
I'm actually implementing a Blender exporter that exports a python script that shall be run inside Blender - got mesh import done for now, and immediately got stuck on what info from the chunks are the "head" and "tail". Anyhow exporting a script would definitely be easier than making a Blender save file ourselves!
https://github.com/Soreepeong/Markemp-Cryengine-Converter/tree/chunkcontroller905
Animation quat directions are... strange. Just sharing what I've done so far if you feel like tackling at it at a later time!
Oof... this is complicated. Your 905 controller is pretty amazing. I'm having a hard time figuring out all the animation basics, but still plugging away at this.
I pretty much let it go for the time being; I can get the animation to play okay if all vertices are manually relocated every animation frame but couldn't figure out how to export it into values that blender or gltf would accept correctly.
Do you mind if I import your updates into my project and do some testing?
Of course not! Feel free to go ahead.
Hello, I was curious if there were any updates to this as exporting Cryengine animations is really exciting to me!
Been sidetracked on a couple of other projects. It's still on my high priority list though!
Waiting patiently
I know. :) Been busy with a few other projects. It's still next on my list though.
I know. :) Been busy with a few other projects. It's still next on my list though.
.caf animations convert is musthave
Just in case - do let me know if any of my code is confusing while working on it!
Any update on animations support? : )
It's done; you just have to compile it yourself until a new release happens. Also note that it has a high chance of not working if not for Rise of Lyric.
It kind of works, but seems that a lot of rotational quats are getting dealt w.r.t. wrong axis. Judging from the connecting part between head and "hair" have distinct border between them, maybe something about model exporting has to be fixed?
I have run the following code, then imported from Blender with another script below.
var animFile = Model.FromFile(@"...\knuckles.dba"); var animChunk = (ChunkController_905)animFile.ChunkMap.Values.First(); using var ms = new FileStream(@"...\knuckles.bin", FileMode.Create, FileAccess.Write); // using var ms = new MemoryStream(); var bw = new BinaryWriter(ms); bw.Write(animChunk.Animations.Count); foreach (var anim in animChunk.Animations) { bw.Write(Encoding.UTF8.GetBytes(anim.Name).Length); bw.Write(Encoding.UTF8.GetBytes(anim.Name)); bw.Write(anim.MotionParams.Start); bw.Write(anim.MotionParams.End); bw.Write(anim.MotionParams.SecsPerTick * anim.MotionParams.TicksPerFrame); bw.Write(anim.Controllers.Count); foreach (var con in anim.Controllers) { var boneName = Bones!.BoneList.Single(x => x.ControllerID == con.ControllerID).boneName; bw.Write(Encoding.UTF8.GetBytes(boneName).Length); bw.Write(Encoding.UTF8.GetBytes(boneName)); if (con.PosTrack != 0xffffffffu) { var kt = animChunk.KeyTimes[(int)con.PosKeyTimeTrack]; var kp = animChunk.KeyPositions[(int)con.PosTrack]; var count = kt.Count; bw.Write(count); for (var i = 0; i < count; i++) { bw.Write(kt[i]); bw.Write(kp[i].X); bw.Write(kp[i].Y); bw.Write(kp[i].Z); } } else bw.Write(0); if (con.RotTrack != 0xffffffffu) { var rt = animChunk.KeyTimes[(int)con.RotKeyTimeTrack]; var rp = animChunk.KeyRotations[(int)con.RotTrack]; var count = rt.Count; bw.Write(count); for (var i = 0; i < count; i++) { bw.Write(rt[i]); bw.Write(rp[i].X); bw.Write(rp[i].Y); bw.Write(rp[i].Z); bw.Write(rp[i].W); } } else bw.Write(0); } }
import dataclasses import struct import typing import pickle import bpy import mathutils @dataclasses.dataclass class MyVector: x: float y: float z: float @classmethod def from_io(cls, fp: typing.IO[bytes]): return cls(*struct.unpack("<3f", fp.read(12))) @property def vector(self): return mathutils.Vector((self.x, self.y, self.z)) @dataclasses.dataclass class MyQuaternion: x: float y: float z: float w: float @classmethod def from_io(cls, fp: typing.IO[bytes]): return cls(*struct.unpack("<4f", fp.read(16))) @property def quaternion(self): x,y,z,w=self.x,self.y,self.z,self.w return mathutils.Quaternion((w,x,y,z)) @dataclasses.dataclass class BoneAnimation: name: str translation: list[tuple[float, MyVector]] rotation: list[tuple[float, MyQuaternion]] @classmethod def from_io(cls, fp: typing.IO[bytes]): return cls( name=fp.read(int.from_bytes(fp.read(4), "little")).decode("utf-8"), translation=[(*struct.unpack("<f", fp.read(4)), MyVector.from_io(fp)) for _ in range(int.from_bytes(fp.read(4), "little"))], rotation=[(*struct.unpack("<f", fp.read(4)), MyQuaternion.from_io(fp)) for _ in range(int.from_bytes(fp.read(4), "little"))] ) @dataclasses.dataclass class Animation: name: str start: int end: int secs_per_frame: float bones: list[BoneAnimation] @classmethod def from_io(cls, fp: typing.IO[bytes]): return cls( name=fp.read(int.from_bytes(fp.read(4), "little")).decode("utf-8"), start=int.from_bytes(fp.read(4), "little"), end=int.from_bytes(fp.read(4), "little"), secs_per_frame=struct.unpack("<f", fp.read(4))[0], bones=[BoneAnimation.from_io(fp) for _ in range(int.from_bytes(fp.read(4), "little"))], ) @dataclasses.dataclass class BinaryFile: animations: list[Animation] @classmethod def from_io(cls, fp: typing.IO[bytes]): return cls(animations=[Animation.from_io(fp) for _ in range(int.from_bytes(fp.read(4), "little"))]) fpath = r"...\knuckles.bin" anims = BinaryFile.from_io(open(fpath, "rb")) pickle.dump(anims.animations[128], open(fpath + ".pkl", "wb")) anims = pickle.load(open(fpath + ".pkl", "rb")) print(anims.name) max_keyframe = anims.end - anims.start + 1 bpy.data.scenes[0].frame_start = 1 bpy.data.scenes[0].frame_end = max_keyframe for action in bpy.data.actions: bpy.data.actions.remove(action) for boneanim in anims.bones: bone = bpy.data.objects["Armature"].pose.bones[boneanim.name] bone.location = mathutils.Vector() bone.rotation_quaternion = mathutils.Quaternion() # continue translations: list[MyVector | None] = [None] * (max_keyframe + 1) rotations: list[MyQuaternion | None] = [None] * (max_keyframe + 1) for t, q in boneanim.rotation: rotations[int(t) - anims.start] = q for t, tr in boneanim.translation: translations[int(t) - anims.start] = tr bone.location = mathutils.Vector() bone.rotation_quaternion = mathutils.Quaternion() for i, (tr, ro) in enumerate(zip(translations, rotations)): if tr and False: # later bone.location = tr.vector bone.keyframe_insert("location", frame=i, group=f"{boneanim.name}") if ro: bone.rotation_quaternion = ro.quaternion bone.keyframe_insert("rotation_quaternion", frame=i, group=f"{boneanim.name}")
you mean this code? Or the animations branch
The master branch.
I'm trying to make sure it doesn't break any existing models, but there are some compatibility issues right now. For example, I can no longer convert .chr files from MWO if they have animation models, since the animations for MWO don't work yet with the changes brought in. I'm holding off on a new release until I can get the compatibility issues fixed.
Sorry for the delay on this. I'm as eager to get the new release out there as anyone, but it does take some time.
I've been working on ChunkController905 support (implemented this from this), and seems that we can obtain the list of tuples of (animationName, controllerId=bone, time, optional, optional).
The problem is that when I wrote to library_animation in collada file, Blender just won't load the animation. I forgot to commit the wip yesterday so code isn't there (will do that later), but just wanted to ask if you got some pointers on exporting animations, in case you have tried that before.