2018-12-18 16:46:36 +01:00
import bpy
import numpy as np
from mathutils import *
import arm . utils
# Exports smaller geometry but is slower
# To be replaced with https://github.com/zeux/meshoptimizer
class Vertex :
__slots__ = ( " co " , " normal " , " uvs " , " col " , " loop_indices " , " index " , " bone_weights " , " bone_indices " , " bone_count " , " vertex_index " )
def __init__ ( self , mesh , loop ) :
self . vertex_index = loop . vertex_index
loop_idx = loop . index
self . co = mesh . vertices [ self . vertex_index ] . co [ : ]
self . normal = loop . normal [ : ]
self . uvs = tuple ( layer . data [ loop_idx ] . uv [ : ] for layer in mesh . uv_layers )
self . col = [ 0.0 , 0.0 , 0.0 ]
if len ( mesh . vertex_colors ) > 0 :
self . col = mesh . vertex_colors [ 0 ] . data [ loop_idx ] . color [ : ]
self . loop_indices = [ loop_idx ]
self . index = 0
def __hash__ ( self ) :
return hash ( ( self . co , self . normal , self . uvs ) )
def __eq__ ( self , other ) :
eq = (
( self . co == other . co ) and
( self . normal == other . normal ) and
( self . uvs == other . uvs ) and
( self . col == other . col )
)
if eq :
indices = self . loop_indices + other . loop_indices
self . loop_indices = indices
other . loop_indices = indices
return eq
2018-12-29 16:34:04 +01:00
def calc_tangents ( posa , nora , uva , ias , scale_pos ) :
num_verts = int ( len ( posa ) / 4 )
tangents = np . empty ( num_verts * 3 , dtype = ' <f4 ' )
# bitangents = np.empty(num_verts * 3, dtype='<f4')
for ar in ias :
ia = ar [ ' values ' ]
num_tris = int ( len ( ia ) / 3 )
for i in range ( 0 , num_tris ) :
i0 = ia [ i * 3 ]
i1 = ia [ i * 3 + 1 ]
i2 = ia [ i * 3 + 2 ]
v0 = Vector ( ( posa [ i0 * 4 ] , posa [ i0 * 4 + 1 ] , posa [ i0 * 4 + 2 ] ) )
v1 = Vector ( ( posa [ i1 * 4 ] , posa [ i1 * 4 + 1 ] , posa [ i1 * 4 + 2 ] ) )
v2 = Vector ( ( posa [ i2 * 4 ] , posa [ i2 * 4 + 1 ] , posa [ i2 * 4 + 2 ] ) )
uv0 = Vector ( ( uva [ i0 * 2 ] , uva [ i0 * 2 + 1 ] ) )
uv1 = Vector ( ( uva [ i1 * 2 ] , uva [ i1 * 2 + 1 ] ) )
uv2 = Vector ( ( uva [ i2 * 2 ] , uva [ i2 * 2 + 1 ] ) )
deltaPos1 = v1 - v0
deltaPos2 = v2 - v0
deltaUV1 = uv1 - uv0
deltaUV2 = uv2 - uv0
d = ( deltaUV1 . x * deltaUV2 . y - deltaUV1 . y * deltaUV2 . x )
if d != 0 :
r = 1.0 / d
else :
r = 1.0
tangent = ( deltaPos1 * deltaUV2 . y - deltaPos2 * deltaUV1 . y ) * r
# bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r
tangents [ i0 * 3 ] + = tangent . x
tangents [ i0 * 3 + 1 ] + = tangent . y
tangents [ i0 * 3 + 2 ] + = tangent . z
tangents [ i1 * 3 ] + = tangent . x
tangents [ i1 * 3 + 1 ] + = tangent . y
tangents [ i1 * 3 + 2 ] + = tangent . z
tangents [ i2 * 3 ] + = tangent . x
tangents [ i2 * 3 + 1 ] + = tangent . y
tangents [ i2 * 3 + 2 ] + = tangent . z
# bitangents[i0 * 3 ] += bitangent.x
# bitangents[i0 * 3 + 1] += bitangent.y
# bitangents[i0 * 3 + 2] += bitangent.z
# bitangents[i1 * 3 ] += bitangent.x
# bitangents[i1 * 3 + 1] += bitangent.y
# bitangents[i1 * 3 + 2] += bitangent.z
# bitangents[i2 * 3 ] += bitangent.x
# bitangents[i2 * 3 + 1] += bitangent.y
# bitangents[i2 * 3 + 2] += bitangent.z
# Orthogonalize
for i in range ( 0 , num_verts ) :
t = Vector ( ( tangents [ i * 3 ] , tangents [ i * 3 + 1 ] , tangents [ i * 3 + 2 ] ) )
# b = Vector((bitangents[i * 3], bitangents[i * 3 + 1], bitangents[i * 3 + 2]))
n = Vector ( ( nora [ i * 2 ] , nora [ i * 2 + 1 ] , posa [ i * 4 + 3 ] / scale_pos ) )
v = t - n * n . dot ( t )
v . normalize ( )
# Calculate handedness
# cnv = n.cross(v)
# if cnv.dot(b) < 0.0:
# v = v * -1.0
tangents [ i * 3 ] = v . x
tangents [ i * 3 + 1 ] = v . y
tangents [ i * 3 + 2 ] = v . z
return tangents
2018-12-18 16:46:36 +01:00
def export_mesh_data ( self , exportMesh , bobject , o , has_armature = False ) :
exportMesh . calc_normals_split ( )
2018-12-20 22:37:39 +01:00
# exportMesh.calc_loop_triangles()
2018-12-18 16:46:36 +01:00
vert_list = { Vertex ( exportMesh , loop ) : 0 for loop in exportMesh . loops } . keys ( )
num_verts = len ( vert_list )
num_uv_layers = len ( exportMesh . uv_layers )
has_tex = self . get_export_uvs ( exportMesh ) == True and num_uv_layers > 0
if self . has_baked_material ( bobject , exportMesh . materials ) :
has_tex = True
has_tex1 = has_tex == True and num_uv_layers > 1
num_colors = len ( exportMesh . vertex_colors )
has_col = self . get_export_vcols ( exportMesh ) == True and num_colors > 0
has_tang = self . has_tangents ( exportMesh )
pdata = np . empty ( num_verts * 4 , dtype = ' <f4 ' ) # p.xyz, n.z
ndata = np . empty ( num_verts * 2 , dtype = ' <f4 ' ) # n.xy
if has_tex :
# Get active uvmap
t0map = 0
2018-12-18 23:48:38 +01:00
uv_layers = exportMesh . uv_layers
2018-12-18 16:46:36 +01:00
if uv_layers != None :
if ' UVMap_baked ' in uv_layers :
for i in range ( 0 , len ( uv_layers ) ) :
if uv_layers [ i ] . name == ' UVMap_baked ' :
t0map = i
break
else :
for i in range ( 0 , len ( uv_layers ) ) :
if uv_layers [ i ] . active_render :
t0map = i
break
t1map = 1 if t0map == 0 else 0
# Alloc data
t0data = np . empty ( num_verts * 2 , dtype = ' <f4 ' )
if has_tex1 :
t1data = np . empty ( num_verts * 2 , dtype = ' <f4 ' )
if has_col :
cdata = np . empty ( num_verts * 3 , dtype = ' <f4 ' )
if has_tex :
# Scale for packed coords
maxdim = 1.0
lay0 = exportMesh . uv_layers [ t0map ]
for v in lay0 . data :
if abs ( v . uv [ 0 ] ) > maxdim :
maxdim = abs ( v . uv [ 0 ] )
if abs ( v . uv [ 1 ] ) > maxdim :
maxdim = abs ( v . uv [ 1 ] )
if maxdim > 1 :
o [ ' scale_tex ' ] = maxdim
invscale_tex = ( 1 / o [ ' scale_tex ' ] ) * 32767
else :
invscale_tex = 1 * 32767
# TODO: handle t1map
# Save aabb
aabb_center = 0.125 * sum ( ( Vector ( b ) for b in bobject . bound_box ) , Vector ( ) )
bobject . data . arm_aabb = [ \
abs ( ( bobject . bound_box [ 6 ] [ 0 ] - bobject . bound_box [ 0 ] [ 0 ] ) / 2 + abs ( aabb_center [ 0 ] ) ) * 2 , \
abs ( ( bobject . bound_box [ 6 ] [ 1 ] - bobject . bound_box [ 0 ] [ 1 ] ) / 2 + abs ( aabb_center [ 1 ] ) ) * 2 , \
abs ( ( bobject . bound_box [ 6 ] [ 2 ] - bobject . bound_box [ 0 ] [ 2 ] ) / 2 + abs ( aabb_center [ 2 ] ) ) * 2 \
]
# Scale for packed coords
maxdim = max ( bobject . data . arm_aabb [ 0 ] , max ( bobject . data . arm_aabb [ 1 ] , bobject . data . arm_aabb [ 2 ] ) )
2018-12-31 16:57:16 +01:00
if maxdim > 2 :
o [ ' scale_pos ' ] = maxdim / 2
else :
o [ ' scale_pos ' ] = 1.0
2018-12-18 16:46:36 +01:00
if has_armature : # Allow up to 2x bigger bounds for skinned mesh
o [ ' scale_pos ' ] * = 2.0
2018-12-31 16:57:16 +01:00
2018-12-18 16:46:36 +01:00
scale_pos = o [ ' scale_pos ' ]
invscale_pos = ( 1 / scale_pos ) * 32767
# Make arrays
for i , v in enumerate ( vert_list ) :
v . index = i
co = v . co
normal = v . normal
i4 = i * 4
i2 = i * 2
pdata [ i4 ] = co [ 0 ]
pdata [ i4 + 1 ] = co [ 1 ]
pdata [ i4 + 2 ] = co [ 2 ]
pdata [ i4 + 3 ] = normal [ 2 ] * scale_pos # Cancel scale
ndata [ i2 ] = normal [ 0 ]
ndata [ i2 + 1 ] = normal [ 1 ]
if has_tex :
uv = v . uvs [ t0map ]
t0data [ i2 ] = uv [ 0 ]
t0data [ i2 + 1 ] = 1.0 - uv [ 1 ] # Reverse Y
if has_tex1 :
uv = v . uvs [ t1map ]
t1data [ i2 ] = uv [ 0 ]
t1data [ i2 + 1 ] = 1.0 - uv [ 1 ]
if has_col :
i3 = i * 3
2019-02-11 11:12:41 +01:00
cdata [ i3 ] = v . col [ 0 ]
cdata [ i3 + 1 ] = v . col [ 1 ]
cdata [ i3 + 2 ] = v . col [ 2 ]
2018-12-18 16:46:36 +01:00
# Indices
prims = { ma . name if ma else ' ' : [ ] for ma in exportMesh . materials }
if not prims :
prims = { ' ' : [ ] }
vert_dict = { i : v for v in vert_list for i in v . loop_indices }
for poly in exportMesh . polygons :
first = poly . loop_start
if len ( exportMesh . materials ) == 0 :
prim = prims [ ' ' ]
else :
mat = exportMesh . materials [ min ( poly . material_index , len ( exportMesh . materials ) - 1 ) ]
prim = prims [ mat . name if mat else ' ' ]
indices = [ vert_dict [ i ] . index for i in range ( first , first + poly . loop_total ) ]
if poly . loop_total == 3 :
prim + = indices
elif poly . loop_total > 3 :
for i in range ( poly . loop_total - 2 ) :
prim + = ( indices [ - 1 ] , indices [ i ] , indices [ i + 1 ] )
# Write indices
o [ ' index_arrays ' ] = [ ]
for mat , prim in prims . items ( ) :
idata = [ 0 ] * len ( prim )
for i , v in enumerate ( prim ) :
idata [ i ] = v
if len ( idata ) == 0 : # No face assigned
continue
ia = { }
ia [ ' values ' ] = idata
ia [ ' material ' ] = 0
# Find material index for multi-mat mesh
if len ( exportMesh . materials ) > 1 :
for i in range ( 0 , len ( exportMesh . materials ) ) :
if ( exportMesh . materials [ i ] != None and mat == exportMesh . materials [ i ] . name ) or \
( exportMesh . materials [ i ] == None and mat == ' ' ) : # Default material for empty slots
ia [ ' material ' ] = i
break
o [ ' index_arrays ' ] . append ( ia )
if has_tang :
2018-12-29 16:34:04 +01:00
tangdata = calc_tangents ( pdata , ndata , t0data , o [ ' index_arrays ' ] , scale_pos )
2018-12-18 16:46:36 +01:00
pdata * = invscale_pos
ndata * = 32767
pdata = np . array ( pdata , dtype = ' <i2 ' )
ndata = np . array ( ndata , dtype = ' <i2 ' )
if has_tex :
t0data * = invscale_tex
t0data = np . array ( t0data , dtype = ' <i2 ' )
if has_tex1 :
t1data * = invscale_tex
t1data = np . array ( t1data , dtype = ' <i2 ' )
if has_col :
cdata * = 32767
cdata = np . array ( cdata , dtype = ' <i2 ' )
if has_tang :
tangdata * = 32767
tangdata = np . array ( tangdata , dtype = ' <i2 ' )
# Output
o [ ' vertex_arrays ' ] = [ ]
o [ ' vertex_arrays ' ] . append ( { ' attrib ' : ' pos ' , ' values ' : pdata } )
o [ ' vertex_arrays ' ] . append ( { ' attrib ' : ' nor ' , ' values ' : ndata } )
if has_tex :
o [ ' vertex_arrays ' ] . append ( { ' attrib ' : ' tex ' , ' values ' : t0data } )
if has_tex1 :
o [ ' vertex_arrays ' ] . append ( { ' attrib ' : ' tex1 ' , ' values ' : t1data } )
if has_col :
o [ ' vertex_arrays ' ] . append ( { ' attrib ' : ' col ' , ' values ' : cdata } )
if has_tang :
o [ ' vertex_arrays ' ] . append ( { ' attrib ' : ' tang ' , ' values ' : tangdata } )
return vert_list
def export_skin ( self , bobject , armature , vert_list , o ) :
# This function exports all skinning data, which includes the skeleton
# and per-vertex bone influence data
oskin = { }
o [ ' skin ' ] = oskin
# Write the skin bind pose transform
otrans = { }
oskin [ ' transform ' ] = otrans
otrans [ ' values ' ] = self . write_matrix ( bobject . matrix_world )
# Write the bone object reference array
oskin [ ' bone_ref_array ' ] = [ ]
oskin [ ' bone_len_array ' ] = [ ]
bone_array = armature . data . bones
bone_count = len ( bone_array )
rpdat = arm . utils . get_rp ( )
max_bones = rpdat . arm_skin_max_bones
if bone_count > max_bones :
log . warn ( bobject . name + ' - ' + str ( bone_count ) + ' bones found, exceeds maximum of ' + str ( max_bones ) + ' bones defined - raise the value in Camera Data - Armory Render Props - Max Bones ' )
for i in range ( bone_count ) :
boneRef = self . find_bone ( bone_array [ i ] . name )
if boneRef :
oskin [ ' bone_ref_array ' ] . append ( boneRef [ 1 ] [ " structName " ] )
oskin [ ' bone_len_array ' ] . append ( bone_array [ i ] . length )
else :
oskin [ ' bone_ref_array ' ] . append ( " " )
oskin [ ' bone_len_array ' ] . append ( 0.0 )
# Write the bind pose transform array
oskin [ ' transformsI ' ] = [ ]
2019-04-06 14:13:38 +02:00
for i in range ( bone_count ) :
skeletonI = ( armature . matrix_world @ bone_array [ i ] . matrix_local ) . inverted_safe ( )
skeletonI = ( skeletonI @ bobject . matrix_world )
oskin [ ' transformsI ' ] . append ( self . write_matrix ( skeletonI ) )
2018-12-18 16:46:36 +01:00
# Export the per-vertex bone influence data
group_remap = [ ]
for group in bobject . vertex_groups :
for i in range ( bone_count ) :
if bone_array [ i ] . name == group . name :
group_remap . append ( i )
break
else :
group_remap . append ( - 1 )
bone_count_array = np . empty ( len ( vert_list ) , dtype = ' <i2 ' )
bone_index_array = np . empty ( len ( vert_list ) * 4 , dtype = ' <i2 ' )
bone_weight_array = np . empty ( len ( vert_list ) * 4 , dtype = ' <i2 ' )
vertices = bobject . data . vertices
count = 0
for index , v in enumerate ( vert_list ) :
bone_count = 0
total_weight = 0.0
bone_values = [ ]
for g in vertices [ v . vertex_index ] . groups :
bone_index = group_remap [ g . group ]
bone_weight = g . weight
if bone_index > = 0 : #and bone_weight != 0.0:
bone_values . append ( ( bone_weight , bone_index ) )
total_weight + = bone_weight
bone_count + = 1
if bone_count > 4 :
bone_count = 4
bone_values . sort ( reverse = True )
bone_values = bone_values [ : 4 ]
bone_count_array [ index ] = bone_count
for bv in bone_values :
bone_weight_array [ count ] = bv [ 0 ] * 32767
bone_index_array [ count ] = bv [ 1 ]
count + = 1
if total_weight != 0.0 and total_weight != 1.0 :
normalizer = 1.0 / total_weight
for i in range ( bone_count ) :
bone_weight_array [ count - i - 1 ] * = normalizer
oskin [ ' bone_count_array ' ] = bone_count_array
oskin [ ' bone_index_array ' ] = bone_index_array [ : count ]
oskin [ ' bone_weight_array ' ] = bone_weight_array [ : count ]
# Bone constraints
for bone in armature . pose . bones :
if len ( bone . constraints ) > 0 :
if ' constraints ' not in oskin :
oskin [ ' constraints ' ] = [ ]
self . add_constraints ( bone , oskin , bone = True )