oh yeah mr crabs
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index 3f2dd793d04ff12d7f8e9091a0189a9d3530cb79..c069d7f925fa5412562eadee4e9bc5e27dd1e38e 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 *
@@ -132,8 +136,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):
@@ -144,18 +147,30 @@ class classtype_skeleton(Structure):
    _pack_ = 1
    _fields_ = [("channels",c_uint32),
                ("ik_count",c_uint32),
+               ("collider_count",c_uint32),
                ("anim_start",c_uint32),
                ("anim_count",c_uint32)]
 
 class classtype_bone(Structure):
    _pack_ = 1
-   _fields_ = [("deform",c_uint32)]
+   _fields_ = [("deform",c_uint32),
+               ("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_ik_bone(Structure):
+class classtype_audio(Structure):
    _pack_ = 1
-   _fields_ = [("deform",c_uint32),
-               ("target",c_uint32),
-               ("pole",c_uint32)]
+   _fields_ = [("pstr_file",c_uint32),
+               ("flags",c_uint32),
+               ("volume",c_float)]
 
 # Exporter
 # ==============================================================================
@@ -204,6 +219,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]
 
@@ -281,6 +299,7 @@ def write_model(collection_name):
             if n.type == 'ARMATURE':
                tree["bones"] = [None] # None is the root transform
                tree["ik_count"] = 0
+               tree["collider_count"] = 0
 
                def _extendb( p, n, d ):
                   nonlocal tree
@@ -302,6 +321,9 @@ def write_model(collection_name):
                         btree["pole"] = c.pole_subtarget
                         tree["ik_count"] += 1
 
+                  if n.cv_data.collider:
+                     tree['collider_count'] += 1
+
                   btree['deform'] = n.use_deform
                   p['children'] += [btree]
 
@@ -310,7 +332,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
@@ -371,10 +397,7 @@ def write_model(collection_name):
          node.parent = node_def["parent"]["uid"]
 
       if objt == 'BONE':
-         if 'target' in node_def:
-            classtype = 'k_classtype_ik_bone'
-         else:
-            classtype = 'k_classtype_bone'
+         classtype = 'k_classtype_bone'
       elif objt == 'ARMATURE':
          classtype = 'k_classtype_skeleton'
       else:
@@ -393,12 +416,16 @@ 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':
                classtype = 'k_classtype_skin'
                armature_def = graph_lookup[mod.object]
+               POSE_OR_REST_CACHE = armature_def['obj'].data.pose_position
+
                armature_def['obj'].data.pose_position = 'REST'
 
          if can_use_cache and obj.data.name in mesh_cache:
@@ -467,7 +494,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):
@@ -580,7 +611,7 @@ def write_model(collection_name):
       s005 = ""
 
       if classtype == 'k_classtype_skin':
-         armature_def['obj'].data.pose_position = 'POSE'
+         armature_def['obj'].data.pose_position = POSE_OR_REST_CACHE
          s005 = F" [armature -> {armature_def['obj'].cv_data.uid}]"
 
       scmp = F"{s002:<32} {s003:<22} {s004} {s005}"
@@ -614,6 +645,7 @@ def write_model(collection_name):
          bones = armature_def['bones']
          skeleton.channels = len(bones)
          skeleton.ik_count = armature_def["ik_count"]
+         skeleton.collider_count = armature_def["collider_count"]
          
          if armature.animation_data:
             previous_frame = bpy.context.scene.frame_current
@@ -648,12 +680,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()
@@ -698,18 +743,49 @@ def write_model(collection_name):
          
          bone = classtype_bone()
          bone.deform = node_def['deform']
-         entdata_buffer += [bone]
-
-      elif classtype == 'k_classtype_ik_bone':
-         node.classtype = 13
-         entdata_length += sizeof( classtype_ik_bone )
          
-         ikbone = classtype_ik_bone()
-         ikbone.target = armature_def['bones'].index( node_def['target'] )
-         ikbone.pole   = armature_def['bones'].index( node_def['pole'] )
-         ikbone.deform = node_def['deform']
+         if 'target' in node_def:
+            bone.ik_target = armature_def['bones'].index( node_def['target'] )
+            bone.ik_pole   = armature_def['bones'].index( node_def['pole'] )
+         else:
+            bone.ik_target = 0
+            bone.ik_pole = 0
+
+         bone.collider = 1 if obj.cv_data.collider else 0
+         if obj.cv_data.collider:
+            bone.hitbox[0][0] =  obj.cv_data.v0[0]
+            bone.hitbox[0][1] =  obj.cv_data.v0[2]
+            bone.hitbox[0][2] = -obj.cv_data.v1[1]
+            bone.hitbox[1][0] =  obj.cv_data.v1[0]
+            bone.hitbox[1][1] =  obj.cv_data.v1[2]
+            bone.hitbox[1][2] = -obj.cv_data.v0[1]
+         else:
+            bone.hitbox[0][0] = 0.0
+            bone.hitbox[0][1] = 0.0
+            bone.hitbox[0][2] = 0.0
+            bone.hitbox[1][0] = 0.0
+            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
 
-         entdata_buffer += [ikbone]
+         bone.deform = node_def['deform']
+         entdata_buffer += [bone]
 
       elif classtype == 'k_classtype_gate':
          node.classtype = 1
@@ -747,6 +823,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
+
+         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
+
+         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
 
@@ -797,7 +897,6 @@ def write_model(collection_name):
          node.classtype = 9
          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]
@@ -973,6 +1072,76 @@ def cv_draw():
       colours += [c0,c1]
 
    for obj in bpy.context.collection.objects:
+      if obj.type == 'ARMATURE':
+         for bone in obj.data.bones:
+            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
+               
+               vs = [None]*8
+               vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
+               vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
+               vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
+               vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
+               vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
+               vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
+               vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
+               vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+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.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
+
+               center=obj.matrix_world@c
+               
+               def _angle_lim( major, minor, amin, amax, colour ):
+                  nonlocal verts, colours
+                  f = 0.05
+                  ay = major*f
+                  ax = minor*f
+
+                  for x in range(16):
+                     t0 = x/16
+                     t1 = (x+1)/16
+                     a0 = amin*(1.0-t0)+amax*t0
+                     a1 = amin*(1.0-t1)+amax*t1
+
+                     p0 = c + major*f*math.cos(a0) + minor*f*math.sin(a0)
+                     p1 = c + major*f*math.cos(a1) + minor*f*math.sin(a1)
+
+                     p0=obj.matrix_world @ p0
+                     p1=obj.matrix_world @ p1
+                     verts += [p0,p1]
+                     colours += [colour,colour]
+
+                     if x == 0:
+                        verts += [p0,c]
+                        colours += [colour,colour]
+                     if x == 15:
+                        verts += [p1,c]
+                        colours += [colour,colour]
+
+                  verts += [c+major*1.2*f,c+major*f*0.8]
+                  colours += [colour,colour]
+                  
+               if bone.cv_data.con0:
+                  _angle_lim( Vector((0,1,0)),Vector((0,0,1)), \
+                              bone.cv_data.mins[0], bone.cv_data.maxs[0], \
+                              (1,0,0,1))
+                  _angle_lim( Vector((0,0,1)),Vector((1,0,0)), \
+                              bone.cv_data.mins[1], bone.cv_data.maxs[1], \
+                              (0,1,0,1))
+                  _angle_lim( Vector((1,0,0)),Vector((0,1,0)), \
+                              bone.cv_data.mins[2], bone.cv_data.maxs[2], \
+                              (0,0,1,1))
+               
 
       if obj.cv_data.classtype == 'k_classtype_gate':
          if obj.type == 'MESH':
@@ -1021,6 +1190,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
@@ -1199,6 +1404,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", \
@@ -1222,9 +1431,43 @@ 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):
+   collider: bpy.props.BoolProperty(name="Collider",default=False)
+   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)
+
+class CV_BONE_PANEL(bpy.types.Panel):
+   bl_label="Bone Config"
+   bl_idname="SCENE_PT_cv_bone"
+   bl_space_type='PROPERTIES'
+   bl_region_type='WINDOW'
+   bl_context='bone'
+
+   def draw(_,context):
+      active_object = context.active_object
+      if active_object == None: return
+
+      bone = active_object.data.bones.active
+      if bone == None: return
+
+      _.layout.prop( bone.cv_data, "collider" )
+      _.layout.prop( bone.cv_data, "v0" )
+      _.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" )
+
 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
    use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
 
@@ -1268,6 +1511,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"
@@ -1300,7 +1550,8 @@ class CV_COMPILE(bpy.types.Operator):
       return {'FINISHED'}
 
 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
-           CV_MESH_SETTINGS, CV_SCENE_SETTINGS]
+           CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
+           CV_BONE_PANEL]
 
 def register():
    global cv_view_draw_handler
@@ -1311,6 +1562,7 @@ def register():
    bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS)
    bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
    bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
+   bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
 
    cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
       cv_draw,(),'WINDOW','POST_VIEW')