isolate world code
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index b6ca3e7208c71d054a869d3b65eeae01902907af..d6da9c2864f358c8be0ee5f51dc7473ac9d4c114 100644 (file)
@@ -2,7 +2,7 @@
 # =============================================================================
 # 
 # Copyright  .        . .       -----, ,----- ,---.   .---.
-# 2021-2022  |\      /| |           /  |      |    | |    /|
+# 2021-2023  |\      /| |           /  |      |    | |    /|
 #            | \    / | +--        /   +----- +---'  |   / |
 #            |  \  /  | |         /    |      |   \  |  /  |
 #            |   \/   | |        /     |      |    \ | /   |
@@ -548,13 +548,14 @@ class classtype_skeleton(Structure):
 class classtype_bone(Structure):
 #{
    _pack_ = 1
-   _fields_ = [("deform",c_uint32),
+   _fields_ = [("flags",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)]
+               ("hitbox",(c_float*3)*2),
+               ("conevx",c_float*3),
+               ("conevy",c_float*3),
+               ("coneva",c_float*3),
+               ("conet",c_float)]
 
    def encode_obj(_, node,node_def):
    #{
@@ -563,19 +564,24 @@ class classtype_bone(Structure):
       armature_def = node_def['linked_armature']
       obj = node_def['bone']
       
-      _.deform = node_def['deform']
+      _.flags = node_def['deform']
       
       if 'ik_target' in node_def:
       #{
+         _.flags |= 0x2
          _.ik_target = armature_def['bones'].index( node_def['ik_target'] )
          _.ik_pole   = armature_def['bones'].index( node_def['ik_pole'] )
       #}
       
       # For ragdolls
       #
-      if obj.cv_data.collider:
+      if obj.cv_data.collider != 'collider_none':
       #{
-         _.collider = 1
+         if obj.cv_data.collider == 'collider_box':
+            _.flags |= 0x4
+         else:
+            _.flags |= 0x8
+
          _.hitbox[0][0] =  obj.cv_data.v0[0]
          _.hitbox[0][1] =  obj.cv_data.v0[2]
          _.hitbox[0][2] = -obj.cv_data.v1[1]
@@ -586,13 +592,17 @@ class classtype_bone(Structure):
 
       if obj.cv_data.con0:
       #{
-         _.use_limits = 1 
-         _.angle_limits[0][0] =  obj.cv_data.mins[0]
-         _.angle_limits[0][1] =  obj.cv_data.mins[2]
-         _.angle_limits[0][2] = -obj.cv_data.maxs[1]
-         _.angle_limits[1][0] =  obj.cv_data.maxs[0]
-         _.angle_limits[1][1] =  obj.cv_data.maxs[2]
-         _.angle_limits[1][2] = -obj.cv_data.mins[1]
+         _.flags |= 0x100
+         _.conevx[0] =  obj.cv_data.conevx[0]
+         _.conevx[1] =  obj.cv_data.conevx[2]
+         _.conevx[2] = -obj.cv_data.conevx[1]
+         _.conevy[0] =  obj.cv_data.conevy[0]
+         _.conevy[1] =  obj.cv_data.conevy[2]
+         _.conevy[2] = -obj.cv_data.conevy[1]
+         _.coneva[0] =  obj.cv_data.coneva[0]
+         _.coneva[1] =  obj.cv_data.coneva[2]
+         _.coneva[2] = -obj.cv_data.coneva[1]
+         _.conet = obj.cv_data.conet
       #}
    #}
 #}
@@ -761,6 +771,61 @@ class classtype_audio(Structure):
    #}
 #}
 
+# Classtype 200
+# 
+#  Purpose: point light
+#
+class classtype_point_light(Structure):
+#{
+   _pack_ = 1
+   _fields_ = [("colour",c_float*4)]
+
+   def encode_obj(_, node, node_def):
+   #{
+      node.classtype = 200
+
+      data = node_def['obj'].data
+      _.colour[0] = data.color[0]
+      _.colour[1] = data.color[1]
+      _.colour[2] = data.color[2]
+      _.colour[3] = data.energy
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      pass
+   #}
+#}
+
+# Classtype 201
+# 
+#  Purpose: lighting settings for world
+#
+class classtype_lighting_info(Structure):
+#{
+   _pack_ = 1
+   _fields_ = [("colours",(c_float*3)*3),
+               ("directions",(c_float*2)*3),
+               ("states",c_uint32*3),
+               ("shadow_spread",c_float),
+               ("shadow_length",c_float),
+               ("ambient",c_float*3)]
+
+   def encode_obj(_, node, node_def):
+   #{
+      node.classtype = 201
+
+      # TODO
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      pass
+   #}
+#}
+
 class classtype_spawn_link(Structure):
 #{
    _pack_ = 1
@@ -1009,8 +1074,8 @@ cxr_graph_mapping = \
          },
          "Mix":
          {
-            "Color1": material_tex_image("tex_diffuse"),
-            "Color2": material_tex_image("tex_decal")
+            "A": material_tex_image("tex_diffuse"),
+            "B": material_tex_image("tex_decal")
          },
       },
       "Normal":
@@ -1063,9 +1128,20 @@ def material_info(mat):
 
          if isinstance( link_def, dict ):
          #{
-            node_link = node.inputs[link]
+            node_link = None
+            for x in node.inputs:
+            #{
+               if isinstance( x, bpy.types.NodeSocketColor ):
+               #{
+                  if link == x.name:
+                  #{
+                     node_link = x
+                     break
+                  #}
+               #}
+            #}
 
-            if node_link.is_linked:
+            if node_link and node_link.is_linked:
             #{
                # look for definitions for the connected node type
                #
@@ -1124,8 +1200,11 @@ def encoder_process_material( mat ):
    dest.pstr_name = encoder_process_pstr( mat.name )
    
    flags = 0x00
-   if mat.cv_data.skate_surface: flags |= 0x1
-   if mat.cv_data.collision: flags |= 0x2
+   if mat.cv_data.collision: 
+      flags |= 0x2
+      if mat.cv_data.skate_surface: flags |= 0x1
+      if mat.cv_data.grind_surface: flags |= (0x8|0x1)
+
    if mat.cv_data.grow_grass: flags |= 0x4
    dest.flags = flags
 
@@ -1217,6 +1296,8 @@ def encoder_build_scene_graph( collection ):
 
       def _extend( p, n, d ):
       #{
+         nonlocal collection
+
          uid = _new_uid()
          tree = {}
          tree["uid"] = uid
@@ -1225,7 +1306,7 @@ def encoder_build_scene_graph( collection ):
          tree["obj"] = n
          tree["parent"] = p
          n.cv_data.uid = uid
-         
+
          # Descend into amature
          #
          if n.type == 'ARMATURE':
@@ -1233,6 +1314,7 @@ def encoder_build_scene_graph( collection ):
             tree["bones"] = [None] # None is the root transform
             tree["ik_count"] = 0
             tree["collider_count"] = 0
+            tree["compile_animation"] = collection.cv_data.animations
             
             # Here also collects some information about constraints, ik and 
             # counts colliders for the armature.
@@ -1265,7 +1347,7 @@ def encoder_build_scene_graph( collection ):
                   #}
                #}
 
-               if n.cv_data.collider:
+               if n.cv_data.collider != 'collider_none':
                   tree['collider_count'] += 1
 
                btree['deform'] = n.use_deform
@@ -1281,7 +1363,6 @@ def encoder_build_scene_graph( collection ):
          #
          for obj1 in n.children:
          #{
-            nonlocal collection
             for c1 in obj1.users_collection:
             #{
                if c1 == collection:
@@ -1336,18 +1417,18 @@ def encoder_vertex_push( vertex_reference, co,norm,uv,colour,groups,weights ):
           int(norm[2]*m+0.5),
           int(uv[0]*m+0.5),
           int(uv[1]*m+0.5),
-          colour[0]*m+0.5,    # these guys are already quantized
-          colour[1]*m+0.5,    # .
-          colour[2]*m+0.5,    # .
-          colour[3]*m+0.5,    # .
-          weights[0]*m+0.5,   # v
-          weights[1]*m+0.5,
-          weights[2]*m+0.5,
-          weights[3]*m+0.5,
-          groups[0]*m+0.5,
-          groups[1]*m+0.5,
-          groups[2]*m+0.5,
-          groups[3]*m+0.5)
+          colour[0],    # these guys are already quantized
+          colour[1],    # .
+          colour[2],    # .
+          colour[3],    # .
+          weights[0],   # v
+          weights[1],
+          weights[2],
+          weights[3],
+          groups[0],
+          groups[1],
+          groups[2],
+          groups[3])
 
    if key in vertex_reference:
       return vertex_reference[key]
@@ -1535,6 +1616,20 @@ def encoder_compile_mesh( node, node_def ):
                      weights[ml] = max( weights[ml], 0 )
                   #}
                #}
+            #}
+            else:
+            #{
+               li1 = tri.loops[(j+1)%3]
+               vi1 = data.loops[li1].vertex_index
+               e0 = data.edges[ data.loops[li].edge_index ]
+
+               if e0.use_freestyle_mark and \
+                     ((e0.vertices[0] == vi and e0.vertices[1] == vi1) or \
+                      (e0.vertices[0] == vi1 and e0.vertices[1] == vi)):
+               #{
+                  weights[0] = 1
+               #}
+            #}
             
             # Add vertex and expand bound box
             #
@@ -1629,7 +1724,12 @@ def encoder_compile_armature( node, node_def ):
    # extra info
    node_def['anim_start'] = len(animdata)
    node_def['anim_count'] = 0
-   
+   if not node_def['compile_animation']:
+   #{
+      return
+   #}  
+
    # Compile anims
    #
    if obj.animation_data:
@@ -1771,6 +1871,11 @@ def encoder_process_definition( node_def ):
 
       if obj_type == 'ARMATURE':
          obj_classtype = 'classtype_skeleton'
+      elif obj_type == 'LIGHT':
+      #{
+         if obj.data.type == 'POINT':
+            obj_classtype = 'classtype_point_light'
+      #}
       else:
       #{
          obj_classtype = obj.cv_data.classtype
@@ -1943,6 +2048,9 @@ def write_model(collection_name):
 #{
    global g_encoder
    print( F"Model graph | Create mode '{collection_name}'" )
+   folder = bpy.path.abspath(bpy.context.scene.cv_data.export_dir)
+   path = F"{folder}{collection_name}.mdl"
+   print( path )
    
    collection = bpy.data.collections[collection_name]
 
@@ -1958,8 +2066,6 @@ def write_model(collection_name):
 
    # Write 
    #
-   # TODO HOLY
-   path = F"/home/harry/Documents/carve/models_src/{collection_name}.mdl"
    encoder_write_to_file( path )
 
    print( F"Completed {collection_name}.mdl" )
@@ -2012,6 +2118,44 @@ def cv_draw_sphere( pos, radius, colour ):
    cv_draw_lines()
 #}
 
+# Draw axis alligned sphere at position with radius
+#
+def cv_draw_halfsphere( pos, tx, ty, tz, radius, colour ):
+#{
+   global cv_view_verts, cv_view_colours
+   
+   ly = pos + tz*radius
+   lx = pos + ty*radius
+   lz = pos + tz*radius
+   
+   pi = 3.14159265358979323846264
+
+   for i in range(16):
+   #{
+      t = ((i+1.0) * 1.0/16.0) * pi
+      s = math.sin(t)
+      c = math.cos(t)
+
+      s1 = math.sin(t*2.0)
+      c1 = math.cos(t*2.0)
+
+      py = pos + s*tx*radius +                c *tz*radius
+      px = pos + s*tx*radius + c *ty*radius 
+      pz = pos +               s1*ty*radius + c1*tz*radius
+
+      cv_view_verts += [ px, lx ]
+      cv_view_verts += [ py, ly ]
+      cv_view_verts += [ pz, lz ]
+
+      cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
+
+      ly = py
+      lx = px
+      lz = pz
+   #}
+   cv_draw_lines()
+#}
+
 # Draw transformed -1 -> 1 cube
 #
 def cv_draw_ucube( transform, colour ):
@@ -2067,9 +2211,9 @@ def cv_draw_line2( p0, p1, c0, c1 ):
    cv_draw_lines()
 #}
 
-# Just the tx because we dont really need ty for this app
+# 
 #
-def cv_tangent_basis_tx( n, tx ):
+def cv_tangent_basis( n, tx, ty ):
 #{
    if abs( n[0] ) >= 0.57735027:
    #{
@@ -2085,6 +2229,11 @@ def cv_tangent_basis_tx( n, tx ):
    #}
 
    tx.normalize()
+   _ty = n.cross( tx )
+
+   ty[0] = _ty[0]
+   ty[1] = _ty[1]
+   ty[2] = _ty[2]
 #}
 
 # Draw coloured arrow
@@ -2098,7 +2247,8 @@ def cv_draw_arrow( p0, p1, c0 ):
    n.normalize()
 
    tx = Vector((1,0,0))
-   cv_tangent_basis_tx( n, tx )
+   ty = Vector((1,0,0))
+   cv_tangent_basis( n, tx, ty )
    
    cv_view_verts += [p0,p1, midpt+(tx-n)*0.15,midpt, midpt+(-tx-n)*0.15,midpt ]
    cv_view_colours += [c0,c0,c0,c0,c0,c0]
@@ -2231,19 +2381,61 @@ def draw_limit( obj, center, major, minor, amin, amax, colour ):
    cv_draw_lines()
 #}
 
+# Cone and twist limit
+#
+def draw_cone_twist( center, vx, vy, va ):
+#{
+   global cv_view_verts, cv_view_colours
+   axis = vy.cross( vx )
+   axis.normalize()
+
+   size = 0.12
+
+   cv_view_verts += [center, center+va*size]
+   cv_view_colours += [ (1,1,1,1), (1,1,1,1) ]
+
+   for x in range(32):
+   #{
+      t0 = (x/32) * math.tau
+      t1 = ((x+1)/32) * math.tau
+
+      c0 = math.cos(t0)
+      s0 = math.sin(t0)
+      c1 = math.cos(t1)
+      s1 = math.sin(t1)
+      
+      p0 = center + (axis + vx*c0 + vy*s0).normalized() * size
+      p1 = center + (axis + vx*c1 + vy*s1).normalized() * size
+
+      col0 = ( abs(c0), abs(s0), 0.0, 1.0 )
+      col1 = ( abs(c1), abs(s1), 0.0, 1.0 )
+
+      cv_view_verts += [center, p0, p0, p1]
+      cv_view_colours += [ (0,0,0,0), col0, col0, col1 ]
+   #}
+
+   cv_draw_lines()
+#}
+
 # Draws constraints and stuff for the skeleton. This isnt documented and wont be
 #
 def draw_skeleton_helpers( obj ):
 #{
    global cv_view_verts, cv_view_colours
 
+   if obj.data.pose_position != 'REST':
+   #{
+      return
+   #}
+
    for bone in obj.data.bones:
    #{
-      if bone.cv_data.collider and (obj.data.pose_position == 'REST'):
+      c = bone.head_local
+      a = Vector((bone.cv_data.v0[0], bone.cv_data.v0[1], bone.cv_data.v0[2]))
+      b = Vector((bone.cv_data.v1[0], bone.cv_data.v1[1], bone.cv_data.v1[2]))
+
+      if bone.cv_data.collider == 'collider_box':
       #{
-         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]))
@@ -2267,20 +2459,67 @@ def draw_skeleton_helpers( obj ):
             cv_view_verts += [(v1[0],v1[1],v1[2])]
             cv_view_colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
          #}
+      #}
+      elif bone.cv_data.collider == 'collider_capsule':
+      #{
+         v0 = b-a
+         major_axis = 0
+         largest = -1.0
 
-         center = obj.matrix_world @ c
-         if bone.cv_data.con0:
+         for i in range(3):
          #{
-            draw_limit( obj, c, Vector((0,1,0)),Vector((0,0,1)), \
-                        bone.cv_data.mins[0], bone.cv_data.maxs[0], \
-                        (1,0,0,1))
-            draw_limit( obj, c, Vector((0,0,1)),Vector((1,0,0)), \
-                        bone.cv_data.mins[1], bone.cv_data.maxs[1], \
-                        (0,1,0,1))
-            draw_limit( obj, c, Vector((1,0,0)),Vector((0,1,0)), \
-                        bone.cv_data.mins[2], bone.cv_data.maxs[2], \
-                        (0,0,1,1))
+            if abs(v0[i]) > largest:
+            #{
+               largest = abs(v0[i])
+               major_axis = i
+            #}
          #}
+
+         v1 = Vector((0,0,0))
+         v1[major_axis] = 1.0
+
+         tx = Vector((0,0,0))
+         ty = Vector((0,0,0))
+
+         cv_tangent_basis( v1, tx, ty )
+         r = (abs(tx.dot( v0 )) + abs(ty.dot( v0 ))) * 0.25
+         l = v0[ major_axis ] - r*2
+
+         p0 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l*-0.5 )
+         p1 = obj.matrix_world@Vector( c + (a+b)*0.5 + v1*l* 0.5 )
+
+         colour = [0.2,0.2,0.2,1.0]
+         colour[major_axis] = 0.5
+
+         cv_draw_halfsphere( p0, -v1, ty, tx, r, colour )
+         cv_draw_halfsphere( p1,  v1, ty, tx, r, colour )
+         cv_draw_line( p0+tx* r, p1+tx* r, colour )
+         cv_draw_line( p0+tx*-r, p1+tx*-r, colour )
+         cv_draw_line( p0+ty* r, p1+ty* r, colour )
+         cv_draw_line( p0+ty*-r, p1+ty*-r, colour )
+      #}
+      else:
+      #{
+         continue
+      #}
+
+      center = obj.matrix_world @ c
+      if bone.cv_data.con0:
+      #{
+         vx = Vector([bone.cv_data.conevx[_] for _ in range(3)])
+         vy = Vector([bone.cv_data.conevy[_] for _ in range(3)])
+         va = Vector([bone.cv_data.coneva[_] for _ in range(3)])
+         draw_cone_twist( center, vx, vy, va )
+
+         #draw_limit( obj, c, Vector((0,0,1)),Vector((0,-1,0)), \
+         #            bone.cv_data.mins[0], bone.cv_data.maxs[0], \
+         #            (1,0,0,1))
+         #draw_limit( obj, c, Vector((0,-1,0)),Vector((1,0,0)), \
+         #            bone.cv_data.mins[1], bone.cv_data.maxs[1], \
+         #            (0,1,0,1))
+         #draw_limit( obj, c, Vector((1,0,0)),Vector((0,0,1)), \
+         #            bone.cv_data.mins[2], bone.cv_data.maxs[2], \
+         #            (0,0,1,1))
       #}
    #}
 #}
@@ -2399,13 +2638,25 @@ class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
 
 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
 #{
-   collider: bpy.props.BoolProperty(name="Collider",default=False)
+   collider: bpy.props.EnumProperty(
+      name="Collider Type", 
+      items = [
+      ('collider_none', "collider_none", "", 0),
+      ('collider_box', "collider_box", "", 1),
+      ('collider_capsule', "collider_capsule", "", 2),
+      ])
+
    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)
+
+   conevx: bpy.props.FloatVectorProperty(name="conevx",size=3)
+   conevy: bpy.props.FloatVectorProperty(name="conevy",size=3)
+   coneva: bpy.props.FloatVectorProperty(name="coneva",size=3)
+   conet:  bpy.props.FloatProperty(name="conet")
 #}
 
 class CV_BONE_PANEL(bpy.types.Panel):
@@ -2430,8 +2681,11 @@ class CV_BONE_PANEL(bpy.types.Panel):
 
       _.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, "conevx" )
+      _.layout.prop( bone.cv_data, "conevy" )
+      _.layout.prop( bone.cv_data, "coneva" )
+      _.layout.prop( bone.cv_data, "conet" )
    #}
 #}
 
@@ -2444,6 +2698,7 @@ class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
 class CV_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
 #{
    pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
+   animations:    bpy.props.BoolProperty( name="Export animation", default=True)
 #}
 
 class CV_MATERIAL_SETTINGS(bpy.types.PropertyGroup):
@@ -2476,6 +2731,11 @@ class CV_MATERIAL_SETTINGS(bpy.types.PropertyGroup):
          default=True,\
          description = "Should the game try to target this surface?" \
    )
+   grind_surface: bpy.props.BoolProperty( \
+         name="Grind Surface", \
+         default=False,\
+         description = "Grind face?" \
+   )
    grow_grass: bpy.props.BoolProperty( \
          name="Grow Grass", \
          default=False,\
@@ -2528,12 +2788,19 @@ class CV_MATERIAL_PANEL(bpy.types.Panel):
 
       info = material_info( active_mat )
 
+      if 'tex_diffuse' in info:
+      #{
+         _.layout.label( icon='INFO', \
+            text=F"{info['tex_diffuse'].name} will be compiled" )
+      #}
+
       _.layout.prop( active_mat.cv_data, "shader" )
       _.layout.prop( active_mat.cv_data, "surface_prop" )
       _.layout.prop( active_mat.cv_data, "collision" )
 
       if active_mat.cv_data.collision:
          _.layout.prop( active_mat.cv_data, "skate_surface" )
+         _.layout.prop( active_mat.cv_data, "grind_surface" )
          _.layout.prop( active_mat.cv_data, "grow_grass" )
 
       if active_mat.cv_data.shader == "terrain_blend":
@@ -2658,6 +2925,7 @@ class CV_INTERFACE(bpy.types.Panel):
       #{
          box.label( text=col.name + ".mdl" )
          box.prop( col.cv_data, "pack_textures" )
+         box.prop( col.cv_data, "animations" )
          box.operator( "carve.compile_this" )
       #}
       else: