Skinning Mesh Format required!

Previous topic - Next topic

Viper

I use tools like fragmotion, Milkshape3d, TrueSpace .... to make and edit 3d model & animations.
And I think b3d format is very inclusive format and  well supported by cheap and good utilities.
So it's a good format for 3d games.(Though it's price goes up as it gets popularity...)
I converted my b3d to md2 and then to ddd. My 1.4MB b3d file become 13MB! so huge!~
So I suggest that GLbasic supports a skinning Mesh format and hope you make a exporter for fragmotion or for
other cheap and good tool!
I think Unwrap3d may be a good tool too.

MikeHart

Is the ddd file format described somewhere?

Viper

#2
No, I didn't see that but I think ddd doesn't support skinning mesh system.
I can't understand why we should know about ddd's structure.

I tested convert3d with my skinning model and non-skinning hierarchicaly animated 3ds model( only with transform by mesh group).
With Fragmotion, my model can be exported to both md2 and xof file.
md2 have geo-sprite data. xof contains Skinning Mesh data in this case.
3ds is from TheGameCreators. It has animation with mesh groups transform information.

With convert3d;
Test 1: md2 -> ddd :ok.  ddd has animation.
Test 2: xof -> ddd:failed. convert3d refused to convert xof to ddd.
Test 3: 3ds ->ddd ok:  ddd has animation.

But popular animation system on today is skinning mesh!
So I want Skinning mesh support.



Kitty Hello

Yes, that's an issue on my TODO list. Next thing to do, propably.
I think about a native B3D loader, since this format seems to be quite reasonable from its specs. The format internally is a total mess, though, that's why I haven't done this earlier.

Schranz0r

#4
Quote from: Kitty Hello on 2009-Aug-11
Yes, that's an issue on my TODO list. Next thing to do, propably.
I think about a native B3D loader, since this format seems to be quite reasonable from its specs. The format internally is a total mess, though, that's why I haven't done this earlier.

Yepp, B3D-Format have a realy smal filesize, and have bones!
I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

doimus

Is there a reason for not implementing COLLADA for example? It's industry standard, open-source file format.
For those who dislike having their resources in open format file, it can be encrypted, or whatever.

Blender is about to get full Collada suppor, next-gen Lightwave is transferring from its proprietary format to Collada as its main file format, 3dsMax and Maya already support it, most other game engines on the market also support it, etc.

B3D is just another closed format which will reach its limit eventually, probably already did. And support for B3D on non-Windows platforms is doubtful, to say the least.

Viper

B3D is not so closed format. B3D's spec is officially opened at  http://www.blitzbasic.com/sdkspecs/sdkspecs/b3dfile_specs.txt
and extended format is allowed. As a result extended format exist here http://www.onigirl.com/pipeline/
B3D is supported by irricht engine, gile, pace maker, ultimated unwrap3d, fragmotion,  lightray3d, milkshape3d, etc.
It was designed originally for blitz3d compiler(DX7). I think it make sense for handheld devices.

Kuron

I would love to see the B3D format supported.  I have no artistic talent, and the models I bought a few years back are all in B3D format.  I have no idea in how to get them converted to something GLBasic can use.

QuoteB3D is not so closed format. B3D's spec is officially opened at  http://www.blitzbasic.com/sdkspecs/sdkspecs/b3dfile_specs.txt

That was for the original release.  The B3D format "may" have changed a little bit since then.

Kitty Hello

Right. The B3D format is quite nice, since many programs support it. but the documentation is horrible to zero.

FutureCow


Kitty Hello

Very nice. What is this:
float rotation[4]

I don't understand what these 4 floats are.

FutureCow

#11
From that page (and I don't claim to know what this means, I just know how to use "find" and "copy/paste"  :P )

QuoteQuaternions are used to specify general orientations. The first value is the quaternion 'w' value, the next 3 are the quaternion 'vector'. A 'null' rotation should be specified as 1,0,0,0.
From later in the file, the rotation values seem to be specified in radians.

Does that help?

From a quick search of the blitz forums... (again, might not be exactly what you're after but it seems relevant), here's two b3d load functions.
(Website http://www.blitzbasic.com/Community/posts.php?topic=42211#474790)
Code (glbasic) Select
; ID: 524
; Author: halo
; Date: 2002-12-07 15:52:04
; Title: LoadB3D()  (updated)
; Description: Load a .b3d file, with access to all the texture data, and some other options.

;LoadB3D( file$, [parent], [texturedir$], [flags] )

;file$ - file to load
;parent (optional) - mesh parent.
;texturedir$ (optional) - directory to load textures from.
;flags (optional) - none yet.

;No support for animations or bones.
;Hierarchies now supported.

Function LoadB3D(file$,parent,texturedir$="",flags=0)
f=ReadFile(file)
If Not f Return
tag$=ReadStringN(f,3)
chunksize=ReadInt(f)
endchunkpos=chunksize+FilePos(f)
texbank=CreateBank()
brushbank=CreateBank()
vertbank=CreateBank()
trisbank=CreateBank()
If tag="BB3D" Return loadchunk("BB3D",f,endchunkpos,parent,texbank,brushbank,vertbank,trisbank,texturedir$)
For n=0 To (BankSize(texbank)-1)/4
texture=PeekInt(texbank,n*4)
If texture FreeTexture texture
Next
For n=0 To (BankSize(brushbank)-1)/4
brush=PeekInt(brushbank,n*4)
If brush FreeBrush brush
Next
FreeBank texbank
FreeBank brushbank
FreeBank vertbank
FreeBank trisbank
End Function

Function loadchunk(tag$,f,endchunkpos,parent=0,texbank,brushbank,vertbank,trisbank,texturedir$)
DebugLog tag
Select tag
Case "BB3D"
version=ReadInt(f)
DebugLog " version: "+version
Case "TEXS"
name$=ReadStringN(f)
name=findfile(name,texturedir$)
flags=ReadInt(f)
blend=ReadInt(f)
x#=ReadFloat(f)
y#=ReadFloat(f)
scalex#=ReadFloat(f)
scaley#=ReadFloat(f)
rotation#=ReadFloat(f)
texture=LoadTexture(name,flags)
If texture
If 65536 And flags TextureCoords texture,1
TextureBlend texture,blend
PositionTexture texture,x,y
ScaleTexture texture,scalex,scaley
RotateTexture texture,rotation
EndIf
ResizeBank texbank,BankSize(texbank)+4
PokeInt(texbank,BankSize(texbank)-4,texture)
DebugLog " name: "+name
DebugLog " flags: "+flags
DebugLog " blend: "+blend
DebugLog " position: "+x+", "+y
DebugLog " scale: "+scalex+", "+scaley
DebugLog " rotation: "+rotation
Case "BRUS"
textures=ReadInt(f)
name$=ReadStringN(f)
red=ReadFloat(f)*255
green=ReadFloat(f)*255
blue=ReadFloat(f)*255
alpha#=ReadFloat(f)
shininess#=ReadFloat(f)
blend=ReadInt(f)
fx=ReadInt(f)
brush=CreateBrush()
BrushColor brush,red,green,blue
BrushAlpha brush,alpha
BrushShininess brush,shininess
BrushBlend brush,blend
BrushFX brush,fx
For n=1 To textures
textureindex=ReadInt(f)
If textureindex*4+4<=BankSize(texbank)
texture=PeekInt(texbank,textureindex*4)
Else
RuntimeError "Texture does not exist."
EndIf
If texture
BrushTexture brush,texture,0,n-1
EndIf
Next
ResizeBank brushbank,BankSize(brushbank)+4
PokeInt brushbank,BankSize(brushbank)-4,brush
;DebugLog " name: "+name
;DebugLog " color: "+red+", "+green+", "+blue
;DebugLog " alpha: "+alpha
;DebugLog " shininess: "+shininess
;DebugLog " blend: "+blend
;DebugLog " fx: "+fx
Case "NODE"
lastmeshendchunkpos=endchunkpos
name$=ReadStringN(f)
DebugLog name
x#=ReadFloat(f)
y#=ReadFloat(f)
z#=ReadFloat(f)
width#=ReadFloat(f)
height#=ReadFloat(f)
depth#=ReadFloat(f)
w#=ReadFloat(f)
pitch#=ReadFloat(f)
yaw#=ReadFloat(f)
roll#=ReadFloat(f)
;DebugLog " name: "+name
;DebugLog " position: "+x+", "+y+", "+z
;DebugLog " scale: "+width+", "+height+", "+depth
;DebugLog " rotation: "+pitch+", "+yaw+", "+roll+", "+w
;If FilePos(f)=endchunkpos
mesh=CreateMesh(parent);this is more compatible than inserting a pivot.
parent=mesh
NameEntity mesh,name
PositionEntity mesh,x,y,z
ScaleEntity mesh,width,height,depth
RotateEntity mesh,pitch,yaw,roll;how do b3d rotations work?
; EndIf
Case "MESH"
brush=ReadInt(f)
;mesh=CreateMesh(parent)
;parent=mesh
;NameEntity mesh,name
;PositionEntity mesh,x#,y#,z#
;ScaleEntity mesh,width,height,depth
;RotateEntity mesh,pitch,yaw,roll;how do b3d rotations work?
Case "VRTS"
flags=ReadInt(f)
texcoordsets=ReadInt(f)
texcoords=ReadInt(f)
;DebugLog " flags: "+flags
;DebugLog " texturecoord sets: "+texcoordsets
;DebugLog " texturecoords:"+texcoords
ResizeBank vertbank,0
While FilePos(f)<endchunkpos
x#=ReadFloat(f)
y#=ReadFloat(f)
z#=ReadFloat(f)
;TFormPoint x,y,z,0,parent
;x=TFormedX()
;y=TFormedY()
;z=TFormedZ()
If texcoords>0
u0#=ReadFloat(f)
If texcoords>1
v0#=ReadFloat(f)
If texcoords>2
w0#=ReadFloat(f)
EndIf
EndIf
EndIf
If texcoordsets>1
If texcoords>0
u1#=ReadFloat(f)
If texcoords>1
v1#=ReadFloat(f)
If texcoords>2
w1#=ReadFloat(f)
EndIf
EndIf
EndIf
EndIf
;DebugLog " position: "+x+", "+y+", "+z
;DebugLog " texturecoords:"+u0+", "+v0+", "+w0
;DebugLog " texturecoords:"+u1+", "+v1+", "+w1
;DebugLog ""
ResizeBank vertbank,BankSize(vertbank)+4*10
PokeFloat vertbank,BankSize(vertbank)-4*10,x
PokeFloat vertbank,BankSize(vertbank)-4*9,y
PokeFloat vertbank,BankSize(vertbank)-4*8,z
PokeFloat vertbank,BankSize(vertbank)-4*7,u0
PokeFloat vertbank,BankSize(vertbank)-4*6,v0
PokeFloat vertbank,BankSize(vertbank)-4*5,w0
PokeFloat vertbank,BankSize(vertbank)-4*4,u1
PokeFloat vertbank,BankSize(vertbank)-4*3,v1
PokeFloat vertbank,BankSize(vertbank)-4*2,w1
PokeInt vertbank,BankSize(vertbank)-4*1,0
Wend
Case "TRIS"
brush=ReadInt(f)
;DebugLog " brush: "+brush
ResizeBank trisbank,0
For n=1 To (BankSize(vertbank))/(4*10)
PokeInt vertbank,36+(n-1)*(4*10),0
Next
While FilePos(f)<endchunkpos
a=ReadInt(f)
b=ReadInt(f)
c=ReadInt(f)
If a*(4*10)<BankSize(vertbank) PokeByte vertbank,36+a*(4*10),1 Else RuntimeError "Vertex does not exist."
If b*(4*10)<BankSize(vertbank) PokeByte vertbank,36+b*(4*10),1 Else RuntimeError "Vertex does not exist."
If c*(4*10)<BankSize(vertbank) PokeByte vertbank,36+c*(4*10),1 Else RuntimeError "Vertex does not exist."
;DebugLog " vertices: "+a+", "+b+", "+c
ResizeBank trisbank,BankSize(trisbank)+12
PokeInt trisbank,BankSize(trisbank)-12,a
PokeInt trisbank,BankSize(trisbank)-8,b
PokeInt trisbank,BankSize(trisbank)-4,c
triscount=triscount+1
Wend
If triscount
surf=CreateSurface(parent)
If brush>-1
If brush*4+4<=BankSize(brushbank)
PaintSurface surf,PeekInt(brushbank,brush*4)
Else
RuntimeError "Brush does not exist."
EndIf
EndIf
EndIf
vertcount=0
For n=1 To (BankSize(vertbank))/(4*10)
If PeekInt(vertbank,36+(n-1)*(4*10))
x#=PeekFloat(vertbank,(n-1)*(4*10))
y#=PeekFloat(vertbank,4+(n-1)*(4*10))
z#=PeekFloat(vertbank,8+(n-1)*(4*10))
u0#=PeekFloat(vertbank,12+(n-1)*(4*10))
v0#=PeekFloat(vertbank,16+(n-1)*(4*10))
w0#=PeekFloat(vertbank,20+(n-1)*(4*10))
u1#=PeekFloat(vertbank,24+(n-1)*(4*10))
v1#=PeekFloat(vertbank,28+(n-1)*(4*10))
w1#=PeekFloat(vertbank,32+(n-1)*(4*10))
AddVertex surf,x,y,z,u0,v0,w0
VertexTexCoords surf,CountVertices(surf)-1,u1,v1,w1,1
vertcount=vertcount+1
PokeInt vertbank,36+(n-1)*(4*10),vertcount
EndIf
Next
For n=1 To (BankSize(trisbank))/12
a=PeekInt(trisbank,(n-1)*12)
b=PeekInt(trisbank,4+(n-1)*12)
c=PeekInt(trisbank,8+(n-1)*12)
For i=1 To (BankSize(vertbank))/(4*10)
If i-1=a a=PeekInt(vertbank,36+(i-1)*(4*10))-1
If i-1=b b=PeekInt(vertbank,36+(i-1)*(4*10))-1
If i-1=c c=PeekInt(vertbank,36+(i-1)*(4*10))-1
Next
AddTriangle surf,a,b,c
Next
UpdateNormals parent
Default
SeekFile f,endchunkpos
End Select
While FilePos(f)<endchunkpos
tag$=ReadStringN(f,3)
chunksize=ReadInt(f)
childendchunkpos=chunksize+FilePos(f)
m=loadchunk(tag,f,childendchunkpos,parent,texbank,brushbank,vertbank,trisbank,texturedir$)
If Not parent parent=m
Wend
Return parent
End Function

Function ReadStringN$(f,maxlength=0)
Repeat
ch=ReadByte(f)
If ch=0 Return t$
If maxlength
If Len(t$)=maxlength Return t$+Chr(ch)
EndIf
t$=t$+Chr$(ch)
Forever
End Function

Function countfiles(dirname$)
dir=ReadDir(dirname)
If Not dir RuntimeError dirname
n=-1
Repeat
n=n+1
Until NextFile(dir)=""
CloseDir dir
Return n
End Function

Function findfile$(file$,root$)
If FileType(root+file)=1 Return root+file
d=ReadDir(root)
For n=1 To countfiles(root)
tfile$=NextFile(d)
If FileType(root+tfile)=2
If tfile<>"." And tfile<>".."
test$=findfile(file,root+tfile+"\")
If test<>"" Return test
EndIf
EndIf
Next
CloseDir d
End Function


and

Code (glbasic) Select
; ID: 866
; Author: jfk
; Date: 2003-12-24 01:23:50
; Title: SaveB3D
; Description: Save multisurface textured Mesh as .B3D

Graphics3D 640,480,32,2
SetBuffer BackBuffer()

meshname$="testsurf.3ds" ; Mesh to save... (one that is using textures)

; BTW: you should run this from inside the folder where the Mesh resides, unless you edit the
; Part labeled by "<<<<<<<<<<<<"

Include "b3dfile.bb"
; this file can be found here: <a href="http://www.blitzbasic.com/sdkspecs/sdkspecs/b3dfile_utils.zip" target="_blank">http://www.blitzbasic.com/sdkspecs/sdkspecs/b3dfile_utils.zip</a>
cam=CreateCamera()
TranslateEntity cam,0,0,-10
mesh=LoadMesh(meshname$)
Global c_surfs=CountSurfaces(mesh)

Print "Mesh "+meshname$+" has "+ c_surfs+" Surfaces, using the following textures:"

Dim c_surf(c_surfs)
Dim c_brush(c_surfs)
Dim c_tex(c_surfs)
Dim c_tex_name$(c_surfs)

; track down used textures (thanks Mark!)
For i=1 To c_surfs
c_surf(i)= GetSurface(mesh,i)
c_brush(i)=GetSurfaceBrush( c_surf(i) )
c_tex(i)=GetBrushTexture( c_brush(i) )
c_tex_name$(i)=Lower$(TextureName$( c_tex(i))) ; Full (!) Texture Path
curdir$=Lower$(CurrentDir$())
c_tex_name$(i)= Replace$(c_tex_name$(i),curdir$,"") ;<<<<<<<<<<<<<<<<<<<
Print c_tex_name$(i)
If c_tex_name$(i)="" Then Print "Error: Surface No."+i+" has no Texture"
If FileType(c_tex_name$(i))<>1 Then Print "Warning: Surface No."+i+" uses nonexistant Texture ("+c_tex_name$(i)+")."
Next

Print "Press any key to save this Mesh as TEMP.B3D"


WaitKey()

; end

WriteBB3D( "temp.b3d",mesh )

For i=1 To c_surfs
FreeBrush c_brush(i); release memory
FreeTexture c_tex(i)
Next


; test if it worked...
FreeEntity mesh
mesh2=LoadMesh("temp.b3d")
While Not KeyDown(1)
TurnEntity mesh2,1,2,3
RenderWorld()
Text 0,0,"TEMP.B3D"
Flip
Wend
End


Function WriteBB3D( f_name$,mesh )

file=WriteFile( f_name$ )

b3dSetFile( file )

b3dBeginChunk( "BB3D" )
b3dWriteInt( 1 ) ;version

b3dBeginChunk( "TEXS" ) ; list all textures used by the mesh
For i=1 To c_surfs
b3dWriteString( c_tex_name$(i) ) ;texture file
b3dWriteInt( 1 ) ;flags
b3dWriteInt( 2 ) ;blend
b3dWriteFloat( 0 ) ;x in tex 0 (hu?)
b3dWriteFloat( 0 ) ;y in tex 0
b3dWriteFloat( 1 ) ;x scale 1
b3dWriteFloat( 1 ) ;y scale 1
b3dWriteFloat( 0 ) ;rotation 0

Next
b3dEndChunk() ;end of TEXS chunk


For i=1 To c_surfs
b3dBeginChunk( "BRUS" ) ; describe all brushes used by the mesh

b3dWriteInt( 1 ) ;number of textures per brush ; (eg 2 with lightmap)
b3dWriteString( "brush"+(i-1) ) ;brushname
b3dWriteFloat( 1 ) ;red
b3dWriteFloat( 1 ) ;green
b3dWriteFloat( 1 ) ;blue
b3dWriteFloat( 1 ) ;alpha
b3dWriteFloat( 0 ) ;shininess
b3dWriteInt( 1 ) ;blendmode
b3dWriteInt( 0 ) ;FX
b3dWriteInt( i-1 ) ;used texture index
; b3dWriteInt( ? ) ;additional texture index (eg lightmap), but here we only use 1 (see above)

b3dEndChunk() ;end of BRUS chunk
Next

b3dBeginChunk( "NODE" )
b3dWriteString( "entity_name_here!" )
b3dWriteFloat( 0 ) ;x_pos
b3dWriteFloat( 0 ) ;y_pos
b3dWriteFloat( 0 ) ;z_pos
b3dWriteFloat( 1 ) ;x_scale
b3dWriteFloat( 1 ) ;y_scale
b3dWriteFloat( 1 ) ;z_scale
b3dWriteFloat( 1 ) ;rot_w
b3dWriteFloat( 0 ) ;rot_x
b3dWriteFloat( 0 ) ;rot_y
b3dWriteFloat( 0 ) ;rot_z
WriteMESH( mesh )
b3dEndChunk() ;end of NODE chunk

b3dEndChunk() ;end of BB3D chunk

CloseFile file
End Function

Function WriteMESH( mesh )

n_surfs=CountSurfaces( mesh )

b3dBeginChunk( "MESH" )
b3dWriteInt( -1 ) ;no 'entity' brush -1

b3dBeginChunk( "VRTS" )
b3dWriteInt( 0 ) ;flags - 0=no normal/color
b3dWriteInt( 1 ) ;number of tex_coord sets (eg: 2 with lightmap)
b3dWriteInt( 2 ) ;coords per set (u,v,w?) 2 with uv, 3 with uvw

For k=1 To n_surfs
surf=GetSurface( mesh,k )
n_verts=CountVertices( surf )-1

For j=0 To n_verts
b3dWriteFloat( VertexX( surf,j ) )
b3dWriteFloat( VertexY( surf,j ) )
b3dWriteFloat( VertexZ( surf,j ) )
b3dWriteFloat( VertexU#( surf,j,0 ) )
b3dWriteFloat( VertexV#( surf,j,0 ) )
; b3dWriteFloat( VertexW#( surf,j,0 ) )
;; b3dWriteFloat( VertexU#( surf,j,1 ) ) ; lightmap uv
;; b3dWriteFloat( VertexV#( surf,j,1 ) ) ; lightmap uv
; b3dWriteFloat( VertexW#( surf,j,1 ) )
Next
Next
b3dEndChunk() ;end of VRTS chunk

first_vert=0
For k=1 To n_surfs
surf=GetSurface( mesh,k )
n_tris=CountTriangles( surf )-1

b3dBeginChunk( "TRIS" )
b3dWriteInt( k-1 ) ;brush for these triangles (surf -1 !!!)

For j=0 To n_tris
b3dWriteInt( first_vert+TriangleVertex( surf,j,0 ) )
b3dWriteInt( first_vert+TriangleVertex( surf,j,1 ) )
b3dWriteInt( first_vert+TriangleVertex( surf,j,2 ) )
Next

b3dEndChunk() ;end of TRIS chunk

first_vert=first_vert+CountVertices( surf )

Next

b3dEndChunk() ;end of MESH chunk

End Function


;-------------------------------------------------------------------------------------------------


From that code it appears to be rotate w,x,y,z - though what the heck the w dimension is I don't know... If I find anything that might explain it I'll post it here.

FutureCow

A bit more digging seems to have gotten an answer - Kitty Hello, let me know if this isn't sufficient information.

http://www.blitzbasic.com/Community/posts.php?topic=62584#699300
Quotewhat is the fourth rotation for the bones ?

The rotation has 4 values because they are stored as Quaternions not Euler angles. Quaternions represent an arbitrary rotation around a fixed axis and do not suffer the gimbal-lock problem of eulers. It is also very easy to interpolate or "slerp" between Quat values which makes it ideal for animation keys.

Kuron

It seems like Mark was making things more difficult than they need to be when he designed the B3D format?

Kitty Hello

It's a total mess. But here's the solution to the quaternion thing:
Code (glbasic) Select

Q54. How do I convert a quaternion to a rotation matrix?
--------------------------------------------------------
  Assuming that a quaternion has been created in the form:

    Q = |X Y Z W|

  Then the quaternion can then be converted into a 4x4 rotation
  matrix using the following expression (Warning: you might have to
  transpose this matrix if you (do not) follow the OpenGL order!):

         Â¦        2     2                                      ¦
         Â¦ 1 - (2Y  + 2Z )   2XY + 2ZW         2XZ - 2YW       Â¦
         Â¦                                                     Â¦
         Â¦                          2     2                    ¦
     M = ¦ 2XY - 2ZW         1 - (2X  + 2Z )   2YZ + 2XW       Â¦
         Â¦                                                     Â¦
         Â¦                                            2     2  ¦
         Â¦ 2XZ + 2YW         2YZ - 2XW         1 - (2X  + 2Y ) ¦
         Â¦                                                     Â¦

  If a 4x4 matrix is required, then the bottom row and right-most column
  may be added.
  The matrix may be generated using the following expression:

    xx      = X * X;
    xy      = X * Y;
    xz      = X * Z;
    xw      = X * W;
    yy      = Y * Y;
    yz      = Y * Z;
    yw      = Y * W;
    zz      = Z * Z;
    zw      = Z * W;
    mat[0]  = 1 - 2 * ( yy + zz );
    mat[1]  =     2 * ( xy - zw );
    mat[2]  =     2 * ( xz + yw );
    mat[4]  =     2 * ( xy + zw );
    mat[5]  = 1 - 2 * ( xx + zz );
    mat[6]  =     2 * ( yz - xw );
    mat[8]  =     2 * ( xz - yw );
    mat[9]  =     2 * ( yz + xw );
    mat[10] = 1 - 2 * ( xx + yy );
    mat[3]  = mat[7] = mat[11] = mat[12] = mat[13] = mat[14] = 0;
    mat[15] = 1;

  The resulting matrix uses the following positions:

      ¦ mat[0]  mat[4] mat[ 8] mat[12] ¦
  M = ¦ mat[1]  mat[5] mat[ 9] mat[13] ¦
      ¦ mat[2]  mat[6] mat[10] mat[14] ¦
      ¦ mat[3]  mat[7] mat[11] mat[15] ¦


OK, might be worth a try now.