Compare commits

...

2 Commits

Author SHA1 Message Date
dca59a1241 fp: Various camera fixes for varying window sizes 2023-10-15 22:48:53 +11:00
9b1e40a368 fp: Fix scanlines 2023-10-15 21:02:28 +11:00
4 changed files with 69 additions and 56 deletions

2
External/tely vendored

@ -1 +1 @@
Subproject commit 60aec2dd926ff7cde98408c25ecbc40eb33fddd7 Subproject commit 29843741bf51c8ed38af467cff8b781a912f15a7

View File

@ -470,9 +470,12 @@ static void FP_PlayReset(FP_Game *game, TELY_Platform *platform)
// NOTE: Heart // NOTE: Heart
game->play.heart = FP_Entity_CreateHeart(game, base_mid_p, "Heart"); game->play.heart = FP_Entity_CreateHeart(game, base_mid_p, "Heart");
play->camera.world_pos = base_mid_p - Dqn_V2_InitV2I(platform->core.window_size * .5f);
play->tile_size = 37; play->tile_size = 37;
// NOTE: Camera ================================================================================
play->camera.world_pos = base_mid_p - Dqn_V2_InitV2I(platform->core.window_size * .5f);
play->camera.scale = Dqn_V2_InitNx1(1); play->camera.scale = Dqn_V2_InitNx1(1);
play->camera.size = Dqn_V2_InitNx2(1826, 1046);
} }
#if defined(DQN_OS_WIN32) #if defined(DQN_OS_WIN32)
@ -1442,6 +1445,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
{ {
Dqn_Profiler_ZoneScopeWithIndex("FP_Update", FP_ProfileZone_FPUpdate); Dqn_Profiler_ZoneScopeWithIndex("FP_Update", FP_ProfileZone_FPUpdate);
FP_GameCameraM2x3 const camera_xforms = FP_Game_CameraModelViewM2x3(game->play.camera);
game->play.update_counter++; game->play.update_counter++;
game->play.clock_ms = DQN_CAST(uint64_t)(platform->input.timer_s * 1000.f); game->play.clock_ms = DQN_CAST(uint64_t)(platform->input.timer_s * 1000.f);
Dqn_ProfilerZone update_zone = Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8("FP_Update: Entity loop"), FP_ProfileZone_FPUpdate_EntityLoop); Dqn_ProfilerZone update_zone = Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8("FP_Update: Entity loop"), FP_ProfileZone_FPUpdate_EntityLoop);
@ -1989,9 +1993,10 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
// NOTE: Move entity by mouse ============================================================== // NOTE: Move entity by mouse ==============================================================
if (game->play.active_entity == entity->handle && entity->flags & FP_GameEntityFlag_MoveByMouse) { if (game->play.active_entity == entity->handle && entity->flags & FP_GameEntityFlag_MoveByMouse) {
Dqn_V2 mouse_p_delta = input->mouse_p_delta * (Dqn_V2_One / game->play.camera.scale);
entity->velocity = {}; entity->velocity = {};
acceleration_meters_per_s = {}; acceleration_meters_per_s = {};
entity->local_pos += input->mouse_p_delta; entity->local_pos += mouse_p_delta;
} }
if (game->play.clicked_entity == entity->handle) { if (game->play.clicked_entity == entity->handle) {
@ -2000,11 +2005,13 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
game->play.in_game_menu = DQN_CAST(FP_GameInGameMenu)(DQN_CAST(uint32_t)game->play.in_game_menu ^ FP_GameInGameMenu_Build); game->play.in_game_menu = DQN_CAST(FP_GameInGameMenu)(DQN_CAST(uint32_t)game->play.in_game_menu ^ FP_GameInGameMenu_Build);
} }
if (entity->flags & FP_GameEntityFlag_CameraTracking) if (entity->flags & FP_GameEntityFlag_CameraTracking) {
game->play.camera.world_pos = FP_Game_CalcEntityWorldPos(game, entity->handle) - Dqn_V2_InitV2I(platform->core.window_size) * .5f; FP_GameCamera *camera = &game->play.camera;
camera->world_pos = FP_Game_CalcEntityWorldPos(game, entity->handle) * camera->scale;
}
FP_GamePlaceableBuilding placeable_building = PLACEABLE_BUILDINGS[game->play.build_mode_building_index]; FP_GamePlaceableBuilding placeable_building = PLACEABLE_BUILDINGS[game->play.build_mode_building_index];
game->play.build_mode_can_place_building = false; game->play.build_mode_can_place_building = false;
uint8_t *inventory_count = nullptr; uint8_t *inventory_count = nullptr;
if (placeable_building.type == FP_EntityType_ChurchTerry) if (placeable_building.type == FP_EntityType_ChurchTerry)
@ -2158,7 +2165,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
} }
// NOTE: Mob spawner ======================================================================= // NOTE: Mob spawner =======================================================================
if (entity->type == FP_EntityType_MobSpawner) { if (entity->type == FP_EntityType_MobSpawner && 0) {
// NOTE: Flush any spawn entities that are dead // NOTE: Flush any spawn entities that are dead
for (FP_SentinelListLink<FP_GameEntityHandle> *link = nullptr; FP_SentinelList_Iterate<FP_GameEntityHandle>(&entity->spawn_list, &link); ) { for (FP_SentinelListLink<FP_GameEntityHandle> *link = nullptr; FP_SentinelList_Iterate<FP_GameEntityHandle>(&entity->spawn_list, &link); ) {
FP_GameEntity *spawned_entity = FP_Game_GetEntity(game, link->data); FP_GameEntity *spawned_entity = FP_Game_GetEntity(game, link->data);
@ -2296,12 +2303,21 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
} }
if (!FP_Game_IsNilEntityHandle(game, game->play.clicked_entity)) { if (!FP_Game_IsNilEntityHandle(game, game->play.clicked_entity)) {
Dqn_usize const target_width = 1800; Dqn_V2 window_size = Dqn_V2_InitV2I(platform->core.window_size);
Dqn_usize const target_height = 1046; FP_GamePlay *play = &game->play;
game->play.camera.world_pos.x = DQN_MIN(game->play.camera.world_pos.x, game->play.map->local_hit_box_size.w * +0.5f - target_width);
game->play.camera.world_pos.x = DQN_MAX(game->play.camera.world_pos.x, game->play.map->local_hit_box_size.w * -0.5f); FP_GameCamera *camera = &play->camera;
game->play.camera.world_pos.y = DQN_MAX(game->play.camera.world_pos.y, game->play.map->local_hit_box_size.h * -0.5f); camera->scale = window_size / camera->size;
game->play.camera.world_pos.y = DQN_MIN(game->play.camera.world_pos.y, game->play.map->local_hit_box_size.h * +0.5f - target_height); Dqn_V2 camera_size_screen = camera->size * camera->scale;
Dqn_V2 map_world_size = play->map->local_hit_box_size;
Dqn_V2 map_screen_size = map_world_size * camera->scale;
Dqn_V2 half_map_screen_size = map_screen_size * .5f;
camera->world_pos.x = DQN_MIN(camera->world_pos.x, half_map_screen_size.w - (camera_size_screen.w * .5f));
camera->world_pos.x = DQN_MAX(camera->world_pos.x, -half_map_screen_size.w + (camera_size_screen.w * .5f));
camera->world_pos.y = DQN_MAX(camera->world_pos.y, -half_map_screen_size.h + (camera_size_screen.h * .5f));
camera->world_pos.y = DQN_MIN(camera->world_pos.y, half_map_screen_size.h - (camera_size_screen.h * .5f));
} }
Dqn_Profiler_EndZone(update_zone); Dqn_Profiler_EndZone(update_zone);
@ -2321,9 +2337,9 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
TELY_RFui_PushFont(rfui, game->jetbrains_mono_font); TELY_RFui_PushFont(rfui, game->jetbrains_mono_font);
TELY_RFui_PushLabelColourV4(rfui, TELY_COLOUR_BLACK_MIDNIGHT_V4); TELY_RFui_PushLabelColourV4(rfui, TELY_COLOUR_BLACK_MIDNIGHT_V4);
Dqn_M2x3 model_view = FP_Game_CameraModelViewM2x3(game->play.camera, platform); FP_GameCameraM2x3 camera_xforms = FP_Game_CameraModelViewM2x3(game->play.camera);
TELY_Render_PushTransform(renderer, model_view); TELY_Render_PushTransform(renderer, camera_xforms.model_view);
Dqn_V2 world_mouse_p = input->mouse_p + game->play.camera.world_pos; Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2(camera_xforms.view_model, input->mouse_p);
// NOTE: Draw tiles ============================================================================ // NOTE: Draw tiles ============================================================================
Dqn_usize tile_count_x = DQN_CAST(Dqn_usize)(platform->core.window_size.w / game->play.tile_size); Dqn_usize tile_count_x = DQN_CAST(Dqn_usize)(platform->core.window_size.w / game->play.tile_size);
@ -3233,7 +3249,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
Dqn_V2 screen_size = Dqn_V2_InitNx2(platform->core.window_size.w, platform->core.window_size.h); Dqn_V2 screen_size = Dqn_V2_InitNx2(platform->core.window_size.w, platform->core.window_size.h);
Dqn_f32 scanline_gap = 4.0f; Dqn_f32 scanline_gap = 4.0f;
Dqn_f32 scanline_thickness = 3.0f; Dqn_f32 scanline_thickness = 3.0f;
FP_GameRenderCameraFollowScanlines(renderer, screen_size, game->play.camera.world_pos, scanline_gap, scanline_thickness); FP_GameRenderScanlines(renderer, scanline_gap, scanline_thickness, screen_size);
} }
// NOTE: Render the other game state modes ===================================================== // NOTE: Render the other game state modes =====================================================
@ -3395,7 +3411,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
Dqn_f32 scanline_gap = 4.0f; Dqn_f32 scanline_gap = 4.0f;
Dqn_f32 scanline_thickness = 3.0f; Dqn_f32 scanline_thickness = 3.0f;
FP_GameRenderCameraFollowScanlines(renderer, window_rect.size, Dqn_V2_Zero, scanline_gap, scanline_thickness); FP_GameRenderScanlines(renderer, scanline_gap, scanline_thickness, window_rect.size);
} }
// NOTE: Debug UI ============================================================================== // NOTE: Debug UI ==============================================================================
@ -3442,8 +3458,8 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
} }
next_y += TELY_Asset_GetFont(assets, TELY_RFui_ActiveFont(rfui))->pixel_height; if (0) {
{ next_y += TELY_Asset_GetFont(assets, TELY_RFui_ActiveFont(rfui))->pixel_height;
TELY_RFuiResult bar = TELY_RFui_Column(rfui, DQN_STRING8("Memory bar")); TELY_RFuiResult bar = TELY_RFui_Column(rfui, DQN_STRING8("Memory bar"));
bar.widget->semantic_position[TELY_RFuiAxis_X].kind = TELY_RFuiPositionKind_Absolute; bar.widget->semantic_position[TELY_RFuiAxis_X].kind = TELY_RFuiPositionKind_Absolute;
bar.widget->semantic_position[TELY_RFuiAxis_X].value = 10.f; bar.widget->semantic_position[TELY_RFuiAxis_X].value = 10.f;
@ -3494,6 +3510,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
DQN_DEFER { TELY_RFui_PopParent(rfui); }; DQN_DEFER { TELY_RFui_PopParent(rfui); };
TELY_RFui_TextF(rfui, "Camera: %.1f, %.1f", game->play.camera.world_pos.x, game->play.camera.world_pos.y); TELY_RFui_TextF(rfui, "Camera: %.1f, %.1f", game->play.camera.world_pos.x, game->play.camera.world_pos.y);
TELY_RFui_TextF(rfui, "Mouse: %.1f, %.1f", input->mouse_p.x, input->mouse_p.y);
// TODO(doyle): On emscripten we need to use Dqn_OS_PerfCounterNow() however those // TODO(doyle): On emscripten we need to use Dqn_OS_PerfCounterNow() however those
// require OS functions which need to be exposed into the platform layer. // require OS functions which need to be exposed into the platform layer.
@ -3652,7 +3669,8 @@ void TELY_DLL_FrameUpdate(void *user_data)
if (game->play.prev_active_entity.id) if (game->play.prev_active_entity.id)
game->play.active_entity = game->play.prev_active_entity; game->play.active_entity = game->play.prev_active_entity;
} else { } else {
Dqn_V2 world_mouse_p = input->mouse_p + game->play.camera.world_pos; FP_GameCameraM2x3 const camera_xforms = FP_Game_CameraModelViewM2x3(game->play.camera);
Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2(camera_xforms.view_model, input->mouse_p);
for (FP_GameEntityIterator it = {}; FP_Game_DFSPreOrderWalkEntityTree(game, &it, game->play.root_entity); ) { for (FP_GameEntityIterator it = {}; FP_Game_DFSPreOrderWalkEntityTree(game, &it, game->play.root_entity); ) {
FP_GameEntity *entity = it.entity; FP_GameEntity *entity = it.entity;
if (entity->local_hit_box_size.x <= 0 || entity->local_hit_box_size.y <= 0) if (entity->local_hit_box_size.x <= 0 || entity->local_hit_box_size.y <= 0)

View File

@ -30,17 +30,29 @@ struct FP_GameCameraM2x3
Dqn_M2x3 view_model; Dqn_M2x3 view_model;
}; };
static Dqn_M2x3 FP_Game_CameraModelViewM2x3(FP_GameCamera camera, TELY_Platform *platform) static FP_GameCameraM2x3 FP_Game_CameraModelViewM2x3(FP_GameCamera camera)
{ {
Dqn_M2x3 result = Dqn_M2x3_Identity(); FP_GameCameraM2x3 result = {};
if (platform) { result.model_view = Dqn_M2x3_Identity();
Dqn_V2 center_offset = Dqn_V2_InitV2I(platform->core.window_size) * .5f; result.view_model = Dqn_M2x3_Identity();
Dqn_V2 rotate_origin = -camera.world_pos - center_offset;
result = Dqn_M2x3_Mul(result, Dqn_M2x3_Translate(rotate_origin)); Dqn_V2 center_offset = camera.size * .5f;
result = Dqn_M2x3_Mul(result, Dqn_M2x3_Rotate(camera.rotate_rads)); Dqn_V2 rotate_origin = -camera.world_pos;
result = Dqn_M2x3_Mul(result, Dqn_M2x3_Scale(camera.scale)); result.model_view = Dqn_M2x3_Mul(result.model_view, Dqn_M2x3_Translate(rotate_origin));
result = Dqn_M2x3_Mul(result, Dqn_M2x3_Translate(center_offset)); result.model_view = Dqn_M2x3_Mul(result.model_view, Dqn_M2x3_Rotate(camera.rotate_rads));
} result.model_view = Dqn_M2x3_Mul(result.model_view, Dqn_M2x3_Scale(camera.scale));
result.model_view = Dqn_M2x3_Mul(result.model_view, Dqn_M2x3_Translate(center_offset));
Dqn_V2 inverse_scale = Dqn_V2_InitNx1(1) / camera.scale;
result.view_model = Dqn_M2x3_Mul(result.view_model, Dqn_M2x3_Translate(-center_offset));
result.view_model = Dqn_M2x3_Mul(result.view_model, Dqn_M2x3_Scale(inverse_scale));
result.view_model = Dqn_M2x3_Mul(result.view_model, Dqn_M2x3_Rotate(-camera.rotate_rads));
result.view_model = Dqn_M2x3_Mul(result.view_model, Dqn_M2x3_Translate(-rotate_origin));
#if 0
Dqn_M2x3 identity = Dqn_M2x3_Mul(result.model_view, result.view_model);
DQN_ASSERT(identity == Dqn_M2x3_Identity());
#endif
return result; return result;
} }
@ -905,35 +917,17 @@ static void FP_Game_EntityTransitionState(FP_Game *game, FP_GameEntity *entity,
entity->action.next_state = desired_state; entity->action.next_state = desired_state;
} }
static void FP_GameRenderScanlines(TELY_Renderer *renderer, Dqn_f32 scanline_gap, Dqn_f32 scanline_thickness, static void FP_GameRenderScanlines(TELY_Renderer *renderer,
Dqn_V2 screen_size, Dqn_V2 camera_offset) Dqn_f32 scanline_gap,
Dqn_f32 scanline_thickness,
Dqn_V2 screen_size)
{ {
TELY_Render_PushTransform(renderer, Dqn_M2x3_Identity());
DQN_DEFER { TELY_Render_PopTransform(renderer); };
Dqn_f32 scanline_interval = scanline_gap + scanline_thickness; Dqn_f32 scanline_interval = scanline_gap + scanline_thickness;
Dqn_f32 y_offset = fmodf(camera_offset.y, scanline_interval); for (Dqn_f32 y = 0; y < screen_size.h; y += scanline_interval) {
for (Dqn_f32 y = -y_offset; y < screen_size.h; y += scanline_interval)
{
Dqn_V2 start = Dqn_V2_InitNx2(0, y); Dqn_V2 start = Dqn_V2_InitNx2(0, y);
Dqn_V2 end = Dqn_V2_InitNx2(screen_size.w, y); Dqn_V2 end = Dqn_V2_InitNx2(screen_size.w, y);
TELY_Render_LineColourV4(renderer, start, end, TELY_Colour_V4Alpha(TELY_COLOUR_WHITE_V4, 0.1f), scanline_thickness); TELY_Render_LineColourV4(renderer, start, end, TELY_Colour_V4Alpha(TELY_COLOUR_WHITE_V4, 0.1f), scanline_thickness);
} }
} }
static void FP_GameRenderCameraFollowScanlines(TELY_Renderer *renderer,
Dqn_V2 screen_size,
Dqn_V2 camera_offset,
Dqn_f32 scanline_gap,
Dqn_f32 scanline_thickness)
{
Dqn_f32 y_offset = camera_offset.y;
// Adjust starting y to be more negative
Dqn_f32 starting_y = -screen_size.h - 150 - y_offset;
for (Dqn_f32 y = starting_y; y < screen_size.h; y += scanline_gap + scanline_thickness) {
Dqn_V2 start = Dqn_V2_InitNx2(camera_offset.x, y + camera_offset.y);
Dqn_V2 end = Dqn_V2_InitNx2(screen_size.w + camera_offset.x, y + camera_offset.y);
TELY_Render_LineColourV4(renderer, start, end, TELY_Colour_V4Alpha(TELY_COLOUR_BLACK_V4, 0.1f), scanline_thickness);
}
}

View File

@ -255,6 +255,7 @@ struct FP_GameEntityIterator
struct FP_GameCamera struct FP_GameCamera
{ {
Dqn_V2 size;
Dqn_V2 world_pos; Dqn_V2 world_pos;
Dqn_f32 rotate_rads; Dqn_f32 rotate_rads;
Dqn_V2 scale; Dqn_V2 scale;