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;
}
""")
+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):
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()
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
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),
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),
("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):
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
# 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='' )
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
cxr_batch_lines()
cxr_view_mesh = None
+ cxr_asset_lib['models'] = {}
+ cxr_asset_lib['materials'] = {}
+ cxr_asset_lib['textures'] = {}
+
scene_redraw()
# libnbvtf
[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
# --------------------------
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()
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:
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)
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):
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:
return {'PASS_THROUGH'}
def invoke(_,context,event):
+ global cxr_error_inf
+
static = _.__class__
wm = context.window_manager
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()
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
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):
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
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