import bpy
import bmesh
import os
import shutil
import struct
from mathutils import * ; from math import *
from bpy_extras . io_utils import ( axis_conversion )
C = bpy . context
D = bpy . data
LEVEL_EXPORT_NAME = " level "
EXPORT_DIRECTORY = " exported "
print ( " \n \n Let ' s get it started " )
if os . path . exists ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) ) :
shutil . rmtree ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) )
os . makedirs ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } " ) )
def write_b8 ( f , boolean : bool ) :
f . write ( bytes ( struct . pack ( " ? " , boolean ) ) )
def write_f32 ( f , number : float ) :
f . write ( bytes ( struct . pack ( " f " , number ) ) )
def write_u64 ( f , number : int ) :
f . write ( bytes ( struct . pack ( " Q " , number ) ) )
def write_i32 ( f , number : int ) :
f . write ( bytes ( struct . pack ( " i " , number ) ) )
def write_v3 ( f , vector ) :
write_f32 ( f , vector . x )
write_f32 ( f , vector . y )
write_f32 ( f , vector . z )
def write_quat ( f , quat ) :
write_f32 ( f , quat . x )
write_f32 ( f , quat . y )
write_f32 ( f , quat . z )
write_f32 ( f , quat . w )
def write_string ( f , s : str ) :
encoded = s . encode ( " utf8 " )
write_u64 ( f , len ( encoded ) )
f . write ( encoded )
def write_4x4matrix ( f , m ) :
# writes each row, sequentially, row major
for row in range ( 4 ) :
for col in range ( 4 ) :
write_f32 ( f , m [ row ] [ col ] )
# for the level.bin
level_object_data = [ ]
collision_cubes = [ ]
placed_entities = [ ]
saved_meshes = set ( )
mapping = axis_conversion (
from_forward = " Y " ,
from_up = " Z " ,
to_forward = " -Z " ,
to_up = " Y " ,
)
mapping . resize_4x4 ( )
# meshes can either be Meshes, or Armatures. Armatures contain all mesh data to draw it, and any anims it has
for o in D . objects :
if o . type == " MESH " :
if o . parent and o . parent . type == " ARMATURE " :
mesh_object = o
o = o . parent
object_transform_info = ( mesh_name , mapping @ mesh_object . location , mesh_object . rotation_euler , mesh_object . scale )
if o . users_collection [ 0 ] . name == ' Level ' :
assert ( False , " Cannot put armatures in the level. The level is for static placed meshes. For dynamic entities, you put them outside of the level collection " )
else :
placed_entities . append ( ( mesh_object . name , ) + object_transform_info )
armature_name = o . data . name
output_filepath = bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { armature_name } .bin " )
print ( f " Exporting armature to { output_filepath } " )
with open ( output_filepath , " wb " ) as f :
write_b8 ( f , True )
bones_in_armature = [ ]
for b in o . data . bones :
bones_in_armature . append ( b )
# the inverse model space pos of the bones
write_u64 ( f , len ( bones_in_armature ) )
for b in bones_in_armature :
model_space_pose = mapping @ b . matrix_local
inverse_model_space_pose = ( mapping @ b . matrix_local ) . inverted ( )
write_4x4matrix ( f , model_space_pose )
write_4x4matrix ( f , inverse_model_space_pose )
write_f32 ( f , b . length )
# write the pose information
write_u64 ( f , len ( o . pose . bones ) )
for pose_bone in o . pose . bones :
parent_index = - 1
if pose_bone . parent :
for i in range ( len ( bones_in_armature ) ) :
if bones_in_armature [ i ] == pose_bone . parent . bone :
parent_index = i
break
if parent_index == - 1 :
assert ( false , f " Couldn ' t find parent of bone { pose_bone } " )
print ( f " Parent of bone { pose_bone . name } is index { parent_index } in list { bones_in_armature } " )
parent_space_pose = None
if pose_bone . parent :
parent_space_pose = pose_bone . parent . matrix . inverted ( ) @ pose_bone . matrix
else :
parent_space_pose = mapping @ pose_bone . matrix
print ( " parent_space_pose of the bone with no parent: " )
print ( parent_space_pose )
write_string ( f , pose_bone . bone . name )
write_i32 ( f , parent_index )
#parent_space_pose = mapping @ pose_bone.matrix
translation = parent_space_pose . to_translation ( )
rotation = parent_space_pose . to_quaternion ( )
scale = parent_space_pose . to_scale ( )
write_v3 ( f , translation )
write_quat ( f , rotation )
write_v3 ( f , scale )
# write the mesh data for the armature
bm = bmesh . new ( )
mesh = mesh_object . to_mesh ( )
bm . from_mesh ( mesh )
bmesh . ops . triangulate ( bm , faces = bm . faces )
bm . transform ( mapping )
bm . to_mesh ( mesh )
vertices = [ ]
for polygon in mesh . polygons :
if len ( polygon . loop_indices ) == 3 :
for loopIndex in polygon . loop_indices :
loop = mesh . loops [ loopIndex ]
position = mesh . vertices [ loop . vertex_index ] . undeformed_co
uv = mesh . uv_layers . active . data [ loop . index ] . uv
normal = loop . normal
vertices . append ( ( position , uv ) )
write_u64 ( f , len ( vertices ) )
for v_and_uv in vertices :
v , uv = v_and_uv
write_f32 ( f , v . x )
write_f32 ( f , v . y )
write_f32 ( f , v . z )
write_f32 ( f , uv . x )
write_f32 ( f , uv . y )
print ( f " Wrote { len ( vertices ) } vertices " )
else : # if the parent type isn't an armature, i.e just a bog standard mesh
mesh_name = o . to_mesh ( ) . name # use this over o.name so instanced objects which refer to the same mesh, both use the same serialized mesh.
object_transform_info = ( mesh_name , mapping @ o . location , o . rotation_euler , o . scale )
if o . users_collection [ 0 ] . name == ' Level ' and mesh_name == " CollisionCube " :
collision_cubes . append ( ( o . location , o . dimensions ) )
else :
if o . users_collection [ 0 ] . name == ' Level ' :
print ( f " Object { o . name } has mesh name { o . to_mesh ( ) . name } " )
assert ( o . rotation_euler . order == ' XYZ ' )
level_object_data . append ( object_transform_info )
else :
placed_entities . append ( ( o . name , ) + object_transform_info )
if mesh_name in saved_meshes :
continue
saved_meshes . add ( mesh_name )
assert ( mesh_name != LEVEL_EXPORT_NAME )
output_filepath = bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { mesh_name } .bin " )
print ( f " Exporting mesh to { output_filepath } " )
with open ( output_filepath , " wb " ) as f :
write_b8 ( f , False )
bm = bmesh . new ( )
mesh = o . to_mesh ( )
bm . from_mesh ( mesh )
bmesh . ops . triangulate ( bm , faces = bm . faces )
bm . transform ( mapping )
bm . to_mesh ( mesh )
vertices = [ ]
for polygon in mesh . polygons :
if len ( polygon . loop_indices ) == 3 :
for loopIndex in polygon . loop_indices :
loop = mesh . loops [ loopIndex ]
position = mesh . vertices [ loop . vertex_index ] . undeformed_co
uv = mesh . uv_layers . active . data [ loop . index ] . uv
normal = loop . normal
vertices . append ( ( position , uv ) )
write_u64 ( f , len ( vertices ) )
for v_and_uv in vertices :
v , uv = v_and_uv
write_f32 ( f , v . x )
write_f32 ( f , v . y )
write_f32 ( f , v . z )
write_f32 ( f , uv . x )
write_f32 ( f , uv . y )
print ( f " Wrote { len ( vertices ) } vertices " )
with open ( bpy . path . abspath ( f " // { EXPORT_DIRECTORY } / { LEVEL_EXPORT_NAME } .bin " ) , " wb " ) as f :
write_u64 ( f , len ( level_object_data ) )
for o in level_object_data :
mesh_name , blender_pos , blender_rotation , blender_scale = o
print ( f " Writing instanced object of mesh { mesh_name } " )
write_string ( f , mesh_name )
write_f32 ( f , blender_pos . x )
write_f32 ( f , blender_pos . y )
write_f32 ( f , blender_pos . z )
write_f32 ( f , blender_rotation . x )
write_f32 ( f , blender_rotation . y )
write_f32 ( f , blender_rotation . z )
write_f32 ( f , blender_scale . x )
write_f32 ( f , blender_scale . y )
write_f32 ( f , blender_scale . z )
write_u64 ( f , len ( collision_cubes ) )
for c in collision_cubes :
blender_pos , blender_dims = c
write_f32 ( f , blender_pos . x )
write_f32 ( f , - blender_pos . y )
write_f32 ( f , blender_dims . x )
write_f32 ( f , blender_dims . y )
assert ( blender_dims . x > 0.0 )
assert ( blender_dims . y > 0.0 )
write_u64 ( f , len ( placed_entities ) )
for p in placed_entities :
# underscore is mesh name, prefer object name for name of npc. More obvious
object_name , _ , blender_pos , blender_rotation , blender_scale = p
print ( f " Writing placed entity ' { object_name } ' " )
write_string ( f , object_name )
write_f32 ( f , blender_pos . x )
write_f32 ( f , blender_pos . y )
write_f32 ( f , blender_pos . z )
write_f32 ( f , blender_rotation . x )
write_f32 ( f , blender_rotation . y )
write_f32 ( f , blender_rotation . z )
write_f32 ( f , blender_scale . x )
write_f32 ( f , blender_scale . y )
write_f32 ( f , blender_scale . z )