Place dynamic npc entities in blender as level editor, allocate on

initializing the gamestate
main
Cameron Murphy Reikes 2 years ago
parent 6531db13d6
commit 6108da2cdc

@ -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)
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)

BIN
art/art.blend (Stored with Git LFS)

Binary file not shown.

244
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();

Loading…
Cancel
Save