diff --git a/art/Exporter.py b/art/Exporter.py index 9425fc4..da8ab3e 100644 --- a/art/Exporter.py +++ b/art/Exporter.py @@ -22,18 +22,20 @@ os.makedirs(bpy.path.abspath(f"//{EXPORT_DIRECTORY}")) def write_f32(f, number: float): f.write(bytes(struct.pack("f", number))) - def write_u64(f, number: int): f.write(bytes(struct.pack("Q", number))) def write_string(f, s: str): encoded = s.encode("utf8") write_u64(f, len(encoded)) + print(f"Writing string '{s}' of length {len(encoded)}") f.write(encoded) + # for the level.bin level_object_data = [] collision_cubes = [] +placed_entities = [] saved_meshes = set() for o in D.objects: @@ -45,17 +47,20 @@ for o in D.objects: ) mesh_name = o.to_mesh().name # use this over o.name so instanced objects which refer to the same mesh, both use the same serialized mesh. + object_transform_info = (mesh_name, mapping @ o.location, o.rotation_euler, o.scale) + if o.users_collection[0].name == 'Level' and mesh_name == "CollisionCube": collision_cubes.append((o.location, o.dimensions)) else: if o.users_collection[0].name == 'Level': print(f"Object {o.name} has mesh name {o.to_mesh().name}") assert(o.rotation_euler.order == 'XYZ') - level_object_data.append((mesh_name, mapping @ o.location, o.rotation_euler, o.scale)) + level_object_data.append(object_transform_info) if mesh_name in saved_meshes: continue saved_meshes.add(mesh_name) - + else: + placed_entities.append((o.name,) + object_transform_info) mapping.resize_4x4() assert(mesh_name != LEVEL_EXPORT_NAME) @@ -95,21 +100,20 @@ for o in D.objects: with open(bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{LEVEL_EXPORT_NAME}.bin"), "wb") as f: write_u64(f, len(level_object_data)) for o in level_object_data: - name, v, rotation, scale = o - print(f"Writing instanced object {name}") - write_string(f, name) - write_f32(f, v.x) - write_f32(f, v.y) - write_f32(f, v.z) + mesh_name, blender_pos, blender_rotation, blender_scale = o + print(f"Writing instanced object of mesh {mesh_name}") + write_string(f, mesh_name) + write_f32(f, blender_pos.x) + write_f32(f, blender_pos.y) + write_f32(f, blender_pos.z) - # rotation fields different because y+ is up now - write_f32(f, rotation.x) - write_f32(f, rotation.y) - write_f32(f, rotation.z) + write_f32(f, blender_rotation.x) + write_f32(f, blender_rotation.y) + write_f32(f, blender_rotation.z) - write_f32(f, scale.x) - write_f32(f, scale.y) - write_f32(f, scale.z) + write_f32(f, blender_scale.x) + write_f32(f, blender_scale.y) + write_f32(f, blender_scale.z) write_u64(f, len(collision_cubes)) for c in collision_cubes: @@ -119,4 +123,22 @@ with open(bpy.path.abspath(f"//{EXPORT_DIRECTORY}/{LEVEL_EXPORT_NAME}.bin"), "wb write_f32(f, blender_dims.x) write_f32(f, blender_dims.y) assert(blender_dims.x > 0.0) - assert(blender_dims.y > 0.0) \ No newline at end of file + assert(blender_dims.y > 0.0) + + write_u64(f, len(placed_entities)) + for p in placed_entities: + # underscore is mesh name, prefer object name for name of npc. More obvious + object_name, _, blender_pos, blender_rotation, blender_scale = p + print(f"Writing placed entity '{object_name}'") + write_string(f, object_name) + write_f32(f, blender_pos.x) + write_f32(f, blender_pos.y) + write_f32(f, blender_pos.z) + + write_f32(f, blender_rotation.x) + write_f32(f, blender_rotation.y) + write_f32(f, blender_rotation.z) + + write_f32(f, blender_scale.x) + write_f32(f, blender_scale.y) + write_f32(f, blender_scale.z) \ No newline at end of file diff --git a/art/art.blend b/art/art.blend index edde59d..15bb22b 100644 --- a/art/art.blend +++ b/art/art.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:341412f1daaa52f32cc8a00736e68335b288d5c74d9b64b31b62b8dc54254dcf -size 1118880 +oid sha256:84cf17eaf853dc4dcbd74ea608bf9f2888eb83906eeda9b109aca9806000bc0a +size 1127344 diff --git a/main.c b/main.c index 6d18264..28e97ac 100644 --- a/main.c +++ b/main.c @@ -793,15 +793,33 @@ typedef struct Mesh MD_String8 name; } Mesh; -typedef struct PlacedMesh +typedef struct { - struct PlacedMesh *next; - Mesh *draw_with; Vec3 offset; Quat rotation; Vec3 scale; +} Transform; + +typedef struct +{ + Vec3 pos; + Vec3 euler_rotation; + Vec3 scale; +} BlenderTransform; + +typedef struct PlacedMesh +{ + struct PlacedMesh *next; + Transform t; + Mesh *draw_with; } PlacedMesh; +typedef struct PlacedEntity +{ + struct PlacedEntity *next; + Transform t; + NpcKind npc_kind; +} PlacedEntity; // mesh_name is for debugging // arena must last as long as the Mesh lasts. Internal data points to `arena`, such as @@ -852,8 +870,34 @@ typedef struct Mesh *mesh_list; PlacedMesh *placed_mesh_list; CollisionCube *collision_list; + PlacedEntity *placed_entity_list; } ThreeDeeLevel; +void ser_BlenderTransform(SerState *ser, BlenderTransform *t) +{ + ser_Vec3(ser, &t->pos); + ser_Vec3(ser, &t->euler_rotation); + ser_Vec3(ser, &t->scale); +} + +Transform blender_to_game_transform(BlenderTransform blender_transform) +{ + Transform to_return = {0}; + + to_return.offset = blender_transform.pos; + + to_return.scale = blender_transform.scale; + + Mat4 rotation_matrix = M4D(1.0f); + rotation_matrix = MulM4(Rotate_RH(AngleRad(blender_transform.euler_rotation.x), V3(1,0,0)), rotation_matrix); + rotation_matrix = MulM4(Rotate_RH(AngleRad(blender_transform.euler_rotation.y), V3(0,0,-1)), rotation_matrix); + rotation_matrix = MulM4(Rotate_RH(AngleRad(blender_transform.euler_rotation.z), V3(0,1,0)), rotation_matrix); + Quat out_rotation = M4ToQ_RH(rotation_matrix); + to_return.rotation = out_rotation; + + return to_return; +} + ThreeDeeLevel load_level(MD_Arena *arena, MD_String8 binary_file) { MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); @@ -866,66 +910,57 @@ ThreeDeeLevel load_level(MD_Arena *arena, MD_String8 binary_file) }; ThreeDeeLevel out = {0}; - MD_u64 num_placed = 0; - ser_MD_u64(&ser, &num_placed); - arena->align = 16; // SSE requires quaternions are 16 byte aligned - for(MD_u64 i = 0; i < num_placed; i++) + // placed meshes { - PlacedMesh *new_placed = MD_PushArray(arena, PlacedMesh, 1); - //PlacedMesh *new_placed = calloc(sizeof(PlacedMesh), 1); - MD_String8 placed_mesh_name = {0}; - ser_MD_String8(&ser, &placed_mesh_name, arena); - ser_Vec3(&ser, &new_placed->offset); - - Vec3 blender_rotation_euler; - ser_Vec3(&ser, &blender_rotation_euler); - Vec3 blender_scale; - ser_Vec3(&ser, &blender_scale); + MD_u64 num_placed = 0; + ser_MD_u64(&ser, &num_placed); + arena->align = 16; // SSE requires quaternions are 16 byte aligned + for(MD_u64 i = 0; i < num_placed; i++) + { + PlacedMesh *new_placed = MD_PushArray(arena, PlacedMesh, 1); + //PlacedMesh *new_placed = calloc(sizeof(PlacedMesh), 1); + MD_String8 placed_mesh_name = {0}; + ser_MD_String8(&ser, &placed_mesh_name, arena); - new_placed->scale.x = blender_scale.x; - new_placed->scale.y = blender_scale.z; - new_placed->scale.z = blender_scale.y; + BlenderTransform blender_transform = {0}; + ser_BlenderTransform(&ser, &blender_transform); - Mat4 rotation_matrix = M4D(1.0f); - rotation_matrix = MulM4(Rotate_RH(AngleRad(blender_rotation_euler.x), V3(1,0,0)), rotation_matrix); - rotation_matrix = MulM4(Rotate_RH(AngleRad(blender_rotation_euler.y), V3(0,0,-1)), rotation_matrix); - rotation_matrix = MulM4(Rotate_RH(AngleRad(blender_rotation_euler.z), V3(0,1,0)), rotation_matrix); - Quat out_rotation = M4ToQ_RH(rotation_matrix); - new_placed->rotation = out_rotation; + new_placed->t = blender_to_game_transform(blender_transform); - MD_StackPush(out.placed_mesh_list, new_placed); + MD_StackPush(out.placed_mesh_list, new_placed); - Log("Placed mesh '%.*s' rotation %f %f %f %f scale %f %f %f\n", MD_S8VArg(placed_mesh_name), qvarg(new_placed->rotation), v3varg(new_placed->scale)); + Log("Placed mesh '%.*s' pos %f %f %f rotation %f %f %f %f scale %f %f %f\n", MD_S8VArg(placed_mesh_name), v3varg(new_placed->t.offset), qvarg(new_placed->t.rotation), v3varg(new_placed->t.scale)); - // load the mesh if we haven't already + // load the mesh if we haven't already - bool mesh_found = false; - for(Mesh *cur = out.mesh_list; cur; cur = cur->next) - { - if(MD_S8Match(cur->name, placed_mesh_name, 0)) + bool mesh_found = false; + for(Mesh *cur = out.mesh_list; cur; cur = cur->next) { - mesh_found = true; - new_placed->draw_with = cur; - assert(cur->name.size > 0); - break; + if(MD_S8Match(cur->name, placed_mesh_name, 0)) + { + mesh_found = true; + new_placed->draw_with = cur; + assert(cur->name.size > 0); + break; + } } - } - if(!mesh_found) - { - MD_String8 to_load_filepath = MD_S8Fmt(scratch.arena, "assets/exported_3d/%.*s.bin", MD_S8VArg(placed_mesh_name)); - Log("Loading '%.*s'...\n", MD_S8VArg(to_load_filepath)); - MD_String8 binary_mesh_file = MD_LoadEntireFile(scratch.arena, to_load_filepath); - if(!binary_mesh_file.str) - { - ser.cur_error = (SerError){.failed = true, .why = MD_S8Fmt(ser.error_arena, "Couldn't load file '%.*s'", to_load_filepath)}; - } - else + if(!mesh_found) { - Mesh *new_mesh = MD_PushArray(arena, Mesh, 1); - *new_mesh = load_mesh(arena, binary_mesh_file, placed_mesh_name); - MD_StackPush(out.mesh_list, new_mesh); - new_placed->draw_with = new_mesh; + MD_String8 to_load_filepath = MD_S8Fmt(scratch.arena, "assets/exported_3d/%.*s.bin", MD_S8VArg(placed_mesh_name)); + Log("Loading '%.*s'...\n", MD_S8VArg(to_load_filepath)); + MD_String8 binary_mesh_file = MD_LoadEntireFile(scratch.arena, to_load_filepath); + if(!binary_mesh_file.str) + { + ser.cur_error = (SerError){.failed = true, .why = MD_S8Fmt(ser.error_arena, "Couldn't load file '%.*s'", to_load_filepath)}; + } + else + { + Mesh *new_mesh = MD_PushArray(arena, Mesh, 1); + *new_mesh = load_mesh(arena, binary_mesh_file, placed_mesh_name); + MD_StackPush(out.mesh_list, new_mesh); + new_placed->draw_with = new_mesh; + } } } } @@ -943,6 +978,41 @@ ThreeDeeLevel load_level(MD_Arena *arena, MD_String8 binary_file) MD_StackPush(out.collision_list, new_cube); } + // placed entities + { + MD_u64 num_placed = 0; + ser_MD_u64(&ser, &num_placed); + arena->align = 16; // SSE requires quaternions are 16 byte aligned + for(MD_u64 i = 0; i < num_placed; i++) + { + PlacedEntity *new_placed = MD_PushArray(arena, PlacedEntity, 1); + MD_String8 placed_entity_name = {0}; + ser_MD_String8(&ser, &placed_entity_name, scratch.arena); + + bool found = false; + ARR_ITER_I(CharacterGen, characters, kind) + { + if(MD_S8Match(MD_S8CString(it->enum_name), placed_entity_name, 0)) + { + found = true; + new_placed->npc_kind = kind; + } + } + if(found) + { + BlenderTransform blender_transform = {0}; + ser_BlenderTransform(&ser, &blender_transform); + new_placed->t = blender_to_game_transform(blender_transform); + MD_StackPush(out.placed_entity_list, new_placed); + } + else + { + Log("Couldn't find placed npc kind '%.*s'...\n", MD_S8VArg(placed_entity_name)); + } + } + } + + assert(!ser.cur_error.failed); MD_ReleaseScratch(scratch); @@ -1726,14 +1796,38 @@ int parse_enumstr_impl(MD_Arena *arena, MD_String8 enum_str, char **enumstr_arra return to_return; } +Vec3 plane_point(Vec2 p) +{ + return V3(p.x, 0.0, p.y); +} + +Vec2 point_plane(Vec3 p) +{ + return V2(p.x, p.z); +} + #define parse_enumstr(arena, enum_str, errors, string_array, enum_kind_name, prefix) parse_enumstr_impl(arena, enum_str, string_array, ARRLEN(string_array), errors, enum_kind_name, prefix) void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *level) { memset(gs, 0, sizeof(GameState)); - gs->player = new_entity(gs); - gs->player->is_character = true; - gs->player->npc_kind = NPC_Player; + + bool found_player = false; + for(PlacedEntity *cur = level->placed_entity_list; cur; cur = cur->next) + { + Entity *cur_entity = new_entity(gs); + cur_entity->pos = point_plane(cur->t.offset); + cur_entity->npc_kind = cur->npc_kind; + cur_entity->is_npc = true; + if(cur_entity->npc_kind == NPC_Player) + { + found_player = true; + cur_entity->is_character = true; + gs->player = cur_entity; + } + } + assert(found_player); + gs->world_entity = new_entity(gs); gs->world_entity->is_world = true; @@ -2296,16 +2390,9 @@ void draw_placed(Mat4 view, Mat4 projection, PlacedMesh *cur) Mat4 model = M4D(1.0f); - model = MulM4(Scale(cur->scale), model); - model = MulM4(QToM4(cur->rotation), model); - /* This works on blender XYZ coords, unmodified. - model = MulM4(Rotate_RH(AngleRad(cur->rotation_euler.x), V3(1,0,0)), model); - model = MulM4(Rotate_RH(AngleRad(cur->rotation_euler.y), V3(0,0,-1)), model); - model = MulM4(Rotate_RH(AngleRad(cur->rotation_euler.z), V3(0,1,0)), model); - */ - model = MulM4(Translate(cur->offset), model); - - // Z * Y * X * Translation * Point + model = MulM4(Scale(cur->t.scale), model); + model = MulM4(QToM4(cur->t.rotation), model); + model = MulM4(Translate(cur->t.offset), model); threedee_vs_params_t params = {0}; memcpy(params.model, (float*)&model, sizeof(model)); @@ -2472,6 +2559,10 @@ void do_serialization_tests() MD_ArenaTemp scratch = MD_GetScratch(0, 0); ThreeDeeLevel level = {0}; + PlacedEntity *placed_player = MD_PushArray(scratch.arena, PlacedEntity, 1); + placed_player->npc_kind = NPC_Player; + MD_StackPush(level.placed_entity_list, placed_player); + GameState gs = {0}; initialize_gamestate_from_threedee_level(&gs, &level); @@ -2553,9 +2644,7 @@ void init(void) MD_ArenaClear(frame_arena); binary_file = MD_LoadEntireFile(frame_arena, MD_S8Lit("assets/exported_3d/level.bin")); - level_threedee = load_level(persistent_arena, binary_file); - reset_level(); #ifdef WEB @@ -3336,11 +3425,6 @@ void dbg3dline(Vec3 from, Vec3 to) dbgline(from_screenspace, to_screenspace); } -Vec3 plane_point(Vec2 p) -{ - return V3(p.x, 0.0, p.y); -} - void colorquadplane(Quad q, Color col) { Quad warped = {0}; @@ -3735,7 +3819,8 @@ Vec2 move_and_slide(MoveSlideParams p) closest_dot = dot; } } - assert(closest_index != -1); + // sometimes when objects are perfectly overlapping, every dot product is negative infinity + if(closest_index == -1) closest_index = 0; Vec2 move_dir = compass_dirs[closest_index]; info.normal = move_dir; dbgplanevec(from_point, MulV2F(move_dir, 30.0f)); @@ -4343,7 +4428,18 @@ void frame(void) draw_placed(view, projection, cur); } - draw_placed(view, projection, &(PlacedMesh){.draw_with = &mesh_player, .offset = V3(gs.player->pos.x, 0.0, gs.player->pos.y), .rotation = Make_Q(0, 0, 0, 1), .scale = V3(1, 1, 1)}); + draw_placed(view, projection, &(PlacedMesh){.draw_with = &mesh_player, .t = (Transform){.offset = V3(gs.player->pos.x, 0.0, gs.player->pos.y), .rotation = Make_Q(0, 0, 0, 1), .scale = V3(1, 1, 1)}, }); + + ENTITIES_ITER(gs.entities) + { + if(it->is_npc) + { + if(it->npc_kind == NPC_Bill) + { + draw_placed(view, projection, &(PlacedMesh){.draw_with = &mesh_player, .t = (Transform){.offset = plane_point(it->pos), .rotation = Make_Q(0, 0, 0, 1), .scale = V3(1, 1, 1)}, }); + } + } + } sg_end_pass();