Compare commits

..

7 Commits

16 changed files with 427 additions and 151 deletions

View File

@@ -1,5 +1,5 @@
CC=g++
C_FLAGS=-g -Wall -Wextra -O0 -Wno-unused-function
C_FLAGS=-g -Wall -Wextra -O0 -Wno-unused-function -MMD -MP
VAL_FLAGS=--leak-check=full --show-leak-kinds=all --track-origins=yes -s
DIR_SRC=src
@@ -11,6 +11,7 @@ OUTBIN=$(DIR_BUILD)/bin/main
SRCS=$(shell find $(DIR_SRC)/ -type f -name '*.cpp')
OBJS=$(patsubst $(DIR_SRC)/%.cpp,$(DIR_BUILD)/obj/%.o,$(SRCS))
DEPMF=$(patsubst $(DIR_SRC)/%.cpp,$(DIR_BUILD)/obj/%.d,$(SRCS))
DEPS_EXT=$(patsubst %,-l%,$(DEPS))
INCS_EXT=$(patsubst %,-I%,$(DIR_INC))
@@ -38,6 +39,8 @@ $(DIR_BUILD)/obj/%.o: $(DIR_SRC)/%.cpp
@mkdir -p $(@D)
$(CC) $(C_FLAGS) $(INCS_EXT) -c $< -o $@
-include $(DEPMF)
dbg: $(DBG_BIN)
gdb $(GDB_FLAGS) ./$(OUTBIN)

158
src/dxd_math.cpp Normal file
View File

@@ -0,0 +1,158 @@
#include "dxd_math.hpp"
#include <SDL3/SDL.h>
vec2 operator+(vec2 const &v)
{
return { .x = v.x, .y = v.y };
}
vec2 operator-(vec2 const &v)
{
return { .x = -v.x, .y = -v.y };
}
vec2 operator+(vec2 const &a, vec2 const &b)
{
return { .x = a.x + b.x, .y = a.y + b.y };
}
vec2 operator-(vec2 const &a, vec2 const &b)
{
return { .x = a.x - b.x, .y = a.y - b.y };
}
vec2 operator*(vec2 const &a, float const &b)
{
return { .x = a.x * b, .y = a.y * b };
}
vec2 operator/(vec2 const &a, float const &b)
{
return { .x = a.x / b, .y = a.y / b };
}
bool operator<(vec2 const &a, vec2 const &b)
{
return a.x < b.x && a.y < b.y;
}
bool operator>(vec2 const &a, vec2 const &b)
{
return a.x > b.x && a.y > b.y;
}
bool operator==(vec2 const &a, vec2 const &b)
{
return a.x == b.x && a.y == b.y;
}
bool operator<(vec2i const &a, vec2i const &b)
{
return a.x < b.x && a.y < b.y;
}
bool operator>(vec2i const &a, vec2i const &b)
{
return a.x > b.x && a.y > b.y;
}
float dot(vec2 const &a, vec2 const &b)
{
return a.x * b.x + a.y * b.y;
}
float norm2(vec2 const &v)
{
return v.x * v.x + v.y * v.y;
}
float norm(vec2 const &v)
{
return std::sqrtf(v.x * v.x + v.y * v.y);
}
vec2 normalize(vec2 const &v)
{
return v / std::sqrt(v.x * v.x + v.y * v.y);
}
vec2 min2(vec2 const &a, vec2 const &b)
{
return {
.x = a.x < b.x ? a.x : b.x,
.y = a.y < b.y ? a.y : b.y,
};
}
vec2 max2(vec2 const &a, vec2 const &b)
{
return {
.x = a.x > b.x ? a.x : b.x,
.y = a.y > b.y ? a.y : b.y,
};
}
vec2 polar_to_vec2(float const angle, float const len)
{
return {
.x = len * sinf(angle),
.y = len * cosf(angle)
};
}
float vec2_angle(vec2 const v)
{
return atan2f(v.x, v.y);
}
vec2 v2i_to_v2(vec2i const v)
{
return { (float)v.x, (float)v.y };
}
float normalize_angle(const float angle)
{
float a = angle;
while (a > 2*M_PIf) a -= 2*M_PIf;
while (a < 0) a += 2*M_PIf;
return a;
}
float normalize_angle_diff(const float diff)
{
float d = diff;
while (d > M_PIf) d -= 2*M_PIf;
while (d < -M_PIf) d += 2*M_PIf;
return d;
}
float angle_to_heading(float const angle)
{
return normalize_angle(angle) * 180.0f / M_PIf;
}
int rand_int(int const min, int const max)
{
return SDL_rand(max - min) + min;
}
float rand_float(float const min, float const max)
{
return SDL_randf() * (max - min) + min;
}
float rand_angle()
{
return rand_float(0, 2*M_PIf);
}
vec2i rand_v2i(vec2i const min, vec2i const max)
{
return { rand_int(min.x, max.x), rand_int(min.y, max.y) };
}
vec2 rand_v2(vec2 const min, vec2 const max)
{
return { rand_float(min.x, max.x), rand_float(min.y, max.y) };
}

58
src/dxd_math.hpp Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <float.h>
#include <math.h>
typedef struct vec2 { float x, y; } vec2;
typedef struct vec2i { int x, y; } vec2i;
static const vec2 vec2_zero = { 0, 0 };
static const vec2 vec2_one = { 1, 1 };
static const vec2 vec2_unitx = { 1, 0 };
static const vec2 vec2_unity = { 0, 1 };
static const vec2 vec2_max = { FLT_MAX, FLT_MAX };
static const vec2 vec2_min = { -FLT_MAX, -FLT_MAX };
static const vec2i vec2i_zero = { 0, 0 };
static const vec2i vec2i_one = { 1, 1 };
// vec2 operators
vec2 operator+(vec2 const &v);
vec2 operator-(vec2 const &v);
vec2 operator+(vec2 const &a, vec2 const &b);
vec2 operator-(vec2 const &a, vec2 const &b);
vec2 operator*(vec2 const &a, float const &b);
vec2 operator/(vec2 const &a, float const &b);
bool operator<(vec2 const &a, vec2 const &b);
bool operator>(vec2 const &a, vec2 const &b);
bool operator==(vec2 const &a, vec2 const &b);
// vec2i operators
bool operator<(vec2i const &a, vec2i const &b);
bool operator>(vec2i const &a, vec2i const &b);
// vec2 functions
float dot(vec2 const &a, vec2 const &b);
float norm2(vec2 const &v);
float norm(vec2 const &v);
vec2 normalize(vec2 const &v);
vec2 min2(vec2 const &a, vec2 const &b);
vec2 max2(vec2 const &a, vec2 const &b);
vec2 polar_to_vec2(float const angle, float const len);
float vec2_angle(vec2 const v);
// vec2i functions
vec2 v2i_to_v2(vec2i const v);
// Angle functions
float normalize_angle(float const angle);
float normalize_angle_diff(float const diff);
float angle_to_heading(float const angle);
// Random generation functions
int rand_int(int const min, int const max);
float rand_float(float const min, float const max);
float rand_angle();
vec2i rand_v2i(vec2i const min, vec2i const max);
vec2 rand_v2(vec2 const min, vec2 const max);

View File

@@ -45,7 +45,7 @@ int main(int argc, char *argv[])
dxd::Renderer renderer = dxd::Renderer(sdl_renderer, width, height);
// World init
dxd::sim::World world = dxd::sim::World({ .x = 10, .y = 10 });
dxd::sim::World world = dxd::sim::World();
world.add_obj(new dxd::sim::AFSpawner());
SDL_Event event;
@@ -72,18 +72,24 @@ int main(int argc, char *argv[])
if (key_states[SDL_SCANCODE_E]) renderer.zoom_camera(-zoom_spd * (1.0f / 60.0f));
// Clear
renderer.color(0, 0, 0, 255);
renderer.color(0, 0, 0);
SDL_RenderClear(sdl_renderer);
// World draw
world.draw(&renderer);
world.tick(1.0f / 60.0f);
// Usual test
renderer.color(64, 64, 64, 255);
// World axis
renderer.color(32, 32, 32);
renderer.line(-vec2_unity * 2000, vec2_unity * 2000);
renderer.line(-vec2_unitx * 2000, vec2_unitx * 2000);
// Scale and position
vec2 center = renderer.get_camera_pos();
float zoom = 100.0f/renderer.get_zoom();
renderer.color(128, 128, 255);
renderer.dbg_txt(0, 0, "(%0.1f, %0.1f) @%0.2f", center.x, center.y, zoom);
SDL_RenderPresent(sdl_renderer);
SDL_Delay(1000 / 60);
}

View File

@@ -1,109 +0,0 @@
#pragma once
#include <float.h>
#include <math.h>
//#define epsilon 0.0000001
typedef struct vec2 { float x, y; } vec2;
typedef struct vec2i { int x, y; } vec2i;
static const vec2 vec2_zero = { 0, 0 };
static const vec2 vec2_one = { 1, 1 };
static const vec2 vec2_unitx = { 1, 0 };
static const vec2 vec2_unity = { 0, 1 };
static const vec2 vec2_max = { FLT_MAX, FLT_MAX };
static const vec2 vec2_min = { -FLT_MAX, -FLT_MAX };
static const vec2i vec2i_zero = { 0, 0 };
static const vec2i vec2i_one = { 1, 1 };
static vec2 operator+(vec2 const &v)
{
return { .x = v.x, .y = v.y };
}
static vec2 operator-(vec2 const &v)
{
return { .x = -v.x, .y = -v.y };
}
static vec2 operator+(vec2 const &a, vec2 const &b)
{
return { .x = a.x + b.x, .y = a.y + b.y };
}
static vec2 operator-(vec2 const &a, vec2 const &b)
{
return { .x = a.x - b.x, .y = a.y - b.y };
}
static vec2 operator*(vec2 const &a, float const &b)
{
return { .x = a.x * b, .y = a.y * b };
}
static vec2 operator/(vec2 const &a, float const &b)
{
return { .x = a.x / b, .y = a.y / b };
}
static bool operator<(vec2 const &a, vec2 const &b)
{
return a.x < b.x && a.y < b.y;
}
static bool operator>(vec2 const &a, vec2 const &b)
{
return a.x > b.x && a.y > b.y;
}
static float dot(vec2 const &a, vec2 const &b)
{
return a.x * b.x + a.y * b.y;
}
static float norm2(vec2 const &v)
{
return v.x * v.x + v.y * v.y;
}
static float norm(vec2 const &v)
{
return std::sqrtf(v.x * v.x + v.y * v.y);
}
static vec2 normalize(vec2 const &v)
{
return v / norm(v);
}
static inline vec2 normalize_h(vec2 const &v)
{
return v / std::sqrt(v.x * v.x + v.y * v.y);
}
static vec2 min2(vec2 const &a, vec2 const &b)
{
return {
.x = a.x < b.x ? a.x : b.x,
.y = a.y < b.y ? a.y : b.y,
};
}
static vec2 max2(vec2 const &a, vec2 const &b)
{
return {
.x = a.x > b.x ? a.x : b.x,
.y = a.y > b.y ? a.y : b.y,
};
}
static vec2 polar_to_vec2(const float angle, const float len)
{
return {
.x = len * sinf(angle),
.y = len * cosf(angle)
};
}

View File

@@ -4,7 +4,7 @@
#include <SDL3/SDL.h>
#include "math.hpp"
#include "dxd_math.hpp"
namespace dxd
{
@@ -60,14 +60,19 @@ public:
_scale /= 1 + delta;
}
vec2 get_camera_pos()
{
return _center;
}
float get_zoom()
{
return 1.0f / _scale;
}
void color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
void color(uint8_t r, uint8_t g, uint8_t b)
{
SDL_SetRenderDrawColor(_sdl, r, g, b, a);
SDL_SetRenderDrawColor(_sdl, r, g, b, 255);
}
void line(vec2 a, vec2 b)
@@ -120,6 +125,31 @@ public:
points[4] = points[0];
SDL_RenderLines(_sdl, points, 5);
}
void dbg_txt(float const x, float const y, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SDL_RenderDebugTextFormat(_sdl, x, y, fmt, args);
va_end(args);
}
void txt(vec2 const pos, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
char buf[128];
vsnprintf(buf, sizeof(buf), fmt, args);
vec2 p = to_screenv2(to_viewv2(pos));
SDL_RenderDebugText(_sdl, p.x, p.y, buf);
va_end(args);
}
};
} // namespace dxd

View File

@@ -1,17 +1,64 @@
#include "af_spawner.hpp"
#include <math.h>
void dxd::sim::AFSpawner::spawn_airfield(World *world)
{
vec2i base_pos;
//TODO: Iter limiter
do
base_pos = rand_v2i({ -_GRID_BOUNDS, -_GRID_BOUNDS }, { _GRID_BOUNDS, _GRID_BOUNDS });
while (_airfields.find(base_pos) != _airfields.end());
vec2 off = rand_v2({ -_GRID_JITTER, -_GRID_JITTER }, { _GRID_JITTER, _GRID_JITTER });
vec2 pos = v2i_to_v2(base_pos) * _GRID_STRIDE + off;
Airfield *af = new Airfield(this, pos, _MIN_AF_TTL + SDL_rand(_MAX_AF_TTL), SDL_randf()*2*M_PIf);
world->add_obj(af);
_airfields.emplace(base_pos, af);
}
void dxd::sim::AFSpawner::tick(float timestep, World *world)
{
(void)timestep;
if (_next_spawn == -1) //Init
{
spawn_airfield(world);
spawn_airfield(world);
_next_spawn = _MIN_SPAWN_DELAY;
return;
}
if (--_next_spawn != 0)
return;
_next_spawn = _MIN_SPAWN_DELAY + SDL_rand(_MAX_SPAWN_DELAY);
//Pos should spawn in a grid, check if free, and offset random from grid coord to look natural
vec2 pos = {(SDL_randf()-.5f)*2*60.0f, (SDL_randf()-.5f)*2*60.0f};
Airfield *af = new Airfield(pos, _MIN_AF_TTL + SDL_rand(_MAX_AF_TTL));
world->add_obj(af);
_airfields.push_back(af);
spawn_airfield(world);
}
void dxd::sim::AFSpawner::draw(Renderer *rend)
{
rend->color(0, 127, 0);
rend->rect(vec2_zero, _SPAWN_BOUNDS*2);
}
dxd::sim::Airfield *dxd::sim::AFSpawner::get_random_airfield(Airfield *exclude)
{
int count = (int)_airfields.size();
Airfield *af = exclude;
//TODO: Iter limiter
do
{
int sel = rand_int(0, count);
auto it = _airfields.begin();
for (; it != _airfields.end() && sel; ++it, --sel);
af = it->second;
if (!af->is_active())
af = exclude;
} while(af == exclude);
return af;
}

View File

@@ -1,34 +1,47 @@
#pragma once
#include <vector>
#include <map>
#include "../math.hpp"
#include "../dxd_math.hpp"
#include "world_object.hpp"
#include "airfield.hpp"
namespace dxd::sim
{
class Airfield;
class AFSpawner : public WorldObject
{
private:
std::vector<Airfield*> _airfields;
std::map<vec2i, Airfield*> _airfields;
int _next_spawn;
const int _SPAWN_BOUNDS = 150;
const int _GRID_STRIDE = 10;
const int _GRID_MARGIN = 5;
const int _GRID_BOUNDS = (_SPAWN_BOUNDS - _GRID_MARGIN * 2) / _GRID_STRIDE;
const float _GRID_JITTER = (float)_GRID_STRIDE * 0.4f;
const int _MIN_SPAWN_DELAY = 450;
const int _MAX_SPAWN_DELAY = 3000;
const int _MIN_AF_TTL = 1200;
const int _MAX_AF_TTL = 6000;
const int _MIN_AF_TTL = 3000;
const int _MAX_AF_TTL = 9000;
void spawn_airfield(World *world);
public:
AFSpawner()
{
_airfields = std::vector<Airfield*>();
_next_spawn = 1;
_airfields = std::map<vec2i, Airfield*>();
_next_spawn = -1;
}
void tick(float timestep, World *world) override;
void draw(Renderer *rend) override { (void)rend; }
void draw(Renderer *rend) override;
Airfield *get_random_airfield(Airfield *exclude);
};
} // namespace dxd::sim

View File

@@ -3,8 +3,25 @@
void dxd::sim::Aircraft::tick(float timestep, World *world)
{
(void)world;
// Refactor into own function
float target_dist = norm2(_target - _position);
if (target_dist < _TARGET_REACHED_DIST2)
{
if (_target == _destination->get_position())
{
_destination->arrived_at();
world->remove_obj(this);
}
_target = _destination->get_position();
}
float target_angle = vec2_angle(_target - _position);
float target_deviation = target_angle - _direction;
target_deviation = normalize_angle_diff(target_deviation);
_direction += SDL_clamp(target_deviation, -_MAX_TURN_RATE, _MAX_TURN_RATE) * timestep;
_direction = normalize_angle(_direction);
_position = _position + polar_to_vec2(_direction, _speed) * timestep;
if (--_trail_cooldown == 0)
@@ -16,7 +33,17 @@ void dxd::sim::Aircraft::tick(float timestep, World *world)
void dxd::sim::Aircraft::draw(Renderer *rend)
{
rend->color(255, 255, 255, 255);
// Indicator
rend->color(255, 255, 255);
rend->box(_position, 1);
rend->line(_position, _position + polar_to_vec2(_direction, 3));
// Status
rend->color(0, 255, 0);
rend->txt(_position - vec2_unity, "%03.0f", angle_to_heading(_direction));
rend->txt(_position - vec2_unity*2.5f, "%03.0f", _speed * _SPD_TO_KTS);
// Target
rend->color(255, 64, 64);
rend->diamond(_target, 1);
}

View File

@@ -1,7 +1,8 @@
#pragma once
#include "../math.hpp"
#include "../dxd_math.hpp"
#include "world_object.hpp"
#include "airfield.hpp"
namespace dxd::sim
{
@@ -12,16 +13,24 @@ private:
vec2 _position;
float _direction;
float _speed;
Airfield *_destination;
vec2 _target;
int _trail_cooldown;
const int _TRAIL_DELAY = 60;
const int _TRAIL_DURATION = 420;
const int _TRAIL_DELAY = 120;
const int _TRAIL_DURATION = 600;
const float _MAX_TURN_RATE = 0.3f;
const float _SPD_TO_KTS = 60.0f;
const float _TARGET_REACHED_DIST = 0.5f;
const float _TARGET_REACHED_DIST2 = _TARGET_REACHED_DIST*_TARGET_REACHED_DIST;
public:
Aircraft(vec2 position, float direction, float speed)
: _position(position), _direction(direction), _speed(speed)
Aircraft(vec2 position, float direction, float speed, Airfield *destination)
: _position(position), _direction(direction), _speed(speed), _destination(destination)
{
_trail_cooldown = _TRAIL_DELAY;
_target = destination->get_approach_point();
destination->going_to();
}
void tick(float timestep, World *world) override;

View File

@@ -13,6 +13,6 @@ void dxd::sim::AircraftTrail::tick(float timestep, World *world)
void dxd::sim::AircraftTrail::draw(Renderer *rend)
{
rend->color(128, 128, 128, 255);
rend->color(128, 128, 128);
rend->box(_position, .5);
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "../math.hpp"
#include "../dxd_math.hpp"
#include "world_object.hpp"
namespace dxd::sim

View File

@@ -5,23 +5,42 @@ void dxd::sim::Airfield::tick(float timestep, World *world)
{
(void)timestep;
if (--_ttl == 0)
if (--_ttl <= 0)
{
world->remove_obj(this);
if (_pending_arrivals == 0)
{
world->remove_obj(this);
//TODO: Free space in af_spawner
}
return;
}
if (--_next_spawn == 0)
{
Aircraft *af = new Aircraft(_position, SDL_randf() * 2 * M_PIf, 5.0f);
vec2 pos = _position + polar_to_vec2(_rw_heading, _RW_LENGTH+0.5);
Airfield *target = _parent->get_random_airfield(this);
Aircraft *af = new Aircraft(pos, _rw_heading, 2.0f, target);
world->add_obj(af);
_next_spawn = _MIN_TAKEOFF_DELAY + SDL_rand(_MAX_TAKEOFF_DELAY - _MIN_TAKEOFF_DELAY);
}
}
void dxd::sim::Airfield::draw(Renderer *rend)
{
rend->color(127, 127, 127, 255);
rend->color(127, 127, 127);
rend->rect(_position, 5);
rend->diamond(_position, 5);
rend->color(64, 64, 255);
vec2 a = _position + polar_to_vec2(_rw_heading, +_RW_LENGTH);
vec2 b = _position + polar_to_vec2(_rw_heading, -_RW_LENGTH);
rend->line(a, b);
}
vec2 dxd::sim::Airfield::get_approach_point()
{
return _position - polar_to_vec2(_rw_heading, _RW_LENGTH + _APPROACH_DIST);
}

View File

@@ -2,28 +2,44 @@
#include <vector>
#include "../math.hpp"
#include "../dxd_math.hpp"
#include "world_object.hpp"
#include "af_spawner.hpp"
namespace dxd::sim
{
class AFSpawner;
class Airfield : public WorldObject
{
private:
AFSpawner *_parent;
vec2 _position;
int _ttl;
int _next_spawn;
float _rw_heading;
const int _MIN_TAKEOFF_DELAY = 240;
const int _MAX_TAKEOFF_DELAY = 600;
int _pending_arrivals;
int const _MIN_TAKEOFF_DELAY = 600;
int const _MAX_TAKEOFF_DELAY = 3000;
int const _RW_LENGTH = 6;
float const _APPROACH_DIST = 20.0f;
public:
Airfield(vec2 position, int ttl) : _position(position), _ttl(ttl)
Airfield(AFSpawner *parent, vec2 position, int ttl, float rw_heading)
: _parent(parent), _position(position), _ttl(ttl), _rw_heading(rw_heading)
{
_next_spawn = 30;
_next_spawn = _MIN_TAKEOFF_DELAY;
}
vec2 get_position() { return _position; }
vec2 get_approach_point();
void going_to() { ++_pending_arrivals; }
void arrived_at() { --_pending_arrivals; }
bool is_active() { return _ttl > 0; }
void tick(float timestep, World *world) override;
void draw(Renderer *rend) override;
};

View File

@@ -2,7 +2,7 @@
#include <vector>
#include "../math.hpp"
#include "../dxd_math.hpp"
#include "world_object.hpp"
namespace dxd::sim
@@ -13,13 +13,12 @@ class WorldObject;
class World
{
private:
vec2 _size;
std::vector<WorldObject*> _objs;
std::vector<WorldObject*> _pending_removes;
std::vector<WorldObject*> _pending_adds;
public:
World(vec2 size) : _size(size)
World()
{
_objs = std::vector<WorldObject*>();
_pending_adds = std::vector<WorldObject*>();

View File

@@ -1,6 +1,6 @@
#pragma once
#include "../math.hpp"
#include "../dxd_math.hpp"
#include "../renderer.hpp"
#include "world.hpp"