well yeah i guess
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index edb758fa64c60ff84c5c9f87c97c3fa19a755f97..2ed183aa075b7708b8f3b8b021700ea1e7e49ea5 100644 (file)
@@ -1,3 +1,7 @@
+#
+# Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+#
+
 import bpy, math, gpu
 import cProfile
 from ctypes import *
@@ -43,6 +47,7 @@ class mdl_node(Structure):
    _fields_ = [("co",c_float*3),
                ( "q",c_float*4),
                ( "s",c_float*3),
+               ("sub_uid",c_uint32),        # dont use
                ("submesh_start",c_uint32),
                ("submesh_count",c_uint32),
                ("classtype",c_uint32),
@@ -55,6 +60,8 @@ class mdl_header(Structure):
    _fields_ = [("identifier",c_uint32),
                ("version",c_uint32),
                ("file_length",c_uint32),
+               ("pad0",c_uint32),
+
                ("vertex_count",c_uint32),
                ("vertex_offset",c_uint32),
 
@@ -72,11 +79,16 @@ class mdl_header(Structure):
 
                ("anim_count",c_uint32),
                ("anim_offset",c_uint32),
-
+               
+               ("strings_length",c_uint32),
                ("strings_offset",c_uint32),
+
+               ("entdata_length",c_uint32),
                ("entdata_offset",c_uint32),
-               ("animdata_offset",c_uint32)
-               ]
+
+               ("keyframe_count",c_uint32),
+               ("keyframe_offset",c_uint32)]
+               
 
 class mdl_animation(Structure):
    _pack_ = 1
@@ -132,8 +144,7 @@ class classtype_route_node(Structure):
 
 class classtype_route(Structure):
    _pack_ = 1
-   _fields_ = [("pstr_name",c_uint32),
-               ("id_start",c_uint32),
+   _fields_ = [("id_start",c_uint32),
                ("colour",c_float*3)]
 
 class classtype_skin(Structure):
@@ -154,8 +165,21 @@ class classtype_bone(Structure):
                ("ik_target",c_uint32),
                ("ik_pole",c_uint32),
                ("collider",c_uint32),
+               ("use_limits",c_uint32),
+               ("angle_limits",(c_float*3)*2),
                ("hitbox",(c_float*3)*2)]
 
+class classtype_achievement_box(Structure):
+   _pack_ = 1
+   _fields_ = [("pstr_name",c_uint32),
+               ("trigger",c_uint32)]
+
+class classtype_audio(Structure):
+   _pack_ = 1
+   _fields_ = [("pstr_file",c_uint32),
+               ("flags",c_uint32),
+               ("volume",c_float)]
+
 # Exporter
 # ==============================================================================
 
@@ -171,6 +195,10 @@ def write_model(collection_name):
    header.node_count = 0
    header.material_count = 0
    header.file_length = 0
+
+   header.strings_length = 0
+   header.entdata_length = 0
+   header.keyframe_count = 0
    
    mesh_cache = {}
    string_cache = {}
@@ -184,10 +212,8 @@ def write_model(collection_name):
    indice_buffer = []
    node_buffer = []
    entdata_buffer = []
-   entdata_length = 0
 
    anim_buffer = []
-   animdata_length = 0
    animdata_buffer = []
 
    def emplace_string( s ):
@@ -203,6 +229,9 @@ def write_model(collection_name):
    def emplace_material( mat ):
       nonlocal material_cache, material_buffer
 
+      if mat == None:
+         return 0
+
       if mat.name in material_cache:
          return material_cache[mat.name]
 
@@ -313,7 +342,11 @@ def write_model(collection_name):
                      _extendb( tree, b, d+1 )
 
             for obj1 in n.children:
-               _extend( tree, obj1, d+1 )
+               nonlocal collection
+               for c1 in obj1.users_collection:
+                  if c1 == collection:
+                     _extend( tree, obj1, d+1 )
+                     break
 
             p["children"] += [tree]
             graph_lookup[n] = tree
@@ -393,7 +426,9 @@ def write_model(collection_name):
          can_use_cache = True
 
          for mod in obj.modifiers:
-            if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP':
+            if mod.type == 'DATA_TRANSFER' or mod.type == 'SHRINKWRAP' or \
+               mod.type == 'BOOLEAN' or mod.type == 'CURVE' or \
+               mod.type == 'ARRAY':
                can_use_cache = False
 
             if mod.type == 'ARMATURE':
@@ -469,7 +504,11 @@ def write_model(collection_name):
                   # WEight groups
                   #
                   if armature_def:
-                     weight_groups = sorted( data.vertices[vi].groups, key = \
+                     src_groups = [_ for _ in data.vertices[vi].groups \
+                                 if obj.vertex_groups[_.group].name in \
+                                    armature_def['bones']]
+
+                     weight_groups = sorted( src_groups, key = \
                                              lambda a: a.weight, reverse=True )
                      tot = 0.0
                      for ml in range(3):
@@ -567,7 +606,7 @@ def write_model(collection_name):
 
       # Process entity data
       # ==================================================================
-      node.offset = entdata_length
+      node.offset = header.entdata_length
 
       if classtype != 'k_classtype_none':
          disptype = classtype
@@ -600,7 +639,7 @@ def write_model(collection_name):
          node.classtype = 12
 
          armature = armature_def['obj']
-         entdata_length += sizeof( classtype_skin )
+         header.entdata_length += sizeof( classtype_skin )
 
          skin = classtype_skin()
          skin.skeleton = armature.cv_data.uid
@@ -608,7 +647,7 @@ def write_model(collection_name):
       
       elif classtype == 'k_classtype_skeleton':
          node.classtype = 11
-         entdata_length += sizeof( classtype_skeleton )
+         header.entdata_length += sizeof( classtype_skeleton )
          skeleton = classtype_skeleton()
 
          armature_def = graph_lookup[obj]
@@ -640,7 +679,7 @@ def write_model(collection_name):
                   anim = mdl_animation()
                   anim.pstr_name = emplace_string( NLAStrip.action.name )
                   anim.rate = 30.0
-                  anim.offset = animdata_length
+                  anim.offset = header.keyframe_count
                   anim.length = anim_end-anim_start
                   
                   # Export the fucking keyframes
@@ -651,12 +690,25 @@ def write_model(collection_name):
                         for pb in armature.pose.bones:
                            if pb.name == bone_name:
                               rb = armature.data.bones[ bone_name ]
-
-                              loc, rot, sca = pb.matrix_basis.decompose()
+                              
+                              # relative bone matrix
+                              if rb.parent is not None:
+                                 offset_mtx = rb.parent.matrix_local
+                                 offset_mtx = offset_mtx.inverted_safe() @ \
+                                              rb.matrix_local
+
+                                 inv_parent = pb.parent.matrix @ offset_mtx
+                                 inv_parent.invert_safe()
+                                 fpm = inv_parent @ pb.matrix 
+                              else:
+                                 bone_mtx = rb.matrix.to_4x4()
+                                 local_inv = rb.matrix_local.inverted_safe()
+                                 fpm = bone_mtx @ local_inv @ pb.matrix
+
+                              loc, rot, sca = fpm.decompose()
 
                               # local position
-                              vp = rb.matrix @ loc
-                              final_pos = Vector(( vp[0], vp[2], -vp[1] ))
+                              final_pos = Vector(( loc[0], loc[2], -loc[1] ))
 
                               # rotation
                               lc_m = pb.matrix_channel.to_3x3()
@@ -681,7 +733,7 @@ def write_model(collection_name):
                               kf.s[2] = sca[1]
 
                               animdata_buffer += [kf]
-                              animdata_length += sizeof(mdl_keyframe)
+                              header.keyframe_count += 1
                               break
 
                   anim_buffer += [anim]
@@ -697,7 +749,7 @@ def write_model(collection_name):
 
       elif classtype == 'k_classtype_bone':
          node.classtype = 10
-         entdata_length += sizeof( classtype_bone )
+         header.entdata_length += sizeof( classtype_bone )
          
          bone = classtype_bone()
          bone.deform = node_def['deform']
@@ -725,12 +777,29 @@ def write_model(collection_name):
             bone.hitbox[1][1] = 0.0
             bone.hitbox[1][2] = 0.0
 
+         if obj.cv_data.con0:
+            bone.use_limits = 1 
+            bone.angle_limits[0][0] =  obj.cv_data.mins[0]
+            bone.angle_limits[0][1] =  obj.cv_data.mins[2]
+            bone.angle_limits[0][2] = -obj.cv_data.maxs[1]
+            bone.angle_limits[1][0] =  obj.cv_data.maxs[0]
+            bone.angle_limits[1][1] =  obj.cv_data.maxs[2]
+            bone.angle_limits[1][2] = -obj.cv_data.mins[1]
+         else:
+            bone.use_limits = 0
+            bone.angle_limits[0][0] = 0.0
+            bone.angle_limits[0][1] = 0.0
+            bone.angle_limits[0][2] = 0.0
+            bone.angle_limits[1][0] = 0.0
+            bone.angle_limits[1][1] = 0.0
+            bone.angle_limits[1][2] = 0.0
+
          bone.deform = node_def['deform']
          entdata_buffer += [bone]
 
       elif classtype == 'k_classtype_gate':
          node.classtype = 1
-         entdata_length += sizeof( classtype_gate )
+         header.entdata_length += sizeof( classtype_gate )
 
          gate = classtype_gate()
          gate.target = 0
@@ -750,7 +819,7 @@ def write_model(collection_name):
 
       elif classtype == 'k_classtype_block':
          node.classtype = 2
-         entdata_length += sizeof( classtype_block )
+         header.entdata_length += sizeof( classtype_block )
 
          source = obj.data.cv_data
 
@@ -764,6 +833,30 @@ def write_model(collection_name):
          block.bbx[1][2] = -source.v0[1]
          entdata_buffer += [block]
 
+      elif classtype == 'k_classtype_achievement_box':
+         node.classtype = 13
+
+         header.entdata_length += sizeof( classtype_achievement_box )
+         ach = classtype_achievement_box()
+         ach.pstr_name = emplace_string( obj.cv_data.strp )
+         ach.trigger = 0
+
+         if obj.cv_data.target != None:
+            ach.trigger = obj.cv_data.target.cv_data.uid
+
+         entdata_buffer += [ach]
+
+      elif classtype == 'k_classtype_audio':
+         node.classtype = 14
+
+         header.entdata_length += sizeof( classtype_audio )
+         aud = classtype_audio()
+         aud.pstr_file = emplace_string( obj.cv_data.strp )
+         aud.flags = obj.cv_data.intp
+         aud.volume = obj.cv_data.fltp
+
+         entdata_buffer += [aud]
+
       elif classtype == 'k_classtype_spawn':
          node.classtype = 3
 
@@ -772,7 +865,7 @@ def write_model(collection_name):
 
       elif classtype == 'k_classtype_car_path':
          node.classtype = 5
-         entdata_length += sizeof( classtype_car_path )
+         header.entdata_length += sizeof( classtype_car_path )
 
          pn = classtype_car_path()
          pn.target = 0
@@ -789,7 +882,7 @@ def write_model(collection_name):
          target = obj.instance_collection
 
          node.classtype = 6
-         entdata_length += sizeof( classtype_instance )
+         header.entdata_length += sizeof( classtype_instance )
 
          inst = classtype_instance()
          inst.pstr_file = emplace_string( F"models/{target.name}.mdl" )
@@ -800,7 +893,7 @@ def write_model(collection_name):
 
       elif classtype == 'k_classtype_route_node':
          node.classtype = 8
-         entdata_length += sizeof( classtype_route_node )
+         header.entdata_length += sizeof( classtype_route_node )
 
          rn = classtype_route_node()
          if obj.cv_data.target != None: 
@@ -812,9 +905,8 @@ def write_model(collection_name):
 
       elif classtype == 'k_classtype_route':
          node.classtype = 9
-         entdata_length += sizeof( classtype_route )
+         header.entdata_length += sizeof( classtype_route )
          r = classtype_route()
-         r.pstr_name = emplace_string("not-implemented")
          r.colour[0] = obj.cv_data.colour[0]
          r.colour[1] = obj.cv_data.colour[1]
          r.colour[2] = obj.cv_data.colour[2]
@@ -832,8 +924,7 @@ def write_model(collection_name):
       node_buffer += [node]
 
    # Write data arrays
-   #
-   header.anim_count = len(anim_buffer)
+   # TODO: 8 BYTE ALIGNMENT
 
    print( "Writing data" )
    fpos = sizeof(header)
@@ -850,13 +941,24 @@ def write_model(collection_name):
    header.material_offset = fpos
    fpos += sizeof(mdl_material)*header.material_count
 
-   print( F"Animation count: {header.anim_count}" )
+   print( F"Animation count: {len(anim_buffer)}" )
+   header.anim_count = len(anim_buffer)
    header.anim_offset = fpos
    fpos += sizeof(mdl_animation)*header.anim_count
 
-   print( F"Entdata length: {entdata_length}" )
+   print( F"Entdata length: {header.entdata_length}" )
    header.entdata_offset = fpos
-   fpos += entdata_length
+   fpos += header.entdata_length
+   
+   print( F"Strings length: {len(strings_buffer)}" )
+   header.strings_offset = fpos
+   header.strings_length = len(strings_buffer)
+   fpos += header.strings_length
+   
+   # Optional array things
+   print( F"Keyframe count: {header.keyframe_count}" )
+   header.keyframe_offset = fpos
+   fpos += sizeof(mdl_keyframe)*header.keyframe_count
    
    print( F"Vertex count: {header.vertex_count}" )
    header.vertex_offset = fpos
@@ -866,14 +968,6 @@ def write_model(collection_name):
    header.indice_offset = fpos
    fpos += sizeof(c_uint32)*header.indice_count
 
-   print( F"Keyframe count: {animdata_length}" )
-   header.animdata_offset = fpos
-   fpos += animdata_length
-   
-   print( F"Strings length: {len(strings_buffer)}" )
-   header.strings_offset = fpos
-   fpos += len(strings_buffer)
-
    header.file_length = fpos
 
    path = F"/home/harry/Documents/carve/models_src/{collection_name}.mdl"
@@ -891,14 +985,17 @@ def write_model(collection_name):
       fp.write( bytearray(a) )
    for ed in entdata_buffer:
       fp.write( bytearray(ed) )
+
+   fp.write( strings_buffer )
+
+   for kf in animdata_buffer:
+      fp.write( bytearray(kf) )
+
    for v in vertex_buffer:
       fp.write( bytearray(v) )
    for i in indice_buffer:
       fp.write( bytearray(i) )
-   for kf in animdata_buffer:
-      fp.write( bytearray(kf) )
 
-   fp.write( strings_buffer )
    fp.close()
 
    print( F"Completed {collection_name}.mdl" )
@@ -992,7 +1089,7 @@ def cv_draw():
    for obj in bpy.context.collection.objects:
       if obj.type == 'ARMATURE':
          for bone in obj.data.bones:
-            if bone.cv_data.collider:
+            if bone.cv_data.collider and obj.data.pose_position == 'REST':
                c = bone.head_local
                a = bone.cv_data.v0
                b = bone.cv_data.v1
@@ -1108,6 +1205,42 @@ def cv_draw():
                obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
          drawbline( obj.location, p1, sw,sw2 )
 
+      elif obj.cv_data.classtype == 'k_classtype_achievement_box':
+         a = Vector((-1,-1,-1))
+         b = Vector((1,1,1))
+         
+         vs = [None]*8
+         vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
+         vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
+         vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
+         vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
+         vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
+         vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
+         vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
+         vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
+
+         indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
+                    (0,4),(1,5),(2,6),(3,7)]
+
+         for l in indices:
+            v0 = vs[l[0]]
+            v1 = vs[l[1]]
+            verts += [(v0[0],v0[1],v0[2])]
+            verts += [(v1[0],v1[1],v1[2])]
+            colours += [(0,1,0,1),(0,1,0,1)]
+
+         if obj.cv_data.target != None:
+            vs = [None]*2
+            vs[0] = obj.location
+            vs[1] = obj.cv_data.target.location
+            indices = [(0,1)]
+            for l in indices:
+               v0 = vs[l[0]]
+               v1 = vs[l[1]]
+               verts += [(v0[0],v0[1],v0[2])]
+               verts += [(v1[0],v1[1],v1[2])]
+               colours += [(0,1,1,1),(0,1,1,1)]
+
 
       elif obj.cv_data.classtype == 'k_classtype_block':
          a = obj.data.cv_data.v0
@@ -1286,6 +1419,10 @@ class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
    uid: bpy.props.IntProperty( name="" )
 
+   strp: bpy.props.StringProperty( name="strp" )
+   intp: bpy.props.IntProperty( name="intp" )
+   fltp: bpy.props.FloatProperty( name="fltp" )
+
    target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
          poll=cv_poll_target )
    target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
@@ -1309,7 +1446,9 @@ class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
       ('k_classtype_route', "k_classtype_route", "", 9 ),
       ('k_classtype_bone',"k_classtype_bone","",10),
       ('k_classtype_SKELETON', "","", 11 ),
-      ('k_classtype_SKIN',"","",12)
+      ('k_classtype_SKIN',"","",12),
+      ('k_classtype_achievement_box',"k_classtype_achievement_box","",13),
+      ('k_classtype_audio',"k_classtype_audio","",14),
       ])
 
 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
@@ -1317,17 +1456,10 @@ class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
    v0: bpy.props.FloatVectorProperty(name="v0",size=3)
    v1: bpy.props.FloatVectorProperty(name="v1",size=3)
 
+   con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
    mins: bpy.props.FloatVectorProperty(name="mins",size=3)
    maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
 
-   con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
-   c0: bpy.props.FloatVectorProperty(name="dir",size=3)
-   s0: bpy.props.FloatVectorProperty(name="limits",size=3)
-
-   con1: bpy.props.BoolProperty(name="Constriant 1",default=False)
-   c1: bpy.props.FloatVectorProperty(name="dir",size=3)
-   s1: bpy.props.FloatVectorProperty(name="limits",size=3)
-
 class CV_BONE_PANEL(bpy.types.Panel):
    bl_label="Bone Config"
    bl_idname="SCENE_PT_cv_bone"
@@ -1347,16 +1479,9 @@ class CV_BONE_PANEL(bpy.types.Panel):
       _.layout.prop( bone.cv_data, "v1" )
 
       _.layout.label( text="Angle Limits" )
+      _.layout.prop( bone.cv_data, "con0" )
       _.layout.prop( bone.cv_data, "mins" )
       _.layout.prop( bone.cv_data, "maxs" )
-      
-      _.layout.prop( bone.cv_data, "con0" )
-      _.layout.prop( bone.cv_data, "c0" )
-      _.layout.prop( bone.cv_data, "s0" )
-
-      _.layout.prop( bone.cv_data, "con1" )
-      _.layout.prop( bone.cv_data, "c1" )
-      _.layout.prop( bone.cv_data, "s1" )
 
 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
    use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
@@ -1401,6 +1526,13 @@ class CV_OBJ_PANEL(bpy.types.Panel):
          mesh = active_object.data
          _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
          _.layout.prop( mesh.cv_data, "v0" )
+      elif active_object.cv_data.classtype == 'k_classtype_achievement_box':
+         _.layout.prop( active_object.cv_data, "strp" )
+         _.layout.prop( active_object.cv_data, "target" )
+      elif active_object.cv_data.classtype == 'k_classtype_audio':
+         _.layout.prop( active_object.cv_data, "strp" )
+         _.layout.prop( active_object.cv_data, "intp" )
+         _.layout.prop( active_object.cv_data, "fltp" )
 
 class CV_INTERFACE(bpy.types.Panel):
    bl_idname = "VIEW3D_PT_carve"