Animation Position Logs

Bone 0 Input:

0.00 0.30 -0.00
0.00 0.16 -0.00
0.00 -0.11 -0.00
0.00 -0.30 -0.00
0.00 -0.36 -0.00
0.00 -0.40 -0.00
0.00 -0.48 -0.00
0.00 0.00 -0.00
0.00 3.00 -0.00
0.00 3.02 -0.00
0.00 2.89 -0.00
0.00 2.63 -0.00
0.00 2.30 -0.00
0.00 1.94 -0.00
0.00 1.58 -0.00
0.00 1.27 -0.00
0.00 1.05 -0.00
0.00 0.90 -0.00
0.00 0.76 -0.00
0.00 0.65 -0.00
0.00 0.54 -0.00
0.00 0.46 -0.00
0.00 0.39 -0.00
0.00 0.31 -0.00

Bone 0 Output:

0.00 0.30 -0.00
0.00 0.15 -0.00
0.00 -0.13 -0.00
0.00 -0.31 -0.00
0.00 -0.36 -0.00
0.00 -0.37 -0.00
0.00 -0.38 -0.00
0.00 -0.38 -0.00
0.00 -0.39 -0.00
0.00 -0.40 -0.00
0.00 -0.43 -0.00
0.00 -0.29 -0.00
0.00 1.29 -0.00
0.00 3.01 -0.00
0.00 2.95 -0.00
0.00 2.75 -0.00
0.00 2.44 -0.00
0.00 2.08 -0.00
0.00 1.71 -0.00
0.00 1.37 -0.00
0.00 1.11 -0.00
0.00 0.94 -0.00
0.00 0.79 -0.00
0.00 0.67 -0.00
0.00 0.56 -0.00
0.00 0.47 -0.00
0.00 0.39 -0.00
0.00 0.35 -0.00

Bone 1 Input:

0.00 3.70 0.20
0.00 3.70 0.20

Bone 1 Output:

0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20
0.00 3.70 0.20

Bone 32 Input:

5.45 0.00 0.00
5.45 0.00 0.00

Bone 32 Output:

5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00
5.45 0.00 0.00

Bone Notes

gltf

<bpy_struct, EditBone("bone_000")>
--- set bone transforms ---
63
0
<Matrix 4x4 (1.0000, 0.0000, 0.0000, -0.0000)
            (0.0000, 1.0000, 0.0000,  0.0000)
            (0.0000, 0.0000, 1.0000, -0.0000)
            (0.0000, 0.0000, 0.0000,  1.0000)>
switch to pose
--- Create Bone ---
<bpy_struct, EditBone("bone_001")>
--- set bone transforms ---
62
0
<Matrix 4x4 (1.0000, 0.0000, 0.0000, -0.0000)
            (0.0000, 1.0000, 0.0000,  3.7000)
            (0.0000, 0.0000, 1.0000,  0.2000)
            (0.0000, 0.0000, 0.0000,  1.0000)>
switch to pose
--- Create Bone ---
<bpy_struct, EditBone("bone_002")>
--- set bone transforms ---
61
0
<Matrix 4x4 (-0.0000, -1.0000, -0.0000, 0.0000)
            ( 0.9551, -0.0000,  0.2963, 3.7000)
            (-0.2963,  0.0000,  0.9551, 0.2000)
            ( 0.0000,  0.0000,  0.0000, 1.0000)>
switch to pose
--- Create Bone ---
<bpy_struct, EditBone("bone_003")>
--- set bone transforms ---
30
0
<Matrix 4x4 (-0.0000, -1.0000, -0.0000, -0.0001)
            ( 0.9988, -0.0000,  0.0499,  6.6001)
            (-0.0499,  0.0000,  0.9988, -0.6998)
            ( 0.0000,  0.0000,  0.0000,  1.0000)>

decompose

<bpy_struct, EditBone("bone_000")>
--- set bone transforms ---
63
0
switch to pose
<Matrix 4x4 (1.0000, 0.0000, 0.0000, -0.0000)
            (0.0000, 1.0000, 0.0000,  0.0000)
            (0.0000, 0.0000, 1.0000, -0.0000)
            (0.0000, 0.0000, 0.0000,  1.0000)>
<Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
<Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
            (0.0000, 1.0000, 0.0000, 0.0000)
            (0.0000, 0.0000, 1.0000, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
--- Create Bone ---
<bpy_struct, EditBone("bone_001")>
--- set bone transforms ---
62
0
switch to pose
<Matrix 4x4 (1.0000, 0.0000, 0.0000, -0.0000)
            (0.0000, 1.0000, 0.0000,  3.7000)
            (0.0000, 0.0000, 1.0000,  0.2000)
            (0.0000, 0.0000, 0.0000,  1.0000)>
<Quaternion (w=1.0000, x=0.0000, y=0.0000, z=0.0000)>
<Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
            (0.0000, 1.0000, 0.0000, 0.0000)
            (0.0000, 0.0000, 1.0000, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
--- Create Bone ---
<bpy_struct, EditBone("bone_002")>
--- set bone transforms ---
61
0
switch to pose
<Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
            (0.0000, 1.0000, 0.0000, 3.7000)
            (0.0000, 0.0000, 1.0000, 0.2000)
            (0.0000, 0.0000, 0.0000, 1.0000)>
<Quaternion (w=0.6991, x=-0.1060, y=0.1060, z=0.6991)>
<Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
            (0.0000, 1.0000, 0.0000, 0.0000)
            (0.0000, 0.0000, 1.0000, 0.0000)
            (0.0000, 0.0000, 0.0000, 1.0000)>

Blender Bookmarks

bmesh

bpy.types.Mesh

bpy.types.Object

https://devtalk.blender.org/t/vertex-weights-and-indices-for-a-bmesh/1457

Bones debug output:
nj : https://pastebin.com/E7LX5HvK
dmf : https://pastebin.com/ygURJ6ip

I guess this means I need to check the original values versus the provided values. I also need to check if I need to multiply by the parent.

Bones transformations:

DMF: https://pastebin.com/fBgT4MTk
NJ : https://pastebin.com/U2JdeCsm
NJF: https://pastebin.com/4VUt8buT

Xentax Import: http://wiki.xentax.com/index.php/Blender_Import_Guide#Bones.2C_weights

Noesis Notes

Got bones and weights working, next step is to figure out how to implement animations.

class NoeBone:
    def __init__(self, index, name, matrix, parentName = None, parentIndex = -1):
        self.index = index
        self.name = name
        self.setMatrix(matrix)
        self.parentName = parentName
        self.parentIndex = parentIndex #parent index may be specified instead of parentName, if it's more convenient. this is an index corresponding to self.index, and not the position in the list.
    def __repr__(self):
        return "(NoeBone:" + repr(self.index) + "," + self.name + "," + repr(self.parentName) + "," + repr(self.parentIndex) + ")"

    def setMatrix(self, matrix):
        if not isinstance(matrix, NoeMat43):
            noesis.doException("Invalid type provided for bone matrix")
        self._matrix = matrix
    def getMatrix(self):
        return self._matrix


#keyframe data type
class NoeKeyFramedValue:
    #value may be NoeQuat, NoeVec3, float, etc. depending on the data type specified
    def __init__(self, time, value):
        self.time = time
        self.value = value
        self.componentIndex = 0
    def __repr__(self):
        return "NoeKFVal(time:" + repr(self.time) + " value:" + repr(self.value) + ")"

    def setComponentIndex(self, componentIndex):
        self.componentIndex = componentIndex

#keyframed bone class
class NoeKeyFramedBone:
    def __init__(self, boneIndex):
        self.boneIndex = boneIndex
        self.setRotation([])
        self.setTranslation([])
        self.setScale([])

    #for the set methods, keys should be a list of NoeKeyFramedValue or an object with similarly available members
    def setRotation(self, keys, type = noesis.NOEKF_ROTATION_QUATERNION_4, interpolationType = noesis.NOEKF_INTERPOLATE_LINEAR):
        self.rotationKeys = keys
        self.rotationType = type
        self.rotationInterpolation = interpolationType
    def setTranslation(self, keys, type = noesis.NOEKF_TRANSLATION_VECTOR_3, interpolationType = noesis.NOEKF_INTERPOLATE_LINEAR):
        self.translationKeys = keys
        self.translationType = type
        self.translationInterpolation = interpolationType
    def setScale(self, keys, type = noesis.NOEKF_SCALE_SCALAR_1, interpolationType = noesis.NOEKF_INTERPOLATE_LINEAR):
        self.scaleKeys = keys
        self.scaleType = type
        self.scaleInterpolation = interpolationType


#keyframed animation class
class NoeKeyFramedAnim:
    def __init__(self, name, bones, kfBones, frameRate = 20.0, flags = 0):
        noesis.validateListType(bones, NoeBone)
        noesis.validateListType(kfBones, NoeKeyFramedBone)
        self.name = name
        self.bones = bones
        self.kfBones = kfBones
        self.frameRate = frameRate
        self.flags = flags
    def __repr__(self):
        return "(NoeKFAnim:" + self.name + ")"


#main animation class
#bones must be a list of NoeBone objects, frameMats must be a flat list of NoeMat43 objects
class NoeAnim:
    def __init__(self, name, bones, numFrames, frameMats, frameRate = 20.0, flags = 0):
        noesis.validateListType(bones, NoeBone)
        noesis.validateListType(frameMats, NoeMat43)
        self.name = name
        self.bones = bones
        self.numFrames = numFrames
        self.frameMats = frameMats
        self.setFrameRate(frameRate)
        self.flags = flags
    def __repr__(self):
        return "(NoeAnim:" + self.name + "," + repr(self.numFrames) + "," + repr(self.frameRate) + ")"

    def setFrameRate(self, frameRate):
        self.frameRate = frameRate

Example Code:

	bones = []
	numBones = bs.readInt()
	for i in range(0, numBones):
		bone = noepyReadBone(bs)
		bones.append(bone)

	anims = []
	numAnims = bs.readInt()
	for i in range(0, numAnims):
		animName = bs.readString()
		numAnimBones = bs.readInt()
		animBones = []
		for j in range(0, numAnimBones):
			animBone = noepyReadBone(bs)
			animBones.append(animBone)
		animNumFrames = bs.readInt()
		animFrameRate = bs.readFloat()
		numFrameMats = bs.readInt()
		animFrameMats = []
		for j in range(0, numFrameMats):
			frameMat = NoeMat43.fromBytes(bs.readBytes(48))
			animFrameMats.append(frameMat)
		anim = NoeAnim(animName, animBones, animNumFrames, animFrameMats, animFrameRate)
		anims.append(anim)

More sample codes: ./plugins/python/fmt_gamebryo_nif.py

    def loadTransformData(self, bs):
        if self.nif.fileVer >= nifVersion(20, 5, 0, 2):
            self.loadObject(bs)
            self.rotKeys = []
            self.trnKeys = []
            self.sclKeys = []
            self.rotKeyType = noesis.NOEKF_INTERPOLATE_LINEAR
            self.trnKeyType = noesis.NOEKF_INTERPOLATE_LINEAR
            self.sclKeyType = noesis.NOEKF_INTERPOLATE_LINEAR

            numKeys = bs.readUInt()
            if numKeys > 0:
                rotKeyType = bs.readUInt()
                if rotKeyType == 0 or rotKeyType == 1 or rotKeyType == 4:
                    self.rotKeyType = noesis.NOEKF_INTERPOLATE_LINEAR
                else:
                    print("WARNING: Unsupported rotation key type:", rotKeyType)
                    return
                for i in range(0, numKeys):
                    if rotKeyType == 0 or rotKeyType == 1:
                        #quats
                        keyTime = bs.readFloat()
                        w = bs.readFloat()
                        keyVal = NoeQuat( (bs.readFloat(), bs.readFloat(), bs.readFloat(), w) ).toMat43(1).toQuat()
                        self.rotKeys.append(NoeKeyFramedValue(keyTime, keyVal))
                    elif rotKeyType == 4:
                        #radians
                        xyz = [ [], [], [] ]
                        for i in range(0, 3):
                            numSubKeys = bs.readUInt()
                            if numSubKeys > 0:
                                radianKeyType = bs.readUInt()
                                for j in range(0, numSubKeys):
                                    keyTime = bs.readFloat()
                                    if radianKeyType == 0 or radianKeyType == 1:
                                        xyz[i].append(NoeKeyFramedValue(keyTime, bs.readFloat()*noesis.g_flRadToDeg))
                                    elif radianKeyType == 2:
                                        #possible todo - interpolate as bezier
                                        xyz[i].append(NoeKeyFramedValue(keyTime, bs.readFloat()*noesis.g_flRadToDeg))
                                        bezierIn = bs.readFloat()
                                        bezierOut = bs.readFloat()
                                    else:
                                        print("WARNING: Unsupported radian rotation type:", radianKeyType)
                                        return
                        #this is largely untested, because the model i found using it only used it for the eyes,
                        #and it's hard to tell if it really works just based on eyes. (that's what she said)
                        xyz = rapi.mergeKeyFramedFloats(xyz)
                        for anglesKey in xyz:
                            self.rotKeys.append(NoeKeyFramedValue(anglesKey.time, NoeAngles(anglesKey.value).toMat43_XYZ().toQuat()))

It looks like key framed value is going to be the best option for implementing the version of animations I have, which defines a time and then gives a set of transformations at that given time.