billboards!
This commit is contained in:
parent
7c0bf39f12
commit
3bfa86066e
@ -106,10 +106,12 @@ set(TWN_NONOPT_SOURCE_FILES
|
|||||||
src/twn_textures.c src/twn_textures_c.h
|
src/twn_textures.c src/twn_textures_c.h
|
||||||
|
|
||||||
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
||||||
|
src/rendering/twn_quads.c
|
||||||
src/rendering/twn_sprites.c
|
src/rendering/twn_sprites.c
|
||||||
src/rendering/twn_rects.c
|
src/rendering/twn_rects.c
|
||||||
src/rendering/twn_text.c
|
src/rendering/twn_text.c
|
||||||
src/rendering/twn_triangles.c
|
src/rendering/twn_triangles.c
|
||||||
|
src/rendering/twn_billboards.c
|
||||||
src/rendering/twn_circles.c
|
src/rendering/twn_circles.c
|
||||||
src/rendering/twn_skybox.c
|
src/rendering/twn_skybox.c
|
||||||
src/rendering/twn_fog.c)
|
src/rendering/twn_fog.c)
|
||||||
|
@ -158,6 +158,8 @@ static void draw_terrain(SceneIngame *scn) {
|
|||||||
(Vec2){ 128, 0 },
|
(Vec2){ 128, 0 },
|
||||||
(Vec2){ 0, 0 },
|
(Vec2){ 0, 0 },
|
||||||
(Vec2){ 0, 128 });
|
(Vec2){ 0, 128 });
|
||||||
|
|
||||||
|
draw_billboard("/assets/grasses/10.png", (Vec3){ (float)x, d1 + 0.1f, (float)y }, (Vec2){0.3f, 0.3f}, (Color){255, 255, 255, 255}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,7 +202,7 @@ static void ingame_tick(State *state) {
|
|||||||
generate_terrain(scn);
|
generate_terrain(scn);
|
||||||
draw_terrain(scn);
|
draw_terrain(scn);
|
||||||
|
|
||||||
draw_skybox("/assets/miramar/miramar_*.tga");
|
// draw_skybox("/assets/miramar/miramar_*.tga");
|
||||||
draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
|
draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
common-data/assets/grasses/10.png
(Stored with Git LFS)
Normal file
BIN
common-data/assets/grasses/10.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -54,6 +54,7 @@ TWN_API void draw_triangle(char const *texture,
|
|||||||
|
|
||||||
// TODO: decide whether it's needed to begin with?
|
// TODO: decide whether it's needed to begin with?
|
||||||
// intended usage for it is baked lighting, i would think.
|
// intended usage for it is baked lighting, i would think.
|
||||||
|
// TODO: instead add optional color parameters to 'draw_triangle'
|
||||||
/* pushes a colored textured 3d triangle onto the render queue */
|
/* pushes a colored textured 3d triangle onto the render queue */
|
||||||
// void unfurl_colored_triangle(const char *path,
|
// void unfurl_colored_triangle(const char *path,
|
||||||
// Vec3 v0,
|
// Vec3 v0,
|
||||||
@ -66,7 +67,7 @@ TWN_API void draw_triangle(char const *texture,
|
|||||||
// Color c1,
|
// Color c1,
|
||||||
// Color c2);
|
// Color c2);
|
||||||
|
|
||||||
TWN_API void draw_billboard(const char *path,
|
TWN_API void draw_billboard(const char *texture,
|
||||||
Vec3 position,
|
Vec3 position,
|
||||||
Vec2 size,
|
Vec2 size,
|
||||||
Color color, /* optional, default: all 255 */
|
Color color, /* optional, default: all 255 */
|
||||||
|
@ -9,6 +9,26 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
|
||||||
|
static inline Vec2 vec2_add(Vec2 a, Vec2 b) {
|
||||||
|
return (Vec2) { a.x + b.x, a.y + b.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_sub(Vec2 a, Vec2 b) {
|
||||||
|
return (Vec2) { a.x - b.x, a.y - b.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
|
||||||
|
return (Vec2) { a.x / b.x, a.y / b.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_mul(Vec2 a, Vec2 b) {
|
||||||
|
return (Vec2) { a.x * b.x, a.y * b.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Vec2 vec2_scale(Vec2 a, float s) {
|
||||||
|
return (Vec2) { a.x * s, a.y * s };
|
||||||
|
}
|
||||||
|
|
||||||
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
|
static inline Vec3 vec3_add(Vec3 a, Vec3 b) {
|
||||||
return (Vec3) { a.x + b.x, a.y + b.y, a.z + b.z };
|
return (Vec3) { a.x + b.x, a.y + b.y, a.z + b.z };
|
||||||
}
|
}
|
||||||
@ -17,12 +37,12 @@ static inline Vec3 vec3_sub(Vec3 a, Vec3 b) {
|
|||||||
return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
|
return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec2 vec2_div(Vec2 a, Vec2 b) {
|
static inline Vec3 vec3_div(Vec3 a, Vec3 b) {
|
||||||
return (Vec2) { a.x / b.x, a.y / b.y };
|
return (Vec3) { a.x / b.x, a.y / b.y, a.z / b.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec2 vec2_scale(Vec2 a, float s) {
|
static inline Vec3 vec3_mul(Vec3 a, Vec3 b) {
|
||||||
return (Vec2) { a.x * s, a.y * s };
|
return (Vec3) { a.x * b.x, a.y * b.y, a.z * b.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Vec3 vec3_scale(Vec3 a, float s) {
|
static inline Vec3 vec3_scale(Vec3 a, float s) {
|
||||||
@ -83,15 +103,23 @@ static inline Vec3 vec3_rotate(Vec3 v, float angle, Vec3 axis) {
|
|||||||
)(p_any_vec2))
|
)(p_any_vec2))
|
||||||
|
|
||||||
#define m_vec_add(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
#define m_vec_add(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
|
Vec2: vec2_add, \
|
||||||
Vec3: vec3_add \
|
Vec3: vec3_add \
|
||||||
)(p_any_vec0, p_any_vec1))
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
|
Vec2: vec2_sub, \
|
||||||
Vec3: vec3_sub \
|
Vec3: vec3_sub \
|
||||||
)(p_any_vec0, p_any_vec1))
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
Vec2: vec2_div \
|
Vec2: vec2_div, \
|
||||||
|
Vec3: vec3_div \
|
||||||
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
|
#define m_vec_mul(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
|
||||||
|
Vec2: vec2_mul, \
|
||||||
|
Vec3: vec3_mul \
|
||||||
)(p_any_vec0, p_any_vec1))
|
)(p_any_vec0, p_any_vec1))
|
||||||
|
|
||||||
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \
|
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \
|
||||||
|
168
src/rendering/twn_billboards.c
Normal file
168
src/rendering/twn_billboards.c
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
#include "twn_draw.h"
|
||||||
|
#include "twn_draw_c.h"
|
||||||
|
#include "twn_engine_context_c.h"
|
||||||
|
#include "twn_textures_c.h"
|
||||||
|
#include "twn_types.h"
|
||||||
|
#include "twn_vec.h"
|
||||||
|
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
|
|
||||||
|
void draw_billboard(const char *texture,
|
||||||
|
Vec3 position,
|
||||||
|
Vec2 size,
|
||||||
|
Color color,
|
||||||
|
bool cylindrical)
|
||||||
|
{
|
||||||
|
// TODO: order drawing by atlas id as well, so that texture rebinding is not as common
|
||||||
|
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, texture);
|
||||||
|
|
||||||
|
struct MeshBatchItem *batch_p = hmgetp_null(ctx.billboard_batches, texture_key);
|
||||||
|
if (!batch_p) {
|
||||||
|
struct MeshBatch item = {0};
|
||||||
|
hmput(ctx.billboard_batches, texture_key, item);
|
||||||
|
batch_p = &ctx.billboard_batches[hmlenu(ctx.billboard_batches) - 1]; /* TODO: can last index be used? */
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpaceBillboard billboard = {
|
||||||
|
.color = color,
|
||||||
|
.cylindrical = cylindrical,
|
||||||
|
.position = position,
|
||||||
|
.size = size,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpaceBillboard *billboards = (struct SpaceBillboard *)(void *)batch_p->value.primitives;
|
||||||
|
|
||||||
|
arrpush(billboards, billboard);
|
||||||
|
batch_p->value.primitives = (uint8_t *)billboards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* reused for all billboards */
|
||||||
|
static Vec3 right_plus_up;
|
||||||
|
static Vec3 right_minus_up;
|
||||||
|
static Vec3 right_plus_up_cylindrical;
|
||||||
|
static Vec3 right_minus_up_cylindrical;
|
||||||
|
|
||||||
|
/* precalculate (right + up) and (right - up) that are used with all batches */
|
||||||
|
static void calculate_intermediates(void) {
|
||||||
|
Vec3 const right = { camera_look_at_matrix.row[0].x, camera_look_at_matrix.row[1].x, camera_look_at_matrix.row[2].x };
|
||||||
|
Vec3 const up = { camera_look_at_matrix.row[0].y, camera_look_at_matrix.row[1].y, camera_look_at_matrix.row[2].y };
|
||||||
|
|
||||||
|
right_plus_up = m_vec_add(right, up);
|
||||||
|
right_minus_up = m_vec_sub(right, up);
|
||||||
|
|
||||||
|
Vec3 const up_cylindrical = { 0, 1, 0 };
|
||||||
|
|
||||||
|
right_plus_up_cylindrical = m_vec_add(right, up_cylindrical);
|
||||||
|
right_minus_up_cylindrical = m_vec_sub(right, up_cylindrical);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2 */
|
||||||
|
void finally_draw_billboard_batch(struct MeshBatch const *batch,
|
||||||
|
TextureKey texture_key)
|
||||||
|
{
|
||||||
|
const size_t primitives_len = arrlenu(batch->primitives);
|
||||||
|
|
||||||
|
/* nothing to do */
|
||||||
|
if (primitives_len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* TODO: only do it once per frame */
|
||||||
|
calculate_intermediates();
|
||||||
|
|
||||||
|
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
||||||
|
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
||||||
|
|
||||||
|
const float wr = srcrect.w / dims.w;
|
||||||
|
const float hr = srcrect.h / dims.h;
|
||||||
|
const float xr = srcrect.x / dims.w;
|
||||||
|
const float yr = srcrect.y / dims.h;
|
||||||
|
|
||||||
|
const Vec2 uv0 = { xr + 1 * wr, yr + 0 * hr };
|
||||||
|
const Vec2 uv1 = { xr + 1 * wr, yr + 1 * hr };
|
||||||
|
const Vec2 uv2 = { xr + 0 * wr, yr + 1 * hr };
|
||||||
|
const Vec2 uv3 = { xr + 0 * wr, yr + 0 * hr };
|
||||||
|
|
||||||
|
/* emit vertex data */
|
||||||
|
VertexBuffer const buffer = get_scratch_vertex_array();
|
||||||
|
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (ElementIndexedBillboard) * primitives_len);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < primitives_len; ++i) {
|
||||||
|
struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[i];
|
||||||
|
|
||||||
|
/* a = (right + up) * size, b = (right - up) * size*/
|
||||||
|
Vec3 a, b;
|
||||||
|
if (billboard.cylindrical) {
|
||||||
|
a = vec3_mul(right_plus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x}));
|
||||||
|
b = vec3_mul(right_minus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x}));
|
||||||
|
} else {
|
||||||
|
a = vec3_mul(right_plus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x}));
|
||||||
|
b = vec3_mul(right_minus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x}));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ElementIndexedBillboard const payload = {
|
||||||
|
/* TODO: use the flat shading to not set two of the colors */
|
||||||
|
.c0 = billboard.color,
|
||||||
|
.c1 = billboard.color,
|
||||||
|
.c2 = billboard.color,
|
||||||
|
.c3 = billboard.color,
|
||||||
|
|
||||||
|
.uv0 = uv0,
|
||||||
|
.uv1 = uv1,
|
||||||
|
.uv2 = uv2,
|
||||||
|
.uv3 = uv3,
|
||||||
|
|
||||||
|
.v0 = vec3_sub(billboard.position, b),
|
||||||
|
.v1 = vec3_sub(billboard.position, a),
|
||||||
|
.v2 = vec3_add(billboard.position, b),
|
||||||
|
.v3 = vec3_add(billboard.position, a),
|
||||||
|
};
|
||||||
|
|
||||||
|
push_to_vertex_buffer_builder(&builder, &payload, sizeof (payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* commit to drawing */
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 3,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||||
|
.offset = offsetof(ElementIndexedBillboard, v0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||||
|
.offset = offsetof(ElementIndexedBillboard, uv0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.colors = (AttributeArrayPointer) {
|
||||||
|
.arity = 4,
|
||||||
|
.type = GL_UNSIGNED_BYTE,
|
||||||
|
.stride = offsetof(ElementIndexedBillboard, v1),
|
||||||
|
.offset = offsetof(ElementIndexedBillboard, c0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.textured = true;
|
||||||
|
command.texture_key = texture_key;
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (GLsizei)primitives_len;
|
||||||
|
command.range_end = 6 * (GLsizei)primitives_len;
|
||||||
|
|
||||||
|
use_texture_mode(textures_get_mode(&ctx.texture_cache, texture_key));
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
#include "twn_draw.h"
|
#include "twn_draw.h"
|
||||||
|
#include "twn_util_c.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
@ -66,3 +67,57 @@ void create_circle_geometry(Vec2 position,
|
|||||||
vertices[num_vertices - 1].y += position.y;
|
vertices[num_vertices - 1].y += position.y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void render_circle(const CirclePrimitive *circle) {
|
||||||
|
static Vec2 vertices[CIRCLE_VERTICES_MAX];
|
||||||
|
static int prev_num_vertices = 0;
|
||||||
|
static Vec2 prev_position = {0};
|
||||||
|
|
||||||
|
int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX);
|
||||||
|
|
||||||
|
if (prev_num_vertices != num_vertices) {
|
||||||
|
create_circle_geometry(circle->position,
|
||||||
|
circle->radius,
|
||||||
|
num_vertices,
|
||||||
|
vertices);
|
||||||
|
prev_num_vertices = num_vertices;
|
||||||
|
prev_position = circle->position;
|
||||||
|
} else {
|
||||||
|
/* reuse the data, but offset it by difference with previously generated position */
|
||||||
|
/* no evil cos sin ops this way, if radius is shared in sequential calls */
|
||||||
|
Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y };
|
||||||
|
for (int i = 0; i < num_vertices; ++i)
|
||||||
|
vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y };
|
||||||
|
prev_position = circle->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexBuffer buffer = get_scratch_vertex_array();
|
||||||
|
specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
|
||||||
|
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = sizeof (Vec2),
|
||||||
|
.offset = 0,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = circle->color;
|
||||||
|
|
||||||
|
command.element_buffer = get_circle_element_buffer();
|
||||||
|
command.element_count = (num_vertices - 2) * 3;
|
||||||
|
command.range_end = (num_vertices - 2) * 3;
|
||||||
|
|
||||||
|
use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY);
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
}
|
||||||
|
@ -29,8 +29,12 @@ void render_queue_clear(void) {
|
|||||||
/* and start overwriting the existing data */
|
/* and start overwriting the existing data */
|
||||||
arrsetlen(ctx.render_queue_2d, 0);
|
arrsetlen(ctx.render_queue_2d, 0);
|
||||||
|
|
||||||
|
/* TODO: free memory if it isn't used for a while */
|
||||||
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
|
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
|
||||||
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
|
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i)
|
||||||
|
arrsetlen(ctx.billboard_batches[i].value.primitives, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -332,20 +336,25 @@ static void render_2d(void) {
|
|||||||
static void render_space(void) {
|
static void render_space(void) {
|
||||||
/* nothing to do, abort */
|
/* nothing to do, abort */
|
||||||
/* as space pipeline isn't used we can have fewer changes and initialization costs */
|
/* as space pipeline isn't used we can have fewer changes and initialization costs */
|
||||||
if (hmlenu(ctx.uncolored_mesh_batches) == 0)
|
if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) {
|
||||||
return;
|
|
||||||
|
|
||||||
use_space_pipeline();
|
use_space_pipeline();
|
||||||
apply_fog();
|
apply_fog();
|
||||||
|
|
||||||
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
|
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
|
||||||
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
|
||||||
ctx.uncolored_mesh_batches[i].key);
|
ctx.uncolored_mesh_batches[i].key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
pop_fog();
|
pop_fog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_skybox(); /* after everything else, as to use depth buffer for early z rejection */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void render(void) {
|
void render(void) {
|
||||||
textures_update_atlas(&ctx.texture_cache);
|
textures_update_atlas(&ctx.texture_cache);
|
||||||
@ -357,7 +366,6 @@ void render(void) {
|
|||||||
|
|
||||||
start_render_frame(); {
|
start_render_frame(); {
|
||||||
render_space();
|
render_space();
|
||||||
render_skybox(); /* after space, as to use depth buffer for early z rejection */
|
|
||||||
render_2d();
|
render_2d();
|
||||||
} end_render_frame();
|
} end_render_frame();
|
||||||
}
|
}
|
||||||
@ -370,6 +378,7 @@ void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) {
|
|||||||
.target = direction,
|
.target = direction,
|
||||||
.up = up,
|
.up = up,
|
||||||
};
|
};
|
||||||
|
|
||||||
camera_projection_matrix = camera_perspective(&camera);
|
camera_projection_matrix = camera_perspective(&camera);
|
||||||
camera_look_at_matrix = camera_look_at(&camera);
|
camera_look_at_matrix = camera_look_at(&camera);
|
||||||
}
|
}
|
||||||
@ -378,9 +387,11 @@ void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) {
|
|||||||
/* TODO: https://stackoverflow.com/questions/62493770/how-to-add-roll-in-camera-class */
|
/* TODO: https://stackoverflow.com/questions/62493770/how-to-add-roll-in-camera-class */
|
||||||
DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, float fov, float roll, float pitch, float yaw) {
|
DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, float fov, float roll, float pitch, float yaw) {
|
||||||
(void)roll;
|
(void)roll;
|
||||||
|
|
||||||
float yawc, yaws, pitchc, pitchs;
|
float yawc, yaws, pitchc, pitchs;
|
||||||
sincosf(yaw, &yaws, &yawc);
|
sincosf(yaw, &yaws, &yawc);
|
||||||
sincosf(pitch, &pitchs, &pitchc);
|
sincosf(pitch, &pitchs, &pitchc);
|
||||||
|
|
||||||
Camera const camera = {
|
Camera const camera = {
|
||||||
.fov = fov,
|
.fov = fov,
|
||||||
.pos = position,
|
.pos = position,
|
||||||
@ -391,6 +402,7 @@ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position,
|
|||||||
})),
|
})),
|
||||||
.up = (Vec3){0, 1, 0},
|
.up = (Vec3){0, 1, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
camera_projection_matrix = camera_perspective(&camera);
|
camera_projection_matrix = camera_perspective(&camera);
|
||||||
camera_look_at_matrix = camera_look_at(&camera);
|
camera_look_at_matrix = camera_look_at(&camera);
|
||||||
|
|
||||||
@ -520,364 +532,3 @@ void issue_deferred_draw_commands(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void render_circle(const CirclePrimitive *circle) {
|
|
||||||
static Vec2 vertices[CIRCLE_VERTICES_MAX];
|
|
||||||
static int prev_num_vertices = 0;
|
|
||||||
static Vec2 prev_position = {0};
|
|
||||||
|
|
||||||
int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX);
|
|
||||||
|
|
||||||
if (prev_num_vertices != num_vertices) {
|
|
||||||
create_circle_geometry(circle->position,
|
|
||||||
circle->radius,
|
|
||||||
num_vertices,
|
|
||||||
vertices);
|
|
||||||
prev_num_vertices = num_vertices;
|
|
||||||
prev_position = circle->position;
|
|
||||||
} else {
|
|
||||||
/* reuse the data, but offset it by difference with previously generated position */
|
|
||||||
/* no evil cos sin ops this way, if radius is shared in sequential calls */
|
|
||||||
Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y };
|
|
||||||
for (int i = 0; i < num_vertices; ++i)
|
|
||||||
vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y };
|
|
||||||
prev_position = circle->position;
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexBuffer buffer = get_scratch_vertex_array();
|
|
||||||
specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
|
|
||||||
|
|
||||||
DeferredCommandDraw command = {0};
|
|
||||||
|
|
||||||
command.vertices = (AttributeArrayPointer) {
|
|
||||||
.arity = 2,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = sizeof (Vec2),
|
|
||||||
.offset = 0,
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
command.constant_colored = true;
|
|
||||||
command.color = circle->color;
|
|
||||||
|
|
||||||
command.element_buffer = get_circle_element_buffer();
|
|
||||||
command.element_count = (num_vertices - 2) * 3;
|
|
||||||
command.range_end = (num_vertices - 2) * 3;
|
|
||||||
|
|
||||||
use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY);
|
|
||||||
|
|
||||||
DeferredCommand final_command = {
|
|
||||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
|
||||||
.draw = command
|
|
||||||
};
|
|
||||||
|
|
||||||
arrpush(deferred_commands, final_command);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void finally_render_quads(const Primitive2D primitives[],
|
|
||||||
const struct QuadBatch batch,
|
|
||||||
const VertexBuffer buffer)
|
|
||||||
{
|
|
||||||
DeferredCommandDraw command = {0};
|
|
||||||
|
|
||||||
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
|
|
||||||
|
|
||||||
if (!batch.constant_colored && batch.textured) {
|
|
||||||
off = offsetof(ElementIndexedQuad, v1);
|
|
||||||
voff = offsetof(ElementIndexedQuad, v0);
|
|
||||||
uvoff = offsetof(ElementIndexedQuad, uv0);
|
|
||||||
coff = offsetof(ElementIndexedQuad, c0);
|
|
||||||
} else if (batch.constant_colored && batch.textured) {
|
|
||||||
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
|
||||||
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
|
||||||
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
|
|
||||||
} else if (!batch.constant_colored && !batch.textured) {
|
|
||||||
off = offsetof(ElementIndexedQuadWithoutTexture, v1);
|
|
||||||
voff = offsetof(ElementIndexedQuadWithoutTexture, v0);
|
|
||||||
coff = offsetof(ElementIndexedQuad, c0);
|
|
||||||
} else if (batch.constant_colored && !batch.textured) {
|
|
||||||
off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
|
|
||||||
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
|
|
||||||
}
|
|
||||||
|
|
||||||
command.vertices = (AttributeArrayPointer) {
|
|
||||||
.arity = 2,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = off,
|
|
||||||
.offset = voff,
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
if (batch.textured)
|
|
||||||
command.texcoords = (AttributeArrayPointer) {
|
|
||||||
.arity = 2,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = off,
|
|
||||||
.offset = uvoff,
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!batch.constant_colored) {
|
|
||||||
command.colors = (AttributeArrayPointer) {
|
|
||||||
.arity = 4,
|
|
||||||
.type = GL_UNSIGNED_BYTE,
|
|
||||||
.stride = off,
|
|
||||||
.offset = coff,
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
command.constant_colored = true;
|
|
||||||
command.color = primitives[0].sprite.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batch.textured) {
|
|
||||||
command.textured = true;
|
|
||||||
command.texture_key = batch.texture_key;
|
|
||||||
command.texture_repeat = batch.repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
command.element_buffer = get_quad_element_buffer();
|
|
||||||
command.element_count = 6 * (GLsizei)batch.size;
|
|
||||||
command.range_end = 6 * (GLsizei)batch.size;
|
|
||||||
|
|
||||||
use_texture_mode(batch.mode);
|
|
||||||
|
|
||||||
DeferredCommand final_command = {
|
|
||||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
|
||||||
.draw = command
|
|
||||||
};
|
|
||||||
|
|
||||||
arrpush(deferred_commands, final_command);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t get_quad_payload_size(struct QuadBatch batch) {
|
|
||||||
if (batch.constant_colored && batch.textured)
|
|
||||||
return sizeof (ElementIndexedQuadWithoutColor);
|
|
||||||
else if (!batch.constant_colored && batch.textured)
|
|
||||||
return sizeof (ElementIndexedQuad);
|
|
||||||
else if (batch.constant_colored && !batch.textured)
|
|
||||||
return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
|
|
||||||
else if (!batch.constant_colored && !batch.textured)
|
|
||||||
return sizeof (ElementIndexedQuadWithoutTexture);
|
|
||||||
|
|
||||||
SDL_assert(false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
|
||||||
VertexBufferBuilder *builder,
|
|
||||||
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
|
||||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
|
||||||
Color color)
|
|
||||||
{
|
|
||||||
if (!batch.constant_colored && batch.textured) {
|
|
||||||
ElementIndexedQuad const buffer_element = {
|
|
||||||
.v0 = v0,
|
|
||||||
.v1 = v1,
|
|
||||||
.v2 = v2,
|
|
||||||
.v3 = v3,
|
|
||||||
|
|
||||||
.uv0 = uv0,
|
|
||||||
.uv1 = uv1,
|
|
||||||
.uv2 = uv2,
|
|
||||||
.uv3 = uv3,
|
|
||||||
|
|
||||||
/* equal for all (flat shaded) */
|
|
||||||
.c0 = color,
|
|
||||||
// .c1 = color,
|
|
||||||
.c2 = color,
|
|
||||||
// .c3 = color,
|
|
||||||
};
|
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
|
||||||
|
|
||||||
} else if (batch.constant_colored && batch.textured) {
|
|
||||||
ElementIndexedQuadWithoutColor const buffer_element = {
|
|
||||||
.v0 = v0,
|
|
||||||
.v1 = v1,
|
|
||||||
.v2 = v2,
|
|
||||||
.v3 = v3,
|
|
||||||
|
|
||||||
.uv0 = uv0,
|
|
||||||
.uv1 = uv1,
|
|
||||||
.uv2 = uv2,
|
|
||||||
.uv3 = uv3,
|
|
||||||
};
|
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
|
||||||
|
|
||||||
} else if (!batch.constant_colored && !batch.textured) {
|
|
||||||
ElementIndexedQuadWithoutTexture const buffer_element = {
|
|
||||||
.v0 = v0,
|
|
||||||
.v1 = v1,
|
|
||||||
.v2 = v2,
|
|
||||||
.v3 = v3,
|
|
||||||
|
|
||||||
/* equal for all (flat shaded) */
|
|
||||||
.c0 = color,
|
|
||||||
// .c1 = color,
|
|
||||||
.c2 = color,
|
|
||||||
// .c3 = color,
|
|
||||||
};
|
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
|
||||||
|
|
||||||
} else if (batch.constant_colored && !batch.textured) {
|
|
||||||
ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = {
|
|
||||||
.v0 = v0,
|
|
||||||
.v1 = v1,
|
|
||||||
.v2 = v2,
|
|
||||||
.v3 = v3,
|
|
||||||
};
|
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_assert(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch,
|
|
||||||
const TextureKey texture_key,
|
|
||||||
const VertexBuffer buffer)
|
|
||||||
{
|
|
||||||
const size_t primitives_len = arrlenu(batch->primitives);
|
|
||||||
|
|
||||||
/* nothing to do */
|
|
||||||
if (primitives_len == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
|
||||||
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
|
||||||
|
|
||||||
const float wr = srcrect.w / dims.w;
|
|
||||||
const float hr = srcrect.h / dims.h;
|
|
||||||
const float xr = srcrect.x / dims.w;
|
|
||||||
const float yr = srcrect.y / dims.h;
|
|
||||||
|
|
||||||
/* update pixel-based uvs to correspond with texture atlases */
|
|
||||||
for (size_t i = 0; i < primitives_len; ++i) {
|
|
||||||
UncoloredSpaceTriangle *payload =
|
|
||||||
&((UncoloredSpaceTriangle *)(void *)batch->primitives)[i];
|
|
||||||
|
|
||||||
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
|
|
||||||
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
|
|
||||||
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
|
|
||||||
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
|
|
||||||
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
|
|
||||||
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
|
|
||||||
}
|
|
||||||
|
|
||||||
specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle));
|
|
||||||
|
|
||||||
DeferredCommandDraw command = {0};
|
|
||||||
|
|
||||||
command.vertices = (AttributeArrayPointer) {
|
|
||||||
.arity = 3,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = offsetof(UncoloredSpaceTriangle, v1),
|
|
||||||
.offset = offsetof(UncoloredSpaceTriangle, v0),
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
command.texcoords = (AttributeArrayPointer) {
|
|
||||||
.arity = 2,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = offsetof(UncoloredSpaceTriangle, v1),
|
|
||||||
.offset = offsetof(UncoloredSpaceTriangle, uv0),
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
command.textured = true;
|
|
||||||
command.texture_key = texture_key;
|
|
||||||
|
|
||||||
command.primitive_count = (GLsizei)(3 * primitives_len);
|
|
||||||
|
|
||||||
DeferredCommand final_command = {
|
|
||||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
|
||||||
.draw = command
|
|
||||||
};
|
|
||||||
|
|
||||||
arrpush(deferred_commands, final_command);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
|
|
||||||
VertexBufferBuilder *builder,
|
|
||||||
stbtt_aligned_quad quad)
|
|
||||||
{
|
|
||||||
(void)font_data;
|
|
||||||
|
|
||||||
ElementIndexedQuadWithoutColor buffer_element = {
|
|
||||||
.v0 = (Vec2){ quad.x0, quad.y0 },
|
|
||||||
.v1 = (Vec2){ quad.x1, quad.y0 },
|
|
||||||
.v2 = (Vec2){ quad.x1, quad.y1 },
|
|
||||||
.v3 = (Vec2){ quad.x0, quad.y1 },
|
|
||||||
|
|
||||||
.uv0 = (Vec2){ quad.s0, quad.t0 },
|
|
||||||
.uv1 = (Vec2){ quad.s1, quad.t0 },
|
|
||||||
.uv2 = (Vec2){ quad.s1, quad.t1 },
|
|
||||||
.uv3 = (Vec2){ quad.s0, quad.t1 },
|
|
||||||
};
|
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void finally_draw_text(FontData const *font_data,
|
|
||||||
size_t len,
|
|
||||||
Color color,
|
|
||||||
VertexBuffer buffer)
|
|
||||||
{
|
|
||||||
DeferredCommandDraw command = {0};
|
|
||||||
|
|
||||||
command.vertices = (AttributeArrayPointer) {
|
|
||||||
.arity = 2,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
|
||||||
.offset = offsetof(ElementIndexedQuadWithoutColor, v0),
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
command.texcoords = (AttributeArrayPointer) {
|
|
||||||
.arity = 2,
|
|
||||||
.type = GL_FLOAT,
|
|
||||||
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
|
||||||
.offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
|
|
||||||
.buffer = buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
command.constant_colored = true;
|
|
||||||
command.color = color;
|
|
||||||
|
|
||||||
command.gpu_texture = font_data->texture;
|
|
||||||
command.uses_gpu_key = true;
|
|
||||||
command.textured = true;
|
|
||||||
|
|
||||||
command.element_buffer = get_quad_element_buffer();
|
|
||||||
command.element_count = 6 * (GLsizei)len;
|
|
||||||
command.range_end = 6 * (GLsizei)len;
|
|
||||||
|
|
||||||
use_texture_mode(TEXTURE_MODE_GHOSTLY);
|
|
||||||
|
|
||||||
DeferredCommand final_command = {
|
|
||||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
|
||||||
.draw = command
|
|
||||||
};
|
|
||||||
|
|
||||||
arrpush(deferred_commands, final_command);
|
|
||||||
|
|
||||||
/* TODO: why doesn't it get restored if not placed here? */
|
|
||||||
// glDepthMask(GL_TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t get_text_payload_size(void) {
|
|
||||||
return sizeof (ElementIndexedQuadWithoutColor);
|
|
||||||
}
|
|
||||||
|
@ -55,16 +55,16 @@ typedef struct RectPrimitive {
|
|||||||
} RectPrimitive;
|
} RectPrimitive;
|
||||||
|
|
||||||
typedef struct CirclePrimitive {
|
typedef struct CirclePrimitive {
|
||||||
|
Vec2 position;
|
||||||
float radius;
|
float radius;
|
||||||
Color color;
|
Color color;
|
||||||
Vec2 position;
|
|
||||||
} CirclePrimitive;
|
} CirclePrimitive;
|
||||||
|
|
||||||
typedef struct TextPrimitive {
|
typedef struct TextPrimitive {
|
||||||
Color color;
|
|
||||||
Vec2 position;
|
Vec2 position;
|
||||||
char *text;
|
char *text;
|
||||||
const char *font;
|
const char *font;
|
||||||
|
Color color;
|
||||||
int height_px;
|
int height_px;
|
||||||
} TextPrimitive;
|
} TextPrimitive;
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ typedef enum Primitive2DType {
|
|||||||
} Primitive2DType;
|
} Primitive2DType;
|
||||||
|
|
||||||
typedef struct Primitive2D {
|
typedef struct Primitive2D {
|
||||||
Primitive2DType type;
|
Primitive2DType type; /* TODO: separate to structure of arrays for more efficient memory usage? */
|
||||||
|
|
||||||
union {
|
union {
|
||||||
SpritePrimitive sprite;
|
SpritePrimitive sprite;
|
||||||
@ -97,6 +97,14 @@ typedef struct UncoloredSpaceTriangle {
|
|||||||
Vec2 uv2; /* in pixels */
|
Vec2 uv2; /* in pixels */
|
||||||
} UncoloredSpaceTriangle;
|
} UncoloredSpaceTriangle;
|
||||||
|
|
||||||
|
typedef struct SpaceBillboard {
|
||||||
|
Vec3 position;
|
||||||
|
Vec2 size;
|
||||||
|
Color color;
|
||||||
|
// TextureKey texture; /* is assumed from other places */
|
||||||
|
bool cylindrical;
|
||||||
|
} SpaceBillboard;
|
||||||
|
|
||||||
/* batch of primitives with overlapping properties */
|
/* batch of primitives with overlapping properties */
|
||||||
typedef struct MeshBatch {
|
typedef struct MeshBatch {
|
||||||
uint8_t *primitives; /* note: interpretation of it is arbitrary */
|
uint8_t *primitives; /* note: interpretation of it is arbitrary */
|
||||||
@ -179,6 +187,26 @@ typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
|
|||||||
Vec2 v3;
|
Vec2 v3;
|
||||||
} ElementIndexedQuadWithoutColorWithoutTexture;
|
} ElementIndexedQuadWithoutColorWithoutTexture;
|
||||||
|
|
||||||
|
/* TODO: no color variant */
|
||||||
|
typedef struct ElementIndexedBillboard {
|
||||||
|
/* upper-left */
|
||||||
|
Vec3 v0;
|
||||||
|
Vec2 uv0;
|
||||||
|
Color c0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec3 v1;
|
||||||
|
Vec2 uv1;
|
||||||
|
Color c1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec3 v2;
|
||||||
|
Vec2 uv2;
|
||||||
|
Color c2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec3 v3;
|
||||||
|
Vec2 uv3;
|
||||||
|
Color c3;
|
||||||
|
} ElementIndexedBillboard;
|
||||||
|
|
||||||
|
|
||||||
/* renders the background, then the primitives in all render queues */
|
/* renders the background, then the primitives in all render queues */
|
||||||
void render(void);
|
void render(void);
|
||||||
@ -207,9 +235,6 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
|
|||||||
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
|
|
||||||
void draw_uncolored_space_traingle_batch(MeshBatch *batch,
|
|
||||||
TextureKey texture_key);
|
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
|
|
||||||
void render_text(const TextPrimitive *text);
|
void render_text(const TextPrimitive *text);
|
||||||
@ -280,8 +305,10 @@ bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
|||||||
Color color);
|
Color color);
|
||||||
|
|
||||||
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
|
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
|
||||||
TextureKey texture_key,
|
TextureKey texture_key);
|
||||||
VertexBuffer buffer);
|
|
||||||
|
void finally_draw_billboard_batch(MeshBatch const *batch,
|
||||||
|
TextureKey texture_key);
|
||||||
|
|
||||||
size_t get_text_payload_size(void);
|
size_t get_text_payload_size(void);
|
||||||
|
|
||||||
|
@ -452,6 +452,7 @@ void finally_draw_command(DeferredCommandDraw command) {
|
|||||||
command.colors.type,
|
command.colors.type,
|
||||||
command.colors.stride,
|
command.colors.stride,
|
||||||
(void *)command.colors.offset);
|
(void *)command.colors.offset);
|
||||||
|
|
||||||
} else if (command.constant_colored)
|
} else if (command.constant_colored)
|
||||||
glColor4ub(command.color.r,
|
glColor4ub(command.color.r,
|
||||||
command.color.g,
|
command.color.g,
|
||||||
|
@ -1,30 +1,171 @@
|
|||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
|
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
|
void finally_render_quads(const Primitive2D primitives[],
|
||||||
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
|
const struct QuadBatch batch,
|
||||||
return collect_sprite_batch(primitives, len);
|
const VertexBuffer buffer)
|
||||||
else if (primitives[0].type == PRIMITIVE_2D_RECT)
|
|
||||||
return collect_rect_batch(primitives, len);
|
|
||||||
else
|
|
||||||
SDL_assert(false);
|
|
||||||
|
|
||||||
return (struct QuadBatch){0};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* assumes that orthogonal matrix setup is done already */
|
|
||||||
void render_quad_batch(const Primitive2D primitives[],
|
|
||||||
const struct QuadBatch batch)
|
|
||||||
{
|
{
|
||||||
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
|
DeferredCommandDraw command = {0};
|
||||||
render_sprite_batch(primitives, batch);
|
|
||||||
else if (primitives[0].type == PRIMITIVE_2D_RECT)
|
|
||||||
render_rect_batch(primitives, batch);
|
|
||||||
else
|
|
||||||
SDL_assert(false);
|
|
||||||
|
|
||||||
return (struct QuadBatch){0};
|
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
|
||||||
|
|
||||||
|
if (!batch.constant_colored && batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuad, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuad, v0);
|
||||||
|
uvoff = offsetof(ElementIndexedQuad, uv0);
|
||||||
|
coff = offsetof(ElementIndexedQuad, c0);
|
||||||
|
} else if (batch.constant_colored && batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
||||||
|
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
|
||||||
|
} else if (!batch.constant_colored && !batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutTexture, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutTexture, v0);
|
||||||
|
coff = offsetof(ElementIndexedQuad, c0);
|
||||||
|
} else if (batch.constant_colored && !batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
|
||||||
|
}
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = off,
|
||||||
|
.offset = voff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
if (batch.textured)
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = off,
|
||||||
|
.offset = uvoff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!batch.constant_colored) {
|
||||||
|
command.colors = (AttributeArrayPointer) {
|
||||||
|
.arity = 4,
|
||||||
|
.type = GL_UNSIGNED_BYTE,
|
||||||
|
.stride = off,
|
||||||
|
.offset = coff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = primitives[0].sprite.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.textured) {
|
||||||
|
command.textured = true;
|
||||||
|
command.texture_key = batch.texture_key;
|
||||||
|
command.texture_repeat = batch.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (GLsizei)batch.size;
|
||||||
|
command.range_end = 6 * (GLsizei)batch.size;
|
||||||
|
|
||||||
|
use_texture_mode(batch.mode);
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t get_quad_payload_size(struct QuadBatch batch) {
|
||||||
|
if (batch.constant_colored && batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutColor);
|
||||||
|
else if (!batch.constant_colored && batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuad);
|
||||||
|
else if (batch.constant_colored && !batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
|
||||||
|
else if (!batch.constant_colored && !batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutTexture);
|
||||||
|
|
||||||
|
SDL_assert(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||||
|
VertexBufferBuilder *builder,
|
||||||
|
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||||
|
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||||
|
Color color)
|
||||||
|
{
|
||||||
|
if (!batch.constant_colored && batch.textured) {
|
||||||
|
ElementIndexedQuad const buffer_element = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
.uv0 = uv0,
|
||||||
|
.uv1 = uv1,
|
||||||
|
.uv2 = uv2,
|
||||||
|
.uv3 = uv3,
|
||||||
|
|
||||||
|
/* equal for all (flat shaded) */
|
||||||
|
.c0 = color,
|
||||||
|
// .c1 = color,
|
||||||
|
.c2 = color,
|
||||||
|
// .c3 = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
|
||||||
|
} else if (batch.constant_colored && batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutColor const buffer_element = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
.uv0 = uv0,
|
||||||
|
.uv1 = uv1,
|
||||||
|
.uv2 = uv2,
|
||||||
|
.uv3 = uv3,
|
||||||
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
|
||||||
|
} else if (!batch.constant_colored && !batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutTexture const buffer_element = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
/* equal for all (flat shaded) */
|
||||||
|
.c0 = color,
|
||||||
|
// .c1 = color,
|
||||||
|
.c2 = color,
|
||||||
|
// .c3 = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
|
||||||
|
} else if (batch.constant_colored && !batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_assert(false);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ void render_skybox(void) {
|
|||||||
if (!paths_in_use)
|
if (!paths_in_use)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
use_space_pipeline();
|
||||||
|
|
||||||
/* note: ownership of 'paths_in_use' goes there */
|
/* note: ownership of 'paths_in_use' goes there */
|
||||||
DeferredCommand command = {
|
DeferredCommand command = {
|
||||||
.type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
|
.type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
|
||||||
|
@ -175,7 +175,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
|
|||||||
|
|
||||||
const size_t len = SDL_strlen(text);
|
const size_t len = SDL_strlen(text);
|
||||||
|
|
||||||
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_text_payload_size() * len);
|
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, sizeof (ElementIndexedQuadWithoutColor) * len);
|
||||||
|
|
||||||
for (size_t i = 0; i < len; ++i) {
|
for (size_t i = 0; i < len; ++i) {
|
||||||
const char c = text[i];
|
const char c = text[i];
|
||||||
@ -318,3 +318,73 @@ float draw_text_width(const char *string, float height, const char *font) {
|
|||||||
|
|
||||||
return (float)length * font_data->scale_factor;
|
return (float)length * font_data->scale_factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
|
||||||
|
VertexBufferBuilder *builder,
|
||||||
|
stbtt_aligned_quad quad)
|
||||||
|
{
|
||||||
|
(void)font_data;
|
||||||
|
|
||||||
|
ElementIndexedQuadWithoutColor buffer_element = {
|
||||||
|
.v0 = (Vec2){ quad.x0, quad.y0 },
|
||||||
|
.v1 = (Vec2){ quad.x1, quad.y0 },
|
||||||
|
.v2 = (Vec2){ quad.x1, quad.y1 },
|
||||||
|
.v3 = (Vec2){ quad.x0, quad.y1 },
|
||||||
|
|
||||||
|
.uv0 = (Vec2){ quad.s0, quad.t0 },
|
||||||
|
.uv1 = (Vec2){ quad.s1, quad.t0 },
|
||||||
|
.uv2 = (Vec2){ quad.s1, quad.t1 },
|
||||||
|
.uv3 = (Vec2){ quad.s0, quad.t1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void finally_draw_text(FontData const *font_data,
|
||||||
|
size_t len,
|
||||||
|
Color color,
|
||||||
|
VertexBuffer buffer)
|
||||||
|
{
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||||
|
.offset = offsetof(ElementIndexedQuadWithoutColor, v0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||||
|
.offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = color;
|
||||||
|
|
||||||
|
command.gpu_texture = font_data->texture;
|
||||||
|
command.uses_gpu_key = true;
|
||||||
|
command.textured = true;
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (GLsizei)len;
|
||||||
|
command.range_end = 6 * (GLsizei)len;
|
||||||
|
|
||||||
|
use_texture_mode(TEXTURE_MODE_GHOSTLY);
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
|
||||||
|
/* TODO: why doesn't it get restored if not placed here? */
|
||||||
|
// glDepthMask(GL_TRUE);
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ void draw_triangle(const char *path,
|
|||||||
Vec2 uv1,
|
Vec2 uv1,
|
||||||
Vec2 uv2)
|
Vec2 uv2)
|
||||||
{
|
{
|
||||||
|
// TODO: order drawing by atlas id as well, so that texture rebinding is not as common
|
||||||
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path);
|
const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path);
|
||||||
|
|
||||||
struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
|
struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
|
||||||
@ -42,10 +43,67 @@ void draw_triangle(const char *path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch,
|
||||||
TextureKey texture_key)
|
const TextureKey texture_key)
|
||||||
{
|
{
|
||||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
const size_t primitives_len = arrlenu(batch->primitives);
|
||||||
|
|
||||||
finally_draw_uncolored_space_traingle_batch(batch, texture_key, vertex_array);
|
/* nothing to do */
|
||||||
|
if (primitives_len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
VertexBuffer const buffer = get_scratch_vertex_array();
|
||||||
|
|
||||||
|
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
|
||||||
|
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
|
||||||
|
|
||||||
|
const float wr = srcrect.w / dims.w;
|
||||||
|
const float hr = srcrect.h / dims.h;
|
||||||
|
const float xr = srcrect.x / dims.w;
|
||||||
|
const float yr = srcrect.y / dims.h;
|
||||||
|
|
||||||
|
/* update pixel-based uvs to correspond with texture atlases */
|
||||||
|
for (size_t i = 0; i < primitives_len; ++i) {
|
||||||
|
UncoloredSpaceTriangle *payload =
|
||||||
|
&((UncoloredSpaceTriangle *)(void *)batch->primitives)[i];
|
||||||
|
|
||||||
|
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
|
||||||
|
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
|
||||||
|
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
|
||||||
|
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
|
||||||
|
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
|
||||||
|
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle));
|
||||||
|
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 3,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = offsetof(UncoloredSpaceTriangle, v1),
|
||||||
|
.offset = offsetof(UncoloredSpaceTriangle, v0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = offsetof(UncoloredSpaceTriangle, v1),
|
||||||
|
.offset = offsetof(UncoloredSpaceTriangle, uv0),
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.textured = true;
|
||||||
|
command.texture_key = texture_key;
|
||||||
|
|
||||||
|
command.primitive_count = (GLsizei)(3 * primitives_len);
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
}
|
}
|
||||||
|
@ -17,4 +17,6 @@
|
|||||||
#include "rendering/twn_sprites.c"
|
#include "rendering/twn_sprites.c"
|
||||||
#include "rendering/twn_rects.c"
|
#include "rendering/twn_rects.c"
|
||||||
#include "rendering/twn_text.c"
|
#include "rendering/twn_text.c"
|
||||||
|
#include "rendering/twn_quads.c"
|
||||||
#include "rendering/twn_triangles.c"
|
#include "rendering/twn_triangles.c"
|
||||||
|
#include "rendering/twn_billboards.c"
|
||||||
|
@ -28,9 +28,10 @@ Matrix4 camera_look_at(const Camera *const camera) {
|
|||||||
result.row[3].x = -m_vec_dot(r, camera->pos);
|
result.row[3].x = -m_vec_dot(r, camera->pos);
|
||||||
result.row[3].y = -m_vec_dot(u, camera->pos);
|
result.row[3].y = -m_vec_dot(u, camera->pos);
|
||||||
result.row[3].z = m_vec_dot(camera->target, camera->pos);
|
result.row[3].z = m_vec_dot(camera->target, camera->pos);
|
||||||
result.row[0].w = result.row[1].w = result.row[2].w = 0.0f;
|
|
||||||
result.row[3].w = 1.0f;
|
result.row[3].w = 1.0f;
|
||||||
|
|
||||||
|
result.row[0].w = result.row[1].w = result.row[2].w = 0.0f;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ typedef struct EngineContext {
|
|||||||
/* rendering */
|
/* rendering */
|
||||||
Primitive2D *render_queue_2d;
|
Primitive2D *render_queue_2d;
|
||||||
MeshBatchItem *uncolored_mesh_batches;
|
MeshBatchItem *uncolored_mesh_batches;
|
||||||
|
MeshBatchItem *billboard_batches;
|
||||||
TextCache text_cache;
|
TextCache text_cache;
|
||||||
TextureCache texture_cache;
|
TextureCache texture_cache;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user