diff --git a/code/game/src/entity_view.c b/code/game/src/entity_view.c index eb9cc8f..39312c1 100644 --- a/code/game/src/entity_view.c +++ b/code/game/src/entity_view.c @@ -22,6 +22,9 @@ pkt_desc pkt_entity_view_desc[] = { { PKT_HALF(entity_view, hp) }, { PKT_HALF(entity_view, max_hp) }, + { PKT_KEEP_IF(entity_view, kind, EKIND_VEHICLE, 1) }, // NOTE(zaklaus): keep for vehicles + { PKT_HALF(entity_view, heading) }, + { PKT_END }, }; diff --git a/code/game/src/entity_view.h b/code/game/src/entity_view.h index e59b0cb..fdb55a5 100644 --- a/code/game/src/entity_view.h +++ b/code/game/src/entity_view.h @@ -48,6 +48,9 @@ typedef struct entity_view { uint8_t is_dirty; int64_t tex; + // NOTE(zaklaus): vehicle + float heading, theading; + // NOTE(zaklaus): internals uint8_t layer_id; uint64_t last_update; diff --git a/code/game/src/platform_raylib.c b/code/game/src/platform_raylib.c index 8a13537..99752a6 100644 --- a/code/game/src/platform_raylib.c +++ b/code/game/src/platform_raylib.c @@ -115,6 +115,7 @@ void lerp_entity_positions(uint64_t key, entity_view * data); void do_entity_fadeinout(uint64_t key, entity_view * data); float zpl_lerp(float,float,float); +float zpl_to_degrees(float); void platform_render() { profile(PROF_ENTITY_LERP) { @@ -220,9 +221,9 @@ void DEBUG_draw_entities(uint64_t key, entity_view * data) { case EKIND_VEHICLE: { float x = data->x; float y = data->y; - float const w = 50; - float const h = 80; - DrawRectanglePro((Rectangle){x,y,w,h}, (Vector2){w/2.0f,h/2.0f}, 0.0f, ColorAlpha(RED, data->tran_time)); + float const w = 80; + float const h = 50; + DrawRectanglePro((Rectangle){x,y,w,h}, (Vector2){w/2.0f,h/2.0f}, zpl_to_degrees(data->heading), ColorAlpha(RED, data->tran_time)); }break; default:break; } @@ -237,9 +238,11 @@ void lerp_entity_positions(uint64_t key, entity_view *data) { #if 1 data->x = smooth_val(data->x, data->tx, view->delta_time[data->layer_id]); data->y = smooth_val(data->y, data->ty, view->delta_time[data->layer_id]); + data->heading = smooth_val_spherical(data->heading, data->theading, view->delta_time[data->layer_id]); #else data->x = data->tx; data->y = data->ty; + data->heading = data->theading; #endif } } diff --git a/code/game/src/prediction.c b/code/game/src/prediction.c index 6279eb4..5d342c2 100644 --- a/code/game/src/prediction.c +++ b/code/game/src/prediction.c @@ -11,6 +11,26 @@ static inline float map_factor(float x) { return 1.0f - x*x*x*x*x*x*x*x; } +static inline float base_angle(float x) { + while (x > ZPL_TAU) x -= ZPL_TAU; + while (x < 0.0f) x += ZPL_TAU; + return x; +} + +static inline float spherical_lerp(float a, float b, float t) { + a = base_angle(a); + b = base_angle(b); + float d = b - a; + + if (d < -ZPL_PI) { + b += ZPL_TAU; + } else if (d > ZPL_PI) { + b -= ZPL_TAU; + } + + return base_angle(zpl_lerp(a, b, t)); +} + float smooth_val(float cur, float tgt, uint64_t dt) { float factor = zpl_clamp01(map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_FAST_MS, WORLD_TRACKER_UPDATE_SLOW_MS))); @@ -24,15 +44,24 @@ float smooth_val(float cur, float tgt, uint64_t dt) { return zpl_lerp(cur, tgt, zpl_lerp(PREDICT_SMOOTH_FACTOR_LO, PREDICT_SMOOTH_FACTOR_HI, factor)); } +float smooth_val_spherical(float cur, float tgt, uint64_t dt) { + float factor = zpl_clamp01(map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_FAST_MS, WORLD_TRACKER_UPDATE_SLOW_MS))); + + return spherical_lerp(cur, tgt, zpl_lerp(PREDICT_SMOOTH_FACTOR_LO, PREDICT_SMOOTH_FACTOR_HI, factor)); +} + void predict_receive_update(entity_view *d, entity_view *data) { if (d && data->flag & EFLAG_INTERP) { // NOTE(zaklaus): store target pos but keep x,y unchanged float tx = data->x; float ty = data->y; + float theading = data->heading; data->x = d->x; data->y = d->y; + data->heading = d->heading; data->tx = tx; data->ty = ty; + data->theading = theading; } data->tran_effect = d->tran_effect; diff --git a/code/game/src/prediction.h b/code/game/src/prediction.h index 4c047d0..2d52420 100644 --- a/code/game/src/prediction.h +++ b/code/game/src/prediction.h @@ -2,4 +2,5 @@ #include "entity_view.h" float smooth_val(float cur, float tgt, uint64_t dt); -void predict_receive_update(entity_view *d, entity_view *data); \ No newline at end of file +void predict_receive_update(entity_view *d, entity_view *data); +float smooth_val_spherical(float cur, float tgt, uint64_t dt); \ No newline at end of file diff --git a/code/game/src/vehicle.c b/code/game/src/vehicle.c index 7a39054..65be8be 100644 --- a/code/game/src/vehicle.c +++ b/code/game/src/vehicle.c @@ -8,7 +8,9 @@ uint64_t vehicle_spawn(void) { ecs_entity_t e = entity_spawn(EKIND_VEHICLE); - ecs_set(world_ecs(), e, Vehicle, {0}); + Vehicle *veh = ecs_get_mut(world_ecs(), e, Vehicle, NULL); + zpl_zero_item(veh); + veh->wheel_base = 60.0f; return (uint64_t)e; } diff --git a/code/game/src/world/world.c b/code/game/src/world/world.c index b42f84f..b91ee07 100644 --- a/code/game/src/world/world.c +++ b/code/game/src/world/world.c @@ -45,6 +45,11 @@ entity_view world_build_entity_view(int64_t e) { view.max_hp = health->max_hp; } + if (ecs_get(world_ecs(), e, Vehicle)) { + Vehicle const* veh = ecs_get(world_ecs(), e, Vehicle); + view.heading = veh->heading; + } + if (ecs_get(world_ecs(), e, Chunk)) { Chunk *chpos = ecs_get_mut(world_ecs(), e, Chunk, 0); view.x = chpos->x; diff --git a/code/game/src/world_view.c b/code/game/src/world_view.c index 15ed5bd..e09fc49 100644 --- a/code/game/src/world_view.c +++ b/code/game/src/world_view.c @@ -59,6 +59,7 @@ int32_t tracker_read_create(librg_world *w, librg_event *e) { if (data.flag & EFLAG_INTERP) { data.tx = data.x; data.ty = data.y; + data.theading = data.heading; } entity_view_update_or_create(&view->entities, entity_id, data); entity_view_mark_for_fadein(&view->entities, entity_id); diff --git a/code/modules/modules/components.h b/code/modules/modules/components.h index 3b15dc8..8cd185f 100644 --- a/code/modules/modules/components.h +++ b/code/modules/modules/components.h @@ -51,6 +51,11 @@ ECS_STRUCT(Classify, { ECS_STRUCT(Vehicle, { uint64_t seats[4]; + + float speed; + float heading; + float steer; + float wheel_base; }); ECS_COMPONENT_EXTERN(Chunk); diff --git a/code/modules/modules/systems.c b/code/modules/modules/systems.c index 96077be..13af947 100644 --- a/code/modules/modules/systems.c +++ b/code/modules/modules/systems.c @@ -201,12 +201,35 @@ void EnterOrLeaveVehicle(ecs_iter_t *it) { } } +#define VEHICLE_MAX_SPEED 500.0f +#define VEHICLE_ACCEL 2.1f +#define VEHICLE_STEER 0.01f + void VehicleHandling(ecs_iter_t *it) { Vehicle *veh = ecs_column(it, Vehicle, 1); Position *p = ecs_column(it, Position, 2); Velocity *v = ecs_column(it, Velocity, 3); for (int i = 0; i < it->count; i++) { + Vehicle *car = &veh[i]; + car->speed *= 0.99f; + car->steer *= 0.97f; + + float fr_x = p[i].x + (car->wheel_base/2.0f) * zpl_cos(car->heading); + float fr_y = p[i].y + (car->wheel_base/2.0f) * zpl_sin(car->heading); + + float bk_x = p[i].x - (car->wheel_base/2.0f) * zpl_cos(car->heading); + float bk_y = p[i].y - (car->wheel_base/2.0f) * zpl_sin(car->heading); + + bk_x += car->speed * zpl_cos(car->heading); + bk_y += car->speed * zpl_sin(car->heading); + fr_x += car->speed * zpl_cos(car->heading + zpl_to_radians(car->steer)); + fr_y += car->speed * zpl_sin(car->heading + zpl_to_radians(car->steer)); + + v[i].x = (fr_x + bk_x) / 2.0f - p[i].x; + v[i].y = (fr_y + bk_y) / 2.0f - p[i].y; + car->heading = zpl_arctan2(fr_y - bk_y, fr_x - bk_x); + for (int j = 0; j < 4; j++) { // NOTE(zaklaus): Perform seat cleanup if (!ecs_is_alive(world_ecs(), veh[i].seats[j])) { @@ -219,15 +242,19 @@ void VehicleHandling(ecs_iter_t *it) { // NOTE(zaklaus): Update passenger position { Position *p2 = ecs_get_mut(world_ecs(), pe, Position, NULL); + Velocity *v2 = ecs_get_mut(world_ecs(), pe, Velocity, NULL); *p2 = p[i]; + *v2 = v[i]; } // NOTE(zaklaus): Handle driver input if (j == 0) { - // TODO(zaklaus): Be lazy about it for now, implement wheels later Input const* in = ecs_get(world_ecs(), pe, Input); - v[i].x += in->x; - v[i].y += in->y; + + car->speed += in->y * VEHICLE_ACCEL; + car->speed = zpl_clamp(car->speed, -VEHICLE_MAX_SPEED, VEHICLE_MAX_SPEED); + car->steer += in->x * -VEHICLE_STEER * (zpl_abs(car->speed*2.5f) / VEHICLE_MAX_SPEED); + car->steer = zpl_clamp(car->steer, -40.0f, 40.0f); } } } @@ -242,7 +269,7 @@ void SystemsImport(ecs_world_t *ecs) { ECS_SYSTEM(ecs, DemoNPCMoveAround, EcsOnLoad, components.Velocity, components.EcsDemoNPC); ECS_SYSTEM(ecs, EnterOrLeaveVehicle, EcsOnLoad, components.Input, components.Position); - ECS_SYSTEM(ecs, MoveWalk, EcsOnUpdate, components.Position, components.Velocity); + ECS_SYSTEM(ecs, MoveWalk, EcsOnUpdate, components.Position, components.Velocity, !components.Vehicle); ECS_SYSTEM(ecs, HurtOnHazardBlock, EcsOnUpdate, components.Position, components.Health); ECS_SYSTEM(ecs, RegenerateHP, EcsOnUpdate, components.Health); ECS_SYSTEM(ecs, VehicleHandling, EcsOnUpdate, components.Vehicle, components.Position, components.Velocity);