From 9c85e110fa8b965195438d96625ff9753af362a6 Mon Sep 17 00:00:00 2001 From: hgn Date: Wed, 13 Jul 2022 02:28:17 +0100 Subject: [PATCH] rework scene format --- blender_export.py | 439 +++++++++++++++++++++++++++-------------- character.h | 78 ++++---- common.h | 7 +- gate.h | 4 +- lighting.h | 104 ---------- main.c | 2 +- mesh.h | 0 model.h | 364 +++++++++++++++++++++++++--------- player.h | 51 ++++- scene.h | 131 +++++------- textures/gradients.png | Bin 47261 -> 74750 bytes world.h | 315 ++++++++++++++--------------- 12 files changed, 871 insertions(+), 624 deletions(-) create mode 100644 mesh.h diff --git a/blender_export.py b/blender_export.py index 16b9764..16d1f38 100644 --- a/blender_export.py +++ b/blender_export.py @@ -1,5 +1,6 @@ import bpy, math, gpu from ctypes import * +from mathutils import * from gpu_extras.batch import batch_for_shader bl_info = { @@ -14,168 +15,246 @@ bl_info = { "category":"Import/Export", } -class model(Structure): +class mdl_vert(Structure): _pack_ = 1 - _fields_ = [("identifier",c_uint32), - ("vertex_count",c_uint32), - ("indice_count",c_uint32), - ("layer_count",c_uint32), - ("marker_count",c_uint32)] + _fields_ = [("co",c_float*3), + ("norm",c_float*3), + ("colour",c_float*4), + ("uv",c_float*2)] -class submodel(Structure): +class mdl_submesh(Structure): _pack_ = 1 _fields_ = [("indice_start",c_uint32), ("indice_count",c_uint32), ("vertex_start",c_uint32), ("vertex_count",c_uint32), ("bbx",(c_float*3)*2), - ("pivot",c_float*3), - ("q",c_float*4), - ("name",c_char*32), - ("material",c_char*32)] + ("material_id",c_uint32)] # index into the material array -class classtype_gate(Structure): +class mdl_material(Structure): _pack_ = 1 - _fields_ = [("target",c_uint32)] + _fields_ = [("pstr_name",c_uint32)] -class marker(Structure): +class mdl_node(Structure): _pack_ = 1 _fields_ = [("co",c_float*3), ( "q",c_float*4), ( "s",c_float*3), + ("submesh_start",c_uint32), + ("submesh_count",c_uint32), ("classtype",c_uint32), ("offset",c_uint32), - ("name",c_char*32)] + ("pstr_name",c_uint32)] -class model_vert(Structure): +class mdl_header(Structure): _pack_ = 1 - _fields_ = [("co",c_float*3), - ("norm",c_float*3), - ("colour",c_float*4), - ("uv",c_float*2)] + _fields_ = [("identifier",c_uint32), + ("version",c_uint32), + ("file_length",c_uint32), + ("vertex_count",c_uint32), + ("vertex_offset",c_uint32), + + ("indice_count",c_uint32), + ("indice_offset",c_uint32), + + ("submesh_count",c_uint32), + ("submesh_offset",c_uint32), + + ("material_count",c_uint32), + ("material_offset",c_uint32), + + ("node_count",c_uint32), + ("node_offset",c_uint32), + + ("strings_offset",c_uint32), + ("entdata_offset",c_uint32) + ] + +# Entity types +# ========================================== + +class classtype_gate(Structure): + _pack_ = 1 + _fields_ = [("target",c_uint32)] + +class classtype_block(Structure): + _pack_ = 1 + _fields_ = [("bbx",(c_float*3)*2)] -def submesh_set_transform( sm, obj ): - sm.pivot[0] = obj.matrix_world.translation[0] - sm.pivot[1] = obj.matrix_world.translation[2] - sm.pivot[2] = -obj.matrix_world.translation[1] +class classtype_spawn(Structure): + _pack_ = 1 + _fields_ = [("temp",c_uint32)] + +class classtype_water(Structure): + _pack_ = 1 + _fields_ = [("temp",c_uint32)] - quat = obj.matrix_world.to_quaternion() - sm.q[0] = quat[1] - sm.q[1] = quat[3] - sm.q[2] = -quat[2] - sm.q[3] = quat[0] +# Exporter +# ============================================================================== def write_model(name): - fp = open(F"/home/harry/Documents/carve/models/{name}.mdl", "wb") + print( F"Create mode {name}" ) + collection = bpy.data.collections[name] - header = model() + header = mdl_header() header.identifier = 0xABCD0000 + header.version = 0 header.vertex_count = 0 header.indice_count = 0 - header.layer_count = 0 - header.marker_count = 1 + header.submesh_count = 0 + header.node_count = 0 + header.material_count = 0 + header.file_length = 0 mesh_cache = {} - layers = [] + string_cache = {} + material_cache = {} + + strings_buffer = b'' + + material_buffer = [] + submesh_buffer = [] vertex_buffer = [] indice_buffer = [] + node_buffer = [] + entdata_buffer = [] + entdata_length = 0 + + def emplace_string( s ): + nonlocal string_cache, strings_buffer + + if s in string_cache: + return string_cache[s] + + string_cache[s] = len( strings_buffer ) + strings_buffer += (s+'\0').encode('utf-8') + return string_cache[s] + + def emplace_material( mat ): + nonlocal material_cache, material_buffer + + if mat.name in material_cache: + return material_cache[mat.name] + + material_cache[mat.name] = header.material_count + dest = mdl_material() + dest.pstr_name = emplace_string( mat.name ) + material_buffer += [dest] + + header.material_count += 1 + return material_cache[mat.name] + + # Create root or empty node and materials + # + none_material = c_uint32(69) + none_material.name = "" + emplace_material( none_material ) + + root = mdl_node() + root.co[0] = 0 + root.co[1] = 0 + root.co[2] = 0 + root.q[0] = 0 + root.q[1] = 0 + root.q[2] = 0 + root.q[3] = 1 + root.s[0] = 1 + root.s[1] = 1 + root.s[2] = 1 + root.pstr_name = emplace_string('') + root.submesh_start = 0 + root.submesh_count = 0 + root.offset = 0 + root.classtype = 0 + node_buffer += [root] + + # Do exporting + # + print( " assigning ids" ) + header.node_count = 1 + for obj in collection.all_objects: + obj.cv_data.uid = header.node_count + header.node_count += 1 + + print( " compiling data" ) + for obj in collection.all_objects: + print( F" [{obj.cv_data.uid}/{header.node_count-1}] {obj.name}" ) + + node = mdl_node() + node.co[0] = obj.location[0] + node.co[1] = obj.location[2] + node.co[2] = -obj.location[1] + + # Convert rotation quat to our space type + quat = obj.matrix_world.to_quaternion() + node.q[0] = quat[1] + node.q[1] = quat[3] + node.q[2] = -quat[2] + node.q[3] = quat[0] + + node.s[0] = obj.scale[0] + node.s[1] = obj.scale[2] + node.s[2] = obj.scale[1] + node.pstr_name = emplace_string( obj.name ) + + # Process entity data + # + node.offset = entdata_length + classtype = obj.cv_data.classtype + + if classtype == 'k_classtype_none': + node.classtype = 0 + node.offset = 0 + + elif classtype == 'k_classtype_gate': + node.classtype = 1 + entdata_length += sizeof( classtype_gate ) + + gate = classtype_gate() + gate.target = 0 + if obj.cv_data.target != None: + gate.target = obj.cv_data.target.cv_data.uid - print( F"Create mode {name}" ) - - rootmarker = marker() - rootmarker.co[0] = 0 - rootmarker.co[1] = 0 - rootmarker.co[2] = 0 - rootmarker.q[0] = 0 - rootmarker.q[1] = 0 - rootmarker.q[2] = 0 - rootmarker.q[3] = 1 - rootmarker.s[0] = 1 - rootmarker.s[1] = 1 - rootmarker.s[2] = 1 - rootmarker.name = "".encode('utf-8') - rootmarker.offset = 0 - rootmarker.classtype = 0 - - markers = [ rootmarker ] # aka entities - entdata_structs = [] - entdata_offset = 0 - - entity_count = 1 - - for obj in collection.objects: - if obj.type == 'EMPTY': - obj.cv_data.uid = entity_count - entity_count += 1 - - for obj in collection.objects: - if obj.type == 'EMPTY': - mk = marker() - mk.co[0] = obj.location[0] - mk.co[1] = obj.location[2] - mk.co[2] = -obj.location[1] - - # Convert rotation quat to our space type - quat = obj.matrix_world.to_quaternion() - mk.q[0] = quat[1] - mk.q[1] = quat[3] - mk.q[2] = -quat[2] - mk.q[3] = quat[0] - - mk.s[0] = obj.scale[0] - mk.s[1] = obj.scale[2] - mk.s[2] = obj.scale[1] - mk.name = obj.name.encode('utf-8') - mk.offset = entdata_offset + entdata_buffer += [gate] - classtype = obj.cv_data.classtype + elif classtype == 'k_classtype_block': + node.classtype = 2 + entdata_length += sizeof( classtype_block ) - if classtype == 'k_classtype_gate': - mk.classtype = 1 - entdata_offset += sizeof( classtype_gate ) + source = obj.data.cv_data - gate = classtype_gate() - gate.target = 0 - if obj.cv_data.target != None: - gate.target = obj.cv_data.target.cv_data.uid + block = classtype_block() + block.bbx[0][0] = source.v0[0] + block.bbx[0][1] = source.v0[2] + block.bbx[0][2] = -source.v0[1] + block.bbx[1][0] = source.v1[0] + block.bbx[1][1] = source.v1[2] + block.bbx[1][2] = -source.v1[1] + entdata_buffer += [block] - entdata_structs += [gate] + elif classtype == 'k_classtype_spawn': + node.classtype = 3 - elif classtype == 'k_thingummybob': - pass + elif classtype == 'k_classtype_water': + node.classtype = 4 - markers += [mk] - header.marker_count += 1 + # Process meshes + # + node.submesh_start = header.submesh_count + node.submesh_count = 0 - elif obj.type == 'MESH': + if obj.type == 'MESH': default_mat = c_uint32(69) default_mat.name = "" if obj.data.name in mesh_cache: ref = mesh_cache[obj.data.name] - for material_id, mref in enumerate(ref['sm']): - print(F" Link submesh({ref['users']}) '{obj.name}:{mat.name}'") - - sm = submodel() - sm.indice_start = mref['indice_start'] - sm.indice_count = mref['indice_count'] - sm.vertex_start = mref['vertex_start'] - sm.vertex_count = mref['vertex_count'] - sm.name = obj.name.encode('utf-8') - sm.material = mref['material'] - sm.bbx = mref['bbx'] - submesh_set_transform( sm, obj ) - layers += [sm] - header.layer_count += 1 - - ref['users'] += 1 + node.submesh_start = ref.submesh_start + node.submesh_count = ref.submesh_count + node_buffer += [node] continue - ref = mesh_cache[obj.data.name] = {} - ref['users'] = 0 - ref['sm'] = [] - dgraph = bpy.context.evaluated_depsgraph_get() data = obj.evaluated_get(dgraph).data data.calc_loop_triangles() @@ -185,24 +264,18 @@ def write_model(name): for material_id, mat in enumerate(mat_list): mref = {} - sm = submodel() + sm = mdl_submesh() sm.indice_start = header.indice_count sm.vertex_start = header.vertex_count sm.vertex_count = 0 sm.indice_count = 0 - submesh_set_transform( sm, obj ) + sm.material_id = emplace_material( mat ) for i in range(3): sm.bbx[0][i] = 999999 sm.bbx[1][i] = -999999 - - sm.name = obj.name.encode('utf-8') - sm.material = mat.name.encode('utf-8') - print( F" Creating submesh '{obj.name}:{mat.name}'" ) + boffa = {} - - hit_count = 0 - miss_count = 0 # Write the vertex / indice data # @@ -231,17 +304,14 @@ def write_model(name): if key in boffa: indice_buffer += [boffa[key]] - hit_count += 1 else: - miss_count += 1 index = c_uint32(sm.vertex_count) sm.vertex_count += 1 boffa[key] = index - indice_buffer += [index] - v = model_vert() + v = mdl_vert() v.co[0] = co[0] v.co[1] = co[2] v.co[2] = -co[1] @@ -267,35 +337,63 @@ def write_model(name): for i in range(3): sm.bbx[j][i] = 0 - layers += [sm] - header.layer_count += 1 + submesh_buffer += [sm] + node.submesh_count += 1 + header.submesh_count += 1 header.vertex_count += sm.vertex_count header.indice_count += sm.indice_count - mref['indice_start'] = sm.indice_start - mref['indice_count'] = sm.indice_count - mref['vertex_start'] = sm.vertex_start - mref['vertex_count'] = sm.vertex_count - mref['bbx'] = sm.bbx - print( F"{sm.bbx[0][0]},{sm.bbx[0][1]},{sm.bbx[0][2]}" ) + mesh_cache[obj.data.name] = node + node_buffer += [node] + + # Write data arrays + # + print( "Writing data" ) + fpos = sizeof(header) + + header.node_offset = fpos + fpos += sizeof(mdl_node)*header.node_count + + header.submesh_offset = fpos + fpos += sizeof(mdl_submesh)*header.submesh_count + + header.material_offset = fpos + fpos += sizeof(mdl_material)*header.material_count - mref['material'] = sm.material - ref['sm'] += [mref] + header.entdata_offset = fpos + fpos += entdata_length + header.vertex_offset = fpos + fpos += sizeof(mdl_vert)*header.vertex_count + + header.indice_offset = fpos + fpos += sizeof(c_uint32)*header.indice_count + + header.strings_offset = fpos + fpos += len(strings_buffer) + + header.file_length = fpos + + fp = open(F"/home/harry/Documents/carve/models/{name}.mdl", "wb") fp.write( bytearray( header ) ) - for l in layers: - fp.write( bytearray(l) ) - for m in markers: - fp.write( bytearray(m) ) + + for node in node_buffer: + fp.write( bytearray(node) ) + for sm in submesh_buffer: + fp.write( bytearray(sm) ) + for mat in material_buffer: + fp.write( bytearray(mat) ) + for ed in entdata_buffer: + fp.write( bytearray(ed) ) for v in vertex_buffer: fp.write( bytearray(v) ) for i in indice_buffer: fp.write( bytearray(i) ) - for ed in entdata_structs: - fp.write( bytearray(ed) ) - + fp.write( strings_buffer ) fp.close() + print( F"Completed {name}.mdl" ) + # Clicky clicky GUI # ------------------------------------------------------------------------------ @@ -314,7 +412,7 @@ def cv_draw(): verts = [] colours = [] - for obj in bpy.context.collection.all_objects: + for obj in bpy.context.collection.objects: if obj.cv_data.classtype == 'k_classtype_gate': if obj.cv_data.target != None: p0 = obj.location @@ -322,6 +420,29 @@ def cv_draw(): verts += [(p0[0],p0[1],p0[2])] verts += [(p1[0],p1[1],p1[2])] colours += [(0,1,0,1.0),(1,0,0,1.0)] + elif obj.cv_data.classtype == 'k_classtype_block': + a = obj.data.cv_data.v0 + b = obj.data.cv_data.v1 + + vs = [None]*8 + vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2])) + vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2])) + vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2])) + vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2])) + vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2])) + vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2])) + vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2])) + vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2])) + + indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\ + (0,4),(1,5),(2,6),(3,7)] + + for l in indices: + v0 = vs[l[0]] + v1 = vs[l[1]] + verts += [(v0[0],v0[1],v0[2])] + verts += [(v1[0],v1[1],v1[2])] + colours += [(1,1,0,1),(1,1,0,1)] lines = batch_for_shader(\ cv_view_shader, 'LINES', \ @@ -336,6 +457,12 @@ def cv_poll_target(scene, obj): return False return True +class CV_MESH_SETTINGS(bpy.types.PropertyGroup): + v0: bpy.props.FloatVectorProperty(name="v0",size=3) + v1: bpy.props.FloatVectorProperty(name="v1",size=3) + v2: bpy.props.FloatVectorProperty(name="v2",size=3) + v3: bpy.props.FloatVectorProperty(name="v3",size=3) + class CV_OBJ_SETTINGS(bpy.types.PropertyGroup): uid: bpy.props.IntProperty( name="" ) @@ -347,6 +474,9 @@ class CV_OBJ_SETTINGS(bpy.types.PropertyGroup): items = [ ('k_classtype_none', "k_classtype_none", "", 0), ('k_classtype_gate', "k_classtype_gate", "", 1), + ('k_classtype_block', "k_classtype_block", "", 2), + ('k_classtype_spawn', "k_classtype_spawn", "", 3), + ('k_classtype_water', "k_classtype_water", "", 4) ]) class CV_OBJ_PANEL(bpy.types.Panel): @@ -360,7 +490,17 @@ class CV_OBJ_PANEL(bpy.types.Panel): active_object = bpy.context.active_object if active_object == None: return _.layout.prop( active_object.cv_data, "classtype" ) - _.layout.prop( active_object.cv_data, "target" ) + + if active_object.cv_data.classtype == 'k_classtype_gate': + _.layout.prop( active_object.cv_data, "target" ) + elif active_object.cv_data.classtype == 'k_classtype_block': + mesh = active_object.data + + _.layout.label( text=F"(i) Data is stored in {mesh.name}" ) + _.layout.prop( mesh.cv_data, "v0" ) + _.layout.prop( mesh.cv_data, "v1" ) + _.layout.prop( mesh.cv_data, "v2" ) + _.layout.prop( mesh.cv_data, "v3" ) class CV_INTERFACE(bpy.types.Panel): bl_idname = "VIEW3D_PT_carve" @@ -383,7 +523,8 @@ class CV_COMPILE(bpy.types.Operator): return {'FINISHED'} -classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE] +classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\ + CV_MESH_SETTINGS] def register(): global cv_view_draw_handler @@ -392,6 +533,8 @@ def register(): bpy.utils.register_class(c) bpy.types.Object.cv_data = bpy.props.PointerProperty(type=CV_OBJ_SETTINGS) + bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS) + cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\ cv_draw,(),'WINDOW','POST_VIEW') diff --git a/character.h b/character.h index c565fc4..5ce456f 100644 --- a/character.h +++ b/character.h @@ -63,7 +63,8 @@ struct character { glmesh mesh; - submodel parts[ PART_COUNT ]; + mdl_submesh parts[ PART_COUNT ]; + v3f origins[ PART_COUNT ]; m4x3f matrices[ PART_COUNT ]; /* Auxillary information */ @@ -96,7 +97,7 @@ struct character static void character_offset( struct character *ch, enum character_part parent, enum character_part child ) { - v3_sub( ch->parts[ child ].pivot, ch->parts[ parent ].pivot, + v3_sub( ch->origins[ child ], ch->origins[ parent ], ch->offsets[ child ] ); } @@ -105,35 +106,44 @@ static int character_load( struct character *ch, const char *name ) char buf[64]; snprintf( buf, sizeof(buf)-1, "models/%s.mdl", name ); - model *src = vg_asset_read( buf ); + mdl_header *src = mdl_load( buf ); if( !src ) - { - vg_error( "Could not open 'models/%s.mdl'", name ); return 0; - } int error_count = 0; for( int i=0; iparts[i], 0, sizeof(mdl_submesh) ); + v3_zero( ch->origins[i] ); + + if( !pnode ) { vg_warn( "Character file does not contain an '_%s' part.\n", character_part_strings[i] ); error_count ++; - - memset( &ch->parts[i], 0, sizeof(submodel) ); + continue; + } + + mdl_submesh *sm = mdl_node_submesh( src, pnode, 0 ); + + if( !sm ) + { + vg_warn( "Character file's '_%s' part has no mesh.\n", + character_part_strings[i] ); + error_count ++; continue; } ch->parts[i] = *sm; + v3_copy( pnode->co, ch->origins[i] ); } - model_unpack( src, &ch->mesh ); + mdl_unpack_glmesh( src, &ch->mesh ); if( !error_count ) vg_success( "Loaded character file '%s' with no errors\n", name ); @@ -489,7 +499,7 @@ static void character_testpose( struct character *ch, float t ) *pole = ch->ik_body.pole; hips[0] = cosf(t*1.325f)*0.25f; - hips[1] = (sinf(t)*0.2f+0.6f) * ch->parts[ k_chpart_body0 ].pivot[1]; + hips[1] = (sinf(t)*0.2f+0.6f) * ch->origins[ k_chpart_body0 ][1]; hips[2] = 0.0f; collar[0] = hips[0]; @@ -568,7 +578,7 @@ static void character_draw( struct character *ch, float temp ) #endif shader_character_uMdl( ch->matrices[i] ); - submodel_draw( &ch->parts[i] ); + mdl_draw_submesh( &ch->parts[i] ); } for( int i=0; i<2; i++ ) @@ -576,14 +586,14 @@ static void character_draw( struct character *ch, float temp ) if( ch->shoes[i] ) { shader_character_uMdl( ch->matrices[i] ); - submodel_draw( &ch->parts[i] ); + mdl_draw_submesh( &ch->parts[i] ); } else { shader_character_uMdl( ch->matrices[i+2] ); - submodel_draw( &ch->parts[i] ); + mdl_draw_submesh( &ch->parts[i] ); shader_character_uMdl( ch->matrices[i] ); - submodel_draw( &ch->parts[i+2] ); + mdl_draw_submesh( &ch->parts[i+2] ); } } } @@ -676,14 +686,14 @@ static void character_init_ragdoll_joints( struct character *ch ) { struct rd_joint *joint = &rd_joints[i]; - float *hinge = ch->parts[joint->ib].pivot; + float *hinge = ch->origins[joint->ib]; v3_sub( hinge, ch->ragdoll[joint->ia].co, joint->lca ); v3_sub( hinge, ch->ragdoll[joint->ib].co, joint->lcb ); } for( int i=0; iparts[i].pivot; + float *pivot = ch->origins[i]; v3_sub( ch->ragdoll[i].co, pivot, ch->ragdoll[i].delta ); } } @@ -700,27 +710,27 @@ static void character_init_ragdoll( struct character *ch ) v3f chest_dims = { chest_depth, chest_height, chest_width }; character_rd_box( ch, k_chpart_body1, chest_dims ); - v3_copy( ch->parts[k_chpart_body1].pivot, rbs[k_chpart_body1].co ); + v3_copy( ch->origins[k_chpart_body1], rbs[k_chpart_body1].co ); rbs[k_chpart_body1].co[1] += chest_height*0.5f; /* Torso */ v3f torso_dims = { chest_depth, offs[k_chpart_body1][1]-offs[k_chpart_leg_l0][1], chest_width*0.85f }; - v3_copy( ch->parts[k_chpart_body0].pivot, rbs[k_chpart_body0].co ); + v3_copy( ch->origins[k_chpart_body0], rbs[k_chpart_body0].co ); character_rd_box( ch, k_chpart_body0, torso_dims ); /* Neck */ v3f neck_dims = { chest_depth*0.5f, offs[k_chpart_head][1], chest_depth*0.5f }; - v3_copy( ch->parts[k_chpart_neck].pivot, rbs[k_chpart_neck].co ); + v3_copy( ch->origins[k_chpart_neck], rbs[k_chpart_neck].co ); rbs[k_chpart_neck].co[1] += neck_dims[1]*0.5f; character_rd_box( ch, k_chpart_neck, neck_dims ); /* Head */ v3f head_dims = { chest_width*0.5f, chest_width*0.5f, chest_width*0.5f }; - v3_copy( ch->parts[k_chpart_head].pivot, rbs[k_chpart_head].co ); + v3_copy( ch->origins[k_chpart_head], rbs[k_chpart_head].co ); rbs[k_chpart_head].co[1] += head_dims[1]*0.5f; character_rd_box( ch, k_chpart_head, head_dims ); @@ -738,18 +748,18 @@ static void character_init_ragdoll( struct character *ch ) character_rd_box( ch, k_chpart_hand_l, hand_dims ); character_rd_box( ch, k_chpart_hand_r, hand_dims ); - v3_copy( ch->parts[k_chpart_arm_l0].pivot, rbs[k_chpart_arm_l0].co ); + v3_copy( ch->origins[k_chpart_arm_l0], rbs[k_chpart_arm_l0].co ); rbs[k_chpart_arm_l0].co[2] += ua_dims[2] * 0.5f; - v3_copy( ch->parts[k_chpart_arm_l1].pivot, rbs[k_chpart_arm_l1].co ); + v3_copy( ch->origins[k_chpart_arm_l1], rbs[k_chpart_arm_l1].co ); rbs[k_chpart_arm_l1].co[2] += la_dims[2] * 0.5f; - v3_copy( ch->parts[k_chpart_hand_l].pivot, rbs[k_chpart_hand_l].co ); + v3_copy( ch->origins[k_chpart_hand_l], rbs[k_chpart_hand_l].co ); rbs[k_chpart_hand_l].co[2] += hand_dims[2] * 0.5f; - v3_copy( ch->parts[k_chpart_arm_r0].pivot, rbs[k_chpart_arm_r0].co ); + v3_copy( ch->origins[k_chpart_arm_r0], rbs[k_chpart_arm_r0].co ); rbs[k_chpart_arm_r0].co[2] -= ua_dims[2] * 0.5f; - v3_copy( ch->parts[k_chpart_arm_r1].pivot, rbs[k_chpart_arm_r1].co ); + v3_copy( ch->origins[k_chpart_arm_r1], rbs[k_chpart_arm_r1].co ); rbs[k_chpart_arm_r1].co[2] -= la_dims[2] * 0.5f; - v3_copy( ch->parts[k_chpart_hand_r].pivot, rbs[k_chpart_hand_r].co ); + v3_copy( ch->origins[k_chpart_hand_r], rbs[k_chpart_hand_r].co ); rbs[k_chpart_hand_r].co[2] -= hand_dims[2] * 0.5f; /* LEgs */ @@ -766,19 +776,19 @@ static void character_init_ragdoll( struct character *ch ) character_rd_box( ch, k_chpart_foot_l, foot_dims ); character_rd_box( ch, k_chpart_foot_r, foot_dims ); - v3_copy( ch->parts[k_chpart_leg_l0].pivot, rbs[k_chpart_leg_l0].co ); + v3_copy( ch->origins[k_chpart_leg_l0], rbs[k_chpart_leg_l0].co ); rbs[k_chpart_leg_l0].co[1] -= ul_dims[1] * 0.5f; - v3_copy( ch->parts[k_chpart_leg_l1].pivot, rbs[k_chpart_leg_l1].co ); + v3_copy( ch->origins[k_chpart_leg_l1], rbs[k_chpart_leg_l1].co ); rbs[k_chpart_leg_l1].co[1] -= ll_dims[1] * 0.5f; - v3_copy( ch->parts[k_chpart_foot_l].pivot, rbs[k_chpart_foot_l].co ); + v3_copy( ch->origins[k_chpart_foot_l], rbs[k_chpart_foot_l].co ); rbs[k_chpart_foot_l].co[1] -= foot_dims[1] * 0.5f; rbs[k_chpart_foot_l].co[0] -= foot_dims[0] * 0.5f; - v3_copy( ch->parts[k_chpart_leg_r0].pivot, rbs[k_chpart_leg_r0].co ); + v3_copy( ch->origins[k_chpart_leg_r0], rbs[k_chpart_leg_r0].co ); rbs[k_chpart_leg_r0].co[1] -= ul_dims[1] * 0.5f; - v3_copy( ch->parts[k_chpart_leg_r1].pivot, rbs[k_chpart_leg_r1].co ); + v3_copy( ch->origins[k_chpart_leg_r1], rbs[k_chpart_leg_r1].co ); rbs[k_chpart_leg_r1].co[1] -= ll_dims[1] * 0.5f; - v3_copy( ch->parts[k_chpart_foot_r].pivot, rbs[k_chpart_foot_r].co ); + v3_copy( ch->origins[k_chpart_foot_r], rbs[k_chpart_foot_r].co ); rbs[k_chpart_foot_r].co[1] -= foot_dims[1] * 0.5f; rbs[k_chpart_foot_r].co[0] -= foot_dims[0] * 0.5f; diff --git a/common.h b/common.h index 1e1ebed..2125154 100644 --- a/common.h +++ b/common.h @@ -5,12 +5,17 @@ #define VG_FRAMEBUFFER_RESIZE 1 #include "vg/vg.h" +/* TODO: he needs a home somewhere */ static float ktimestep = 1.0f/60.0f; +/* TODO: he needs a home somewhere */ enum classtype { k_classtype_none = 0, - k_classtype_gate = 1 + k_classtype_gate = 1, + k_classtype_block = 2, + k_classtype_spawn = 3, + k_classtype_water = 4 }; /* TODO: he needs a home somewhere */ diff --git a/gate.h b/gate.h index 3151878..a370d81 100644 --- a/gate.h +++ b/gate.h @@ -62,8 +62,8 @@ static void gate_init(void) { fb_init( &grender.fb ); - model *mgate = vg_asset_read( "models/rs_gate.mdl" ); - model_unpack( mgate, &grender.mdl ); + mdl_header *mgate = mdl_load( "models/rs_gate.mdl" ); + mdl_unpack_glmesh( mgate, &grender.mdl ); free( mgate ); } diff --git a/lighting.h b/lighting.h index 8f9bdb3..e69de29 100644 --- a/lighting.h +++ b/lighting.h @@ -1,104 +0,0 @@ -#ifndef LIGHTING_H -#define LIGHTING_H - -#include "common.h" -#include "scene.h" - -static int ray_world( v3f pos, v3f dir, ray_hit *hit ); - -typedef struct voxel_gi voxel_gi; - -struct voxel_gi -{ - GLuint hw_texture, - pt_texture; - - v3i pt_dims; /* Page table dimentions */ - i32 page_size; - - v3f origin; -}; - -static void voxel_gi_setup( voxel_gi *gi, scene *sc, - i32 page_size, float voxel_res ) -{ - v3_copy( sc->bbx[0], gi->origin ); - gi->page_size = page_size; - - v3f extent; - v3_sub( sc->bbx[1], sc->bbx[0], extent ); - - float fpage_size = voxel_res * (float)page_size; - - for( int i=0; i<3; i++ ) - { - i32 voxel_count = extent[i] / voxel_res; - gi->pt_dims[i] = (voxel_count+page_size-1) / page_size; - } - - i32 pt_capacity = gi->pt_dims[0]*gi->pt_dims[1]*gi->pt_dims[2]; - vg_info( "Page table size: %dkb\n", (pt_capacity*2*2)/1024 ); - - u16 *page_table = malloc( pt_capacity*sizeof(u16)*3 ); - - - u32 active_count = 0; - - for( int z=0; zpt_dims[2]; z++ ) - { - for( int y=0; ypt_dims[1]; y++ ) - { - for( int x=0; xpt_dims[0]; x++ ) - { - v3f base = {x,y,z}, - end = {x+1,y+1,z+1}; - - boxf page_region; - - v3_muladds( sc->bbx[0], base, ((float)page_size)*voxel_res, base ); - v3_muladds( sc->bbx[0], end, ((float)page_size)*voxel_res, end ); - v3_copy( base, page_region[0] ); - v3_copy( end, page_region[1] ); - - u32 nothing[2]; - if( bh_select( &sc->bhtris, page_region, nothing, 2 )) - { - active_count ++; - - /* Calculate lighting */ - } - } - } - } - - /* Calculate physical storage size */ - vg_info( "Hardware texture required size: %dmb\n", - ((active_count*page_size*page_size*page_size*1)/1024)/1024 ); - vg_info( "Required physical blocks: %d\n", active_count ); - - free( page_table ); -} - -static void compute_lighting_vertex( scene *sc ) -{ - v3f light_dir = { 0.2f, 1.0f, 1.0f }; - v3_normalize( light_dir ); - - for( int i=0; ivertex_count; i++ ) - { - model_vert *mv = &sc->verts[i]; - - ray_hit hit; - hit.dist = 100.0f; - - ray_world( mv->co, light_dir, &hit ); - float amt = hit.dist / 100.0f; - - mv->colour[0] = amt; - mv->colour[1] = amt; - mv->colour[2] = amt; - mv->colour[3] = 1.0f; - } -} - -#endif /* LIGHTING_H */ diff --git a/main.c b/main.c index aa90b02..84de410 100644 --- a/main.c +++ b/main.c @@ -151,7 +151,7 @@ void vg_start(void) world_load(); - reset_player( 1, (const char *[]){ "tutorial" } ); + reset_player( 1, (const char *[]){ "start" } ); player_transform_update(); } diff --git a/mesh.h b/mesh.h new file mode 100644 index 0000000..e69de29 diff --git a/model.h b/model.h index 430ed26..4ed39d4 100644 --- a/model.h +++ b/model.h @@ -1,28 +1,35 @@ #ifndef MODEL_H #define MODEL_H -#include "vg/vg.h" +#include "common.h" -typedef struct model model; typedef struct glmesh glmesh; -typedef struct submodel submodel; -typedef struct model_vert model_vert; -typedef struct model_marker model_marker; -typedef struct sdf_primative sdf_primative; -typedef enum esdf_type esdf_type; + +typedef struct mdl_vert mdl_vert; +typedef struct mdl_submesh mdl_submesh; +typedef struct mdl_material mdl_material; +typedef struct mdl_node mdl_node; +typedef struct mdl_header mdl_header; + +#define MDL_SIZE_MAX 0x1000000 +#define MDL_VERT_MAX 1000000 +#define MDL_INDICE_MAX 1000000 +#define MDL_MATERIAL_MAX 500 +#define MDL_NODE_MAX 4000 +#define MDL_SUBMESH_MAX 8000 +#define MDL_STRING_LENGTH_MAX 64 #pragma pack(push,1) -struct model -{ - u32 identifier; - u32 vertex_count, - indice_count, - layer_count, - marker_count; +struct mdl_vert +{ + v3f co, + norm; + v4f colour; + v2f uv; }; -struct submodel +struct mdl_submesh { u32 indice_start, indice_count, @@ -30,36 +37,69 @@ struct submodel vertex_count; boxf bbx; - v3f pivot; /* same as co? */ - v4f q; - char name[32]; - char material[32]; + u32 material_id; }; -struct classtype_gate +struct mdl_material { - u32 target; + u32 pstr_name; }; -struct model_marker +struct mdl_node { v3f co; v4f q; v3f s; - u32 classtype; - u32 offset; - char name[32]; + + u32 submesh_start, + submesh_count, + classtype, + offset, + pstr_name; }; -struct model_vert +struct mdl_header { - v3f co, - norm; - v4f colour; - v2f uv; + u32 identifier, version, file_length; + + u32 vertex_count, vertex_offset, + indice_count, indice_offset, + submesh_count, submesh_offset, + material_count, material_offset, + node_count, node_offset, + strings_offset, entdata_offset; +}; + +/* + * Entity data structures + */ + +struct classtype_block +{ + boxf bbx; +}; + +struct classtype_gate +{ + u32 target; +}; + +struct classtype_spawn +{ + u32 target; +}; + +struct classtype_water +{ + u32 temp; }; + #pragma pack(pop) +/* + * Simple mesh interface for OpenGL + */ + struct glmesh { GLuint vao, vbo, ebo; @@ -67,7 +107,7 @@ struct glmesh }; static void mesh_upload( glmesh *mesh, - model_vert *verts, u32 vert_count, + mdl_vert *verts, u32 vert_count, u32 *indices, u32 indice_count ) { glGenVertexArrays( 1, &mesh->vao ); @@ -75,29 +115,29 @@ static void mesh_upload( glmesh *mesh, glGenBuffers( 1, &mesh->ebo ); glBindVertexArray( mesh->vao ); + size_t stride = sizeof(mdl_vert); + glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo ); - glBufferData( GL_ARRAY_BUFFER, vert_count*sizeof(model_vert), - verts, GL_STATIC_DRAW ); + glBufferData( GL_ARRAY_BUFFER, vert_count*stride, verts, GL_STATIC_DRAW ); glBindVertexArray( mesh->vao ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, indice_count*sizeof(u32), indices, GL_STATIC_DRAW ); - glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, - sizeof(model_vert), (void*)0 ); + glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 ); glEnableVertexAttribArray( 0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, - sizeof(model_vert), (void *)offsetof(model_vert, norm) ); + stride, (void *)offsetof(mdl_vert, norm) ); glEnableVertexAttribArray( 1 ); glVertexAttribPointer( 2, 4, GL_FLOAT, GL_FALSE, - sizeof(model_vert), (void *)offsetof(model_vert, colour) ); + stride, (void *)offsetof(mdl_vert, colour) ); glEnableVertexAttribArray( 2 ); glVertexAttribPointer( 3, 2, GL_FLOAT, GL_FALSE, - sizeof(model_vert), (void *)offsetof(model_vert, uv) ); + stride, (void *)offsetof(mdl_vert, uv) ); glEnableVertexAttribArray( 3 ); VG_CHECK_GL(); @@ -120,92 +160,227 @@ static void mesh_draw( glmesh *mesh ) mesh_drawn( 0, mesh->indice_count ); } +static void mesh_free( glmesh *mesh ) +{ + glDeleteVertexArrays( 1, &mesh->vao ); + glDeleteBuffers( 1, &mesh->ebo ); + glDeleteBuffers( 1, &mesh->vbo ); +} + + /* - * Helper functions for file offsets - * TODO: Revise this + * Model implementation */ -static submodel *model_get_submodel( model *mdl, int id ) + +static mdl_header *mdl_load( const char *path ) { - return ((submodel*)(mdl+1)) + id; + i64 size; + mdl_header *header = vg_asset_read_s( path, &size ); + + /* + * Check file is valid + */ + if( !header ) + { + vg_error( "Could not open '%s'\n", path ); + return NULL; + } + + if( size < sizeof(mdl_header) ) + { + free( header ); + vg_error( "Invalid file '%s' (too small for header)\n", path ); + return NULL; + } + + if( header->file_length != size ) + { + vg_error( "Invalid file '%s'" + "(wrong .file_length, %ub != real file size %ub)\n", + path, header->file_length, size ); + free( header ); + return NULL; + } + + /* + * Validate offsets and memory sections, to ensure all arrays are in-bounds, + * and that they do not overlap. + */ + + struct memregion + { + const char *desc; + u32 count, max_count, size, offset; + } + regions[] = { + { + "Vertices", + header->vertex_count, MDL_VERT_MAX, + sizeof(mdl_vert), header->vertex_offset + }, + { + "Indices", + header->indice_count, MDL_INDICE_MAX, + sizeof(u32), header->indice_offset + }, + { + "Submesh", + header->submesh_count, MDL_SUBMESH_MAX, + sizeof(mdl_submesh), header->submesh_offset + }, + { + "Materials", + header->material_count, MDL_MATERIAL_MAX, + sizeof(mdl_material), header->material_offset + }, + { + "Nodes", + header->node_count, MDL_NODE_MAX, + sizeof(mdl_node), header->node_count + } + }; + + for( int i=0; icount == 0 ) + continue; + + if( ri->count > ri->max_count ) + { + free( header ); + vg_error( "'%s': '%s' buffer exceeds the maximum (%u/%u)\n", + path, ri->desc, ri->count, ri->max_count ); + return NULL; + } + + if( ri->offset >= header->file_length ) + { + free( header ); + vg_error( "'%s': '%s' buffer offset is out of range\n", + path, ri->desc ); + return NULL; + } + + if( ri->offset + ri->size*ri->count > header->file_length ) + { + free( header ); + vg_error( "'%s': '%s' buffer size is out of range\n", + path, ri->desc ); + return NULL; + } + + for( int j=0; jcount == 0 ) + continue; + + if( ri->offset >= rj->offset && + (ri->offset+ri->size*ri->count < rj->offset+rj->size*rj->count)) + { + free( header ); + vg_error( "'%s': '%s' buffer overlaps '%s'\n", + path, ri->desc, rj->desc ); + return NULL; + } + } + } + + /* + * Pointer validation TODO(workshop) + */ + + /* + * strings TODO(workshop) + */ + + return header; } -static model_marker *model_get_marker( model *mdl, int id ) +static void *mdl_baseptr( mdl_header *mdl, u32 offset ) { - return ((model_marker*)model_get_submodel(mdl,mdl->layer_count)) + id; + return (void *)mdl + offset; } -static model_vert *model_vertex_base( model *mdl ) +static const char *mdl_pstr( mdl_header *mdl, u32 pstr ) { - return (model_vert *)model_get_marker( mdl, mdl->marker_count ); + return (const char *)(mdl_baseptr( mdl, mdl->strings_offset )) + pstr; } -static u32 *model_indice_base( model *mdl ) +static mdl_node *mdl_node_from_id( mdl_header *mdl, u32 id ) { - return (u32 *)(model_vertex_base( mdl ) + mdl->vertex_count); + return ((mdl_node *)mdl_baseptr( mdl, mdl->node_offset )) + id; } -static model_vert *submodel_vert_data( model *mdl, submodel *sub ) +static mdl_node *mdl_node_from_name( mdl_header *mdl, const char *name ) { - return model_vertex_base(mdl) + sub->vertex_start; + for( int i=0; inode_count; i++ ) + { + mdl_node *pnode = mdl_node_from_id( mdl, i ); + + if( !strcmp( name, mdl_pstr( mdl, pnode->pstr_name )) ) + return pnode; + } + + return NULL; } -static u32 *submodel_indice_data( model *mdl, submodel *sub ) +static mdl_submesh *mdl_submesh_from_id( mdl_header *mdl, u32 id ) { - return model_indice_base(mdl) + sub->indice_start; + if( id >= mdl->submesh_count ) + return NULL; + + return ((mdl_submesh *)mdl_baseptr( mdl, mdl->submesh_offset )) + id; } -static void *get_entdata_raw( model *mdl, model_marker *marker ) +static mdl_submesh *mdl_node_submesh( mdl_header *mdl, mdl_node *node, u32 i ) { - return ((void *)(model_indice_base(mdl) + mdl->indice_count)) + - marker->offset; + if( i >= node->submesh_count ) + return NULL; + + return mdl_submesh_from_id( mdl, node->submesh_start+i ); } -static submodel *submodel_get( model *mdl, const char *name ) +static u32 *mdl_submesh_indices( mdl_header *mdl, mdl_submesh *sm ) { - for( int i=0; ilayer_count; i++ ) - { - submodel *pmdl =model_get_submodel(mdl,i); - - if( !strcmp( pmdl->name, name ) ) - return pmdl; - } - - return NULL; + return ((u32 *)mdl_baseptr( mdl, mdl->indice_offset )) + sm->indice_start; } -static model_marker *model_marker_get( model *mdl, const char *name ) +static mdl_vert *mdl_submesh_vertices( mdl_header *mdl, mdl_submesh *sm ) { - for( int i=0; imarker_count; i++ ) - { - model_marker *mk = model_get_marker( mdl,i ); + return ((mdl_vert *)mdl_baseptr(mdl,mdl->vertex_offset)) + sm->vertex_start; +} - if( !strcmp( mk->name, name ) ) - return mk; - } - - return NULL; +static mdl_material *mdl_material_from_id( mdl_header *mdl, u32 id ) +{ + return ((mdl_material *)mdl_baseptr(mdl,mdl->material_offset)) + id; } -static void submodel_draw( submodel *sm ) +static void mdl_node_transform( mdl_node *pnode, m4x3f transform ) { - mesh_drawn( sm->indice_start, sm->indice_count ); + q_m3x3( pnode->q, transform ); + transform[0][0] *= pnode->s[0]; + transform[1][1] *= pnode->s[1]; + transform[2][2] *= pnode->s[2]; + v3_copy( pnode->co, transform[3] ); } -static void model_unpack_submodel( model *model, glmesh *mesh, submodel *sm ) +static void mdl_unpack_submesh( mdl_header *mdl, glmesh *mesh, mdl_submesh *sm ) { - mesh_upload( mesh, - model_vertex_base( model ) + sm->vertex_start, sm->vertex_count, - model_indice_base( model ) + sm->indice_start, sm->indice_count ); + mesh_upload( mesh, mdl_submesh_vertices( mdl, sm ), sm->vertex_count, + mdl_submesh_indices( mdl, sm ), sm->indice_count ); } -static void model_unpack( model *model, glmesh *mesh ) +static void mdl_unpack_glmesh( mdl_header *mdl, glmesh *mesh ) { - u32 offset = model_get_submodel( model, 0 )->vertex_count; + u32 offset = mdl_submesh_from_id( mdl, 0 )->vertex_count; - for( int i=1; ilayer_count; i++ ) + for( int i=1; i< mdl->submesh_count; i++ ) { - submodel *sm = model_get_submodel( model, i ); - u32 *indices = submodel_indice_data( model, sm ); + mdl_submesh *sm = mdl_submesh_from_id( mdl, i ); + u32 *indices = mdl_submesh_indices( mdl, sm ); for( u32 j=0; jindice_count; j++ ) indices[j] += offset; @@ -213,15 +388,22 @@ static void model_unpack( model *model, glmesh *mesh ) offset += sm->vertex_count; } - mesh_upload( mesh, model_vertex_base( model ), model->vertex_count, - model_indice_base( model ), model->indice_count ); + mdl_vert *vertex_base = mdl_baseptr( mdl, mdl->vertex_offset ); + u32 *indice_base = mdl_baseptr( mdl, mdl->indice_offset ); + + mesh_upload( mesh, vertex_base, mdl->vertex_count, + indice_base, mdl->indice_count ); } -static void mesh_free( glmesh *mesh ) +static void mdl_draw_submesh( mdl_submesh *sm ) { - glDeleteVertexArrays( 1, &mesh->vao ); - glDeleteBuffers( 1, &mesh->ebo ); - glDeleteBuffers( 1, &mesh->vbo ); + mesh_drawn( sm->indice_start, sm->indice_count ); } +static void *mdl_get_entdata( mdl_header *mdl, mdl_node *pnode ) +{ + return mdl_baseptr( mdl, mdl->entdata_offset ) + pnode->offset; +} + + #endif diff --git a/player.h b/player.h index 3501c72..bd7304d 100644 --- a/player.h +++ b/player.h @@ -66,15 +66,49 @@ static void player_transform_update(void) static int reset_player( int argc, char const *argv[] ) { - v3_copy( world.tutorial, player.co ); - if( argc == 1 ) + struct respawn_point *rp = NULL, *r; + + if( argc > 1 ) { - if( !strcmp( argv[0], "tutorial" )) - v3_copy( world.tutorial, player.co ); + for( int i=0; iname, argv[0] ) ) + { + rp = r; + break; + } + } + + if( !rp ) + vg_warn( "No spawn named '%s'\n", argv[0] ); } - v3_copy( (v3f){ 0.0f, 0.0f, -0.2f }, player.v ); - q_identity( player.rot ); + if( !rp ) + { + float min_dist = INFINITY; + for( int i=0; ico, player.co ); + + if( d < min_dist ) + { + min_dist = d; + rp = r; + } + } + } + + if( !rp ) + { + vg_error( "No spawn found\n" ); + return 0; + } + + v4_copy( r->q, player.rot ); + v3_copy( r->co, player.co ); + player.vswitch = 1.0f; player.slip_last = 0.0f; player.is_dead = 0; @@ -85,7 +119,8 @@ static int reset_player( int argc, char const *argv[] ) player.mdl.shoes[1] = 1; player_transform_update(); - return 0; + m3x3_mulv( player.to_world, (v3f){ 0.0f, 0.0f, -0.2f }, player.v ); + return 1; } static void player_mouseview(void) @@ -597,7 +632,7 @@ static void player_do_motion(void) static int player_walkgrid_tri_walkable( u32 tri[3] ) { - return tri[0] < world.sm_road.vertex_count; + return tri[0] < world.sm_surface.vertex_count; } #define WALKGRID_SIZE 16 diff --git a/scene.h b/scene.h index 76baf54..3e3829e 100644 --- a/scene.h +++ b/scene.h @@ -11,7 +11,7 @@ struct scene { glmesh mesh; - model_vert *verts; + mdl_vert *verts; u32 *indices; bh_tree bhtris; @@ -25,10 +25,12 @@ struct scene u32 shadower_count, shadower_cap; - submodel submesh; + mdl_submesh submesh; }; +#if 0 GLuint tex_dual_noise; +#endif static void scene_init( scene *pscene ) { @@ -44,6 +46,7 @@ static void scene_init( scene *pscene ) v3_fill( pscene->bbx[0], 999999.9f ); v3_fill( pscene->bbx[1], -999999.9f ); +#if 0 static int noise_ready = 0; if( !noise_ready ) { @@ -78,6 +81,7 @@ static void scene_init( scene *pscene ) free( buf ); } +#endif } static void *buffer_reserve( void *buffer, u32 count, u32 *cap, u32 amount, @@ -93,85 +97,37 @@ static void *buffer_reserve( void *buffer, u32 count, u32 *cap, u32 amount, return buffer; } +static void *buffer_fix( void *buffer, u32 count, u32 *cap, size_t emsize ) +{ + *cap = count; + return realloc( buffer, (*cap) * emsize ); +} + /* * Append a model into the scene with a given transform */ -static void scene_add_model( scene *pscene, model *mdl, submodel *submodel, - v3f pos, float yaw, float scale ) -{ - pscene->verts = buffer_reserve( pscene->verts, pscene->vertex_count, - &pscene->vertex_cap, submodel->vertex_count, sizeof(model_vert) ); - pscene->indices = buffer_reserve( pscene->indices, pscene->indice_count, - &pscene->indice_cap, submodel->indice_count, sizeof(u32) ); - - /* Transform and place vertices */ - model_vert *src_verts = submodel_vert_data( mdl, submodel ); - u32 *src_indices = submodel_indice_data( mdl, submodel ); - - m4x3f mtx; - m4x3_identity( mtx ); - m4x3_translate( mtx, pos ); - m4x3_rotate_y( mtx, yaw ); - m4x3_scale( mtx, scale ); - - boxf bbxnew; - box_copy( submodel->bbx, bbxnew ); - m4x3_transform_aabb( mtx, bbxnew ); - box_concat( pscene->bbx, bbxnew ); - - m3x3f rotation; - m4x3_to_3x3( mtx, rotation ); - float rand_hue = vg_randf(); - - for( u32 i=0; ivertex_count; i++ ) - { - model_vert *pvert = &pscene->verts[ pscene->vertex_count+i ], - *src = &src_verts[ i ]; - - m4x3_mulv( mtx, src->co, pvert->co ); - m3x3_mulv( rotation, src->norm, pvert->norm ); - - v4_copy( src->colour, pvert->colour ); - v2_copy( src->uv, pvert->uv ); - - float rel_y = src->co[1] / submodel->bbx[1][1]; - pvert->colour[0] = rel_y; - pvert->colour[2] = rand_hue; - } - - for( u32 i=0; iindice_count; i++ ) - { - u32 *pidx = &pscene->indices[ pscene->indice_count+i ]; - *pidx = src_indices[i] + pscene->vertex_count; - } - - pscene->vertex_count += submodel->vertex_count; - pscene->indice_count += submodel->indice_count; -} - -static void scene_add_foliage( scene *pscene, model *mdl, submodel *submodel, - m4x3f transform ) +static void scene_add_submesh( scene *pscene, mdl_header *mdl, + mdl_submesh *sm, m4x3f transform ) { pscene->verts = buffer_reserve( pscene->verts, pscene->vertex_count, - &pscene->vertex_cap, submodel->vertex_count, sizeof(model_vert) ); + &pscene->vertex_cap, sm->vertex_count, sizeof(mdl_vert) ); pscene->indices = buffer_reserve( pscene->indices, pscene->indice_count, - &pscene->indice_cap, submodel->indice_count, sizeof(u32) ); + &pscene->indice_cap, sm->indice_count, sizeof(u32) ); /* Transform and place vertices */ - model_vert *src_verts = submodel_vert_data( mdl, submodel ); - u32 *src_indices = submodel_indice_data( mdl, submodel ); + mdl_vert *src_verts = mdl_submesh_vertices( mdl, sm ); + u32 *src_indices = mdl_submesh_indices( mdl, sm ); boxf bbxnew; - box_copy( submodel->bbx, bbxnew ); + box_copy( sm->bbx, bbxnew ); m4x3_transform_aabb( transform, bbxnew ); box_concat( pscene->bbx, bbxnew ); - float rand_hue = vg_randf(); - for( u32 i=0; ivertex_count; i++ ) + for( u32 i=0; ivertex_count; i++ ) { - model_vert *pvert = &pscene->verts[ pscene->vertex_count+i ], - *src = &src_verts[ i ]; + mdl_vert *pvert = &pscene->verts[ pscene->vertex_count+i ], + *src = &src_verts[ i ]; m4x3_mulv( transform, src->co, pvert->co ); m3x3_mulv( transform, src->norm, pvert->norm ); @@ -180,17 +136,17 @@ static void scene_add_foliage( scene *pscene, model *mdl, submodel *submodel, v2_copy( src->uv, pvert->uv ); } - for( u32 i=0; iindice_count; i++ ) + for( u32 i=0; iindice_count; i++ ) { u32 *pidx = &pscene->indices[ pscene->indice_count+i ]; *pidx = src_indices[i] + pscene->vertex_count; } - pscene->vertex_count += submodel->vertex_count; - pscene->indice_count += submodel->indice_count; + pscene->vertex_count += sm->vertex_count; + pscene->indice_count += sm->indice_count; } -static void scene_copy_slice( scene *pscene, submodel *sm ) +static void scene_copy_slice( scene *pscene, mdl_submesh *sm ) { sm->indice_start = pscene->submesh.indice_start; sm->indice_count = pscene->indice_count - sm->indice_start; @@ -202,6 +158,15 @@ static void scene_copy_slice( scene *pscene, submodel *sm ) pscene->submesh.vertex_start = pscene->vertex_count; } +static void scene_fix( scene *pscene ) +{ + buffer_fix( pscene->verts, pscene->vertex_count, + &pscene->vertex_cap, sizeof( mdl_vert )); + + buffer_fix( pscene->indices, pscene->indice_count, + &pscene->indice_cap, sizeof( mdl_vert )); +} + static void scene_upload( scene *pscene ) { mesh_upload( &pscene->mesh, @@ -223,6 +188,14 @@ static void scene_draw( scene *pscene ) mesh_drawn( 0, pscene->indice_count ); } +static void scene_free( scene *pscene ) +{ + free( pscene->verts ); + free( pscene->indices ); + + /* TODO: bvh */ +} + static void scene_register(void) { } @@ -234,9 +207,9 @@ static void scene_register(void) static void scene_bh_expand_bound( void *user, boxf bound, u32 item_index ) { scene *s = user; - model_vert *pa = &s->verts[ s->indices[item_index*3+0] ], - *pb = &s->verts[ s->indices[item_index*3+1] ], - *pc = &s->verts[ s->indices[item_index*3+2] ]; + mdl_vert *pa = &s->verts[ s->indices[item_index*3+0] ], + *pb = &s->verts[ s->indices[item_index*3+1] ], + *pc = &s->verts[ s->indices[item_index*3+2] ]; box_addpt( bound, pa->co ); box_addpt( bound, pb->co ); @@ -246,9 +219,9 @@ static void scene_bh_expand_bound( void *user, boxf bound, u32 item_index ) static float scene_bh_centroid( void *user, u32 item_index, int axis ) { scene *s = user; - model_vert *pa = &s->verts[ s->indices[item_index*3+0] ], - *pb = &s->verts[ s->indices[item_index*3+1] ], - *pc = &s->verts[ s->indices[item_index*3+2] ]; + mdl_vert *pa = &s->verts[ s->indices[item_index*3+0] ], + *pb = &s->verts[ s->indices[item_index*3+1] ], + *pc = &s->verts[ s->indices[item_index*3+2] ]; return (pa->co[axis] + pb->co[axis] + pc->co[axis]) * (1.0f/3.0f); } @@ -278,9 +251,9 @@ static void scene_bh_debug( void *user, u32 item_index ) { scene *s = user; u32 idx = item_index*3; - model_vert *pa = &s->verts[ s->indices[ idx+0 ] ], - *pb = &s->verts[ s->indices[ idx+1 ] ], - *pc = &s->verts[ s->indices[ idx+2 ] ]; + mdl_vert *pa = &s->verts[ s->indices[ idx+0 ] ], + *pb = &s->verts[ s->indices[ idx+1 ] ], + *pc = &s->verts[ s->indices[ idx+2 ] ]; vg_line( pa->co, pb->co, 0xff0000ff ); vg_line( pb->co, pc->co, 0xff0000ff ); diff --git a/textures/gradients.png b/textures/gradients.png index 37d3e74434be5b4cf18d7c107ff5c3f2ef291cb1..5118b9c5e5668be2871c6167dbc3043c4c963c93 100644 GIT binary patch delta 40988 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 diff --git a/world.h b/world.h index 20c4401..d8b3616 100644 --- a/world.h +++ b/world.h @@ -26,7 +26,15 @@ static int ray_world( v3f pos, v3f dir, ray_hit *hit ); static struct gworld { /* gameplay */ - v3f tutorial; + struct respawn_point + { + v3f co; + v4f q; + char name[32]; + } + spawns[32]; + u32 spawn_count; + teleport_gate gates[64]; u32 gate_count; @@ -37,11 +45,10 @@ static struct gworld /* Rendering & geometry */ scene geo, foliage, props; - submodel sm_road, sm_terrain; + mdl_submesh sm_surface; glmesh skybox, skydome; - submodel dome_upper, - dome_lower; + mdl_submesh dome_upper, dome_lower; } world; @@ -64,7 +71,7 @@ static int ray_world( v3f pos, v3f dir, ray_hit *hit ) static int ray_hit_is_ramp( ray_hit *hit ) { - return hit->tri[0] < world.sm_road.vertex_count; + return hit->tri[0] < world.sm_surface.vertex_count; } static void world_register(void) @@ -82,146 +89,39 @@ static void world_free(void) } static void render_world_depth( m4x4f projection, m4x3f camera ); -static void world_load(void) -{ - /* - * Setup scene - * - * TODO: Call world_free when its ready here - * - */ - scene_init( &world.geo ); - model *mworld = vg_asset_read( "models/mp_dev.mdl" ); - for( int i=0; ilayer_count; i++ ) +static void add_all_if_material( scene *pscene, mdl_header *mdl, u32 id ) +{ + for( int i=0; inode_count; i++ ) { - submodel *sm = model_get_submodel( mworld, i ); - if( !strcmp( sm->material, "surf" ) ) - scene_add_model( &world.geo, mworld, sm, sm->pivot, 0.0f, 1.0f ); + mdl_node *pnode = mdl_node_from_id( mdl, i ); - } - for( int i=0; ilayer_count; i++ ) - { - submodel *sm = model_get_submodel( mworld, i ); - if( !strcmp( sm->material, "vertex_blend" ) ) + for( int j=0; jsubmesh_count; j++ ) { - m4x3f transform; - q_m3x3( sm->q, transform ); - v3_copy( sm->pivot, transform[3] ); - scene_add_foliage( &world.geo, mworld, sm, transform ); - } - } - scene_copy_slice( &world.geo, &world.sm_road ); + mdl_submesh *sm = mdl_node_submesh( mdl, pnode, j ); - for( int i=0; ilayer_count; i++ ) - { - submodel *sm = model_get_submodel( mworld, i ); - if( !strcmp( sm->material, "terrain" ) ) - scene_add_model( &world.geo, mworld, sm, sm->pivot, 0.0f, 1.0f ); - } - - - scene_copy_slice( &world.geo, &world.sm_terrain ); - - /* - * TODO: Parametric marker import - */ - v3_copy( model_marker_get( mworld, "start" )->co, world.tutorial ); - - /* - * Initialize gates - */ - - world.gate_count = 0; - for( int i=0; imarker_count; i++ ) - { - model_marker *ga = model_get_marker( mworld, i ); - - if( ga->classtype == k_classtype_gate ) - { - struct classtype_gate *data = get_entdata_raw( mworld, ga ); - - if( data->target ) + if( sm->material_id == id ) { - model_marker *gb = model_get_marker( mworld, data->target ); - - teleport_gate *gate = &world.gates[ world.gate_count ++ ]; - - v3_copy( ga->co, gate->co[0] ); - v3_copy( gb->co, gate->co[1] ); - v4_copy( ga->q, gate->q[0] ); - v4_copy( gb->q, gate->q[1] ); - v2_copy( ga->s, gate->dims ); - - gate_transform_update( gate ); + m4x3f transform; + mdl_node_transform( pnode, transform ); + scene_add_submesh( pscene, mdl, sm, transform ); } } } +} - /* - * Load water mesh (1 per world) - */ - for( int i=0; ilayer_count; i++ ) - { - submodel *sm = model_get_submodel( mworld, i ); - if( !strcmp( sm->material, "water" ) ) - { - glmesh surf; - model_unpack_submodel( mworld, &surf, sm ); - - water_init(); - water_set_surface( &surf, sm->pivot[1] ); - - vg_info( "%.3f\n", sm->pivot[1] ); - - break; - } - } - - scene_bh_create( &world.geo ); - scene_upload( &world.geo ); - +static void world_apply_foliage(void) +{ scene_init( &world.foliage ); - model *mfoliage = vg_asset_read("models/rs_foliage.mdl"); - - /* - * TODO: Load any other meshes into the foliage scene, and create rbs for - * them. - * - * then compute bvh - */ - - for( int i=0; ilayer_count; i++ ) - { - submodel *sm = model_get_submodel( mworld, i ); - if( !strcmp( sm->material, "surf" ) || - !strcmp( sm->material, "terrain" ) || - !strcmp( sm->material, "water" ) || - !strcmp( sm->material, "vertex_blend") ) - continue; - - m4x3f transform; - q_m3x3( sm->q, transform ); - v3_copy( sm->pivot, transform[3] ); - scene_add_foliage( &world.foliage, mworld, sm, transform ); - - rigidbody *rb = &world.temp_rbs[ world.rb_count ++ ]; - - box_copy( sm->bbx, rb->bbx ); - v3_copy( sm->pivot, rb->co ); - rb_init( rb ); - v4_copy( sm->q, rb->q ); - rb_update_transform( rb ); - } + mdl_header *mfoliage = mdl_load("models/rs_foliage.mdl"); v3f volume; v3_sub( world.geo.bbx[1], world.geo.bbx[0], volume ); volume[1] = 1.0f; m4x3f transform; - - submodel *sm_blob = submodel_get( mfoliage, "blob" ), - *sm_tree = submodel_get( mfoliage, "tree" ); + mdl_node *mblob = mdl_node_from_name( mfoliage, "blob" ); + mdl_submesh *sm_blob = mdl_node_submesh( mfoliage, mblob, 0 ); for( int i=0;i<100000;i++ ) { @@ -250,41 +150,142 @@ static void world_load(void) q_m3x3( qsurface, transform ); v3_copy( hit.pos, transform[3] ); - - if( vg_randf() < 0.0006f ) - { - m3x3_identity( transform ); - scene_add_foliage( &world.foliage, mfoliage, sm_tree, transform); - } - else - scene_add_foliage( &world.foliage, mfoliage, sm_blob, transform); + scene_add_submesh( &world.foliage, mfoliage, sm_blob, transform); } } } - free( mfoliage ); scene_upload( &world.foliage ); - - - /* Prop layer */ + free( mfoliage ); +} + +static void world_load(void) +{ + mdl_header *mworld = mdl_load( "models/mp_dev.mdl" ); + + world.spawn_count = 0; + world.gate_count = 0; + world.rb_count = 0; + + scene_init( &world.geo ); scene_init( &world.props ); - for( int i=0; ilayer_count; i++ ) + + /* + * Compile meshes into the world scenes + */ + u32 mat_surf = 0, + mat_surf_oob = 0, + mat_vertex_blend = 0; + + for( int i=1; imaterial_count; i++ ) + { + mdl_material *mat = mdl_material_from_id( mworld, i ); + const char *mat_name = mdl_pstr( mworld, mat->pstr_name ); + + vg_info( "%d %s\n", mat->pstr_name, mat_name ); + + if( !strcmp( "surf", mat_name )) + mat_surf = i; + else if( !strcmp( "surf_oob", mat_name )) + mat_surf_oob = i; + else if( !strcmp( "vertex_blend", mat_name )) + mat_vertex_blend = i; + } + + if( mat_surf ) + add_all_if_material( &world.geo, mworld, mat_surf ); + + scene_copy_slice( &world.geo, &world.sm_surface ); + + if( mat_surf_oob ) + add_all_if_material( &world.geo, mworld, mat_surf_oob ); + else + vg_warn( "No OOB surface\n" ); + + scene_bh_create( &world.geo ); + scene_upload( &world.geo ); + + if( mat_vertex_blend ) + add_all_if_material( &world.props, mworld, mat_vertex_blend ); + + /* TODO bvh? */ + + /* + * Process entities + */ + for( int i=0; inode_count; i++ ) { - submodel *sm = model_get_submodel( mworld, i ); - if( !strcmp( sm->material, "vertex_blend" ) ) + mdl_node *pnode = mdl_node_from_id( mworld, i ); + + if( pnode->classtype == k_classtype_none ) + {} + else if( pnode->classtype == k_classtype_gate ) + { + struct classtype_gate *entgate = mdl_get_entdata( mworld, pnode ); + mdl_node *pother = mdl_node_from_id( mworld, entgate->target ); + + teleport_gate *gate = &world.gates[ world.gate_count ++ ]; + + v3_copy( pnode->co, gate->co[0] ); + v3_copy( pother->co, gate->co[1] ); + v4_copy( pnode->q, gate->q[0] ); + v4_copy( pother->q, gate->q[1] ); + v2_copy( pnode->s, gate->dims ); + + gate_transform_update( gate ); + } + else if( pnode->classtype == k_classtype_block ) { + struct classtype_block *block = mdl_get_entdata( mworld, pnode ); + m4x3f transform; - q_m3x3( sm->q, transform ); - v3_copy( sm->pivot, transform[3] ); - scene_add_foliage( &world.props, mworld, sm, transform ); + mdl_node_transform( pnode, transform ); + + rigidbody *rb = &world.temp_rbs[ world.rb_count ++ ]; + + box_copy( block->bbx, rb->bbx ); /* TODO: apply scale */ + v3_copy( pnode->co, rb->co ); + rb_init( rb ); + v4_copy( pnode->q, rb->q ); + rb_update_transform( rb ); + } + else if( pnode->classtype == k_classtype_spawn ) + { + struct respawn_point *rp = &world.spawns[ world.spawn_count ++ ]; + + v3_copy( pnode->co, rp->co ); + v4_copy( pnode->q, rp->q ); + strcpy( rp->name, mdl_pstr( mworld, pnode->pstr_name ) ); + } + else if( pnode->classtype == k_classtype_water ) + { + if( wrender.enabled ) + { + vg_warn( "Multiple water surfaces in level! ('%s')\n", + mdl_pstr( mworld, pnode->pstr_name )); + continue; + } + + mdl_submesh *sm = mdl_node_submesh( mworld, pnode, 0 ); + + if( sm ) + { + glmesh surf; + mdl_unpack_submesh( mworld, &surf, sm ); + water_init(); + water_set_surface( &surf, pnode->co[1] ); + } } } scene_upload( &world.props ); - free( mworld ); + bh_create( &world.bhcubes, &bh_system_rigidbodies, world.temp_rbs, world.rb_count ); - + + world_apply_foliage(); + free( mworld ); + /* * Rendering the depth map */ @@ -346,12 +347,14 @@ static void world_init(void) &tex_terrain_noise }, 2 ); - model *msky = vg_asset_read("models/rs_skydome.mdl"); - model_unpack( msky, &world.skydome ); - - world.dome_lower = *submodel_get( msky, "dome_lower" ); - world.dome_upper = *submodel_get( msky, "dome_upper" ); + mdl_header *msky = mdl_load("models/rs_skydome.mdl"); + mdl_unpack_glmesh( msky, &world.skydome ); + mdl_node *nlower = mdl_node_from_name( msky, "dome_lower" ), + *nupper = mdl_node_from_name( msky, "dome_upper" ); + + world.dome_lower = *mdl_node_submesh( msky, nlower, 0 ); + world.dome_upper = *mdl_node_submesh( msky, nupper, 0 ); free(msky); } @@ -427,8 +430,8 @@ static void render_lowerdome( m4x3f camera ) shader_planeinf_uPv(full); shader_planeinf_uCamera(camera[3]); shader_planeinf_uPlane( (v4f){0.0f,1.0f,0.0f, water_height()} ); - - submodel_draw( &world.dome_lower ); + + mdl_draw_submesh( &world.dome_lower ); } static void render_sky(m4x3f camera) @@ -457,7 +460,7 @@ static void render_sky(m4x3f camera) glDisable( GL_DEPTH_TEST ); mesh_bind( &world.skydome ); - submodel_draw( &world.dome_upper ); + mdl_draw_submesh( &world.dome_upper ); glEnable( GL_DEPTH_TEST ); glDepthMask( GL_TRUE ); -- 2.25.1