+ loop_data = (cxr_static_loop*len(data.loops))()
+ polygon_data = (cxr_polygon*len(data.polygons))()
+
+ for i, poly in enumerate(data.polygons):
+ loop_start = poly.loop_start
+ loop_end = poly.loop_start + poly.loop_total
+ for loop_index in range(loop_start, loop_end):
+ loop = data.loops[loop_index]
+ loop_data[loop_index].index = loop.vertex_index
+ loop_data[loop_index].edge_index = loop.edge_index
+
+ if data.uv_layers:
+ uv = data.uv_layers.active.data[loop_index].uv
+ loop_data[loop_index].uv[0] = c_double(uv[0])
+ loop_data[loop_index].uv[1] = c_double(uv[1])
+ 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
+
+ polygon_data[i].loop_start = poly.loop_start
+ polygon_data[i].loop_total = poly.loop_total
+ polygon_data[i].normal[0] = normal[0]
+ polygon_data[i].normal[1] = normal[1]
+ polygon_data[i].normal[2] = normal[2]
+ polygon_data[i].center[0] = center[0]
+ polygon_data[i].center[1] = center[1]
+ polygon_data[i].center[2] = center[2]
+ polygon_data[i].material_id = poly.material_index
+
+ edge_data = (cxr_edge*len(data.edges))()
+
+ for i, edge in enumerate(data.edges):
+ edge_data[i].i0 = edge.vertices[0]
+ edge_data[i].i1 = edge.vertices[1]
+ edge_data[i].freestyle = edge.use_freestyle_mark
+ edge_data[i].sharp = edge.use_edge_sharp
+
+ material_data = (cxr_material*len(obj.material_slots))()
+
+ for i, ms in enumerate(obj.material_slots):
+ inf = material_info(ms.material)
+ material_data[i].res[0] = inf['res'][0]
+ material_data[i].res[1] = inf['res'][1]
+ material_data[i].name = inf['name'].encode('utf-8')
+
+ mesh.edges = cast(edge_data, POINTER(cxr_edge))
+ mesh.vertices = cast(vertex_data, POINTER(c_double*3))
+ 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))
+
+ mesh.poly_count = len(data.polygons)
+ mesh.vertex_count = len(data.vertices)
+ mesh.edge_count = len(data.edges)
+ mesh.loop_count = len(data.loops)
+ mesh.material_count = len(obj.material_slots)
+
+ if orig_state != None:
+ bpy.ops.object.mode_set(mode=orig_state)
+
+ return mesh
+
+# Callback ctypes indirection things.. not really sure.
+c_libcxr_log_callback = None
+c_libcxr_line_callback = None
+
+# Public API
+# -------------------------------------------------------------
+libcxr_decompose = extern( "cxr_decompose",
+ [POINTER(cxr_static_mesh), POINTER(c_int32)],
+ 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
+)
+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 + with open wrapper
+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 )
+
+class vdf_structure():
+ def __init__(_,path):
+ _.path = path
+ def __enter__(_):
+ _.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.call(_.fp)
+ def put(_,s):
+ libcxr_vdf_put.call(_.fp, s.encode('utf-8') )
+ def node(_,name):
+ libcxr_vdf_node.call(_.fp, name.encode('utf-8') )
+ def edon(_):
+ libcxr_vdf_edon.call(_.fp)
+ def kv(_,k,v):
+ libcxr_vdf_kv.call(_.fp, k.encode('utf-8'), v.encode('utf-8'))
+
+# 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_char_p )
+libcxr_load_mdl = extern( "cxr_load_mdl", [c_char_p], POINTER(cxr_tri_mesh) )
+
+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, \
+ libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \
+ libcxr_load_mdl ]
+
+# Callbacks
+def libcxr_log_callback(logStr):
+ print( F"{logStr.decode('utf-8')}",end='' )
+
+cxr_line_positions = None
+cxr_line_colours = None
+
+def cxr_reset_lines():
+ global cxr_line_positions, cxr_line_colours
+
+ cxr_line_positions = []
+ cxr_line_colours = []
+
+def cxr_batch_lines():
+ global cxr_line_positions, cxr_line_colours, cxr_view_shader, cxr_view_lines
+
+ cxr_view_lines = batch_for_shader(\
+ cxr_view_shader, 'LINES',\
+ { "pos": cxr_line_positions, "color": cxr_line_colours })
+
+def libcxr_line_callback( p0,p1,colour ):
+ global cxr_line_colours, cxr_line_positions
+
+ cxr_line_positions += [(p0[0],p0[1],p0[2])]
+ cxr_line_positions += [(p1[0],p1[1],p1[2])]
+ cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])]
+ 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
+ cxr_jobs_inf = None
+ cxr_jobs_batch = None
+ cxr_error_inf = None
+
+ cxr_reset_lines()
+ cxr_batch_lines()
+ cxr_view_mesh = None
+
+ scene_redraw()
+
+# libnbvtf
+# ------------------------------------------------------------------------------
+
+libnbvtf = None
+
+# Constants
+NBVTF_IMAGE_FORMAT_ABGR8888 = 1
+NBVTF_IMAGE_FORMAT_BGR888 = 3
+NBVTF_IMAGE_FORMAT_DXT1 = 13
+NBVTF_IMAGE_FORMAT_DXT5 = 15
+NBVTF_TEXTUREFLAGS_CLAMPS = 0x00000004
+NBVTF_TEXTUREFLAGS_CLAMPT = 0x00000008
+NBVTF_TEXTUREFLAGS_NORMAL = 0x00000080
+NBVTF_TEXTUREFLAGS_NOMIP = 0x00000100
+NBVTF_TEXTUREFLAGS_NOLOD = 0x00000200
+
+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_init = extern( "nbvtf_init", [], None )
+libnbvtf_funcs = [ libnbvtf_convert, libnbvtf_init ]
+
+# Loading
+# --------------------------
+
+def shared_reload():
+ global libcxr, libnbvtf, libcxr_funcs, libnbvtf_funcs
+
+ # Unload libraries if existing
+ def _reload( lib, path ):
+ if CXR_GNU_LINUX==1:
+ if lib != None:
+ _handle = lib._handle
+ for i in range(10): libc_dlclose( _handle )
+ lib = None
+ del lib
+
+ libpath = F'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
+ return cdll.LoadLibrary( libpath )
+
+ libnbvtf = _reload( libnbvtf, "libnbvtf" )
+ libcxr = _reload( libcxr, "libcxr" )
+
+ for fd in libnbvtf_funcs:
+ fd.loadfrom( libnbvtf )
+ libnbvtf_init.call()
+
+ for fd in libcxr_funcs:
+ fd.loadfrom( libcxr )
+
+ # Callbacks
+ 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)
+
+ LINE_FUNCTION_TYPE = CFUNCTYPE(None,\
+ POINTER(c_double), POINTER(c_double), POINTER(c_double))
+ c_libcxr_line_callback = LINE_FUNCTION_TYPE(libcxr_line_callback)
+
+ libcxr.cxr_set_log_function(cast(c_libcxr_log_callback,c_void_p))
+ libcxr.cxr_set_line_function(cast(c_libcxr_line_callback,c_void_p))
+
+ build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
+ print( F"libcxr build time: {build_time.value}" )
+
+shared_reload()
+
+# Configuration
+# ------------------------------------------------------------------------------
+
+# Standard entity functions, think of like base.fgd
+#
+def cxr_get_origin(context):
+ return context['object'].location * context['transform']['scale'] + \
+ mathutils.Vector(context['transform']['offset'])
+
+def cxr_get_angles(context):
+ obj = context['object']
+ euler = [ a*57.295779513 for a in obj.rotation_euler ]
+ angle = [0,0,0]
+ angle[0] = euler[1]
+ angle[1] = euler[2]
+ angle[2] = euler[0]
+ return angle
+
+def cxr_baseclass(classes, other):
+ base = other.copy()
+ for x in classes:
+ base.update(x.copy())
+ return base
+
+def ent_soundscape(context):
+ obj = context['object']
+ kvs = cxr_baseclass([ent_origin],\
+ {
+ "radius": obj.scale.x * bpy.context.scene.cxr_data.scale_factor,
+ "soundscape": {"type":"string","default":""}
+ })
+
+ return kvs
+
+# EEVEE Light component converter -> Source 1
+#
+def ent_lights(context):
+ obj = context['object']
+ kvs = cxr_baseclass([ent_origin],\
+ {
+ "_distance": (0.0 if obj.data.cxr_data.realtime else -1.0),
+ "_lightHDR": '-1 -1 -1 1',
+ "_lightscaleHDR": 1
+ })
+
+ light_base = [(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] +\
+ [obj.data.energy * bpy.context.scene.cxr_data.light_scale]
+
+ if obj.data.type == 'SPOT' or obj.data.type == 'SUN':
+ # Blenders directional lights are -z forward
+ # Source is +x, however, it seems to use a completely different system.
+ # Since we dont care about roll for spotlights, we just take the
+ # pitch and yaw via trig
+
+ _,mtx_rot,_ = obj.matrix_world.decompose()
+ fwd = mtx_rot @ mathutils.Vector((0,0,-1))
+ dir_pitch = math.asin(fwd[2]) * 57.295779513
+ dir_yaw = math.atan2(fwd[1],fwd[0]) * 57.295779513
+
+ if obj.data.type == 'SPOT':
+ kvs['_light'] = [ int(x) for x in light_base ]
+ kvs['_cone'] = obj.data.spot_size*(57.295779513/2.0)
+ kvs['_inner_cone'] = (1.0-obj.data.spot_blend)*kvs['_cone']
+
+ kvs['pitch'] = dir_pitch
+ kvs['angles'] = [ 0, dir_yaw, 0 ]
+ kvs['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
+ # Really bad...
+ #
+ # Blender's default has a much more 'nice'
+ # look.
+ kvs['_linear_attn'] = 1.0
+
+ elif obj.data.type == 'POINT':
+ kvs['_light'] = [ int(x) for x in light_base]
+ kvs['_quadratic_attn'] = 1.0
+ kvs['_linear_attn'] = 1.0
+
+ elif obj.data.type == 'SUN':
+ light_base[3] *= 300.0 * 5
+ kvs['_light'] = [ int(x) for x in light_base ]
+
+ ambient = bpy.context.scene.world.color
+ kvs['_ambient'] = [int(pow(ambient[i],1.0/2.2)*255.0) for i in range(3)] +\
+ [80 * 5]
+ kvs['_ambientHDR'] = [-1,-1,-1,1]
+ kvs['_AmbientScaleHDR'] = 1
+ kvs['pitch'] = dir_pitch
+ kvs['angles'] = [ dir_pitch, dir_yaw, 0.0 ]
+ kvs['SunSpreadAngle'] = 0
+
+ return kvs
+
+def ent_prop(context):
+ if isinstance( context['object'], bpy.types.Collection ):
+ kvs = {}
+ target = context['object']
+ pos = mathutils.Vector(context['origin'])
+ pos += mathutils.Vector(context['transform']['offset'])
+
+ kvs['origin'] = [pos[1],-pos[0],pos[2]]
+ kvs['angles'] = [0,180,0]
+ kvs['uniformscale'] = 1.0
+ else:
+ kvs = cxr_baseclass([ent_origin],{})
+ target = context['object'].instance_collection
+
+ obj = context['object']
+ euler = [ a*57.295779513 for a in obj.rotation_euler ]
+ angle = [0,0,0]
+ angle[0] = euler[1]
+ angle[1] = euler[2] + 180.0 # Dunno...
+ angle[2] = euler[0]
+
+ kvs['angles'] = angle
+ kvs['uniformscale'] = obj.scale[0]
+
+ if target.cxr_data.shadow_caster:
+ kvs['enablelightbounce'] = 1
+ kvs['disableshadows'] = 0
+ else:
+ kvs['enablelightbounce'] = 0
+ kvs['disableshadows'] = 1
+
+ kvs['fademindist'] = -1
+ kvs['fadescale'] = 1
+ kvs['model'] = F"{asset_path('models',target)}.mdl".lower()
+ kvs['renderamt'] = 255
+ kvs['rendercolor'] = [255, 255, 255]
+ kvs['skin'] = 0
+ kvs['solid'] = 6
+
+ return kvs
+
+def ent_sky_camera(context):
+ settings = bpy.context.scene.cxr_data
+ scale = settings.scale_factor / settings.skybox_scale_factor
+
+ kvs = {
+ "origin": [_ for _ in context['transform']['offset']],
+ "angles": [ 0, 0, 0 ],
+ "fogcolor": [255, 255, 255],
+ "fogcolor2": [255, 255, 255],
+ "fogdir": [1,0,0],
+ "fogend": 2000.0,
+ "fogmaxdensity": 1,
+ "fogstart": 500.0,
+ "HDRColorScale": 1.0,
+ "scale": scale
+ }
+ return kvs
+
+def ent_cubemap(context):
+ obj = context['object']
+ return cxr_baseclass([ent_origin], {"cubemapsize": obj.data.cxr_data.size})
+
+ent_origin = { "origin": cxr_get_origin }
+ent_angles = { "angles": cxr_get_angles }
+ent_transform = cxr_baseclass( [ent_origin], ent_angles )
+
+#include the user config
+exec(open(F'{os.path.dirname(__file__)}/config.py').read())
+
+# Blender state callbacks
+# ------------------------------------------------------------------------------
+
+@persistent
+def cxr_on_load(dummy):
+ global cxr_view_lines, cxr_view_mesh
+
+ cxr_view_lines = None
+ cxr_view_mesh = None
+
+@persistent
+def cxr_dgraph_update(scene,dgraph):
+ return
+ print( F"Hallo {time.time()}" )
+
+# Convexer compilation functions
+# ------------------------------------------------------------------------------
+
+# Asset path management
+
+def asset_uid(asset):
+ if isinstance(asset,str):
+ return asset
+
+ # Create a unique ID string
+ base = "bopshei"
+ v = asset.cxr_data.asset_id
+ name = ""
+
+ if v == 0:
+ name = "a"
+ else:
+ dig = []
+
+ while v:
+ dig.append( int( v % len(base) ) )
+ v //= len(base)
+
+ for d in dig[::-1]:
+ name += base[d]
+
+ if bpy.context.scene.cxr_data.include_names:
+ name += asset.name.replace('.','_')
+
+ return name
+
+# -> <project_name>/<asset_name>
+def asset_name(asset):
+ return F"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
+
+# -> <subdir>/<project_name>/<asset_name>
+def asset_path(subdir, asset):
+ return F"{subdir}/{asset_name(asset_uid(asset))}"
+
+# -> <csgo>/<subdir>/<project_name>/<asset_name>
+def asset_full_path(sdir,asset):
+ return F"{bpy.context.scene.cxr_data.subdir}/"+\
+ F"{asset_path(sdir,asset_uid(asset))}"
+
+# Decomposes mesh, and sets global error information if failed.
+# - returns None on fail
+# - returns world on success
+def cxr_decompose_globalerr( mesh_src ):
+ global cxr_error_inf
+
+ err = c_int32(0)
+ world = libcxr_decompose.call( mesh_src, pointer(err) )
+
+ if not world:
+ cxr_view_mesh = None
+ cxr_batch_lines()
+
+ cxr_error_inf = [\
+ ("No Error", "There is no error?"),\
+ ("Bad input", "Non manifold geometry is present in the input mesh"),\
+ ("Bad result","An invalid manifold was generated, try to simplify"),\
+ ("Bad result","Make sure there is a clear starting point"),\
+ ("Bad result","Implicit vertex was invalid, try to simplify"),\
+ ("Bad input","Non coplanar vertices are in the source mesh"),\
+ ("Bad input","Non convex polygon is in the source mesh"),\
+ ("Bad result","Undefined failure"),\
+ ("Invalid Input", "Undefined failure"),\
+ ][err.value]
+
+ scene_redraw()
+
+ return world
+
+# Entity functions / infos
+# ------------------------
+
+def cxr_collection_purpose(collection):
+ if collection.name.startswith('.'): return None
+ if collection.hide_render: return None
+ if collection.name.startswith('mdl_'): return 'model'
+ return 'group'
+
+def cxr_object_purpose(obj):
+ objpurpose = None
+ group = None
+
+ def _search(collection):
+ nonlocal objpurpose, group, obj
+
+ purpose = cxr_collection_purpose( collection )
+ if purpose == None: return
+ if purpose == 'model':
+ for o in collection.objects:
+ if o == obj:
+ if o.type != 'EMPTY':
+ objpurpose = 'model'
+ group = collection
+ return
+ return
+ for o in collection.objects:
+ if o == obj:
+ classname = cxr_classname(o)
+ if classname != None:
+ objpurpose = 'entity'
+ if o.type == 'MESH':
+ objpurpose = 'brush_entity'
+ group = collection
+ else:
+ if o.type == 'MESH':
+ objpurpose = 'brush'
+ group = collection
+ return
+ for c in collection.children:
+ _search(c)
+
+ if 'main' in bpy.data.collections:
+ _search( bpy.data.collections['main'] )
+
+ if objpurpose == None and 'skybox' in bpy.data.collections:
+ _search( bpy.data.collections['skybox'] )
+
+ return (group,objpurpose)
+
+def cxr_intrinsic_classname(obj):
+ if obj.type == 'LIGHT':
+ return {
+ 'SPOT': "light_spot",
+ 'POINT': "light",
+ 'SUN': "light_environment" }[ obj.data.type ]
+
+ elif obj.type == 'LIGHT_PROBE':
+ return "env_cubemap"
+ elif obj.type == 'EMPTY':
+ if obj.is_instancer:
+ return "prop_static"
+
+ return None
+
+def cxr_custom_class(obj):
+ if obj.type == 'MESH': custom_class = obj.cxr_data.brushclass
+ else: custom_class = obj.cxr_data.classname
+
+ return custom_class
+
+def cxr_classname(obj):
+ intr = cxr_intrinsic_classname(obj)
+ if intr != None: return intr
+
+ custom_class = cxr_custom_class(obj)
+ if custom_class != 'NONE':
+ return custom_class
+
+ return None
+
+# Returns array of:
+# intinsic: (k, False, value)
+# property: (k, True, value or default)
+#
+# Error: None
+#
+def cxr_entity_keyvalues(context):
+ classname = context['classname']
+ obj = context['object']
+ if classname not in cxr_entities: return None
+
+ result = []