reattiva / Urho3D-Blender

Blender to Urho3D mesh exporter
Other
123 stars 51 forks source link

Houdini or Maya export #39

Open sabotage3d opened 10 years ago

sabotage3d commented 10 years ago

Hello I am thinking of using your code for making a Houdini or Maya export tool for Urho3d . Is there a simple way to seperate the Blender specific code from the Urho3d specific code. I am familiar with Maya and Houdini API's in python I just need a bit of direction on which classes exactly do I need. I think the trickiest would be the weights and bones animations. If you can provide a command line example on how to feed some data that would be great.

Thanks in advance for any help.

Alex

reattiva commented 10 years ago

Great! File-wise, you have: **init.py, here you find all the Blender user interface code (not useful), at the end there is the function "ExecuteUrhoExport" which is the main export function (useful); decompose.py (not useful), here you find the code to extract all the informations to be exported from the Blender scene (Vertices, Skeletons, Animations ...). These informations are stored in classes which names start with "T" (TVertex, TGeometry ...). The main function here is "Scan" which does the decomposition of objects in the T classes and saves them in a TData class, it uses the class TOption for the options selected by the user. export_urho.py (useful), here you find the code to convert the informations extracted from Blender and stored in the Txxx classes to classes ready for the Urho export. These classes' names start with "Urho" (UrhoVertex, UrhoVertexBuffer ...). The function "UrhoExport" does this conversion and saves the resulting Urho classes in a UrhoExportData class, the options are stored in a UrhoExportData class. You also find the functions used to write the Models and Animation in Urho3D format. utils.py (useful) contains some write utilities. export_scene.py** (not useful) contains the Scene, Nodes and Materials export code. This is a work in progress by Mike3D.

The work in "ExecuteUrhoExport" is something like:

So mainly you have to rewrite the code in "decompose.py" to populate the Txxx classes. Keep in mind that Blender uses the right hand rule (like OpenGL) and Urho uses the left hand rule (like DirectX).

sabotage3d commented 10 years ago

Thanks a lot, these will be super useful and they are really well written.

sabotage3d commented 9 years ago

I am a bit stuck with the order of the vertices position and indices order where is that bit in your code where you reorder the vertices position and indices to match Urho ?

reattiva commented 9 years ago

For the indices, here: https://github.com/reattiva/Urho3D-Blender/blob/master/io_mesh_urho/decompose.py#L1788 A index points to a vertex in TData.verticesList, so working only on the indices will do.

sabotage3d commented 9 years ago

Hey man do you have a simple example on how to use UrhoWriteModel(model, filename) A simple triangle example would be great.

reattiva commented 9 years ago
def WriteTriangleExample():
    uModel = UrhoModel()
    uModel.name = "triangle"

    uGeometry = UrhoGeometry()
    uModel.geometries.append(uGeometry)

    uLodLevel = UrhoLodLevel()
    uLodLevel.distance = 0.0
    uLodLevel.primitiveType = TRIANGLE_LIST
    uLodLevel.vertexBuffer = 0
    uLodLevel.indexBuffer = 0
    uLodLevel.startIndex = 0
    uLodLevel.countIndex = 3
    uGeometry.lodLevels.append(uLodLevel)

    vertexBuffer = UrhoVertexBuffer()
    vertexBuffer.morphMinIndex = 0
    vertexBuffer.morphMaxIndex = 0
    uModel.vertexBuffers.append(vertexBuffer)

    indexBuffer = UrhoIndexBuffer()
    indexBuffer.indexSize = 2
    uModel.indexBuffers.append(indexBuffer)

    tVertex = TVertex()
    tVertex.pos = Vector((0.0, 0.0, 0.0))
    tVertex.normal = Vector((0.0, 0.0, -1.0))

    uVertex = UrhoVertex(tVertex)
    vertexBuffer.updateMask(uVertex.mask)
    vertexBuffer.vertices.append(uVertex)
    indexBuffer.indexes.append(0)
    uModel.boundingBox.merge(uVertex.pos)
    uGeometry.center += uVertex.pos;

    tVertex = TVertex()
    tVertex.pos = Vector((0.0, 1.0, 0.0))
    tVertex.normal = Vector((0.0, 0.0, -1.0))

    uVertex = UrhoVertex(tVertex)
    vertexBuffer.updateMask(uVertex.mask)
    vertexBuffer.vertices.append(uVertex)
    indexBuffer.indexes.append(1)
    uModel.boundingBox.merge(uVertex.pos)
    uGeometry.center += uVertex.pos;

    tVertex = TVertex()
    tVertex.pos = Vector((1.0, 0.0, 0.0))
    tVertex.normal = Vector((0.0, 0.0, -1.0))

    uVertex = UrhoVertex(tVertex)
    vertexBuffer.updateMask(uVertex.mask)
    vertexBuffer.vertices.append(uVertex)
    indexBuffer.indexes.append(2)
    uModel.boundingBox.merge(uVertex.pos)
    uGeometry.center += uVertex.pos;

    uGeometry.center /= 3;

    UrhoWriteModel(uModel, "e:/temp/triangle.mdl")
sabotage3d commented 9 years ago

Works like a charm. Thanks a lot :+1:

sabotage3d commented 9 years ago

I am getting this error when I run the example. I just bypassed the raise VertexMaskError(oldMask, vertexMask) . Would that cause any issues ?

raise VertexMaskError(oldMask, vertexMask)
export_urho.VertexMaskError: 0002 vs 0003
 differences: Position
reattiva commented 9 years ago

The vertex buffer only accepts vertices with the same attributes. You have added to the vertex buffer a vertex with only the normal attribute (0x0002) then you try to add a vertex with normal and position attributes (0x0002 | 0x0001).

sabotage3d commented 9 years ago

I think I am making a mistake somewhere. Let say you have 16 vertices, 8 positions, 16 indices, 16 vertex normals . Normally we would send 8 positions and 16 indices to represent 16 vertices but we have 16 unique normals do we have to pass then 16 positions, 16 indices and 16 vertex normals ?

reattiva commented 9 years ago

That's right, a different normal means a different vertex even if its position is the same.

sabotage3d commented 9 years ago

Isn't that inneficient ? Having duplicate positions of vertices that share the same index ?

reattiva commented 9 years ago

Over position and normals, you also have UV, tangents, colors ... you can add anything you want to a vertex. I don't know if it is efficient, as you are suggesting, defining a vertex as a set of indices one for each of these attributes. Do some research but as far as I know, with a VBO you reuse a vertex only if all its attributes are the same.

sabotage3d commented 9 years ago

thanks man. It all makes sense now.

sabotage3d commented 9 years ago

I am trying to understand the skeleton data now. I can't fully understand how to do the skinning properly. Do you have a simple example on how to pass a single bone to the MDL ? What exactly is the float[12] 4x3 offset matrix suppose to do ? Where do we pass vertices weights to specific bone ? How does the .ani format is asociating bones, just by names ?

reattiva commented 9 years ago

Unfortunately I don't have an example for that. You pass weights and indices as attributes of a vertex. The index points to the bone array of the model, the weight indicates how strong the vertex position will be affected. Each vertex can be influenced by 4 bones at maximum. The total number of bones affecting a subgeometry should be less that 64, there is a remapping system for having a skeleton with more that 64 bones and then each subgeometry is remapping for having (when possible) less than that. However I think the 64 bones limit was changed lately. The offset matrix should be the bone transformation (rotation + position + scale) in object space. Normally in the skeleton you specify the position and rotation of a bone in respect of its parent, this matrix specifies the same data but in object space (in respect of its origin). And yes, in the 'ani' there is a track for each bone and the association happens by name. I went by memory so take all of this with a grain of salt.

sabotage3d commented 9 years ago

Thanks a lot for all the help I managed to get most of the things working. I am having problems with flipping bones. Is there a specific stable method to convert from world space matrix of a bone to the offset matrix for Urho3d. So far I was multiplying by the inverse matrix of a parent bone then doing quaternion and negating the frist two components but I am getting flips.

reattiva commented 9 years ago

I must be honest, I don't remember how it works anymore. However re-reading my comments I think the "offset matrix" should be relative to the armature and not to the parent bone, in other words it should be in object space, so you shouldn't be multiplying by the inverse.

sabotage3d commented 9 years ago

In Houdini I am getting world space transformation of a bone to bring it to object space I am multiplying it by inverse of the parent of this bone, which brings it back to centre in object space. Let me know if I am doing something wrong ?

reattiva commented 9 years ago

If you multiply the world space transformation (rotation+translation) matrix of a bone B by the inverse of world space matrix of its parent bone P, you get the transformation of B relative to P. This is not the object space. If you armature A is at the center of the world and it's not rotated, the world space matrix of the bone B is the object space matrix, instead if A is traslated or rotated, then you multiply the world space matrix of B by the inverse of the world space matrix of A, and you get the trasformation of B relative to A, that is the object space matrix of B.

sabotage3d commented 9 years ago

So the skinning offset matrix should be relative to the root of the skeleton. What about the actual quaternion and translation of a bone? In your code it looks like they are relative to its parent ?

        if parent:
            boneMatrix = parent.matrix_local.inverted() * boneMatrix
        else:
            boneMatrix = originMatrix * boneMatrix

My character left side is correct but his right side is flipped, I am just doing this parent.matrix_local.inverted() * boneMatrix and then negating x and y of the quaternion .

reattiva commented 9 years ago

'boneMatrix' is not the skinning offset matrix, try to follow the code more carefully, start with 'ml' (just below your quoted code), then 'worldTransform' in the TBone class, then 'inverseMatrix' in UrhoBone class, this is the matrix written to file. Again, trust me not.

sabotage3d commented 9 years ago

Yeah I know that it is not the offset skinning matrix. I already got the offset matrix working the whole flipping seems to be caused by the quaternion which I think I am doing exactly like in your code. If we have bone1 and bone2 and bone1 is the parent of bone 2. Then I am doing bone2.world_matrix * bone1.world_matrix.inverted() then from this matrix I am doing the quaternion, the root is at 0,0,0. Is there something I am missing ?

reattiva commented 9 years ago

That is bone2 relative to bone1. Anyway, try to print the matrices in my last post and see where they don't match with yours. Edit: mmm... actually, I think I wasn't following you, you're trying to say that the offset matrix is ok, but the problem could be in the bone translation and rotation! Yeah, sorry about that. Yes, what you have written is correct: the bone rotation and traslation must be relative to the parent. But it can be tricky with the left/right hand and head/root of the bone also in Blender bones are normally oriented toward +Y. I cannot help you there, as I said I'm off the loop. Try with the simplest skeleton, print your matrices step by step and compare them to the Urho Asset Importer results. It is best if you can get the correct matrix matematically on paper first.

sabotage3d commented 9 years ago

Thanks you are right apprently in Houdini every bone can have different orientation which is causing the issues. It will take me a while to fix it.