Compare commits
12 Commits
e47b761a2c
...
f90b973d86
Author | SHA1 | Date | |
---|---|---|---|
|
f90b973d86 | ||
|
32675c012c | ||
|
a97515e948 | ||
|
ed8e826b94 | ||
|
4e5ff9433c | ||
|
55829a1bef | ||
|
119bd52c51 | ||
|
5abd1ced1c | ||
|
80db96672d | ||
|
2f6f7852be | ||
|
307d5552f6 | ||
|
5911cbd980 |
apps/demos/scenery/scenes
include
share
src
third-party/stb
@ -19,10 +19,215 @@
|
||||
#define PLAYER_HEIGHT 0.6f
|
||||
#define TREE_DENSITY 0.03f
|
||||
|
||||
#define G_CONST 10.0f
|
||||
|
||||
/* TODO: pregenerate grid of levels of detail */
|
||||
static float heightmap[TERRAIN_DISTANCE][TERRAIN_DISTANCE];
|
||||
|
||||
|
||||
/* vehicle sim ! */
|
||||
/* https://www.youtube.com/watch?v=pwbwFdWBkU0 */
|
||||
/* representation is a "jelly" box with spring configuration trying to make it coherent */
|
||||
|
||||
/* == springs == */
|
||||
/* damped spring: F = -kx - cv */
|
||||
/* x = length(p1-p0) - at_rest_length */
|
||||
/* v = dot(v1 - v0, unit(p1-p0)) */
|
||||
/* F = (-kx - cv) * unit(p1-p0) */
|
||||
/* v += (F/m)*t */
|
||||
/* one points gains positive F, other negative F, to come together */
|
||||
|
||||
/* == ground interaction == */
|
||||
/* if point is under terrain, then apply this: */
|
||||
/* x = y difference on point and ground */
|
||||
/* -x(n) = x * normal */
|
||||
/* -v(n) = dot(v, normal) */
|
||||
/* -F = (-kx(n)-cv(n)) * normal */
|
||||
|
||||
/* == friction == */
|
||||
/* v(o)/F(o) are perpendicular to slope (x - x(n)) */
|
||||
/* if v(o) == 0, then */
|
||||
/* -- at rest, static friction overcomes */
|
||||
/* if F(o) <= f(s)*x(n), F(o) = 0 */
|
||||
/* else, F(o) -= f(k) * x(n) */
|
||||
/* else if length(v(o) + (F(o)/m)*t) <= (f(k)*x(n)*t), v(o) = 0 */
|
||||
/* else, F = -unit(v(o)*f(k)*x(n)) */
|
||||
|
||||
#define VEHICLE_MASS 200.0f
|
||||
#define VEHICLE_LENGTH 3.0f
|
||||
#define VEHICLE_WIDTH 1.7f
|
||||
#define VEHICLE_HEIGHT 1.3f
|
||||
/* spring constant */
|
||||
#define VEHICLE_SPRING_K 20000.0f
|
||||
#define VEHICLE_SPRING_GK 80000.0f
|
||||
/* damping constant */
|
||||
#define VEHICLE_SPRING_C 800.0f
|
||||
#define VEHICLE_SPRING_GC 100.0f
|
||||
#define VEHICLE_FRICTION_S 1000.0f
|
||||
#define VEHICLE_FRICTION_K 100.0f
|
||||
|
||||
/* TODO: shock springs, that are more loose, which are used to simulate the wheels */
|
||||
/* initial, ideal corner positions */
|
||||
static const Vec3 vbpi[8] = {
|
||||
[0] = { 0, 0, 0 },
|
||||
[1] = { VEHICLE_LENGTH, 0, 0 },
|
||||
[2] = { VEHICLE_LENGTH, 0, VEHICLE_WIDTH },
|
||||
[3] = { 0, 0, VEHICLE_WIDTH },
|
||||
[4] = { 0, VEHICLE_HEIGHT, 0 },
|
||||
[5] = { VEHICLE_LENGTH, VEHICLE_HEIGHT, 0 },
|
||||
[6] = { VEHICLE_LENGTH, VEHICLE_HEIGHT, VEHICLE_WIDTH },
|
||||
[7] = { 0, VEHICLE_HEIGHT, VEHICLE_WIDTH },
|
||||
};
|
||||
/* corner positions in simulation */
|
||||
static Vec3 vbp[8] = { vbpi[0], vbpi[1], vbpi[2], vbpi[3],
|
||||
vbpi[4], vbpi[5], vbpi[6], vbpi[7], };
|
||||
/* corner velocities */
|
||||
static Vec3 vbv[8];
|
||||
/* springs */
|
||||
static uint8_t vbs[28][2] = {
|
||||
{0, 1}, {4, 5}, {0, 4},
|
||||
{1, 2}, {5, 6}, {1, 5},
|
||||
{2, 3}, {6, 7}, {2, 6},
|
||||
{3, 0}, {7, 4}, {3, 7},
|
||||
|
||||
{0, 2}, {0, 5}, {0, 7},
|
||||
{1, 3}, {1, 6}, {1, 4},
|
||||
{4, 6}, {2, 7}, {2, 5},
|
||||
{5, 7}, {3, 4}, {3, 6},
|
||||
|
||||
{0, 6}, {1, 7}, {2, 4}, {3, 5},
|
||||
};
|
||||
|
||||
|
||||
static float height_at(SceneIngame *scn, Vec2 position);
|
||||
static Vec3 normal_at(SceneIngame *scn, Vec2 position);
|
||||
|
||||
|
||||
static void draw_vehicle(SceneIngame *scn) {
|
||||
for (size_t i = 0; i < 12; ++i)
|
||||
draw_line_3d(vbp[vbs[i][0]], vbp[vbs[i][1]], 1, (Color){255, 255, 255, 255});
|
||||
for (size_t i = 12; i < 24; ++i)
|
||||
draw_line_3d(vbp[vbs[i][0]], vbp[vbs[i][1]], 1, (Color){200, 200, 200, 255});
|
||||
for (size_t i = 24; i < 28; ++i)
|
||||
draw_line_3d(vbp[vbs[i][0]], vbp[vbs[i][1]], 1, (Color){255, 125, 125, 255});
|
||||
}
|
||||
|
||||
|
||||
static void process_vehicle(SceneIngame *scn) {
|
||||
/* apply gravity */
|
||||
Vec3 Facc[8] = {0};
|
||||
|
||||
Vec3 const Fg = { .y = -VEHICLE_MASS * G_CONST };
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
Facc[i] = vec3_add(Facc[i], Fg);
|
||||
|
||||
/* apply springs */
|
||||
for (size_t i = 0; i < 28; ++i) {
|
||||
Vec3 const p0 = vbp[vbs[i][0]];
|
||||
Vec3 const p1 = vbp[vbs[i][1]];
|
||||
Vec3 const v0 = vbv[vbs[i][0]];
|
||||
Vec3 const v1 = vbv[vbs[i][1]];
|
||||
Vec3 const pd = vec3_sub(p1, p0);
|
||||
Vec3 const pn = vec3_norm(pd);
|
||||
/* TODO: length at rest could be precalculated */
|
||||
float const lar = vec3_length(vec3_sub(vbpi[vbs[i][1]], vbpi[vbs[i][0]]));
|
||||
float const x = vec3_length(pd) - lar;
|
||||
float const v = vec3_dot(vec3_sub(v1, v0), pn);
|
||||
Vec3 const Fs = vec3_scale(pn, -VEHICLE_SPRING_K * x - VEHICLE_SPRING_C * v);
|
||||
Facc[vbs[i][0]] = vec3_sub(Facc[vbs[i][0]], Fs);
|
||||
Facc[vbs[i][1]] = vec3_add(Facc[vbs[i][1]], Fs);
|
||||
}
|
||||
|
||||
/* spring and friction against the ground */
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
Vec3 const p = vbp[i];
|
||||
Vec3 const v = vbv[i];
|
||||
float const h = height_at(scn, (Vec2){ p.x, p.z });
|
||||
if (h >= p.y) {
|
||||
/* wheel processing */
|
||||
if (i == 0 || i == 3 || i == 4 || i == 7) {
|
||||
if (scn->camera_mode == 2 && input_action_pressed("player_forward")) {
|
||||
Vec3 dir = i == 0 ? vec3_sub(vbp[1], vbp[0]) : vec3_sub(vbp[2], vbp[3]);
|
||||
Facc[i] = vec3_add(Facc[i], vec3_scale(vec3_norm(dir), 6500));
|
||||
}
|
||||
}
|
||||
|
||||
/* normal force, for displacement */
|
||||
Vec3 const n = normal_at(scn, (Vec2){ p.x, p.z });
|
||||
float const xn = (h - p.y) * n.y;
|
||||
float const vn = vec3_dot(v, n);
|
||||
Vec3 const Fn = vec3_scale(n, -VEHICLE_SPRING_GK * xn - VEHICLE_SPRING_GC * vn);
|
||||
Facc[i] = vec3_sub(Facc[i], Fn);
|
||||
|
||||
/* friction force, perpendicular to normal force */
|
||||
/* TODO: is it right? aren't vn+vol should be = |v| */
|
||||
Vec3 const von = vec3_norm(vec3_cross(n, vec3_cross(v, n)));
|
||||
draw_line_3d(vbp[i], vec3_add(vbp[i], von), 1, (Color){255,255,0,255});
|
||||
Vec3 const vo = vec3_scale(vec3_scale(von, vec3_length(v) - vn), -1);
|
||||
float const vol = vec3_length(vo);
|
||||
Vec3 const Fon = vec3_norm(vec3_cross(n, vec3_cross(Facc[i], n)));
|
||||
Vec3 Fo = vec3_scale(vec3_scale(Fon, vec3_length(Facc[i]) - vec3_dot(Facc[i], n)), -1);
|
||||
draw_line_3d(vbp[i], vec3_add(vbp[i], Fon), 1, (Color){255,255,0,255});
|
||||
/* portion of total force along the surface */
|
||||
float const Fol = vec3_length(Fo);
|
||||
float const fkxn = VEHICLE_FRICTION_K * xn;
|
||||
/* at rest, might want to start moving */
|
||||
if (fabsf(0.0f - vol) <= 0.0001f) {
|
||||
/* cannot overcome static friction, force along the surface is zeroed */
|
||||
if (Fol <= VEHICLE_FRICTION_S * xn) {log_info("test 0"); Fo = vec3_scale(Fo, -1);}
|
||||
/* resist the force by friction, while starting to move */
|
||||
else {log_info("test 1"); Fo = vec3_sub(Fo, vec3_scale(von, fkxn));}
|
||||
/* not at rest, stop accelerating along the surface */
|
||||
} else if (vol + (Fol / VEHICLE_MASS) * ctx.frame_duration <= fkxn * ctx.frame_duration * 2) {
|
||||
/* ugh ... */
|
||||
vbv[i] = vec3_add(vbv[i], vo);
|
||||
log_info("test 2");
|
||||
/* just apply friction */
|
||||
} else {
|
||||
Fo = vec3_scale(von, -fkxn * 400);
|
||||
}
|
||||
Facc[i] = vec3_add(Facc[i], Fo);
|
||||
// Facc[i] = vec3_sub(Fo, Fn);
|
||||
// Facc[i] = vec3_sub(Fo, Fn);
|
||||
Vec3 vd = vec3_scale(vec3_scale(Facc[i], (1.0f / VEHICLE_MASS)), ctx.frame_duration);
|
||||
vbv[i] = vec3_add(vbv[i], vd);
|
||||
vbp[i] = vec3_add(vbp[i], vec3_scale(vbv[i], ctx.frame_duration));
|
||||
} else {
|
||||
Vec3 vd = vec3_scale(vec3_scale(Facc[i], (1.0f / VEHICLE_MASS)), ctx.frame_duration);
|
||||
vbv[i] = vec3_add(vbv[i], vd);
|
||||
vbp[i] = vec3_add(vbp[i], vec3_scale(vbv[i], ctx.frame_duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process_vehicle_mode(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
Vec3 const top_center = vec3_sub(vbp[4], vec3_scale(vec3_sub(vbp[4], vbp[6]), 1.0f / 2.0f));
|
||||
// Vec3 const front_center = vec3_add(vbp[4], vec3_scale(vec3_sub(vbp[4], vbp[7]), 1.0f / 2.0f));
|
||||
// Vec3 const facing_direction = vec3_sub(top_center, front_center);
|
||||
|
||||
float yawc, yaws, pitchc, pitchs;
|
||||
sincosf(scn->yaw + (float)M_PI_2, &yaws, &yawc);
|
||||
sincosf(scn->pitch, &pitchs, &pitchc);
|
||||
|
||||
Vec3 const looking_direction = vec3_norm(((Vec3){
|
||||
yawc * pitchc,
|
||||
pitchs,
|
||||
yaws * pitchc,
|
||||
}));
|
||||
|
||||
Vec3 const orbit = vec3_sub(top_center, vec3_scale(looking_direction, 7.5));
|
||||
|
||||
draw_camera(orbit, looking_direction, (Vec3){0,1,0}, (float)M_PI_2 * 0.8f, 1, TERRAIN_RADIUS * sqrtf(3));
|
||||
|
||||
scn->looking_direction = looking_direction;
|
||||
|
||||
scn->pos = top_center;
|
||||
}
|
||||
|
||||
|
||||
static void process_fly_mode(State *state) {
|
||||
SceneIngame *scn = (SceneIngame *)state->scene;
|
||||
|
||||
@ -32,7 +237,7 @@ static void process_fly_mode(State *state) {
|
||||
scn->looking_direction = dir_and_up.direction;
|
||||
|
||||
const Vec3 right = m_vec_norm(m_vec_cross(dir_and_up.direction, dir_and_up.up));
|
||||
const float speed = 0.04f; /* TODO: put this in a better place */
|
||||
const float speed = 0.1f; /* TODO: put this in a better place */
|
||||
if (input_action_pressed("player_left"))
|
||||
scn->pos = vec3_sub(scn->pos, m_vec_scale(right, speed));
|
||||
|
||||
@ -52,33 +257,39 @@ static void process_fly_mode(State *state) {
|
||||
scn->pos.y -= speed;
|
||||
}
|
||||
|
||||
/* TODO: could be baked in map format */
|
||||
static Vec3 normal_at(SceneIngame *scn, Vec2 position) {
|
||||
int const x = (int)(floorf(position.x - scn->world_center.x));
|
||||
int const y = (int)(floorf(position.y - scn->world_center.y));
|
||||
|
||||
float const height0 = heightmap[x][y];
|
||||
float const height1 = heightmap[x + 1][y];
|
||||
float const height2 = heightmap[x][y + 1];
|
||||
|
||||
Vec3 const a = { .x = -1, .y = height0 - height1, .z = 0 };
|
||||
Vec3 const b = { .x = 0, .y = height0 - height2, .z = -1 };
|
||||
|
||||
return vec3_scale(vec3_norm(vec3_cross(a, b)), -1);
|
||||
}
|
||||
|
||||
/* TODO: don't operate on triangles, instead interpolate on quads */
|
||||
static float height_at(SceneIngame *scn, Vec2 position) {
|
||||
float height0, height1, height2, weight0, weight1, weight2;
|
||||
int const x = (int)(floorf(position.x - scn->world_center.x));
|
||||
int const y = (int)(floorf(position.y - scn->world_center.y));
|
||||
|
||||
int const x = (int)(HALF_TERRAIN_DISTANCE + (position.x - scn->pos.x));
|
||||
int const y = (int)(HALF_TERRAIN_DISTANCE + (position.y - scn->pos.z));
|
||||
|
||||
height0 = heightmap[x][y];
|
||||
height1 = heightmap[x + 1][y + 1];
|
||||
float const height0 = heightmap[x][y];
|
||||
float const height1 = heightmap[x + 1][y];
|
||||
float const height2 = heightmap[x][y + 1];
|
||||
float const height3 = heightmap[x + 1][y + 1];
|
||||
|
||||
Vec2 incell = { position.x - floorf(position.x), position.y - floorf(position.y) };
|
||||
|
||||
/* who needs barycentric coordinates, am i right? */
|
||||
weight0 = 1 / sqrtf(powf(incell.x, 2) + powf(incell.y, 2));
|
||||
weight1 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(1 - incell.y, 2));
|
||||
float const weight0 = (1 - incell.x) * (1 - incell.y);
|
||||
float const weight1 = ( incell.x) * (1 - incell.y);
|
||||
float const weight2 = (1 - incell.x) * ( incell.y);
|
||||
float const weight3 = ( incell.x) * ( incell.y);
|
||||
|
||||
/* find which triangle we're directly under */
|
||||
/* for this manhattan distance is sufficient */
|
||||
if (incell.x + (1 - incell.y) < (1 - incell.x) + incell.y) {
|
||||
height2 = heightmap[x][y + 1];
|
||||
weight2 = 1 / sqrtf(powf(incell.x, 2) + powf(1 - incell.y, 2));
|
||||
} else {
|
||||
height2 = heightmap[x + 1][y];
|
||||
weight2 = 1 / sqrtf(powf(1 - incell.x, 2) + powf(incell.y, 2));
|
||||
}
|
||||
|
||||
return (height0 * weight0 + height1 * weight1 + height2 * weight2) / (weight0 + weight1 + weight2);
|
||||
return (height0 * weight0 + height1 * weight1 + height2 * weight2 + height3 * weight3) / (weight0 + weight1 + weight2 + weight3);
|
||||
}
|
||||
|
||||
|
||||
@ -147,6 +358,8 @@ static void generate_terrain(SceneIngame *scn) {
|
||||
heightmap[lx][ly] = height;
|
||||
}
|
||||
}
|
||||
|
||||
scn->world_center = (Vec2){ floorf(scn->pos.x - HALF_TERRAIN_DISTANCE), floorf(scn->pos.z - HALF_TERRAIN_DISTANCE) };
|
||||
}
|
||||
|
||||
|
||||
@ -264,12 +477,14 @@ static void ingame_tick(State *state) {
|
||||
}
|
||||
|
||||
if (input_action_just_pressed("toggle_camera_mode"))
|
||||
scn->flying_camera = !scn->flying_camera;
|
||||
scn->camera_mode = scn->camera_mode == 2 ? 0 : scn->camera_mode + 1;
|
||||
|
||||
if (scn->flying_camera) {
|
||||
if (scn->camera_mode == 1) {
|
||||
process_fly_mode(state);
|
||||
} else {
|
||||
} else if (scn->camera_mode == 0) {
|
||||
process_ground_mode(state);
|
||||
} else if (scn->camera_mode) {
|
||||
process_vehicle_mode(state);
|
||||
}
|
||||
|
||||
/* toggle mouse capture with end key */
|
||||
@ -279,7 +494,10 @@ static void ingame_tick(State *state) {
|
||||
ctx.mouse_capture = scn->mouse_captured;
|
||||
|
||||
generate_terrain(scn);
|
||||
process_vehicle(scn);
|
||||
|
||||
draw_terrain(scn);
|
||||
draw_vehicle(scn);
|
||||
|
||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
||||
|
||||
|
@ -13,6 +13,7 @@ typedef struct SceneIngame {
|
||||
Scene base;
|
||||
|
||||
Vec3 looking_direction;
|
||||
Vec2 world_center;
|
||||
|
||||
Vec3 pos;
|
||||
float yaw;
|
||||
@ -20,7 +21,7 @@ typedef struct SceneIngame {
|
||||
float roll;
|
||||
|
||||
bool mouse_captured;
|
||||
bool flying_camera;
|
||||
int camera_mode;
|
||||
} SceneIngame;
|
||||
|
||||
|
||||
|
@ -47,6 +47,13 @@ TWN_API void draw_line(Vec2 start,
|
||||
float thickness, /* optional, default: 1 */
|
||||
Color color); /* optional, default: all 255 */
|
||||
|
||||
/* intended for debugging and spatial reference */
|
||||
TWN_API void draw_line_3d(Vec3 start,
|
||||
Vec3 finish,
|
||||
float thickness, /* optional, default: 1 */
|
||||
Color color); /* optional, default: all 255 */
|
||||
|
||||
|
||||
/* TODO: combine with draw_rectangle()? */
|
||||
TWN_API void draw_box(Rect rect,
|
||||
float thickness, /* optional, default: 1 */
|
||||
|
@ -144,6 +144,18 @@
|
||||
]
|
||||
},
|
||||
|
||||
"draw_line_3d": {
|
||||
"module": "draw",
|
||||
"symbol": "line_3d",
|
||||
"header": "twn_draw.h",
|
||||
"params": [
|
||||
{ "name": "start", "type": "Vec3" },
|
||||
{ "name": "finish", "type": "Vec3" },
|
||||
{ "name": "thickness", "type": "float", "default": 1 },
|
||||
{ "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }
|
||||
]
|
||||
},
|
||||
|
||||
"draw_triangle": {
|
||||
"module": "draw",
|
||||
"symbol": "triangle",
|
||||
|
@ -56,6 +56,9 @@ void render_clear(void) {
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.quad_batches); ++i)
|
||||
arrsetlen(ctx.quad_batches[i].value.primitives, 0);
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.line_batches); ++i)
|
||||
arrsetlen(ctx.line_batches[i].value.vertices, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -328,7 +331,8 @@ static void render_2d(void) {
|
||||
render_circle(&invocation.primitive->circle);
|
||||
break;
|
||||
case PRIMITIVE_2D_LINES:
|
||||
render_lines(&invocation.primitive->line);
|
||||
render_lines(&invocation.primitive->line, false);
|
||||
stbds_arrfreef(invocation.primitive->line.vertices);
|
||||
break;
|
||||
case PRIMITIVE_2D_TEXT:
|
||||
default:
|
||||
@ -360,7 +364,7 @@ static void render_2d(void) {
|
||||
render_text(&invocation.primitive->text);
|
||||
break;
|
||||
case PRIMITIVE_2D_LINES:
|
||||
render_lines(&invocation.primitive->line);
|
||||
render_lines(&invocation.primitive->line, false);
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
@ -387,6 +391,9 @@ static void render_space(void) {
|
||||
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i)
|
||||
finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key);
|
||||
|
||||
for (size_t i = 0; i < hmlenu(ctx.line_batches); ++i)
|
||||
render_lines(&ctx.line_batches[i].value, true);
|
||||
|
||||
render_skybox(); /* after everything else, as to use depth buffer for early z rejection */
|
||||
}
|
||||
|
||||
|
@ -322,7 +322,7 @@ IndexBuffer get_circle_element_buffer(void);
|
||||
|
||||
void render_circle(const CirclePrimitive *circle);
|
||||
|
||||
void render_lines(LinePrimitive *line);
|
||||
void render_lines(const LinePrimitive *line, bool is_3d);
|
||||
|
||||
void render_rectangle(const RectPrimitive *rectangle);
|
||||
|
||||
|
@ -14,6 +14,11 @@ void draw_line(Vec2 start,
|
||||
if (fabsf(1.0f - thickness) >= 0.00001f)
|
||||
log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
|
||||
|
||||
if (thickness < 1.0f) {
|
||||
log_warn("Invalid thickness given.");
|
||||
return;
|
||||
}
|
||||
|
||||
struct LineVertex const v0 = { .position = (Vec3){start.x, start.y, 0}, .color = color };
|
||||
struct LineVertex const v1 = { .position = (Vec3){finish.x, finish.y, 0}, .color = color };
|
||||
|
||||
@ -46,7 +51,36 @@ void draw_line(Vec2 start,
|
||||
}
|
||||
|
||||
|
||||
void render_lines(LinePrimitive *line) {
|
||||
void draw_line_3d(Vec3 start,
|
||||
Vec3 finish,
|
||||
float thickness,
|
||||
Color color)
|
||||
{
|
||||
if (fabsf(1.0f - thickness) >= 0.00001f)
|
||||
log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
|
||||
|
||||
if (thickness < 1.0f) {
|
||||
log_warn("Invalid thickness given.");
|
||||
return;
|
||||
}
|
||||
|
||||
struct LineVertex const v0 = { .position = start, .color = color };
|
||||
struct LineVertex const v1 = { .position = finish, .color = color };
|
||||
|
||||
/* 3d case, unordered depth based draw */
|
||||
struct LineBatchItemKey const key = { .color = color, .thickness = (uint8_t)(floorf(thickness)) };
|
||||
struct LineBatchItem *batch_p = hmgetp_null(ctx.line_batches, key);
|
||||
if (!batch_p) {
|
||||
hmput(ctx.line_batches, key, ((struct LinePrimitive){.thickness = thickness, .color = color}));
|
||||
batch_p = &ctx.line_batches[hmlenu(ctx.line_batches) - 1];
|
||||
}
|
||||
arrput(batch_p->value.vertices, v0);
|
||||
arrput(batch_p->value.vertices, v1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void render_lines(LinePrimitive const *line, bool is_3d) {
|
||||
DeferredCommandDraw command = {0};
|
||||
|
||||
VertexBuffer buffer = get_scratch_vertex_array();
|
||||
@ -68,10 +102,12 @@ void render_lines(LinePrimitive *line) {
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
command.primitive_count = arrlenu(line->vertices);
|
||||
command.primitive_count = (uint32_t)arrlenu(line->vertices);
|
||||
|
||||
command.geometry_mode = DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES;
|
||||
command.pipeline = PIPELINE_2D;
|
||||
command.pipeline = is_3d ? PIPELINE_SPACE : PIPELINE_2D;
|
||||
|
||||
command.texture_mode = line->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY;
|
||||
|
||||
command.depth_range_high = depth_range_high;
|
||||
command.depth_range_low = depth_range_low;
|
||||
@ -81,7 +117,5 @@ void render_lines(LinePrimitive *line) {
|
||||
.draw = command
|
||||
};
|
||||
|
||||
/* TODO: should it be deleted here? */
|
||||
arrfree(line->vertices);
|
||||
arrpush(deferred_commands, final_command);
|
||||
}
|
||||
|
@ -50,6 +50,13 @@ typedef struct EngineContext {
|
||||
MeshBatchItem *uncolored_mesh_batches;
|
||||
MeshBatchItem *billboard_batches;
|
||||
MeshBatchItem *quad_batches;
|
||||
struct LineBatchItem {
|
||||
struct LineBatchItemKey {
|
||||
Color color;
|
||||
uint8_t thickness;
|
||||
} key;
|
||||
struct LinePrimitive value;
|
||||
} *line_batches;
|
||||
TextCache text_cache;
|
||||
TextureCache texture_cache;
|
||||
|
||||
|
@ -255,17 +255,17 @@ static void upload_texture_from_surface(GPUTexture texture, SDL_Surface *surface
|
||||
}
|
||||
|
||||
|
||||
static void recreate_current_atlas_texture(TextureCache *cache) {
|
||||
static void recreate_current_atlas_texture(TextureCache *cache, int atlas_index) {
|
||||
profile_start("atlas recreation");
|
||||
|
||||
/* TODO: should surfaces be freed after they cannot be referenced in atlas builing? */
|
||||
/* for example, if full page of 64x64 tiles was already filled, there's no real reason to process them further */
|
||||
SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
|
||||
SDL_Surface *atlas_surface = cache->atlas_surfaces[atlas_index];
|
||||
|
||||
/* blit the texture surfaces onto the atlas */
|
||||
for (size_t i = 0; i < shlenu(cache->hash); ++i) {
|
||||
/* skip all that aren't part of currently built one */
|
||||
if (cache->hash[i].value.atlas_index != cache->atlas_index)
|
||||
if (cache->hash[i].value.atlas_index != atlas_index)
|
||||
continue;
|
||||
|
||||
/* skip loners */
|
||||
@ -284,7 +284,7 @@ static void recreate_current_atlas_texture(TextureCache *cache) {
|
||||
}
|
||||
|
||||
/* texturize it! */
|
||||
upload_texture_from_surface(cache->atlas_textures[cache->atlas_index], atlas_surface);
|
||||
upload_texture_from_surface(cache->atlas_textures[atlas_index], atlas_surface);
|
||||
|
||||
profile_end("atlas recreation");
|
||||
}
|
||||
@ -337,16 +337,18 @@ static stbrp_rect *filter_unpacked_rects(stbrp_rect *rects) {
|
||||
|
||||
/* updates the original rects array with the data from packed_rects */
|
||||
/* returns true if all rects were packed successfully */
|
||||
static bool update_rects(TextureCache *cache, stbrp_rect *rects, stbrp_rect *packed_rects) {
|
||||
static bool update_rects(TextureCache *cache, stbrp_rect *rects, stbrp_rect *packed_rects, int atlas_index) {
|
||||
/* !!! do not grow either of the arrays !!! */
|
||||
/* the reallocation will try to reassign the array pointer, to no effect. */
|
||||
/* see stb_ds.h */
|
||||
bool packed_all = true;
|
||||
|
||||
|
||||
for (size_t i = 0; i < arrlenu(packed_rects); ++i) {
|
||||
/* we can check if any rects failed to be packed right here */
|
||||
/* it's not ideal, but it avoids another iteration */
|
||||
if (!packed_rects[i].was_packed) {
|
||||
/* mark it as potentially filled in next atlas page */
|
||||
cache->hash[packed_rects[i].id].value.atlas_index = atlas_index + 1;
|
||||
packed_all = false;
|
||||
continue;
|
||||
}
|
||||
@ -355,7 +357,7 @@ static bool update_rects(TextureCache *cache, stbrp_rect *rects, stbrp_rect *pac
|
||||
/* while the order of the elements in the hash map is unknown to us, */
|
||||
/* their equivalents in `rects` are in that same (unknown) order, which means */
|
||||
/* we can use the index we had saved to find the original texture struct */
|
||||
cache->hash[packed_rects[i].id].value.atlas_index = cache->atlas_index;
|
||||
cache->hash[packed_rects[i].id].value.atlas_index = atlas_index;
|
||||
}
|
||||
|
||||
return packed_all;
|
||||
@ -566,9 +568,6 @@ void textures_update_atlas(TextureCache *cache) {
|
||||
upload_texture_from_surface(response.loner_texture, response.data);
|
||||
response.srcrect = (Rect) { .w = (float)response.data->w, .h = (float)response.data->h };
|
||||
|
||||
} else {
|
||||
/* will be fully populated as the atlas updates */
|
||||
response.atlas_index = cache->atlas_index;
|
||||
}
|
||||
|
||||
cache->hash[texture_load_queue[i].index].value = response;
|
||||
@ -576,40 +575,42 @@ void textures_update_atlas(TextureCache *cache) {
|
||||
|
||||
arrsetlen(texture_load_queue, 0);
|
||||
|
||||
/* this function makes a lot more sense if you read stb_rect_pack.h */
|
||||
stbrp_context pack_ctx; /* target info */
|
||||
stbrp_init_target(&pack_ctx,
|
||||
(int)ctx.texture_atlas_size,
|
||||
(int)ctx.texture_atlas_size,
|
||||
cache->node_buffer,
|
||||
(int)ctx.texture_atlas_size);
|
||||
|
||||
stbrp_rect *rects = create_rects_from_cache(cache);
|
||||
int current_atlas_index = 0;
|
||||
|
||||
/* we have to keep packing, and creating atlases if necessary, */
|
||||
/* until all rects have been packed. */
|
||||
/* ideally, this will not iterate more than once. */
|
||||
bool textures_remaining = true;
|
||||
while (textures_remaining) {
|
||||
/* this function makes a lot more sense if you read stb_rect_pack.h */
|
||||
stbrp_context pack_ctx; /* target info */
|
||||
stbrp_init_target(&pack_ctx,
|
||||
(int)ctx.texture_atlas_size,
|
||||
(int)ctx.texture_atlas_size,
|
||||
cache->node_buffer,
|
||||
(int)ctx.texture_atlas_size);
|
||||
|
||||
stbrp_rect *rects_to_pack = filter_unpacked_rects(rects);
|
||||
stbrp_pack_rects(&pack_ctx, rects_to_pack, (int)arrlen(rects_to_pack));
|
||||
|
||||
textures_remaining = !update_rects(cache, rects, rects_to_pack);
|
||||
textures_remaining = !update_rects(cache, rects, rects_to_pack, current_atlas_index);
|
||||
arrfree(rects_to_pack); /* got what we needed */
|
||||
|
||||
/* some textures couldn't be packed */
|
||||
if (textures_remaining) {
|
||||
update_texture_rects_in_atlas(cache, rects);
|
||||
recreate_current_atlas_texture(cache);
|
||||
recreate_current_atlas_texture(cache, current_atlas_index);
|
||||
|
||||
/* need a new atlas for next time */
|
||||
add_new_atlas(cache);
|
||||
++cache->atlas_index;
|
||||
if (current_atlas_index >= cache->atlas_index) ++cache->atlas_index;
|
||||
++current_atlas_index;
|
||||
}
|
||||
}
|
||||
|
||||
update_texture_rects_in_atlas(cache, rects);
|
||||
recreate_current_atlas_texture(cache);
|
||||
recreate_current_atlas_texture(cache, current_atlas_index);
|
||||
|
||||
cache->is_dirty = false;
|
||||
|
||||
|
@ -181,13 +181,10 @@ bool file_exists(const char *path) {
|
||||
void textures_dump_atlases(void) {
|
||||
PHYSFS_mkdir("/dump");
|
||||
|
||||
/* TODO: png instead of bmp */
|
||||
const char string_template[] = "/dump/atlas%zd.bmp";
|
||||
|
||||
size_t i = 0;
|
||||
for (; i < arrlenu(ctx.texture_cache.atlas_surfaces); ++i) {
|
||||
char *buf = NULL;
|
||||
SDL_asprintf(&buf, string_template, i);
|
||||
SDL_asprintf(&buf, "/dump/atlas%zd.bmp", (ssize_t)i);
|
||||
|
||||
SDL_RWops *handle = PHYSFSRWOPS_openWrite(buf);
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
#include "twn_util.h"
|
||||
#include "twn_workers_c.h"
|
||||
#include "rendering/twn_draw_c.h"
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
SDL_sem *workers_job_semaphore;
|
||||
static SDL_sem *workers_job_semaphore;
|
||||
|
||||
static SDL_mutex *workers_mutex;
|
||||
static SDL_sem *workers_exit_semaphore; /* should come to count of `workers_pool_size` */
|
||||
|
12
third-party/stb/stb_ds.h
vendored
12
third-party/stb/stb_ds.h
vendored
@ -1079,8 +1079,8 @@ static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed)
|
||||
} while (0)
|
||||
|
||||
for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) {
|
||||
data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
|
||||
data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4
|
||||
data = d[0] | (d[1] << 8) | (d[2] << 16) | ((size_t) d[3] << 24);
|
||||
data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | ((size_t) d[7] << 24)) << 16 << 16; // discarded if size_t == 4
|
||||
|
||||
v3 ^= data;
|
||||
for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j)
|
||||
@ -1092,10 +1092,10 @@ static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed)
|
||||
case 7: data |= ((size_t) d[6] << 24) << 24; // fall through
|
||||
case 6: data |= ((size_t) d[5] << 20) << 20; // fall through
|
||||
case 5: data |= ((size_t) d[4] << 16) << 16; // fall through
|
||||
case 4: data |= (d[3] << 24); // fall through
|
||||
case 3: data |= (d[2] << 16); // fall through
|
||||
case 2: data |= (d[1] << 8); // fall through
|
||||
case 1: data |= d[0]; // fall through
|
||||
case 4: data |= ((size_t) d[3] << 24); // fall through
|
||||
case 3: data |= ((size_t) d[2] << 16); // fall through
|
||||
case 2: data |= ((size_t) d[1] << 8); // fall through
|
||||
case 1: data |= (size_t) d[0]; // fall through
|
||||
case 0: break;
|
||||
}
|
||||
v3 ^= data;
|
||||
|
Loading…
Reference in New Issue
Block a user