X-Git-Url: https://harrygodden.com/git/?p=convexer.git;a=blobdiff_plain;f=__init__.py;h=05aefbcb1066a2c5592a6c5d9f1ac5c3e55bd0c0;hp=6b6185cb6a208c2844e42d026110df0cc8997204;hb=HEAD;hpb=2b81894272ade16dbe3f71514e8eb25b2962bf9e diff --git a/__init__.py b/__init__.py index 6b6185c..05aefbc 100644 --- a/__init__.py +++ b/__init__.py @@ -51,9 +51,18 @@ cxr_view_mesh = None cxr_jobs_batch = None cxr_jobs_inf = [] cxr_error_inf = None +cxr_test_mdl = None + +cxr_asset_lib = \ +{ + "models": {}, + "materials": {}, + "textures": {} +} # Shaders cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') + cxr_ui_shader = gpu.types.GPUShader(""" uniform mat4 ModelViewProjectionMatrix; uniform float scale; @@ -78,6 +87,100 @@ void main() } """) +cxr_mdl_shader = gpu.types.GPUShader(""" +uniform mat4 modelMatrix; +uniform mat4 viewProjectionMatrix; + +in vec3 aPos; +in vec3 aNormal; +in vec2 aUv; + +out vec3 lPos; +out vec3 lNormal; +out vec2 lUv; + +void main() +{ + vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0); + vec3 worldPos = pWorldPos.xyz; + + gl_Position = viewProjectionMatrix * pWorldPos; + lNormal = normalize(mat3(transpose(inverse(modelMatrix))) * aNormal); + lPos = worldPos; + lUv = aUv; +} +""",""" + +uniform vec4 colour; +uniform vec3 testLightDir; +uniform sampler2D uBasetexture; + +in vec3 lNormal; +in vec3 lPos; +in vec2 lUv; + +out vec4 FragColor; + +float SoftenCosineTerm( float flDot ) +{ + return ( flDot + ( flDot * flDot ) ) * 0.5; +} + +vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir ) +{ + float fResult = 0.0; + float NDotL = dot( worldNormal, lightDir ); + + fResult = clamp( NDotL, 0.0, 1.0 ); + fResult = SoftenCosineTerm( fResult ); + + vec3 fOut = vec3( fResult, fResult, fResult ); + return fOut; +} + +vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal ) +{ + vec3 linearColor = vec3(0.0,0.0,0.0); + linearColor += DiffuseTerm( worldNormal, testLightDir ); + + return linearColor; +} + +vec3 LinearToGamma( vec3 f3linear ) +{ + return pow( f3linear, vec3(1.0 / 2.2) ); +} + +vec3 GammaToLinear( vec3 f3gamma ) +{ + return pow( f3gamma, vec3(2.2) ); +} + +void main() +{ + vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 ); + vec4 normalTexel = vec4(1.0,1.0,1.0,1.0); + vec3 colorInput = GammaToLinear( texture( uBasetexture, lUv ).rgb ); + + vec4 baseColor = vec4( colorInput * colour.rgb, 1.0 ); + + //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord ); + //tangentSpaceNormal = 2.0 * normalTexel - 1.0; + + vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 ); + + vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 ); + diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal ); + + // multiply by .5 since we want a 50% (in gamma space) reflective surface) + diffuseLighting *= pow( 0.5, 2.2 ); + + vec3 result = diffuseLighting * baseColor.xyz; + + FragColor = vec4( LinearToGamma(result), 1.0 ); +} +""") + # Render functions # def cxr_ui(_,context): @@ -137,7 +240,8 @@ def cxr_ui(_,context): CXR_COMPILER_CHAIN.WAIT_REDRAW = False def cxr_draw(): - global cxr_view_shader, cxr_view_mesh, cxr_view_lines + global cxr_view_shader, cxr_view_mesh, cxr_view_lines, cxr_mdl_shader,\ + cxr_mdl_mesh, cxr_test_mdl cxr_view_shader.bind() @@ -150,11 +254,37 @@ def cxr_draw(): if cxr_view_lines != None: cxr_view_lines.draw( cxr_view_shader ) - gpu.state.depth_test_set('LESS_EQUAL') - gpu.state.blend_set('ADDITIVE') if cxr_view_mesh != None: + gpu.state.depth_test_set('LESS_EQUAL') + gpu.state.blend_set('ADDITIVE') + cxr_view_mesh.draw( cxr_view_shader ) + # Models + gpu.state.depth_mask_set(True) + gpu.state.depth_test_set('LESS_EQUAL') + gpu.state.face_culling_set('FRONT') + gpu.state.blend_set('NONE') + + cxr_mdl_shader.bind() + cxr_mdl_shader.uniform_float("viewProjectionMatrix", \ + bpy.context.region_data.perspective_matrix) + + if cxr_test_mdl != None: + cxr_mdl_shader.uniform_float('colour',(1.0,1.0,1.0,1.0)) + + #temp light dir + testmdl = bpy.context.scene.objects['target'] + light = bpy.context.scene.objects['point'] + relative = light.location - testmdl.location + relative.normalize() + cxr_mdl_shader.uniform_float("modelMatrix", testmdl.matrix_world) + cxr_mdl_shader.uniform_float("testLightDir", relative) + + for part in cxr_test_mdl: + cxr_mdl_shader.uniform_sampler("uBasetexture", part[0]['basetexture']) + part[1].draw( cxr_mdl_shader ) + def cxr_jobs_update_graph(jobs): global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf @@ -258,7 +388,8 @@ class cxr_edge(Structure): class cxr_static_loop(Structure): _fields_ = [("index",c_int32), ("edge_index",c_int32), - ("uv",c_double * 2)] + ("uv",c_double * 2), + ("alpha",c_double)] class cxr_polygon(Structure): _fields_ = [("loop_start",c_int32), @@ -286,6 +417,8 @@ class cxr_static_mesh(Structure): class cxr_tri_mesh(Structure): _fields_ = [("vertices",POINTER(c_double *3)), + ("normals",POINTER(c_double *3)), + ("uvs",POINTER(c_double *2)), ("colours",POINTER(c_double *4)), ("indices",POINTER(c_int32)), ("indices_count",c_int32), @@ -309,6 +442,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): @@ -351,6 +511,14 @@ def mesh_cxr_format(obj): else: loop_data[loop_index].uv[0] = c_double(0.0) loop_data[loop_index].uv[1] = c_double(0.0) + + if data.vertex_colors: + alpha = data.vertex_colors.active.data[loop_index].color[0] + else: + alpha = 0.0 + + loop_data[loop_index].alpha = alpha + center = obj.matrix_world @ poly.center normal = mtx_rot @ poly.normal @@ -472,15 +640,36 @@ class vdf_structure(): # Other 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_int32], c_void_p ) +libcxr_fs_free = extern( "cxr_fs_free", [c_void_p], None ) +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_world_preview, libcxr_free_tri_mesh, \ + libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \ + libcxr_fs_find, libcxr_fs_free, \ + libcxr_valve_load_model, libcxr_valve_free_model,\ + libcxr_valve_load_material, libcxr_valve_free_material ] # Callbacks - def libcxr_log_callback(logStr): print( F"{logStr.decode('utf-8')}",end='' ) @@ -509,7 +698,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 @@ -518,6 +708,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 @@ -540,8 +734,15 @@ 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_read = extern( "nbvtf_read", \ + [c_void_p,POINTER(c_int32),POINTER(c_int32), c_int32], \ + POINTER(c_uint8) ) + +libnbvtf_free = extern( "nbvtf_free", [POINTER(c_uint8)], None ) + libnbvtf_init = extern( "nbvtf_init", [], None ) -libnbvtf_funcs = [ libnbvtf_convert, libnbvtf_init ] +libnbvtf_funcs = [ libnbvtf_convert, libnbvtf_init, libnbvtf_read, \ + libnbvtf_free ] # Loading # -------------------------- @@ -820,7 +1021,7 @@ def cxr_decompose_globalerr( mesh_src ): err = c_int32(0) world = libcxr_decompose.call( mesh_src, pointer(err) ) - if world == None: + if not world: cxr_view_mesh = None cxr_batch_lines() @@ -986,12 +1187,13 @@ def material_info(mat): def _variant_apply( val ): nonlocal mat - if isinstance( val, str ): - return val - else: + if isinstance( val, list ): for shader_variant in val: if shader_variant[0] == mat.cxr_data.shader: return shader_variant[1] + return val[0][1] + else: + return val # Find rootnodes if node == None: @@ -999,36 +1201,38 @@ def material_info(mat): for node_idname in node_def: for n in mat.node_tree.nodes: - if n.bl_idname == node_idname: + if n.name == node_idname: node_def = node_def[node_idname] node = n break for link in node_def: - if isinstance( node_def[link], dict ): - inputt = node.inputs[link] - inputt_def = node_def[link] + link_def = _variant_apply( node_def[link] ) + + if isinstance( link_def, dict ): + node_link = node.inputs[link] - if inputt.is_linked: + if node_link.is_linked: # look for definitions for the connected node type - con = inputt.links[0].from_node + from_node = node_link.links[0].from_node - for node_idname in inputt_def: - if con.bl_idname == node_idname: - con_def = inputt_def[ node_idname ] - _graph_read( con_def, con, depth+1 ) + node_name = from_node.name.split('.')[0] + if node_name in link_def: + from_node_def = link_def[ node_name ] + + _graph_read( from_node_def, from_node, depth+1 ) - # No definition found! :( + # No definition! :( # TODO: Make a warning for this? else: - if "default" in inputt_def: - prop = _variant_apply( inputt_def['default'] ) - info[prop] = inputt.default_value + if "default" in link_def: + prop = _variant_apply( link_def['default'] ) + info[prop] = node_link.default_value else: - prop = _variant_apply( node_def[link] ) - info[prop] = getattr(node,link) + prop = _variant_apply( link_def ) + info[prop] = getattr( node, link ) _graph_read(cxr_graph_mapping) @@ -1576,6 +1780,158 @@ class CXR_DEV_OPERATOR(bpy.types.Operator): libcxr_write_test_data.call( pointer(mesh_src) ) return {'FINISHED'} +class CXR_INIT_FS_OPERATOR(bpy.types.Operator): + bl_idname="convexer.fs_init" + bl_label="Initialize filesystem" + + def execute(_,context): + gameinfo = F'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt' + + if libcxr_fs_set_gameinfo.call( gameinfo.encode('utf-8') ) == 1: + print( "File system ready" ) + else: + print( "File system failed to initialize" ) + + return {'FINISHED'} + +def cxr_load_texture( path, is_normal ): + global cxr_asset_lib + + if path in cxr_asset_lib['textures']: + return cxr_asset_lib['textures'][path] + + print( F"cxr_load_texture( '{path}' )" ) + + pvtf = libcxr_fs_get.call( path.encode('utf-8'), 0 ) + + if not pvtf: + print( "vtf failed to load" ) + cxr_asset_lib['textures'][path] = None + return None + + x = c_int32(0) + y = c_int32(0) + + img_data = libnbvtf_read.call( pvtf, pointer(x), pointer(y), \ + c_int32(is_normal) ) + + x = x.value + y = y.value + + if not img_data: + print( "vtf failed to decode" ) + libcxr_fs_free.call( pvtf ) + cxr_asset_lib['textures'][path] = None + return None + + img_buf = gpu.types.Buffer('FLOAT', [x*y*4], [_/255.0 for _ in img_data[:x*y*4]]) + + tex = cxr_asset_lib['textures'][path] = \ + gpu.types.GPUTexture( size=(x,y), layers=0, is_cubemap=False,\ + format='RGBA8', data=img_buf ) + + libnbvtf_free.call( img_data ) + libcxr_fs_free.call( pvtf ) + 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') ) + + if not pvmt: + cxr_asset_lib['materials'][path] = None + return None + + vmt = pvmt[0] + mat = cxr_asset_lib['materials'][path] = {} + + if vmt.basetexture: + mat['basetexture'] = cxr_load_texture( vmt.basetexture.decode('utf-8'), 0) + + if vmt.bumpmap: + mat['bumpmap'] = cxr_load_texture( vmt.bumpmap.decode('utf-8'), 1) + + 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) ] + + uvs = [ (mdl.vertex_data[i*8+6], \ + mdl.vertex_data[i*8+7]) for i in range(mdl.vertex_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(part.ibcount//3) ] + + 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_test_mdl, cxr_mdl_shader, cxr_asset_lib + + cxr_test_mdl = cxr_load_model_full( bpy.context.scene.cxr_data.dev_mdl ) + + scene_redraw() + return {'FINISHED'} + # UI: Preview how the brushes will looks in 3D view # class CXR_PREVIEW_OPERATOR(bpy.types.Operator): @@ -1660,7 +2016,7 @@ def cxr_temp_file( fn ): else: filepath = bpy.data.filepath directory = os.path.dirname(filepath) - return F"{directory}/{fn}.txt" + return F"{directory}/{fn}" def cxr_winepath( path ): if CXR_GNU_LINUX == 1: @@ -1789,6 +2145,8 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): return {'PASS_THROUGH'} def invoke(_,context,event): + global cxr_error_inf + static = _.__class__ wm = context.window_manager @@ -1834,7 +2192,12 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): if ms.material.cxr_data.shader == 'VertexLitGeneric': errmat = ms.material.name errnam = brush['object'].name + + cxr_error_inf = ( "Shader error", \ + F"Vertex shader ({errmat}) used on model ({errnam})" ) + print( F"Vertex shader {errmat} used on {errnam}") + scene_redraw() return {'CANCELLED'} a_models = set() @@ -1875,7 +2238,12 @@ class CXR_COMPILER_CHAIN(bpy.types.Operator): errmat = ms.material.name errnam = obj.name + + cxr_error_inf = ( "Shader error", \ + F"Lightmapped shader ({errmat}) used on model ({errnam})" ) + print( F"Lightmapped shader {errmat} used on {errnam}") + scene_redraw() return {'CANCELLED'} # Collect images @@ -2129,6 +2497,9 @@ class CXR_VIEW3D( bpy.types.Panel ): row.scale_y = 2 row.operator("convexer.reset") + layout.prop( bpy.context.scene.cxr_data, "dev_mdl" ) + layout.operator( "convexer.model_load" ) + # Main scene properties interface, where all the settings go # class CXR_INTERFACE(bpy.types.Panel): @@ -2142,6 +2513,7 @@ class CXR_INTERFACE(bpy.types.Panel): if CXR_GNU_LINUX==1: _.layout.operator("convexer.reload") _.layout.operator("convexer.dev_test") + _.layout.operator("convexer.fs_init") _.layout.operator("convexer.hash_reset") settings = context.scene.cxr_data @@ -2536,13 +2908,16 @@ class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup): comp_compile: bpy.props.BoolProperty(name="Compile",default=True) comp_pack: bpy.props.BoolProperty(name="Pack",default=False) + dev_mdl: bpy.props.StringProperty(name="Model",default="") + classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \ 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_PREVIEW_OPERATOR,\ CXR_VIEW3D, CXR_COMPILER_CHAIN, CXR_RESET_HASHES,\ - CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET ] + CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET, \ + CXR_INIT_FS_OPERATOR, CXR_LOAD_MODEL_OPERATOR ] vmt_param_dynamic_class = None