From 3e5e15a4923c752be703d7afb1214d5e5a767fad Mon Sep 17 00:00:00 2001 From: aura Date: Wed, 25 Feb 2026 08:58:39 +0100 Subject: finish wall collisions (clipvelocity), some utils --- src/editor/view3d.cpp | 4 +- src/game.cpp | 8 ++- src/game/physics/movement.cpp | 157 ++++++++++++++++++++++++++++++++++++++++++ src/game/physics/movement.h | 16 +++++ src/game/player.cpp | 95 +++++++------------------ src/game/player.h | 2 +- src/game/world/bsp.cpp | 1 + src/game/world/map.cpp | 4 +- src/game/world/trace.cpp | 7 +- src/util/fnv.h | 8 +-- src/util/input.cpp | 2 +- src/util/input.h | 4 +- src/util/string.h | 30 +++++++- 13 files changed, 249 insertions(+), 89 deletions(-) create mode 100644 src/game/physics/movement.cpp diff --git a/src/editor/view3d.cpp b/src/editor/view3d.cpp index f8d1def..656eda9 100644 --- a/src/editor/view3d.cpp +++ b/src/editor/view3d.cpp @@ -93,7 +93,7 @@ void gui_editor_3dview_input_fn( void* ptr ) { if( input.mouse.pos.x >= view_x && input.mouse.pos.x < view_x + view->w && input.mouse.pos.y >= view_y && input.mouse.pos.y < view_y + view->h - EDITORVIEW_TOOLBAR_OFFSET ) { if( !input.mouselock && !view->heldoutbounds ) - input_capture_mouse( true ); + input_capture_mouse( 1 ); } else { view->heldoutbounds = 1; } @@ -109,7 +109,7 @@ void gui_editor_3dview_create_toolbar( GUI_EDITOR_3DVIEW* view ) { gui_button( x, y, 90, 18, "compile bsp", pfn( void* b ) { if( editor->map->bsp ) bsp_free( editor->map->bsp ); - editor->map->bsp = bsp_build_map( editor->map ); + bsp_build_map( editor->map ); } ); x += 100; gui_checkbox( x, y, "draw bsp", &e->drawbsp ); diff --git a/src/game.cpp b/src/game.cpp index 285f288..6202145 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,6 +1,8 @@ #include #include "game.h" +#include "game/physics/movement.h" +#include "game/world/bsp.h" #include "game/world/draw.h" #include "render/gl_2d.h" #include "render/gl_3d.h" @@ -35,6 +37,8 @@ GAME_DATA* game_init( GL_DATA* gl ) { varl = vars_init(); objl = objl_init(); + gmove_init( game ); + #if IS_EDITOR game->editor = editor_create( game ); #else @@ -88,6 +92,7 @@ WORLD_MAP* game_load_map( GAME_DATA* game, const char* mapname ) { return 0; } + bsp_build_map( m ); objl_load_world( game, m ); game->state.map = m; return m; @@ -102,7 +107,8 @@ void game_unload_map( GAME_DATA* game ) { void game_draw( GAME_DATA* game ) { if( !objl->pl || !objl->world ) { - game_load_map( game, "../assets/maps/test.hmap" ); + dlog( "game_draw() : no world" ); + return; } VEC2 window = { 270.f, 25.f }; diff --git a/src/game/physics/movement.cpp b/src/game/physics/movement.cpp new file mode 100644 index 0000000..e9e0132 --- /dev/null +++ b/src/game/physics/movement.cpp @@ -0,0 +1,157 @@ +#include "movement.h" +#include "../world/trace.h" +#include "../../util/math.h" +#include "../../util/string.h" +#include "../../game.h" + +GAME_MOVEMENT* gmove; + +void gmove_init( GAME_DATA* game ) { + gmove = new GAME_MOVEMENT; + gmove->game = game; +} + +U8 gmove_check_valid( STR<256> from ) { + if( !gmove->game ) { + dlog( "%s : game not init\n", from.data ); + return 0; + } + + if( !gmove->pl ) { + dlog( "%s : player null\n", from.data ); + return 0; + } + + if( !gmove->input ) { + dlog( "%s : input null\n", from.data ); + return 0; + } + + if( !gmove->game->state.map ) { + dlog( "%s : no map loaded\n", from.data ); + return 0; + } + + if( !gmove->game->state.map->bsp ) { + dlog( "%s : no bsp loaded\n", from.data ); + return 0; + } + + return 1; +} + +void gmove_set_player( PLAYER* player ) { + gmove->pl = player; + gmove->input = &player->input; +} + +VEC3 gmove_clip_velocity( VEC3 in, VEC3 norm, F32 overbounce ) { + F32 blocked = vec_dot( in, norm ); + F32 backoff = blocked * overbounce; + VEC3 out = in - norm * backoff; + + F32 adjust = vec_dot( out, norm ); + if( adjust < 0.f ) + out -= (norm * adjust); + + // uncomment to enable quake/hl1-like wallboosting + // F32 len = vec_len( out ); + // if( len > 0.f ) + // out *= ( -1.f * blocked + len ) / len; + + return out; +} + +VEC3 gmove_clip_planes( VEC3 vel, LIST* planes, F32 overbounce ) { + if( planes->size > 2 ) + vel = {}; + + planes->each( fn( VEC3* p ) { + vel = gmove_clip_velocity( vel, *p, overbounce ); + } ); + + if( planes->size > 1 ) { + for( U32 i = 0; i < planes->size; ++i ) { + if( vec_dot( vel, planes->data[i] ) < 0.f ) { + VEC3 dir = vec_cross( planes->data[0], planes->data[1] ); + F32 len = vec_len( dir ); + if( len > 0.00001f ) { + dir *= 1.0f / len; + vel = dir * vec_dot( vel, dir ); + } else { + vel = {}; + } + + break; + } + } + } + + return vel; +} + +F32 gmove_try_move( BSP_TRACE* t, AABB aabb, VEC3* pos, VEC3 vel ) { + F32 dt = TICK_INTERVAL; + LIST planes; + + for( U32 bump = 0; bump < 4; ++bump ) { + VEC3 wishmove = vel * dt; + bsp_trace( t, gmove->game->state.map->bsp, aabb, *pos, *pos + wishmove ); + if( !t->hit ) { + *pos = t->point; + break; + } + + *pos += wishmove * t->frac; + // nudge player away from wall + *pos += t->normal * (BSP_TRACE_EPSILON * 2.f); + + planes.push( t->normal ); + + dt *= (1.f - t->frac); + if( dt <= 0.0001f ) + break; + + vel = gmove_clip_planes( vel, &planes, 1.f /* surface friction */ ); + } + + return dt < 0 ? 0 : dt; +} + +void gmove_walk_move() { + PLAYER* p = gmove->pl; + VEC2 move = gmove->input->move; + F32 yawrad = m_deg2rad( p->rot.y ); + VEC3 wishdir = { + move.x * cosf( yawrad ) - move.y * sinf( yawrad ), + move.y * cosf( yawrad ) + move.x * sinf( yawrad ), + 0 + }; + + VEC3 vel = wishdir * p->maxspeed; + VEC3 wishmove = vel * TICK_INTERVAL; + p->velocity = vel; + + if( vec_len( wishmove ) < BSP_TRACE_EPSILON ) + return; + + BSP_TRACE tr{}; + AABB aabb{}; + aabb.min = p->mins; + aabb.max = p->maxs; + VEC3 npos = p->pos; + + F32 frac = gmove_try_move( &tr, aabb, &npos, vel ); + p->velocity *= frac / TICK_INTERVAL; + p->pos = npos; +} + +void gmove_process_move() { + gmove_walk_move(); +} + +void gmove_tick() { + if( !gmove_check_valid( "gmove_tick()" ) ) + return; + gmove_process_move(); +} diff --git a/src/game/physics/movement.h b/src/game/physics/movement.h index e69de29..7c78d79 100644 --- a/src/game/physics/movement.h +++ b/src/game/physics/movement.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../player.h" + +struct GAME_MOVEMENT { + PLAYER* pl; + PLAYER_INPUT* input; + GAME_DATA* game; +}; + +extern void gmove_init( GAME_DATA* game ); +extern void gmove_set_player( PLAYER* player ); +extern void gmove_tick(); +extern void gmove_process_move(); + +extern GAME_MOVEMENT* gmove; diff --git a/src/game/player.cpp b/src/game/player.cpp index 51d1cf8..65daa9b 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -1,6 +1,7 @@ #include "../game.h" #include "SDL_scancode.h" #include "objlist.h" +#include "physics/movement.h" #include "world/trace.h" #include #include "player.h" @@ -34,8 +35,9 @@ PLAYER* player_create( VEC3 origin, F32 yaw ) { void capture_mouse( PLAYER* p ) { F32* yaw = &p->input.cam.pos.y; F32* pitch = &p->input.cam.pos.x; + if( input.keys[SDL_SCANCODE_F1] ) { - input_capture_mouse( false ); + input_capture_mouse( 0 ); } if( input.mouselock ) { @@ -56,59 +58,47 @@ void capture_mouse( PLAYER* p ) { void capture_move_keys( PLAYER* p ) { VEC2* move = &p->input.move; if( input.keys[input.binds.fwd] ) { - if( !p->input.fwd_held ) - move->x = 1.f; - p->input.fwd_held = true; + if( !p->input.fwd_held ) move->x = 1.f; + p->input.fwd_held = 1; } else { if( p->input.fwd_held ) { - if( p->input.bk_held ) - move->x = -1.f; - else - move->x = 0.f; + if( p->input.bk_held ) move->x = -1.f; + else move->x = 0.f; } - p->input.fwd_held = false; + p->input.fwd_held = 0; } if( input.keys[input.binds.back] ) { - if( !p->input.bk_held ) - move->x = -1.f; - p->input.bk_held = true; + if( !p->input.bk_held ) move->x = -1.f; + p->input.bk_held = 1; } else { if( p->input.bk_held ) { - if( p->input.fwd_held ) - move->x = 1.f; - else - move->x = 0.f; + if( p->input.fwd_held ) move->x = 1.f; + else move->x = 0.f; } - p->input.bk_held = false; + p->input.bk_held = 0; } if( input.keys[input.binds.left] ) { - if( !p->input.left_held ) - move->y = -1.f; - p->input.left_held = true; + if( !p->input.left_held ) move->y = -1.f; + p->input.left_held = 1; } else { if( p->input.left_held ) { - if( p->input.right_held ) - move->y = 1.f; - else - move->y = 0.f; + if( p->input.right_held ) move->y = 1.f; + else move->y = 0.f; } - p->input.left_held = false; + p->input.left_held = 0; } if( input.keys[input.binds.right] ) { - if( !p->input.right_held ) - move->y = 1.f; - p->input.right_held = true; + if( !p->input.right_held ) move->y = 1.f; + p->input.right_held = 1; } else { if( p->input.right_held ) { - if( p->input.left_held ) - move->y = -1.f; - else - move->y = 0.f; + if( p->input.left_held ) move->y = -1.f; + else move->y = 0.f; } - p->input.right_held = false; + p->input.right_held = 0; } } @@ -121,43 +111,8 @@ void player_input( GAME_DATA* game, PLAYER* p ) { } void player_move( GAME_DATA* game, PLAYER* p ) { - VEC2 move = p->input.move; - F32 yawrad = m_deg2rad( p->rot.y ); - - VEC3 wishdir = { - move.x * cosf( yawrad ) - move.y * sinf( yawrad ), - move.y * cosf( yawrad ) + move.x * sinf( yawrad ), - 0 - }; - - VEC3 vel = wishdir * 70.f; - p->velocity = vel; - VEC3 wishmove = vel * TICK_INTERVAL; - // todo : should never be false - if( vec_len( wishmove ) > BSP_TRACE_EPSILON && game->state.map->bsp ) { - BSP_TRACE tr{}; - AABB aabb{}; - tr.in_start = p->pos; - tr.in_end = tr.in_start + wishmove; - aabb.min = p->mins; - aabb.max = p->maxs; - - bsp_trace( &tr, game->state.map->bsp, aabb ); - if( !tr.hit ) - p->pos += wishmove; - else { - p->pos += wishmove * tr.frac; - - - // TODO: clipvelocity fixes this, clips velocity to 1 wall (should do 4) - VEC3 left = wishmove * (1.f - tr.frac); - F32 into = vec_dot( left, tr.normal ); - if( into < 0.f ) - left -= tr.normal * into; - - p->pos += left; - } - } + gmove_set_player( p ); + gmove_tick(); } VEC3 player_get_view_pos( PLAYER* p ) { diff --git a/src/game/player.h b/src/game/player.h index 6617f01..c0e126d 100644 --- a/src/game/player.h +++ b/src/game/player.h @@ -20,10 +20,10 @@ struct PLAYER : OBJECT { F32 eyeoffset{40.f}; PLAYER_INPUT input; - VEC3 mins; VEC3 maxs; + F32 maxspeed{70.f}; VEC3 velocity; }; diff --git a/src/game/world/bsp.cpp b/src/game/world/bsp.cpp index a95a693..c6c9726 100644 --- a/src/game/world/bsp.cpp +++ b/src/game/world/bsp.cpp @@ -736,6 +736,7 @@ BSP* bsp_build_map( WORLD_MAP* m ) { bsp_build_portals( bsp ); bsp_build_pvs( bsp ); + m->bsp = bsp; return bsp; } diff --git a/src/game/world/map.cpp b/src/game/world/map.cpp index aba283d..53cfe22 100644 --- a/src/game/world/map.cpp +++ b/src/game/world/map.cpp @@ -518,13 +518,13 @@ WORLD_MAP* map_from_file( GAME_DATA* game, const char* path ) { CFG_SECTION* skybox = cfg_section( s, "skybox" ); if( !skybox ) { dlog( errstr, path, "skybox" ); - dlog( "using default skybox" ); + dlog( "using default skybox\n" ); m->skybox = map_default_skybox(); } else { if( !OK( map_skybox_from_section( skybox, game, m ) ) ) { dlog( errstr, path, "skybox" ); - dlog( "using default skybox" ); + dlog( "using default skybox\n" ); m->skybox = map_default_skybox(); } } diff --git a/src/game/world/trace.cpp b/src/game/world/trace.cpp index c8ad3be..43f5f68 100644 --- a/src/game/world/trace.cpp +++ b/src/game/world/trace.cpp @@ -152,7 +152,10 @@ U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, VEC3 start, VEC3 end ) { trace->in_start = start; trace->in_end = end; - return bsp_trace( trace, bsp ); + U8 ret = bsp_trace( trace, bsp ); + if( !ret ) + trace->point = trace->in_end; + return ret; } U8 bsp_trace( BSP_TRACE* trace, BSP* bsp ) { @@ -378,6 +381,8 @@ U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, AABB hull ) { VEC3 end = trace->in_end; U8 ret = bsp_trace_sweep_aabb( trace, bsp, hull, start, end, bsp->root ); + if( !ret ) + trace->point = trace->in_end; return ret; } diff --git a/src/util/fnv.h b/src/util/fnv.h index 534063d..679bea9 100644 --- a/src/util/fnv.h +++ b/src/util/fnv.h @@ -1,4 +1,5 @@ #pragma once +#include "string.h" #include "typedef.h" typedef U32 FNV1A; @@ -8,13 +9,6 @@ enum : FNV1A { FNV1A_BASIS = 0x811C9DC5 }; -inline constexpr U32 strlen_ct( const char* str ) { - U32 s = 0; - for( ; !!str[s]; ++s ); - - return s; -} - inline constexpr FNV1A fnv1a( const U8* data, const U32 size ) { FNV1A out = FNV1A_BASIS; diff --git a/src/util/input.cpp b/src/util/input.cpp index 284ebfe..1222658 100644 --- a/src/util/input.cpp +++ b/src/util/input.cpp @@ -83,7 +83,7 @@ U8 input_is_key_down( U32 key ) { return input.keys[key]; } -void input_capture_mouse( bool capture ) { +void input_capture_mouse( U8 capture ) { input_reset_mouse_accumulator(); input.mouselock = capture; SDL_SetRelativeMouseMode( capture ? SDL_TRUE : SDL_FALSE ); diff --git a/src/util/input.h b/src/util/input.h index 86d715c..1317f94 100644 --- a/src/util/input.h +++ b/src/util/input.h @@ -38,7 +38,7 @@ using ON_INPUT_FN = std::function; struct INPUT_DATA { MOUSE_DATA mouse; U8 keys[0xff]; - bool mouselock; + U8 mouselock; F32 msens = .3f; F32 myaw = 1.f; F32 mpitch = 1.f; @@ -54,7 +54,7 @@ extern void input_frame_end(); extern void input_on_event( SDL_Event* e ); extern void input_on_mouse( I32 type, I32 x, I32 y ); extern U8 input_is_key_down( U32 key ); -extern void input_capture_mouse( bool capture ); +extern void input_capture_mouse( U8 capture ); extern void input_reset_mouse_accumulator(); #define kb_down( key ) input_is_key_down( key ) diff --git a/src/util/string.h b/src/util/string.h index b261e76..47da1a0 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -3,9 +3,15 @@ #include "typedef.h" +constexpr U32 strlen_ct( const char* str ) { + U32 len = 0; + for( ; !!str[len]; ++len ); + return len; +} + template struct STR { - char data[N]; + char data[N]{ 0 }; enum { size = N }; @@ -13,11 +19,31 @@ struct STR { memset( data, 0, N ); } STR( const char* str ) { - memcpy( data, str, N ); + memcpy( data, str, strlen_ct( str ) ); } STR( const STR& str ) { memcpy( data, str.data, N ); } + template + auto operator+( const STR& rhs ) { + constexpr U32 l1 = strlen_ct( data ); + constexpr U32 l2 = strlen_ct( rhs.data ); + + constexpr U32 high = N > other ? N : other; + constexpr U32 max = (l1 + l2 > high) ? l1 + l2 + 1 : high; + + STR result; + memcpy( result.data, data, l1 ); + memcpy( result.data + l1, rhs.data, l2 ); + result.data[l1 + l2] = '\0'; + return result; + } + + template + auto concat( const STR& str ) { + return *this + str; + } + operator char*() { return data; } }; -- cgit v1.2.3