Major API revision
[convexer.git] / __init__.py
index 8cc9638c943c8d88247529c9640eda238b4049ab..544d770b19433a22b846762c0bf2ac2afe7ca5ba 100644 (file)
@@ -25,31 +25,135 @@ vmt_param_dynamic_class = None
 
 # libcxr interface (TODO: We can probably automate this)
 # ======================================================
-libcxr = None
+
+class extern():
+   def __init__(_,name,argtypes,restype):
+      _.name = name
+      _.argtypes = argtypes
+      _.restype = restype
+      _.call = None
+
+   def loadfrom(_,so):
+      _.call = getattr(so,_.name)
+      _.call.argtypes = _.argtypes
+
+      if _.restype != None:
+         _.call.restype = _.restype
+
 libc_dlclose = None
 libc_dlclose = cdll.LoadLibrary(None).dlclose
 libc_dlclose.argtypes = [c_void_p]
 
+# Callback ctypes wrapper...
+libcxr = None
 c_libcxr_log_callback = None
 c_libcxr_line_callback = None
 
-libcxr_write_test_data = None
-libcxr_context_reset = None
-libcxr_set_offset = None
-libcxr_set_scale_factor = None
+# Structure definitions
+class cxr_edge(Structure):
+   _fields_ = [("i0",c_int32),
+               ("i1",c_int32),
+               ("freestyle",c_int32)]
+
+class cxr_static_loop(Structure):
+   _fields_ = [("index",c_int32),
+               ("edge_index",c_int32),
+               ("uv",c_double * 2)]
+
+class cxr_polygon(Structure):
+   _fields_ =  [("loop_start",c_int32),
+                ("loop_total",c_int32),
+                ("normal",c_double * 3),
+                ("center",c_double * 3),
+                ("material_id",c_int32)]
+
+class cxr_material(Structure):
+   _fields_ = [("res",c_int32 * 2),
+               ("name",c_char_p)]
 
-# Vdf writing interface
-libcxr_vdf_open = None
-libcxr_vdf_close = None
-libcxr_vdf_put = None
-libcxr_vdf_node = None
-libcxr_vdf_edon = None
-libcxr_vdf_kv = None
+class cxr_static_mesh(Structure):
+   _fields_ = [("vertices",POINTER(c_double * 3)),
+               ("edges",POINTER(cxr_edge)),
+               ("loops",POINTER(cxr_static_loop)),
+               ("polys",POINTER(cxr_polygon)),
+               ("materials",POINTER(cxr_material)),
+
+               ("poly_count",c_int32),
+               ("vertex_count",c_int32),
+               ("edge_count",c_int32),
+               ("loop_count",c_int32),
+               ("material_count",c_int32)]
+
+class cxr_tri_mesh(Structure):
+   _fields_ = [("vertices",POINTER(c_double *3)),
+               ("colours",POINTER(c_double *4)),
+               ("indices",POINTER(c_int32)),
+               ("indices_count",c_int32),
+               ("vertex_count",c_int32)]
+
+class cxr_vmf_context(Structure):
+   _fields_ = [("mapversion",c_int32),
+               ("skyname",c_char_p),
+               ("detailvbsp",c_char_p),
+               ("detailmaterial",c_char_p),
+               ("scale",c_double),
+               ("offset",c_double *3),
+               ("lightmap_scale",c_int32),
+               ("brush_count",c_int32),
+               ("entity_count",c_int32),
+               ("face_count",c_int32)]
+
+# Public API
+libcxr_decompose = extern( "cxr_decompose", \
+      [POINTER(cxr_static_mesh)], c_void_p )
+
+libcxr_free_world = extern( "cxr_free_world", [c_void_p], None )
+libcxr_write_test_data = extern( "cxr_write_test_data", \
+      [POINTER(cxr_static_mesh)], None )
+libcxr_world_preview = extern( "cxr_world_preview", [c_void_p], \
+        POINTER(cxr_tri_mesh))
+libcxr_free_tri_mesh = extern( "cxr_free_tri_mesh", [c_void_p], None )
+
+# VMF
+libcxr_begin_vmf = extern( "cxr_begin_vmf", \
+      [POINTER(cxr_vmf_context), c_void_p], None )
+
+libcxr_vmf_begin_entities = extern( "cxr_vmf_begin_entities", \
+      [POINTER(cxr_vmf_context), c_void_p], None )
+
+libcxr_push_world_vmf = extern("cxr_push_world_vmf", \
+      [c_void_p,POINTER(cxr_vmf_context),c_void_p], None )
+
+libcxr_end_vmf = extern( "cxr_end_vmf", \
+      [POINTER(cxr_vmf_context),c_void_p], None )
+
+# VDF
+libcxr_vdf_open = extern( "cxr_vdf_open", [c_char_p], c_void_p )
+libcxr_vdf_close = extern( "cxr_vdf_close", [c_void_p], None )
+libcxr_vdf_put = extern( "cxr_vdf_put", [c_void_p,c_char_p], None )
+libcxr_vdf_node = extern( "cxr_vdf_node", [c_void_p,c_char_p], None )
+libcxr_vdf_edon = extern( "cxr_vdf_edon", [c_void_p], None )
+libcxr_vdf_kv = extern( "cxr_vdf_kv", [c_void_p,c_char_p,c_char_p], None )
+
+# Other
+libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], 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_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\
+                 libcxr_world_preview, libcxr_free_tri_mesh ]
 
 # libnbvtf interface
 # ==================
 libnbvtf = None
-libnbvtf_convert = None
+
+libnbvtf_convert = extern( "nbvtf_convert", \
+      [c_char_p,c_int32,c_int32,c_int32,c_int32,c_int32,c_uint32,c_char_p],
+      c_int32 )
+
+libnbvtf_funcs = [ libnbvtf_convert ]
 
 # NBVTF constants
 # ===============
@@ -69,76 +173,34 @@ class vdf_structure():
    def __init__(_,path):
       _.path = path
    def __enter__(_):
-      _.fp = libcxr_vdf_open( _.path.encode('utf-8') )
+      _.fp = libcxr_vdf_open.call( _.path.encode('utf-8') )
       if _.fp == None:
          print( F"Could not open file {_.path}" )
          return None
       return _
    def __exit__(_,type,value,traceback):
       if _.fp != None:
-         libcxr_vdf_close(_.fp)
+         libcxr_vdf_close.call(_.fp)
 
    def put(_,s):
-      libcxr_vdf_put(_.fp, s.encode('utf-8') )
+      libcxr_vdf_put.call(_.fp, s.encode('utf-8') )
    def node(_,name):
-      libcxr_vdf_node(_.fp, name.encode('utf-8') )
+      libcxr_vdf_node.call(_.fp, name.encode('utf-8') )
    def edon(_):
-      libcxr_vdf_edon(_.fp)
+      libcxr_vdf_edon.call(_.fp)
    def kv(_,k,v):
-      libcxr_vdf_kv(_.fp, k.encode('utf-8'), v.encode('utf-8'))
+      libcxr_vdf_kv.call(_.fp, k.encode('utf-8'), v.encode('utf-8'))
 
 class cxr_object_context():
    def __init__(_,scale,offset_z):
       _.scale=scale
       _.offset_z=offset_z
 
-libcxr_convert_mesh_to_vmf = None
-
 debug_gpu_lines = None
+debug_gpu_mesh = None
 debug_gpu_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
 debug_draw_handler = None
 
-class cxr_settings(Structure):
-   _fields_ = [("debug",c_int32),
-               ("lightmap_scale",c_int32),
-               ("light_scale",c_double)]
-
-class cxr_input_loop(Structure):
-   _fields_ = [("index",c_int32),
-               ("edge_index",c_int32),
-               ("uv",c_double * 2)]
-
-class cxr_polygon(Structure):
-   _fields_ =  [("loop_start",c_int32),
-                ("loop_total",c_int32),
-                ("normal",c_double * 3),
-                ("center",c_double * 3),
-                ("material_id",c_int32)]
-
-class cxr_edge(Structure):
-   _fields_ = [("i0",c_int32),
-               ("i1",c_int32),
-               ("freestyle",c_int32)]
-
-class cxr_material(Structure):
-   _fields_ = [("res",c_int32 * 2),
-               ("vmt_path",c_char_p)]
-
-class cxr_input_mesh(Structure):
-   _fields_ = [("vertices",POINTER(c_double * 3)),
-               ("edges",POINTER(cxr_edge)),
-               ("loops",POINTER(cxr_input_loop)),
-               ("polys",POINTER(cxr_polygon)),
-               ("materials",POINTER(cxr_material)),
-
-               ("poly_count",c_int32),
-               ("vertex_count",c_int32),
-               ("edge_count",c_int32),
-               ("loop_count",c_int32),
-               ("material_count",c_int32)]
-
-class cxr_output_mesh(Structure):
-   _fields_ = []
 
 def libcxr_log_callback(logStr):
    print( F"{logStr.decode('utf-8')}",end='' )
@@ -184,35 +246,36 @@ def libcxr_line_callback(p0,p1,colour):
 def cxr_draw():
    global debug_gpu_lines
    global debug_gpu_shader
-   
+   global debug_gpu_mesh
+
+   debug_gpu_shader.bind()
+
+   gpu.state.depth_test_set('LESS_EQUAL')
+   gpu.state.depth_mask_set(False)
+   gpu.state.line_width_set(1.5)
+   gpu.state.face_culling_set('BACK')
+
    gpu.state.blend_set('ALPHA')
    if debug_gpu_lines != None:
       debug_gpu_lines.draw(debug_gpu_shader)
 
+   gpu.state.blend_set('ADDITIVE')
+   if debug_gpu_mesh != None:
+      debug_gpu_mesh.draw(debug_gpu_shader)
+
 class CXR_RELOAD(bpy.types.Operator):
    bl_idname="convexer.reload"
    bl_label="Reload convexer"
 
    def execute(_,context):
-      global libcxr, libnbvtf, libnbvtf_convert
+      global libcxr, libnbvtf, libcxr_funcs, libnbvtf_funcs
 
       # Load vtf library
       libnbvtf = cdll.LoadLibrary( os.path.dirname(__file__)+'/libnbvtf.so')
-      libnbvtf_convert = libnbvtf.nbvtf_convert
-      libnbvtf_convert.argtypes = [\
-        c_char_p, \
-        c_int32, \
-        c_int32, \
-        c_int32, \
-        c_int32, \
-        c_uint32, \
-        c_char_p ]
-      libnbvtf_convert.restype = c_int32
 
       if libcxr != None:
          _handle = libcxr._handle
          
-         # TODO: Find a propper way to do this
          for i in range(10): libc_dlclose( _handle )
          del libcxr
 
@@ -222,62 +285,14 @@ class CXR_RELOAD(bpy.types.Operator):
       build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
       print( F"libcxr build time: {build_time.value}" )
    
-      # Public API
-      global libcxr_write_test_data, libcxr_convert_mesh_to_vmf
-
-      libcxr_write_test_data = libcxr.cxr_write_test_data
-      libcxr_write_test_data.argtypes = [\
-         POINTER(cxr_input_mesh)
-      ]
-      libcxr_write_test_data.restype = c_int32
-
-      libcxr_convert_mesh_to_vmf = libcxr.cxr_convert_mesh_to_vmf
-      libcxr_convert_mesh_to_vmf.argtypes = [\
-         POINTER(cxr_input_mesh),\
-         c_void_p,\
-      ]
-      libcxr_convert_mesh_to_vmf.restype = c_int32
-      
-      global libcxr_context_reset, libcxr_set_offset,\
-             libcxr_set_scale_factor
-      libcxr_context_reset = libcxr.cxr_context_reset
-      
-      libcxr_set_offset = libcxr.cxr_set_offset
-      libcxr_set_offset.argtypes = [ c_double ]
-      
-      libcxr_set_scale_factor = libcxr.cxr_set_scale_factor
-      libcxr_set_scale_factor.argtypes = [ c_double ]
-
-      # VDF
-      global libcxr_vdf_open, \
-            libcxr_vdf_close, \
-            libcxr_vdf_put, \
-            libcxr_vdf_node, \
-            libcxr_vdf_edon, \
-            libcxr_vdf_kv
-      
-      libcxr_vdf_open = libcxr.cxr_vdf_open
-      libcxr_vdf_open.argtypes = [ c_char_p ]
-      libcxr_vdf_open.restype = c_void_p
-
-      libcxr_vdf_close = libcxr.cxr_vdf_close
-      libcxr_vdf_close.argtypes = [ c_void_p ]
-
-      libcxr_vdf_put = libcxr.cxr_vdf_put
-      libcxr_vdf_put.argtypes = [ c_void_p, c_char_p ]
-
-      libcxr_vdf_node = libcxr.cxr_vdf_node
-      libcxr_vdf_node.argtypes = [ c_void_p, c_char_p ]
+      for fd in libnbvtf_funcs:
+         fd.loadfrom( libnbvtf )
 
-      libcxr_vdf_edon = libcxr.cxr_vdf_edon
-      libcxr_vdf_edon.argtypes = [ c_void_p ]
-
-      libcxr_vdf_kv = libcxr.cxr_vdf_kv
-      libcxr_vdf_kv.argtypes = [ c_void_p, c_char_p, c_char_p ]
+      for fd in libcxr_funcs:
+         fd.loadfrom( libcxr )
 
       # Callbacks
-      global c_libcxr_log_callback
-      global c_libcxr_line_callback
+      global c_libcxr_log_callback, c_libcxr_line_callback
 
       LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p)
       c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback)
@@ -295,20 +310,6 @@ def libcxr_use():
 
    if libcxr == None:
       bpy.ops.convexer.reload()
-   
-   def _bool_int(b):
-      return 1 if b else 0
-   
-   scene = bpy.context.scene
-   cxr = scene.cxr_data
-
-   settings=cxr_settings()
-   settings.debug=_bool_int(cxr.debug)
-
-   settings.lightmap_scale=cxr.lightmap_scale
-   settings.light_scale=cxr.light_scale
-
-   libcxr.cxr_settings_update(pointer(settings))
 
 def to_aeiou( v ):
    ret = ""
@@ -817,7 +818,7 @@ def mesh_cxr_format(obj):
    
    _,mtx_rot,_ = obj.matrix_world.decompose()
 
-   mesh = cxr_input_mesh()
+   mesh = cxr_static_mesh()
 
    vertex_data = ((c_double*3)*len(data.vertices))()
    for i, vert in enumerate(data.vertices):
@@ -826,7 +827,7 @@ def mesh_cxr_format(obj):
       vertex_data[i][1] = c_double(v[1])
       vertex_data[i][2] = c_double(v[2])
 
-   loop_data = (cxr_input_loop*len(data.loops))()
+   loop_data = (cxr_static_loop*len(data.loops))()
    polygon_data = (cxr_polygon*len(data.polygons))()
 
    for i, poly in enumerate(data.polygons):
@@ -874,7 +875,7 @@ def mesh_cxr_format(obj):
    
    mesh.edges = cast(edge_data, POINTER(cxr_edge))
    mesh.vertices = cast(vertex_data, POINTER(c_double*3))
-   mesh.loops = cast(loop_data,POINTER(cxr_input_loop))
+   mesh.loops = cast(loop_data,POINTER(cxr_static_loop))
    mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
    mesh.materials = cast(material_data, POINTER(cxr_material))
    
@@ -909,41 +910,24 @@ class CXR_WRITE_VMF(bpy.types.Operator):
       
       # States
       material_info.references = set()
-      libcxr_context_reset()
       
       output_vmf = F"{directory}/{settings.project_name}.vmf"
 
       with vdf_structure(output_vmf) as m:
          print( F"Write: {output_vmf}" )
 
-         m.node('versioninfo')
-         m.kv('editorversion','400')
-         m.kv('editorbuild','8456')
-         m.kv('mapversion','4')
-         m.kv('formatversion','100')
-         m.kv('prefab','0')
-         m.edon()
-
-         m.node('visgroups')
-         m.edon()
-
-         m.node('viewsettings')
-         m.kv('bSnapToGrid','1')
-         m.kv('bShowGrid','1')
-         m.kv('bShowLogicalGrid','0')
-         m.kv('nGridSpacing','64')
-         m.kv('bShow3DGrid','0')
-         m.edon()
-
-         m.node('world')
-         m.kv('id','1')
-         m.kv('mapversion','1')
-         m.kv('classname','worldspawn')
-         m.kv('skyname','sky_csgo_night02b')
-         m.kv('maxpropscreenwidth','-1')
-         m.kv('detailvbsp','detail.vbsp')
-         m.kv('detailmaterial','detail/detailsprites')
+         vmfinfo = cxr_vmf_context()
+         vmfinfo.mapversion = 4
+         vmfinfo.skyname = b"sky_csgo_night02b"
+         vmfinfo.detailvbsp = b"detail.vbsp"
+         vmfinfo.detailmaterial = b"detail/detailsprites"
+         vmfinfo.lightmap_scale = 12
+         vmfinfo.brush_count = 0
+         vmfinfo.entity_count = 0
+         vmfinfo.face_count = 0
          
+         libcxr_begin_vmf.call( pointer(vmfinfo), m.fp )
+
          # Make sure all of our asset types have a unique ID
          def _uid_prepare(objtype):
             used_ids = [0]
@@ -1007,13 +991,31 @@ class CXR_WRITE_VMF(bpy.types.Operator):
          if 'skybox' in bpy.data.collections:
             _collect( bpy.data.collections['skybox'], transform_sky )
          
+         def _buildsolid( obj, ctx ):
+            nonlocal m
+
+            baked = mesh_cxr_format( brush[0] )
+            world = libcxr_decompose.call( baked )
+            
+            if world == None:
+               return False
+
+            vmfinfo.scale = brush[1].scale
+            vmfinfo.offset[0] = 0.0
+            vmfinfo.offset[1] = 0.0
+            vmfinfo.offset[2] = brush[1].offset_z
+
+            libcxr_push_world_vmf.call( world, pointer(vmfinfo), m.fp )
+            libcxr_free_world.call( world )
+
+            return True
+
          # World geometry
          for brush in _collect.geo:
-            baked = mesh_cxr_format( brush[0] )
-            libcxr_set_scale_factor( brush[1].scale )
-            libcxr_set_offset( brush[1].offset_z )
-            libcxr_convert_mesh_to_vmf(baked,m.fp)
-         
+            if not _buildsolid( brush[0], brush[1] ):
+               print( "error" )
+               return {'CANCELLED'}
+
          m.edon()
          
          # Entities
@@ -1031,11 +1033,8 @@ class CXR_WRITE_VMF(bpy.types.Operator):
                   m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
                else: m.kv( kv[0], str(kv[2]) )
 
-            if obj.type == 'MESH':
-               baked = mesh_cxr_format( obj )
-               libcxr_set_scale_factor( ctx.scale )
-               libcxr_set_offset( ctx.offset_z )
-               libcxr_convert_mesh_to_vmf(baked,m.fp)
+            if not _buildsolid( obj, ctx ):
+               return {'CANCELLED'}
 
             m.edon()
          
@@ -1062,12 +1061,57 @@ class CXR_DEV_OPERATOR(bpy.types.Operator):
       mesh_src = mesh_cxr_format(context.active_object)
       
       libcxr_reset_debug_lines()
-      libcxr_write_test_data( pointer(mesh_src) )
+      libcxr_write_test_data.call( pointer(mesh_src) )
       libcxr_batch_debug_lines()
          
       scene_redraw()
       return {'FINISHED'}
 
+class CXR_PREVIEW_OPERATOR(bpy.types.Operator):
+   bl_idname="convexer.preview"
+   bl_label="Preview"
+
+   def execute(_,context):
+      libcxr_use()
+
+      libcxr_reset_debug_lines()
+
+      mesh_src = mesh_cxr_format(context.active_object)
+      world = libcxr_decompose.call( mesh_src )
+      
+      global debug_gpu_shader, debug_gpu_mesh
+
+      if world == None:
+         debug_gpu_mesh = None
+         libcxr_batch_debug_lines()
+         return {'CANCELLED'}
+      
+      ptrpreview = libcxr_world_preview.call( world )
+      preview = ptrpreview[0]
+
+      vertices = preview.vertices[:preview.vertex_count]
+      vertices = [(_[0],_[1],_[2]) for _ in vertices]
+
+      colours = preview.colours[:preview.vertex_count]
+      colours = [(_[0],_[1],_[2],_[3]) for _ in colours]
+
+      indices = preview.indices[:preview.indices_count]
+      indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
+                  for i in range(int(preview.indices_count/3)) ]
+
+      debug_gpu_mesh = batch_for_shader(
+         debug_gpu_shader, 'TRIS',
+         { "pos": vertices, "color": colours },
+         indices = indices,
+      )
+
+      libcxr_free_tri_mesh.call( ptrpreview )
+      libcxr_free_world.call( world )
+      
+      libcxr_batch_debug_lines()
+      scene_redraw()
+      return {'FINISHED'}
+
 class CXR_INTERFACE(bpy.types.Panel):
    bl_label="Convexer"
    bl_idname="SCENE_PT_convexer"
@@ -1078,6 +1122,7 @@ class CXR_INTERFACE(bpy.types.Panel):
    def draw(_,context):
       _.layout.operator("convexer.reload")
       _.layout.operator("convexer.dev_test")
+      _.layout.operator("convexer.preview")
       _.layout.operator("convexer.write_vmf")
 
       settings = context.scene.cxr_data
@@ -1137,7 +1182,7 @@ def compile_image(img,flags):
          flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
          flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
 
-      if libnbvtf_convert(src,dims[0],dims[1],mipmap,fmt,flags_full,dst):
+      if libnbvtf_convert.call(src,dims[0],dims[1],mipmap,fmt,0,flags_full,dst):
          img.cxr_data.last_hash = comphash
 
    return name
@@ -1542,7 +1587,7 @@ classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \
             CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
             CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
             CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
-            CXR_ENTITY_PANEL, CXR_LIGHT_PANEL ]
+            CXR_ENTITY_PANEL, CXR_LIGHT_PANEL, CXR_PREVIEW_OPERATOR ]
 
 def register():
    global debug_draw_handler, vmt_param_dynamic_class