largely loadable model assets
[convexer.git] / __init__.py
index 9eba53c76808714d79414d3a69485091795a47ea..8d9254e69b49a1ba2e5971712fbc4f2f4bb173a8 100644 (file)
@@ -53,6 +53,13 @@ cxr_jobs_batch = None
 cxr_jobs_inf = []
 cxr_error_inf = None
 
+cxr_asset_lib = \
+{
+   "models": {},
+   "materials": {},
+   "textures": {}
+}
+
 # Shaders
 cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
 
@@ -96,7 +103,7 @@ void main()
    vec3 worldPos = pWorldPos.xyz;
 
    gl_Position = viewProjectionMatrix * pWorldPos;
-   lNormal = aNormal; //mat3(transpose(inverse(modelMatrix))) * aNormal;
+   lNormal = normalize(mat3(transpose(inverse(modelMatrix))) * aNormal);
    lPos = worldPos;
 }
 ""","""
@@ -419,6 +426,33 @@ class cxr_vmf_context(Structure):
                ("entity_count",c_int32),
                ("face_count",c_int32)]
 
+# Valve wrapper types
+class fs_locator(Structure):
+   _fields_ = [("vpk_entry",c_void_p),
+               ("path",c_char_p*1024)]
+
+class valve_material(Structure):
+   _fields_ = [("basetexture",c_char_p),
+               ("bumpmap",c_char_p)]
+
+class valve_model_batch(Structure):
+   _fields_ = [("material",c_uint32),
+               ("ibstart",c_uint32),
+               ("ibcount",c_uint32)]
+
+class valve_model(Structure):
+   _fields_ = [("vertex_data",POINTER(c_float)),
+               ("indices",POINTER(c_uint32)),
+               ("indices_count",c_uint32),
+               ("vertex_count",c_uint32),
+               ("part_count",c_uint32),
+               ("material_count",c_uint32),
+               ("materials",POINTER(c_char_p)),
+               ("parts",POINTER(valve_model_batch)),
+               ("studiohdr",c_void_p),
+               ("vtxhdr",c_void_p),
+               ("vvdhdr",c_void_p)]
+
 # Convert blenders mesh format into CXR's static format (they are very similar)
 #
 def mesh_cxr_format(obj):
@@ -593,17 +627,30 @@ libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], None )
 # Binary file formats and FS
 libcxr_fs_set_gameinfo = extern( "cxr_fs_set_gameinfo", [c_char_p], c_int32 )
 libcxr_fs_exit = extern( "cxr_fs_exit", [], None )
-libcxr_fs_get = extern( "cxr_fs_get", [c_char_p], c_char_p )
-libcxr_load_mdl = extern( "cxr_load_mdl", [c_char_p], POINTER(cxr_tri_mesh) )
+libcxr_fs_get = extern( "cxr_fs_get", [c_char_p, c_int32], c_char_p )
+libcxr_fs_find = extern( "cxr_fs_find", [c_char_p, POINTER(fs_locator)],\
+      c_int32 )
+
+libcxr_valve_load_model = extern( "valve_load_model", [c_char_p], \
+      POINTER(valve_model) )
+libcxr_valve_free_model = extern( "valve_free_model", [POINTER(valve_model)],\
+      None )
+
+libcxr_valve_load_material = extern( "valve_load_material", [c_char_p], \
+      POINTER(valve_material) )
+libcxr_valve_free_material = extern( "valve_free_material", \
+      [POINTER(valve_material)], None )
 
 libcxr_funcs = [ libcxr_decompose, libcxr_free_world, libcxr_begin_vmf, \
                  libcxr_vmf_begin_entities, libcxr_push_world_vmf, \
                  libcxr_end_vmf, libcxr_vdf_open, libcxr_vdf_close, \
-                 libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon, 
+                 libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon, \
                  libcxr_vdf_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\
                  libcxr_world_preview, libcxr_free_tri_mesh, \
                  libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \
-                 libcxr_load_mdl ]
+                 libcxr_fs_find,\
+                 libcxr_valve_load_model, libcxr_valve_free_model,\
+                 libcxr_valve_load_material, libcxr_valve_free_material ]
 
 # Callbacks
 def libcxr_log_callback(logStr):
@@ -634,7 +681,8 @@ def libcxr_line_callback( p0,p1,colour ):
    cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])]
 
 def cxr_reset_all():
-   global cxr_jobs_inf, cxr_jobs_batch, cxr_error_inf, cxr_view_mesh
+   global cxr_jobs_inf, cxr_jobs_batch, cxr_error_inf, cxr_view_mesh, \
+          cxr_asset_lib
    cxr_jobs_inf = None
    cxr_jobs_batch = None
    cxr_error_inf = None
@@ -643,6 +691,10 @@ def cxr_reset_all():
    cxr_batch_lines()
    cxr_view_mesh = None
 
+   cxr_asset_lib['models'] = {}
+   cxr_asset_lib['materials'] = {}
+   cxr_asset_lib['textures'] = {}
+   
    scene_redraw()
 
 # libnbvtf
@@ -1718,40 +1770,115 @@ class CXR_INIT_FS_OPERATOR(bpy.types.Operator):
 
       return {'FINISHED'}
 
-class CXR_LOAD_MODEL_OPERATOR(bpy.types.Operator):
-   bl_idname="convexer.model_load"
-   bl_label="Load model"
+def cxr_load_texture( path ):
+   global cxr_asset_lib
 
-   def execute(_,context):
-      global cxr_mdl_mesh, cxr_mdl_shader
+   if path in cxr_asset_lib['textures']:
+      return cxr_asset_lib['textures'][path]
 
-      mdlpath = bpy.context.scene.cxr_data.dev_mdl.encode('utf-8')
-      pmesh = libcxr_load_mdl.call( mdlpath )
+   print( F"cxr_load_texture( '{path}' )" )
 
-      if not pmesh:
-         print( "Failed to load model" )
-         return {'FINISHED'}
-      
-      mesh = pmesh[0]
+   # TODO
 
-      #TODO: remove code dupe
-      vertices = mesh.vertices[:mesh.vertex_count]
-      vertices = [(_[0],_[1],_[2]) for _ in vertices]
+   tex = cxr_asset_lib['textures'][path] = None
+   return tex
+
+def cxr_load_material( path ):
+   global cxr_asset_lib
+
+   if path in cxr_asset_lib['materials']:
+      return cxr_asset_lib['materials'][path]
+
+   print( F"cxr_load_material( '{path}' )" )
+   
+   pvmt = libcxr_valve_load_material.call( path.encode( 'utf-8') )
+   vmt = pvmt[0]
+
+   mat = cxr_asset_lib['materials'][path] = {}
+
+   if vmt.basetexture:
+      mat['basetexture'] = cxr_load_texture( vmt.basetexture.decode('utf-8') )
+   
+   if vmt.bumpmap:
+      mat['bumpmap'] = cxr_load_texture( vmt.bumpmap.decode('utf-8') )
+
+   libcxr_valve_free_material.call( pvmt )
+
+   return mat
+
+def cxr_load_model_full( path ):
+   global cxr_asset_lib, cxr_mdl_shader
+
+   if path in cxr_asset_lib['models']:
+      return cxr_asset_lib['models'][path]
+   
+   pmdl = libcxr_valve_load_model.call( path.encode( 'utf-8' ) )
+
+   print( F"cxr_load_model_full( '{path}' )" )
+
+   if not pmdl:
+      print( "Failed to load model" )
+      cxr_asset_lib['models'][path] = None
+      return None
+
+   mdl = pmdl[0]
+
+   # Convert our lovely interleaved vertex stream into, whatever this is.
+   positions = [ (mdl.vertex_data[i*8+0], \
+                  mdl.vertex_data[i*8+1], \
+                  mdl.vertex_data[i*8+2]) for i in range(mdl.vertex_count) ]
+
+   normals = [ (mdl.vertex_data[i*8+3], \
+                mdl.vertex_data[i*8+4], \
+                mdl.vertex_data[i*8+5]) for i in range(mdl.vertex_count) ]
 
-      normals = mesh.normals[:mesh.vertex_count]
-      normals = [(_[0],_[1],_[2]) for _ in normals]
+   uvs = [ (mdl.vertex_data[i*8+6], \
+            mdl.vertex_data[i*8+7]) for i in range(mdl.vertex_count) ]
 
-      indices = mesh.indices[:mesh.indices_count]
+   fmt = gpu.types.GPUVertFormat()
+   fmt.attr_add(id="aPos", comp_type='F32', len=3, fetch_mode='FLOAT')
+   fmt.attr_add(id="aNormal", comp_type='F32', len=3, fetch_mode='FLOAT')
+   fmt.attr_add(id="aUv", comp_type='F32', len=2, fetch_mode='FLOAT')
+
+   vbo = gpu.types.GPUVertBuf(len=mdl.vertex_count, format=fmt)
+   vbo.attr_fill(id="aPos", data=positions )
+   vbo.attr_fill(id="aNormal", data=normals )
+   vbo.attr_fill(id="aUv", data=uvs )
+
+   batches = cxr_asset_lib['models'][path] = []
+
+   for p in range(mdl.part_count):
+      part = mdl.parts[p]
+      indices = mdl.indices[part.ibstart:part.ibstart+part.ibcount]
       indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
-                  for i in range(int(mesh.indices_count/3)) ]
-      
-      cxr_mdl_mesh = batch_for_shader(
-         cxr_mdl_shader, 'TRIS',
-         { "aPos": vertices, "aNormal": normals },
-         indices = indices,
-      )
+                  for i in range(part.ibcount//3) ]
 
-      libcxr_free_tri_mesh.call( pmesh )
+      ibo = gpu.types.GPUIndexBuf( type='TRIS', seq=indices )
+
+      batch = gpu.types.GPUBatch( type='TRIS', buf=vbo, elem=ibo )
+      batch.program_set( cxr_mdl_shader )
+
+      mat_str = cast( mdl.materials[ part.material ], c_char_p )
+      batches += [( cxr_load_material( mat_str.value.decode('utf-8') ), batch )]
+
+   libcxr_valve_free_model.call( pmdl )
+
+   return batches
+
+class CXR_LOAD_MODEL_OPERATOR(bpy.types.Operator):
+   bl_idname="convexer.model_load"
+   bl_label="Load model"
+
+   def execute(_,context):
+      global cxr_mdl_mesh, cxr_mdl_shader, cxr_asset_lib
+
+      test_mdl = cxr_load_model_full( bpy.context.scene.cxr_data.dev_mdl )
+
+      if test_mdl != None:
+         # just draw first batch part for now
+         cxr_mdl_mesh = test_mdl[0][1]
+      else:
+         cxr_mdl_mesh = None
 
       scene_redraw()
       return {'FINISHED'}