Little Skeleton importer module (alpha-beta)
Posted: Tue Feb 24, 2026 6:23 pm
Update
1) I wrote a fully 100% PureBasic skeleton importer for Ogre meshs (see bellow, updated code, still "beta", and only most actual format supported) original problem was that I missed Bones and Animation names
2) There are (yet) undocumented procedures already in PB that do what I was/we were looking
for (see bellow for pf shadoko's posts)
3) next goals: make a 100% purebasic skeleton serializer, make the mesh unserializer, once that is done a homebrew "CatchMesh" procedure, or perhaps some editor things but I'm bad at math XD
4) Get some wishes and feedback from you, is there something I should put my eyes on? The way it is written for now it has no dependencies, it could be run in a command line program on linux off desktop (RGB() makes linux console programs say they want a desktop).
5) Sorry for being lazy and haven't tried myself, yet. What could already be tested is if the data is correct, if you can create the skeleton in PB/Ogre on the mesh and it works flawlessly. If the imported bone positions are identical with the positions when no animations are running. And simple "editing" on the animations is certainly also possible, like time stretching. As for inserting key frames there needs to be a math genius for that to happen easily, it's not so obvious for me what it means but perhaps the importer isn't working right. I'll do that next. Many little procedures could be possible, like querying all child bones of a parent etc. When the serializer is there renaming, copying, changing animation or skeleton data should be easy. I'm curious about the mysterious Animation Link File thing.
I had to share this quickly with you. Perhaps I will start the mesh file reader this weekend already, we'll see
Btw. the sinbad.skeleton output takes more than 12 seconds on my computer to complete, robo is fastest with 0.5 s and ninja takes 2 s debugging the imported data.
1) I wrote a fully 100% PureBasic skeleton importer for Ogre meshs (see bellow, updated code, still "beta", and only most actual format supported) original problem was that I missed Bones and Animation names
2) There are (yet) undocumented procedures already in PB that do what I was/we were looking
3) next goals: make a 100% purebasic skeleton serializer, make the mesh unserializer, once that is done a homebrew "CatchMesh" procedure, or perhaps some editor things but I'm bad at math XD
4) Get some wishes and feedback from you, is there something I should put my eyes on? The way it is written for now it has no dependencies, it could be run in a command line program on linux off desktop (RGB() makes linux console programs say they want a desktop).
5) Sorry for being lazy and haven't tried myself, yet. What could already be tested is if the data is correct, if you can create the skeleton in PB/Ogre on the mesh and it works flawlessly. If the imported bone positions are identical with the positions when no animations are running. And simple "editing" on the animations is certainly also possible, like time stretching. As for inserting key frames there needs to be a math genius for that to happen easily, it's not so obvious for me what it means but perhaps the importer isn't working right. I'll do that next. Many little procedures could be possible, like querying all child bones of a parent etc. When the serializer is there renaming, copying, changing animation or skeleton data should be easy. I'm curious about the mysterious Animation Link File thing.
I had to share this quickly with you. Perhaps I will start the mesh file reader this weekend already, we'll see
Btw. the sinbad.skeleton output takes more than 12 seconds on my computer to complete, robo is fastest with 0.5 s and ninja takes 2 s debugging the imported data.
Code: Select all
; source: GetBoneNamesFromFile.pb
; date: 27/Feb/2026
; author: benubi
; OS: all
; License: free
;
; Description:
; ============
; o3d helper modules for Ogre3D
; -------------------------------------------------------------------------------------------------------------
DeclareModule o3d
; Extract Bone & Pose names from .skeleton files
; Get from file in memory
Declare GetBoneNamesFromMemory(*memory, size, Array Result.s(1)) ; Get bone names from memory
Declare GetAnimationNamesFromMemory(*memory, size, Array Result.s(1)) ; Get pose names from memory
Declare ImportSkeletonFromMemory(*memory, size, filename$ = #Empty$) ; returns a filled *o3d_Skeleton structure or null
; Get from file on medium
Declare GetBoneNamesFromFile(File$, Array Result.s(1)) ; Get bone names from file$
Declare GetAnimationNamesFromFile(File$, Array Result.s(1)) ; Get pose names from file$
Declare ImportSkeletonFromFile(File$) ; returns a filled *o3d_Skeleton structure or null
; Count tags (main section only, yet, no sub-tags support, yet)
Declare CountTagsInFile(File$, tag_id.w)
; misc.
Declare o3d_Bload(File$)
; Skeleton construction
Declare o3d_DeleteSkeleton(*This)
Declare o3d_NewSkeleton()
; Interface iSkeleton
; Free()
;
; GetAnimationLinkCount()
; GetAnimationLinkNames(Array result$(1))
; GetAnimationLinkScale.f(Name$)
;
; GetBoneCount()
; GetBoneByName(name$)
; GetBoneByHandle(Handle)
; GetBoneNames(Array result$(1))
;
; GetAnimationCount()
; GetAnimationNames(Array result$(1))
; GetAnimationByName(name$)
;
; EndInterface
;
Structure o3d_any
StructureUnion
a.a
b.b
c.c
d.d
f.f
i.i
l.l
*ptr.o3d_any
q.q
u.u
w.w
aa.a[0]
bb.b[0]
cc.c[0]
dd.d[0]
ff.f[0]
ii.i[0]
ll.l[0]
; *ptr.o3d_any[0]
*pptr[0]
qq.q[0]
uu.u[0]
ww.w[0]
EndStructureUnion
EndStructure
Structure o3d_TagHeader
type_id.w
size.l
; *name string size not count
; data.b[size] follows string/name
EndStructure
Structure o3d_Bone
BoneId.u
ParentBoneId.u
pos.vector3
ori.vector4
scale.vector3 ; optional /default xyz = 1 1 1
Name.s ;
List ChildNode.u()
EndStructure
Structure o3d_KeyFrame
time.f
ori.vector4
translation.vector3
scale.vector3 ; optional /default xyz 1 1 1
EndStructure
Structure o3d_AnimationTrack
BoneId.u
List KeyFrame.o3d_KeyFrame() ; key frames sorted by time
EndStructure
Structure o3d_Animation
Name.s
Length.f ; in seconds
BaseAnimation.s ;
BaseKeyFrame.f
List AnimationTrack.o3d_AnimationTrack() ; "dyn" array must be sorted by BoneId
EndStructure
Structure o3d_AnimationLink
SkeletonName.s
Scale.f
EndStructure
Structure o3d_Skeleton
*VT
Name.s
Filename.s
Map Bone.o3d_Bone()
Map Animation.o3d_Animation()
Map AnimationLink.o3d_AnimationLink()
EndStructure
Structure o3d_Pose
; ???????
EndStructure
Structure o3d_SubMesh
SubMeshName.s
; ????
EndStructure
Structure o3d_Mesh
*VT
InternalName.s
Filename.s
VertexCount.i
VertexFormat.i
Array Vertex.MeshVertex(1)
SubMeshCount.i
Array SubMesh.o3d_SubMesh(1)
PoseCount.i
Array Pose.o3d_Pose(1)
; ???
EndStructure
EndDeclareModule
Module o3d
EnableExplicit
#ID_OGRE3D_STREAM = $1000
#SIGNATURE_SKELETON = "[Serializer_v1.10]" ; most actual by ogre version 14.2
#SIGNATURE_MESHFILE = "[MeshSerializer_v1.100]" ; 1.100 is most actual (1.80 is previous version)
;{ From: OgreSkeletonFileFormat.h (includes directory)
; SKELETON_HEADER = 0x1000,
#SKELETON_HEADER = $1000
; // char* version : Version number check
; SKELETON_BLENDMODE = 0x1010, // optional
#SKELETON_BLENDMODE = $1010; // optional
; // unsigned short blendmode : SkeletonAnimationBlendMode
;
; SKELETON_BONE = 0x2000,
#SKELETON_BONE = $2000
; // Repeating section defining each bone in the system.
; // Bones are assigned indexes automatically based on their order of declaration
; // starting with 0.
;
; // char* name : name of the bone
; // unsigned short handle : handle of the bone, should be contiguous & start at 0
; // Vector3 position : position of this bone relative to parent
; // Quaternion orientation : orientation of this bone relative to parent
; // Vector3 scale : scale of this bone relative to parent
;
; SKELETON_BONE_PARENT = 0x3000,
#SKELETON_BONE_PARENT = $3000
; // Record of the parent of a single bone, used to build the node tree
; // Repeating section, listed in Bone Index order, one per Bone
;
; // unsigned short handle : child bone
; // unsigned short parentHandle : parent bone
;
; SKELETON_ANIMATION = 0x4000,
#SKELETON_ANIMATION = $4000
; // A single animation for this skeleton
;
; // char* name : Name of the animation
; // float length : Length of the animation in seconds
;
; SKELETON_ANIMATION_BASEINFO = 0x4010,
#SKELETON_ANIMATION_BASEINFO = $4010
; // [Optional] base keyframe information
; // char* baseAnimationName (blank for self)
; // float baseKeyFrameTime
;
; SKELETON_ANIMATION_TRACK = 0x4100,
#SKELETON_ANIMATION_TRACK = $4100
; // A single animation track (relates to a single bone)
; // Repeating section (within SKELETON_ANIMATION)
;
; // unsigned short boneIndex : Index of bone to apply to
;
; SKELETON_ANIMATION_TRACK_KEYFRAME = 0x4110,
#SKELETON_ANIMATION_TRACK_KEYFRAME = $4110
; // A single keyframe within the track
; // Repeating section
;
; // float time : The time position (seconds)
; // Quaternion rotate : Rotation to apply at this keyframe
; // Vector3 translate : Translation to apply at this keyframe
; // Vector3 scale : Scale to apply at this keyframe
; SKELETON_ANIMATION_LINK = 0x5000
#SKELETON_ANIMATION_LINK = $5000
; // Link to another skeleton, to re-use its animations
;
; // char* skeletonName : name of skeleton to get animations from
; // float scale : scale to apply to trans/scale keys
;}
Macro o3d_ClearMemfile() ; clean up
*o3d_tag = 0
*o3d_tag_next = 0
o3d_tag_size = 0
*o3d_base = 0
*o3d_limit = 0
o3d_file_type = 0
*o3d_file_sig = 0
o3d_tag_id = 0
*o3d_tag_name_start = 0
o3d_tag_name_length = 0
*o3d_reader = 0
EndMacro
Macro o3d_eof() ; *memory file eof
Bool(*o3d_reader >= *o3d_limit Or *o3d_tag >= *o3d_limit)
EndMacro
Macro o3d_readdata(_MEM_, _BYTES_)
CompilerIf Not Defined(*o3d_out, #PB_Variable)
Protected *o3d_out.o3d_Any
Protected *o3d_out_lim
CompilerEndIf
*o3d_out = _MEM_
*o3d_out_lim = _MEM_ + _BYTES_
While *o3d_out + 8 <= *o3d_out_lim And *o3d_reader + 8 <= *o3d_limit
*o3d_out\q = *o3d_reader\q
*o3d_out + 8 : *o3d_reader + 8
Wend
If *o3d_out + 4 <= *o3d_out_lim And *o3d_reader + 4 <= *o3d_limit
*o3d_out\l = *o3d_reader\l
*o3d_out + 4 : *o3d_reader + 4
EndIf
If *o3d_out + 2 <= *o3d_out_lim And *o3d_reader + 2 <= *o3d_limit
*o3d_out\w = *o3d_reader\w
*o3d_out + 2 : *o3d_reader + 2
EndIf
If *o3d_out < *o3d_out_lim And *o3d_reader < *o3d_limit
*o3d_out\b = *o3d_reader\b
*o3d_out + 1 : *o3d_reader + 1
EndIf
EndMacro
Macro o3d_readshort()
(*o3d_reader\u) : *o3d_reader + 2
EndMacro
Macro o3d_readlong()
(*o3d_reader\l) : *o3d_reader + 4
EndMacro
Macro o3d_readfloat()
*o3d_reader\f : *o3d_reader + 4
EndMacro
Macro o3d_readstring(_RESULT_VAR_NAME_)
*__ = *o3d_reader
___ = 0
While *o3d_reader < *o3d_limit And *o3d_reader\a <> #LF
*o3d_reader + 1
___ + 1
Wend
_RESULT_VAR_NAME_ = PeekS(*__, ___, #PB_Ascii)
*o3d_reader + Bool(*o3d_reader < *o3d_limit)
EndMacro
Macro o3d_FileType() ; 1 = mesh file , 2 = skeleton file
(o3d_file_type)
EndMacro
Macro o3d_InitMemFile(_MEMORY_, _SIZE_) ; Check memory for file signatures and init parser variables
CompilerIf Not Defined(o3d_tag, #PB_Variable)
Protected *o3d_tag.o3d_TagHeader
Protected *o3d_tag_next
Protected o3d_tag_size ; 32bit long
Protected *o3d_tag_data
Protected *o3d_base
Protected *o3d_limit
Protected o3d_file_type
Protected *o3d_file_sig
Protected o3d_tag_id
Protected *o3d_tag_name_start
Protected o3d_tag_name_length
Protected *o3d_reader.o3d_any
Protected *__.o3d_any, ___
CompilerEndIf
*o3d_reader = _MEMORY_
*o3d_base = _MEMORY_
*o3d_limit = _MEMORY_ + _SIZE_
*o3d_tag = _MEMORY_
*o3d_tag_next = 0
o3d_tag_size = 0
o3d_file_type = *o3d_reader\w
*o3d_reader = _MEMORY_
*o3d_file_sig = *o3d_reader + 2
If o3d_file_type <> $1000
; bad file type
o3d_ClearMemfile()
DebuggerWarning(#PB_Compiler_Procedure + " - *memory is not an ogre3d file or stream")
Else
;Debug "File signature: " + PeekS(*o3d_file_sig, -1, #PB_Ascii)
Select PeekS(*o3d_file_sig, -1, #PB_Ascii)
Case #SIGNATURE_MESHFILE + #LF$
o3d_file_type = 1
; Debug "this is an ogre3d mesh file"
Case #SIGNATURE_SKELETON + #LF$
o3d_file_type = 2
; Debug "this is an ogre3d skeleton file"
Default
DebuggerWarning(#PB_Compiler_Procedure + ": This is an unrecognized file signature!")
o3d_file_type = 0
EndSelect
EndIf
EndMacro
Macro o3d_StringByteLength(_RESULT_, boolAdvance = #False) ; get string length from reader position ; optionally skip string, set reader cursor behind terminator
*__ = *o3d_reader
___ = 1 ; pre-add null byte
While *__\a <> #LF ; while not null byte
*__ + 1 ; next byte
___ + 1 ; increase length
Wend
_RESULT_ = ___
*o3d_reader + (Bool(boolAdvance) * (1 + ___)) ; Move read cursor behind LF
EndMacro
Macro o3d_SkipString() ; read a string until LF + null (0A 00) terminator is encountered and put reader pointer behind it
While *o3d_reader\a <> #LF And *o3d_reader < *o3d_limit
*o3d_reader + 1
Wend
*o3d_reader + Bool(*o3d_reader < *o3d_limit) ; Skip LF
EndMacro
Macro o3d_PrepareNext() ; used by FirstTag() and NextTag(), *** also prepares the name string ***
If o3d_isSqueezyTag(o3d_tag_id)
*o3d_tag_name_start = *o3d_reader
o3d_StringByteLength(o3d_tag_name_length, #True)
Else
*o3d_tag_name_start = 0
o3d_tag_name_length = 0
EndIf
*o3d_tag_data = *o3d_reader
*o3d_tag_next = *o3d_tag + o3d_tag_size + o3d_tag_name_length
If o3d_IsNamed(o3d_tag_id)
*o3d_tag_name_start = *o3d_tag + 6
o3d_StringByteLength(o3d_tag_name_length, #True)
EndIf
EndMacro
Macro o3d_FirstTag() ; start parsing, first tag after file signature
*o3d_reader = 2 + *o3d_base
o3d_SkipString()
*o3d_tag = *o3d_reader
o3d_tag_id = *o3d_tag\type_id
o3d_tag_size = *o3d_tag\size
*o3d_reader = *o3d_tag + 6
o3d_PrepareNext()
EndMacro
Macro o3d_Loc() ; unused yet
(*o3d_reader - *o3d_base)
EndMacro
Macro o3d_Seek(offset, boolRelative = 0) ; unused yet
CompilerIf boolRelative
*o3d_reader + offset
CompilerElse
*o3d_reader = *base + offset
CompilerEndIf
EndMacro
Macro o3d_NextTag() ; InitMemFile() FirstTag() must be called first
*o3d_tag = *o3d_tag_next
o3d_tag_id = *o3d_tag\type_id
o3d_tag_size = *o3d_tag\size
*o3d_reader = *o3d_tag + 6
o3d_PrepareNext()
EndMacro
Macro o3d_isSqueezyTag(_ID_) ;
Bool((_ID_ = $2000 And o3d_file_type = 2))
EndMacro
Macro o3d_IsNamed(_ID_) ; incomplete tags that embed the name strings in their payload
Bool((_ID_ = $4000 And o3d_file_type = 2) Or (_ID_ = $9000 And o3d_file_type = 1))
EndMacro
Macro o3d_TagId()
(o3d_tag_id)
EndMacro
Macro o3d_TagSize()
(o3d_tag_size)
EndMacro
Macro o3d_TagName(_RESULT_) ;
If *o3d_tag_name_start
_RESULT_ = PeekS(*o3d_tag_name_start, o3d_tag_name_length - 1, #PB_Ascii)
Else
_RESULT_ = #Empty$
EndIf
EndMacro
Procedure o3d_CountTags(Tag_id.w, *Memory, Size, boolCheckContainers = 0) ; count the tags in an ogre file (incomplete needs containers support)
Protected count, name$
o3d_InitMemFile(*Memory, Size)
If o3d_FileType()
o3d_FirstTag()
While Not o3d_eof()
; o3d_TagName(name$)
; Debug Hex(o3d_TagID()) + " " + Str(o3d_TagSize()) + " " + name$
If o3d_TagID() = Tag_id
count + 1
EndIf
o3d_NextTag()
Wend
EndIf
ProcedureReturn count
EndProcedure
Macro o3d_CountBones(_memory_, _size_)
o3d_CountTags($2000, _memory_, _size_)
EndMacro
Procedure o3d_Bload(File$) ; Load a file into new allocated memory and return the new memory pointer
Protected fh = ReadFile( - 1, file$)
Protected size
Protected *result
If fh
size = Lof(fh)
If size < 6
*result = AllocateMemory(6) ; minimum tag size is 6 (int16=ID int32=tag length)
If size
ReadData(fh, *result, size)
EndIf
Else
*result = AllocateMemory(size, #PB_Memory_NoClear)
ReadData(fh, *result, size)
EndIf
CloseFile(fh)
Else
DebuggerWarning(#PB_Compiler_Procedure + ": Could not read file '" + file$ + "'")
EndIf
ProcedureReturn *result
EndProcedure
Procedure GetAnimationNamesFromMemory(*memory, size, Array result.s(1))
Protected c, i
o3d_InitMemFile(*memory, size)
If o3d_FileType() = 2
c = o3d_CountTags($4000, *memory, size)
Dim result(c)
o3d_FirstTag()
While Not o3d_eof()
If o3d_TagID() = $4000
o3d_TagName(result(i))
i + 1
EndIf
o3d_NextTag()
Wend
Else
DebuggerWarning(#PB_Compiler_Procedure + ": The *memory data needs to be an Ogre3D .skeleton file.")
EndIf
ProcedureReturn c
EndProcedure
Procedure GetBoneNamesFromMemory(*memory, size, Array Result.s(1))
Protected i, c
Protected *W_ID.WORD, *S
Protected *A.Ascii, len
*W_ID = *memory ; skeleton file from memory
If *W_ID\w & $FFFF <> #ID_OGRE3D_STREAM ; is serializer stream?
DebuggerWarning(#PB_Compiler_Procedure + ": *Memory is not an Ogre3D mesh or skeleton file header.")
ProcedureReturn 0
EndIf
If PeekS(*W_ID + 2, -1, #PB_Ascii) <> #SIGNATURE_SKELETON + #LF$
DebuggerWarning(#PB_Compiler_Procedure + ": *Memory needs to be an Ogre3D .skeleton file, unsupported serializer version '" + PeekS(*W_ID + 2, -1, #PB_Ascii) + "'. ")
ProcedureReturn 0
EndIf
; Count bone names
*W_ID = *memory + $15
While *W_ID\w = $2000 ; is bone?
*A = *W_ID + 6 ; set name start
c + 1 ; increase bone count
While *A\a <> #LF ; string ends with LF
*A + 1
Wend
*W_ID = *A + 31 ; next tag, skip null byte + bone data
Wend
; Dim result array
Dim result(c)
; Set bone names in array
*W_ID = *memory + $15
While *W_ID\w = $2000
*A = *W_ID + 6 ; set name start
len = 0 ; clear name length
*S = *A ; save name start
While *A\a <> #LF ; check for LF terminator
len + 1 ; inc length
*A + 1
Wend
result(i) = PeekS(*S, len, #PB_Ascii) ; set result
; *a+1 ; LF
; BoneNumber = *a\a : *A+1
; ParentBoneNumber = *a\a : *A+1
; flags (1) and/or floats (28 bytes)
i + 1 ; next bone name
*W_ID = *A + 31 ; next tag
Wend
ProcedureReturn c
EndProcedure
Procedure GetBoneNamesFromFile(File$, Array result.s(1))
Protected *skel = o3d_Bload(file$)
Protected c
If *skel
c = GetBoneNamesFromMemory(*skel, MemorySize(*skel), result())
FreeMemory(*skel)
EndIf
ProcedureReturn c
EndProcedure
Procedure GetAnimationNamesFromFile(File$, Array result.s(1))
Protected *skel = o3d_Bload(file$)
Protected c
If *skel
c = GetAnimationNamesFromMemory(*skel, MemorySize(*skel), result())
FreeMemory(*skel)
EndIf
ProcedureReturn c
EndProcedure
Procedure CountTagsInFile(File$, tag_id.w)
Protected *mem = o3d_Bload(file$)
Protected c
If *mem
c = o3d_CountTags(tag_id, *mem, MemorySize(*mem))
FreeMemory(*mem)
EndIf
ProcedureReturn c
EndProcedure
Procedure o3d_DeleteSkeleton(*This.o3d_Skeleton)
If *this
ClearStructure(*This, o3d_Skeleton)
FreeMemory(*This)
EndIf
EndProcedure
Procedure o3d_NewSkeleton()
Protected *Skeleton.o3d_Skeleton
*Skeleton = AllocateMemory(SizeOf(o3d_Skeleton))
InitializeStructure(*Skeleton, o3d_Skeleton)
ProcedureReturn *Skeleton
EndProcedure
Procedure ImportSkeletonFromMemory(*Memory, size, filename$ = #Empty$)
; Debug #PB_Compiler_Procedure
Protected *new.o3d_Skeleton
o3d_InitMemFile(*Memory, Size)
If o3d_FileType() <> 2
DebuggerWarning("*Memory is not an Ogre3D .skeleton file")
ProcedureReturn #Null
EndIf
*new = o3d_NewSkeleton()
*new\Filename = filename$
Protected bone_count, blendmode, animation_count, animation_link_count, bone_parent_count, temp
Protected temp_name$, child, parent, *sub_tag.o3d_TagHeader, *track_start, *track_limit, *keyframe.o3d_TagHeader, *keyframe_next
Protected *Bone.o3d_Bone, *Anim.o3d_Animation, *Track.o3d_AnimationTrack, *K.o3d_KeyFrame, *Link.o3d_AnimationLink, error_count, track_count, keyframe_count, base_info_count
Macro debugPos(_ident_, _str_, _ptr_ = *o3d_tag)
Debug LSet("+", _ident_, "+") + " " + _str_ + " @" + Hex(_ptr_ - *o3d_base) + " (" + Str(_ptr_ - *o3d_base) + ")"
EndMacro
Macro debugPosTag(_ident_, _str_, _ptr_ = *o3d_tag, _T_ = *o3d_tag )
Debug LSet("+", _ident_, "+") + " " + _str_ + " @" + Hex(_ptr_ - *o3d_base) + " (" + Str(_ptr_ - *o3d_base) + ") tag_id: $" + Hex(_T_\type_id) + " size: " + Str(_T_\size)
EndMacro
o3d_FirstTag()
While Not o3d_eof()
Select o3d_TagID()
Case #SKELETON_BLENDMODE
; todo: read blending mode
; debugPosTag(1, "blendmode")
blendmode + 1
Case #SKELETON_BONE
; read bone
o3d_TagName(temp_name$)
o3d_SkipString()
; debugPosTag(1, "bone " + temp_name$)
*Bone = *new\Bone(temp_name$)
*Bone\Name = temp_name$
*Bone\BoneId = o3d_readshort() ; it's a handle, and it's rarely perfectly sorted
*Bone\ParentBoneId = -1
*Bone\scale\x = 1
*Bone\scale\y = 1
*Bone\scale\z = 1
o3d_readdata(@*Bone\pos, o3d_TagSize() - 8) ; floats should appear in structure order :)
bone_count + 1
Case #SKELETON_BONE_PARENT
; read bone parent pair
; debugPosTag(1, "bone parent")
bone_parent_count + 1
child = o3d_readshort()
parent = o3d_readshort()
; set relationship
ForEach *new\Bone()
*Bone = *new\Bone()
If *Bone\BoneId = child ; set parent id aka handle
*Bone\ParentBoneId = parent
ElseIf *Bone\BoneId = parent ; add child to list (there might be more than 1)
AddElement( *Bone\ChildNode() )
*Bone\ChildNode() = child
EndIf
Next
Case #SKELETON_ANIMATION
; read animation
*o3d_reader = *o3d_tag_data
animation_count + 1
o3d_readstring(temp_name$)
; debugPosTag(1, "animation " + temp_name$)
;o3d_SkipString()
*Anim = *new\Animation(temp_name$)
*Anim\Name = temp_name$
*Anim\Length = o3d_readfloat()
; Debug "anim length: " + *Anim\Length
While *o3d_reader < *o3d_tag_next
*sub_tag = *o3d_reader
*o3d_reader + 6
Select *sub_tag\type_id
Case #SKELETON_ANIMATION_BASEINFO
; read optional base info
o3d_readstring(*Anim\BaseAnimation)
*Anim\BaseKeyFrame = o3d_readfloat()
debugPosTag(2, "base info", *sub_tag)
base_info_count + 1
Case #SKELETON_ANIMATION_TRACK
; read track
track_count + 1
*Track = AddElement(*Anim\AnimationTrack())
*Track\BoneId = o3d_readshort()
; debugPosTag(2, "animation track #" + Str(track_count) + " [" + Str(*Track\BoneId) + "] {" + *Anim\Name + "}", *sub_tag, *sub_tag)
*track_start = *o3d_reader
*track_limit = *sub_tag + *sub_tag\size
*keyframe = *o3d_reader
While *keyframe\type_id = #SKELETON_ANIMATION_TRACK_KEYFRAME
; read all track key frames
keyframe_count + 1
; debugPosTag(3, "add key frame #" + Str(keyframe_count) + " [" + Str(*Track\BoneId) + "] {" + *Anim\Name + "}", *keyframe, *keyframe)
*o3d_reader = *keyframe + 6
*keyframe_next = *keyframe + *keyframe\size
AddElement(*Track\KeyFrame())
*K = @*Track\KeyFrame()
*K\scale\x = 1
*K\scale\y = 1
*K\scale\z = 1
temp = *keyframe\size - 6
If temp > SizeOf(o3d_KeyFrame)
temp = SizeOf(o3d_KeyFrame)
EndIf
o3d_readdata(*K, temp) ; should match PB structure layout
*keyframe = *keyframe_next
Wend
Default
error_count + 1
Debug " ### Loc: " + Hex(o3d_Loc()) + " (" + Str(o3d_Loc()) + ")"
Debug " ### unknown type: " + Hex(*sub_tag\type_id & $FFFF)
Break
EndSelect
*sub_tag = *sub_tag + *sub_tag\size
Wend
Case #SKELETON_ANIMATION_LINK
; untested yet, no fitting data yet
animation_link_count + 1
temp_name$ = ""
o3d_readstring(temp_name$)
; debugPosTag(1, "animation link " + temp_name$)
*Link = *new\AnimationLink(temp_name$)
*Link\SkeletonName = temp_name$
*Link\Scale = o3d_readfloat()
Default
Debug "## ERROR UNEXPECTED TAG ##"
Debug "Loc: " + Hex(o3d_Loc()) + " (" + Str(o3d_Loc()) + ")"
Debug "Tag ID : $" + Hex(o3d_TagId())
Debug "Size : " + Str(o3d_TagSize())
error_count + 1
EndSelect
o3d_NextTag()
Wend
; Debug "parse results: "
; Debug " "
; Debug "Error count = " + Str(error_count)
; Debug " "
; Debug "Blendmode count =" + Str(blendmode)
; Debug "Bone count = " + Str(bone_count)
; Debug "Bone-parent count = " + Str(bone_parent_count)
; Debug "Animation count = " + Str(animation_count)
; Debug "Animation link count = " + Str(animation_link_count)
; Debug "Animation track count = " + Str(track_count)
; Debug "Animation key frame count = " + Str(keyframe_count)
; Debug "Animation base info count = " + Str(base_info_count)
;
;
ProcedureReturn *new
EndProcedure
Procedure ImportSkeletonFromFile(File$)
Protected *Skel = o3d_Bload(File$)
Protected *result
If *Skel
*result = ImportSkeletonFromMemory(*Skel, MemorySize(*Skel), File$)
FreeMemory(*skel)
EndIf
ProcedureReturn *result
EndProcedure
EndModule
; --------------------------------------------------------------------------------------------------------------------------------------------------------
; --------------------------------------------------------------------------------------------------------------------------------------------------------
; --------------------------------------------------------------------------------------------------------------------------------------------------------
;
; TESTING
;
; ------- -------------------------------------------------------------------------------------------------------------------------------------------------
; --------------------------------------------------------------------------------------------------------------------------------------------------------
; --------------------------------------------------------------------------------------------------------------------------------------------------------
;
UseModule o3d
Procedure$ GetPos(*Float.float, amount = 3) ; Helper function to print float vectors
Protected i, result$
While i < amount
result$ + " " + StrF(*Float\f)
*Float + 4
i + 1
Wend
ProcedureReturn LTrim(result$)
EndProcedure
; Extract bone & pose names (quick and dirty way)
; select skeleton file
Define file$ = OpenFileRequester("Select file", #PB_Compiler_Home + "examples" + #PS$ + "3D" + #PS$ + "data" + #PS$ + "Models" + #PS$ + "ninja.skeleton", "ogre3d files|*.mesh;*.skeleton|All|*.*", 0)
Define c, i, t0, t1, t2, t3, t4
Dim result$(0)
t0 = ElapsedMilliseconds()
; Poses
c = GetAnimationNamesFromFile(file$, result$())
t1 = ElapsedMilliseconds() - t0
Debug "Pose names (" + Str(c) + ") in '" + GetFilePart(file$) + "'"
While i < c
Debug RSet(Str(i), 6) + " " + result$(i)
i + 1
Wend
; Bones
t0 = ElapsedMilliseconds()
c = GetBoneNamesFromFile(file$, result$())
t2 = ElapsedMilliseconds() - t0
Debug "Bone names (" + Str(c) + ") in '" + GetFilePart(file$) + "'"
i = 0
While i < c
Debug RSet(Str(i), 6) + " " + result$(i)
i + 1
Wend
; --------------------------------
; Import the entire skeleton file
; --------------------------------
Debug "..... Skeleton import test ...."
t0 = ElapsedMilliseconds()
Define *Skel.o3d_Skeleton = ImportSkeletonFromFile(file$)
t3 = ElapsedMilliseconds() - t0
If *Skel
Debug "*Skel!"
t0 = ElapsedMilliseconds()
Debug "Original Filename (not imported)= " + *Skel\Filename
Debug "Name=" + *Skel\Name ; this is perhaps useless since it can't be imported or exported
Debug "Bones=" + MapSize(*Skel\Bone())
Debug "Animations=" + MapSize(*Skel\Animation()) ; The animations
Debug "Links=" + MapSize(*Skel\AnimationLink()) ; links to inherited animations
Debug LSet("[ Bones ]", 100, "_")
Define ln$, *Bone.o3d_Bone, *Anim.o3d_Animation, *Track.o3d_AnimationTrack, *KeyFrame.o3d_KeyFrame
ForEach *Skel\bone()
*Bone = *Skel\Bone()
Debug LSet("", 50, "-")
Debug "Bone Name: " + *Bone\Name
Debug "Bone Id/Handle: " + *Bone\BoneId
Debug "Parent Bone Id: " + *Bone\ParentBoneId
Debug "Orientation x y z w: " + GetPos(*Bone\ori)
Debug "Position x y z: " + GetPos(*Bone\pos)
Debug "Bone scale x y z: " + GetPos(*Bone\scale)
ln$ = ""
ForEach *Bone\ChildNode()
ln$ + Str(*Bone\ChildNode()) + " "
Next
Debug "Child nodes (" + Str(ListSize(*Bone\ChildNode())) + "): " + ln$
Next
Debug LSet("[ Animations ]", 100, "_")
ForEach *Skel\Animation()
*Anim = *Skel\Animation()
Debug LSet("", 50, "-")
Debug "Animation Name: " + *Anim\Name
Debug "Animation Length: " + StrF(*Anim\Length)
Debug "Base Animation: " + *Anim\BaseAnimation
Debug "Base key frame: " + StrF(*Anim\BaseKeyFrame)
Debug "Tracks: " + Str(ListSize(*Anim\AnimationTrack()))
Debug " "
Debug "Animation Track Overview:"
Debug LSet("", 50, "-")
ForEach *Anim\AnimationTrack()
*Track = *Anim\AnimationTrack()
Debug "Track BoneId: " + Str(*Track\BoneId) + " key frames: " + Str(ListSize(*Track\KeyFrame()))
Next
Debug " "
Debug "Animation Key Frames"
Debug LSet("", 50, "-")
ForEach *Anim\AnimationTrack()
*Track = *Anim\AnimationTrack()
Debug "{" + *Anim\Name + "} BoneId [" + Str(*Track\BoneId) + "] track"
ForEach *Track\KeyFrame()
*KeyFrame = *Track\KeyFrame()
Debug " " + Str(1 + ListIndex(*Track\KeyFrame())) + ". Keyframe time=" + StrF(*KeyFrame\time) + " ori=" + GetPos(*KeyFrame\ori, 4) + " translation=" + GetPos(*KeyFrame\translation) + " scale=" + GetPos(*KeyFrame\scale)
Next
Debug " "
Next
Next
Debug " "
Debug LSet("[ Animation Links ]", 100, "_")
Debug " "
ForEach *Skel\AnimationLink()
Debug "SkeletonName=" + *Skel\AnimationLink()\SkeletonName + " Scale=" + StrF(*Skel\AnimationLink()\Scale)
Next
If MapSize(*Skel\AnimationLink()) = 0
Debug " no animation links"
EndIf
Debug " "
t4 = ElapsedMilliseconds() - t0
Debug LSet("[ Chronometers & Statistics ]", 100, "_")
Debug " "
Debug "Skeleton content"
Debug "Bone count: "+FormatNumber(MapSize(*Skel\Bone()),0)
Debug "Animation count: "+FormatNumber(MapSize(*Skel\Animation()),0)
Define trackcount, keyframecount
ForEach *Skel\Animation()
trackcount = trackcount + ListSize(*Skel\Animation()\AnimationTrack())
ForEach *Skel\Animation()\AnimationTrack()
keyframecount = keyframecount + ListSize(*Skel\Animation()\AnimationTrack()\KeyFrame())
Next
Next
Debug "Track count: "+FormatNumber(trackcount,0)
Debug "Keyframes total: "+FormatNumber(keyframecount,0)
Debug "Animation links: "+FormatNumber(MapSize(*Skel\AnimationLink()),0)
Debug " "
Debug "Parsing chronometers"
Debug "GetAnimationNamesFromFile() = " + FormatNumber(t1, 0) + " ms"
Debug "GetBoneNamesFromFile() = " + FormatNumber(t2, 0) + " ms"
Debug "ImportSkeletonFromFile() = " + FormatNumber(t3, 0) + " ms"
Debug "Debug detailed info = " + FormatNumber(t4, 0) + " ms"
EndIf
Debug "~~~~~~~~ FINISHED ~~~~~~~~~~"