Strictly speaking I shouldn’t be uploading this tutorial yet because I have not imported the exported data into a 3d environment. But then the script illustrated is basically a compilation of the bundled export_mdd.py and the export_ply.py scripts, so if they work, then unless I tripped somewhere mine should to. I do know that the script exports what looks like the right sort of numbers, and anyway, it might be useful as a foray into Blender 2.5’s API as it exists as of NOW! Assuming the script is good, there should be little problem reconfiguring the data to output xml in a format able to fool jME3 into believing it is processing an Ogre xml file.
Here is the link:
Thank you very much for this tutorial.
I have recently updated the Blender exporter for the 2.59 version. The code can be found at S Jones Art under the c# SDL tab, and is posted below. The tangent exporter part needs explanation because there is a lot of bs spoken about tangent space. Normals are not stored in tangent space they are stored as if the smooth normal of the vertex, for all vertices on the model, was pointing (0,0,1) or up the z axis. The normal map stores offsets, x and y variations off that supposed normal to encode perturbations of the model’s surface. The tangent space is simply a base frame created about the point (vertex to be more accurate, then interpolated) where light hits the model, which base frame is used to rotate the the light vector (etc) so that it shines upon the normal in the normal map as if it would shine upon the normal on the model. All the carry on with texture coordinates is to create a base frame to act as a transformation matrix. But you don’t need all that. Consider the earth as if it had normals all over it. If you use the normal as the z axis of the base frame, and you cross that with true y (0, 1, 0) you get a tangent always parallel to the equator. This tangent is the x axis of the base frame. If you cross the normal and the tangent you get the bitangent which is the y axis of the base frame. This base frame is truly orthogonal, and truly centered about the smoothed normal. Texture space tangents, on the contrary, are either centered about face normals and therefore do not accurately stop light vectors that should fall below the horizon, or they are not orthogonal because the normals need to be bent from face normals into smooth vertex normals while leaving uv coords and therefore tangent and bitangent mapped to the face.
This exporter works by creating lists of faces. Each face is a class which contains 3 (tri) or 4 (quad) sets of attribute data. Copy and past the code into Blender’s text window and hit alt p. You must specify a file path where you want the xml file written… find it after the imports.
[java]# ##### BEGIN GPL LICENSE BLOCK #####
#
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
#
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
#
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##### END GPL LICENSE BLOCK #####
#Author Stephen Jones… S Jones Art.
import bpy
import mathutils
import os
import collections
#you must hand code this
filename=‘C:\c#\Tutorials\08ShadedTextures\Game\Scene\assets\xml\pillar.xml’
frame=1
class Struct:
def init (self, *argv, **argd):
if len(argd):
Update by dictionary
self.dict.update (argd)
else:
Update by position
attrs = filter (lambda x: x[0:2] != “__”, dir(self))
for n in range(len(argv)):
setattr(self, attrs[n], argv[n])
class Vat(Struct):
faceIndex=0
v1=v2=v3=v4=0
vert1=vert2=vert3=vert4=mathutils.Vector()
coord1=coord2=coord3=coord4=mathutils.Vector()
norm1=norm2=norm3=norm4=mathutils.Vector()
tan1=tan2=tan3=tan4=mathutils.Vector()
def roundVec3(v):
return round(v[0], 4), round(v[1], 4), round(v[2], 4)
def roundVec2(v):
#uv values are 1.0 - uv to spin things around to fit the geometry
return round(v[0], 4), round(v[1], 4)
flippy=mathutils.Matrix()
flippy[1].y=0.0
flippy[1].z=-1.0
flippy[2].y=1.0
flippy[2].z=0.0
context=bpy.context
scene=context.scene
scene.frame_set(frame)
scene.update
obj=context.object
mesh=obj.to_mesh(scene, True, ‘PREVIEW’)
mesh.transform(obj.matrix_world)
mesh.transform(flippy)
smooth=mesh.faces[0].use_smooth
quads=list()
tris=list()
count=0
#get face index, add to Vat object and store Vat in either tri or quad list
for f in mesh.faces:
tmp=Vat()
tmp.faceIndex=f.index
if len(f.vertices)=:3:
tris.append(tmp)
if len(f.vertices)==4:
quads.append(tmp)
#get data for tris and add to respective Vat
for t in tris:
t.v1=mesh.faces[t.faceIndex].vertices[0]
t.v2=mesh.faces[t.faceIndex].vertices[1]
t.v3=mesh.faces[t.faceIndex].vertices[2]
for t in tris:
t.vert1=roundVec3(tuple(mesh.vertices[t.v1].co))
t.vert2=roundVec3(tuple(mesh.vertices[t.v2].co))
t.vert3=roundVec3(tuple(mesh.vertices[t.v3].co))
if mesh.faces[q.faceIndex].use_smooth:
t.norm1=roundVec3(tuple(mesh.vertices[t.v1].normal))
t.norm2=roundVec3(tuple(mesh.vertices[t.v2].normal))
t.norm3=roundVec3(tuple(mesh.vertices[t.v3].normal))
else:
t.norm1=roundVec3(tuple(mesh.faces[t.faceIndex].normal))
t.norm2=roundVec3(tuple(mesh.faces[t.faceIndex].normal))
t.norm3=roundVec3(tuple(mesh.faces[t.faceIndex].normal))
t.coords1=roundVec2(tuple(mesh.uv_textures[0].data[t.faceIndex].uv1))
t.coords2=roundVec2(tuple(mesh.uv_textures[0].data[t.faceIndex].uv2))
t.coords3=roundVec2(tuple(mesh.uv_textures[0].data[t.faceIndex].uv3))
#get data for quads and add to respective Vat
for q in quads:
q.v1=mesh.faces[q.faceIndex].vertices[0]
q.v2=mesh.faces[q.faceIndex].vertices[1]
q.v3=mesh.faces[q.faceIndex].vertices[2]
q.v4=mesh.faces[q.faceIndex].vertices[3]
for q in quads:
q.vert1=roundVec3(tuple(mesh.vertices[q.v1].co))
q.vert2=roundVec3(tuple(mesh.vertices[q.v2].co))
q.vert3=roundVec3(tuple(mesh.vertices[q.v3].co))
q.vert4=roundVec3(tuple(mesh.vertices[q.v4].co))
if mesh.faces[q.faceIndex].use_smooth:
q.norm1=roundVec3(tuple(mesh.vertices[q.v1].normal))
q.norm2=roundVec3(tuple(mesh.vertices[q.v2].normal))
q.norm3=roundVec3(tuple(mesh.vertices[q.v3].normal))
q.norm4=roundVec3(tuple(mesh.vertices[q.v4].normal))
else:
q.norm1=roundVec3(tuple(mesh.faces[q.faceIndex].normal))
q.norm2=roundVec3(tuple(mesh.faces[q.faceIndex].normal))
q.norm3=roundVec3(tuple(mesh.faces[q.faceIndex].normal))
q.norm4=roundVec3(tuple(mesh.faces[q.faceIndex].normal))
q.coords1=roundVec2(tuple(mesh.uv_textures[0].data[q.faceIndex].uv1))
q.coords2=roundVec2(tuple(mesh.uv_textures[0].data[q.faceIndex].uv2))
q.coords3=roundVec2(tuple(mesh.uv_textures[0].data[q.faceIndex].uv3))
q.coords4=roundVec2(tuple(mesh.uv_textures[0].data[q.faceIndex].uv4))
#calculate tangents and add to Vat
def cross(u, v):
ret=mathutils.Vector()
ret.x=u[1]*v[2]-u[2]-v[1]
ret.y=u[2]*v[0]-u[0]*v[2]
ret.z=u[0]*v[1]-u[1]*v[0]
return roundVec3(ret)
up=mathutils.Vector()
up.y=1.0
for t in tris:
if t.norm1[0]==up[0] and t.norm1[1]==up[1] and t.norm1[2]==up[2]:
print(“same”)
t.tan1=cross(up, t.norm1)
if t.norm2[0]==up[0] and t.norm2[1]==up[1] and t.norm2[2]==up[2]:
print(“same”)
t.tan2=cross(up, t.norm2)
if t.norm3[0]==up[0] and t.norm3[1]==up[1] and t.norm3[2]==up[2]:
print(“same”)
t.tan3=cross(up, t.norm3)
for q in quads:
if q.norm1[0]==up[0] and q.norm1[1]==up[1] and q.norm1[2]==up[2]:
print(“same”)
q.tan1=cross(up, q.norm1)
if q.norm2[0]==up[0] and q.norm2[1]==up[1] and q.norm2[2]==up[2]:
print(“same”)
q.tan2=cross(up, q.norm2)
if q.norm3[0]==up[0] and q.norm3[1]==up[1] and q.norm3[2]==up[2]:
print(“same”)
q.tan3=cross(up, q.norm3)
if q.norm4[0]==up[0] and q.norm4[1]==up[1] and q.norm4[2]==up[2]:
print(“same”)
q.tan4=cross(up, q.norm4)
#multiply vertex positions by 10
def mult(v, n):
ret=mathutils.Vector()
ret.x=v[0]*n
ret.y=v[1]*n
ret.z=v[2]*n
return roundVec3(ret)
for t in tris:
t.vert1=mult(t.vert1, 10)
t.vert2=mult(t.vert2, 10)
t.vert3=mult(t.vert3, 10)
for q in quads:
q.vert1=mult(q.vert1, 10)
q.vert2=mult(q.vert2, 10)
q.vert3=mult(q.vert3, 10)
q.vert4=mult(q.vert4, 10)
#construct xml print strings
tv="<tv>"
tn="<tn>"
tc="<tc>"
tt="<tt>"
for t in tris:
tv=tv + "%.6f %.6f %.6f " %t.vert1
tv=tv + "%.6f %.6f %.6f " %t.vert2
tv=tv + "%.6f %.6f %.6f " %t.vert3
tn=tn + "%.6f %.6f %.6f " %t.norm1
tn=tn + “%.6f %.6f %.6f " %t.norm2
tn=tn + “%.6f %.6f %.6f " %t.norm3
tc=tc + “%.6f %.6f " %t.coords1
tc=tc + “%.6f %.6f " %t.coords2
tc=tc + “%.6f %.6f " %t.coords3
tt=tt + “%.6f %.6f %.6f " %t.tan1
tt=tt + “%.6f %.6f %.6f " %t.tan2
tt=tt + “%.6f %.6f %.6f " %t.tan3
tv=tv.rstrip(’ ‘)
tv=tv + “</tv>n”
tn=tn.rstrip(’ ‘)
tn=tn + “</tn>n”
tc=tc.rstrip(’ ‘)
tc=tc + “</tc>n”
tt=tt.rstrip(’ ')
tt=tt + “</tt>n”
qv=”<qv>”
qn=”<qn>”
qc=”<qc>”
qt=”<qt>”
for q in quads:
qv=qv + "%.6f %.6f %.6f " %q.vert1
qv=qv + "%.6f %.6f %.6f " %q.vert2
qv=qv + "%.6f %.6f %.6f " %q.vert3
qv=qv + "%.6f %.6f %.6f " %q.vert4
qn=qn + "%.6f %.6f %.6f " %q.norm1
qn=qn + "%.6f %.6f %.6f " %q.norm2
qn=qn + "%.6f %.6f %.6f " %q.norm3
qn=qn + "%.6f %.6f %.6f " %q.norm4
qc=qc + "%.6f %.6f " %q.coords1
qc=qc + "%.6f %.6f " %q.coords2
qc=qc + "%.6f %.6f " %q.coords3
qc=qc + "%.6f %.6f " %q.coords4
qt=qt + "%.6f %.6f %.6f " %q.tan1
qt=qt + "%.6f %.6f %.6f " %q.tan2
qt=qt + “%.6f %.6f %.6f " %q.tan3
qt=qt + “%.6f %.6f %.6f " %q.tan4
qv=qv.rstrip(’ ‘)
qv=qv + “</qv>n”
qn=qn.rstrip(’ ‘)
qn=qn + “</qn>n”
qc=qc.rstrip(’ ‘)
qc=qc + “</qc>n”
qt=qt.rstrip(’ ‘)
qt=qt + “</qt>n”
file=open(filename, ‘w’)
file.write(’<?xml version=“1.0” encoding=“utf-8”?>n’)
file.write(’<staticModel>n’)
file.write(tv)
file.write(tn)
file.write(tc)
file.write(tt)
file.write(qv)
file.write(qn)
file.write(qc)
file.write(qt)
file.write(’<mat> </mat>n’)
file.write(’<base>back.png</base>n’)
file.write(’<norm s=“1” t=“1”>subwayHKwall_n.png</norm>n’)
file.write(’<tile s=“1” t=“1”>subwayHKpillar_t.png</tile>n’)
file.write(”</staticModel>”)
file.close()[/java]