Files
nunuhara_xsystem4/src/dungeon/renderer.c
T
kichikuou afae3fc9a6 DrawDungeon14: Implement raster effect
It is implemented as a post-processing effect. The hack for the
projection transform matrix has been removed, because now the image is
flipped upside down in the post-processing stage.
2023-09-01 21:45:46 +09:00

794 lines
24 KiB
C

/* Copyright (C) 2021 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <cglm/cglm.h>
#include "gfx/gl.h"
#include "system4.h"
#include "system4/buffer.h"
#include "system4/cg.h"
#include "dungeon/dungeon.h"
#include "dungeon/dgn.h"
#include "dungeon/dtx.h"
#include "dungeon/polyobj.h"
#include "dungeon/renderer.h"
#include "dungeon/skybox.h"
#include "gfx/gfx.h"
struct marker_info {
int event_type;
int texture_index;
int texture_count;
int floor_marker;
};
enum floor_marker {
// Rance VI
R6_MAGIC_CIRCLE = 1,
R6_RED_OCTAGRAM = 63,
// GALZOO Island
GZ_MAGIC_CIRCLE = 0,
};
static const struct marker_info rance6_marker_info[] = {
{ 11, 41, 3, R6_MAGIC_CIRCLE}, // treasure chest
{ 12, 51, 7, R6_RED_OCTAGRAM}, // enemy
{ 14, 8, 5, R6_MAGIC_CIRCLE}, // green star
{ 15, 21, 10, R6_MAGIC_CIRCLE}, // heart
{ 16, 45, 5, R6_MAGIC_CIRCLE}, // BP cross
{ 17, 58, 5, R6_MAGIC_CIRCLE}, // exit
{ 18, 31, 10, R6_MAGIC_CIRCLE}, // teleporter
{140, 13, 5, R6_RED_OCTAGRAM}, // red star
{0},
};
static const struct marker_info galzoo_marker_info[] = {
{11, 1, 0, GZ_MAGIC_CIRCLE}, // item
{12, 2, 0, GZ_MAGIC_CIRCLE}, // enemy
{14, 3, 0, GZ_MAGIC_CIRCLE}, // star
{15, 4, 0, GZ_MAGIC_CIRCLE}, // heart
{17, 5, 0, GZ_MAGIC_CIRCLE}, // exit
{18, 6, 0, GZ_MAGIC_CIRCLE}, // teleporter
{0},
};
static const struct marker_info *marker_tables[] = {
[DRAW_DUNGEON_1] = rance6_marker_info,
[DRAW_DUNGEON_14] = galzoo_marker_info
};
struct geometry;
struct material;
struct object3d;
struct raster_shader {
struct shader s;
GLint amp;
GLint t;
};
struct dungeon_renderer {
struct shader shader;
// Uniform variable locations
GLint local_transform;
GLint proj_transform;
GLuint alpha_mod;
// Attribute variable locations
GLint vertex_uv;
struct geometry *wall_geometry;
struct geometry *door_left_geometry;
struct geometry *door_right_geometry;
struct geometry *roof_geometry;
struct geometry *stairs_geometry;
struct geometry *floating_marker_geometry;
struct material *materials;
int nr_materials;
int nr_dtx_columns;
GLuint *event_textures;
int nr_event_textures;
const struct marker_info *marker_table;
bool draw_event_markers;
int nr_polyobjs;
struct object3d *polyobjs;
int nr_polyobj_materials;
struct material *polyobj_materials;
struct skybox *skybox;
struct raster_shader raster_shader;
bool raster_scroll;
float raster_amp;
};
static const struct marker_info *get_marker_info(struct dungeon_renderer *r, int event_type)
{
for (const struct marker_info *info = r->marker_table; info->event_type; info++) {
if (info->event_type == event_type)
return info;
}
return NULL;
}
struct vertex {
GLfloat x, y, z, u, v;
};
static const struct vertex wall_vertices[] = {
{-1.0, 1.0, 0.0, 0.0, 0.0},
{-1.0, -1.0, 0.0, 0.0, 1.0},
{ 1.0, 1.0, 0.0, 1.0, 0.0},
{ 1.0, -1.0, 0.0, 1.0, 1.0},
};
static const struct vertex door_left_vertices[] = {
{ 0.0, 1.0, 0.0, 0.0, 0.0},
{ 0.0, -1.0, 0.0, 0.0, 1.0},
{ 1.0, 1.0, 0.0, 0.5, 0.0},
{ 1.0, -1.0, 0.0, 0.5, 1.0},
};
static const struct vertex door_right_vertices[] = {
{-1.0, 1.0, 0.0, 0.5, 0.0},
{-1.0, -1.0, 0.0, 0.5, 1.0},
{ 0.0, 1.0, 0.0, 1.0, 0.0},
{ 0.0, -1.0, 0.0, 1.0, 1.0},
};
static const struct vertex roof_vertices[] = {
{-1.0, 1.0, -1.0, 0.0, 0.0},
{-1.0, -1.0, 1.0, 0.0, 1.0},
{ 1.0, 1.0, -1.0, 1.0, 0.0},
{ 1.0, -1.0, 1.0, 1.0, 1.0},
};
static const struct vertex stairs_vertices[] = {
{ 1, 3/3.f, -3/3.f, 1, 0/12.f},
{-1, 3/3.f, -3/3.f, 0, 0/12.f},
{ 1, 3/3.f, -2/3.f, 1, 1/12.f},
{-1, 3/3.f, -2/3.f, 0, 1/12.f},
{ 1, 2/3.f, -2/3.f, 1, 2/12.f},
{-1, 2/3.f, -2/3.f, 0, 2/12.f},
{ 1, 2/3.f, -1/3.f, 1, 3/12.f},
{-1, 2/3.f, -1/3.f, 0, 3/12.f},
{ 1, 1/3.f, -1/3.f, 1, 4/12.f},
{-1, 1/3.f, -1/3.f, 0, 4/12.f},
{ 1, 1/3.f, 0/3.f, 1, 5/12.f},
{-1, 1/3.f, 0/3.f, 0, 5/12.f},
{ 1, 0/3.f, 0/3.f, 1, 6/12.f},
{-1, 0/3.f, 0/3.f, 0, 6/12.f},
{ 1, 0/3.f, 1/3.f, 1, 7/12.f},
{-1, 0/3.f, 1/3.f, 0, 7/12.f},
{ 1, -1/3.f, 1/3.f, 1, 8/12.f},
{-1, -1/3.f, 1/3.f, 0, 8/12.f},
{ 1, -1/3.f, 2/3.f, 1, 9/12.f},
{-1, -1/3.f, 2/3.f, 0, 9/12.f},
{ 1, -2/3.f, 2/3.f, 1, 10/12.f},
{-1, -2/3.f, 2/3.f, 0, 10/12.f},
{ 1, -2/3.f, 3/3.f, 1, 11/12.f},
{-1, -2/3.f, 3/3.f, 0, 11/12.f},
{ 1, -3/3.f, 3/3.f, 1, 12/12.f},
{-1, -3/3.f, 3/3.f, 0, 12/12.f},
};
static const struct vertex floating_marker_vertices[] = {
{-0.9, 0.9, 0.0, 0.0, 0.0},
{-0.9, -0.9, 0.0, 0.0, 1.0},
{ 0.9, 0.9, 0.0, 1.0, 0.0},
{ 0.9, -0.9, 0.0, 1.0, 1.0},
{ 0.9, -0.9, 0.0, 1.0, 1.0}, // degenerate triangle
{ 0.9, 0.9, 0.0, 0.0, 0.0}, // degenerate triangle
{ 0.9, 0.9, 0.0, 0.0, 0.0},
{ 0.9, -0.9, 0.0, 0.0, 1.0},
{-0.9, 0.9, 0.0, 1.0, 0.0},
{-0.9, -0.9, 0.0, 1.0, 1.0},
};
struct geometry {
GLuint vao;
GLuint attr_buffer;
int nr_vertices;
GLenum mode;
};
static struct geometry *geometry_create(struct dungeon_renderer *r, const struct vertex *vertices, size_t size, GLenum mode)
{
struct geometry *g = xcalloc(1, sizeof(struct geometry));
g->nr_vertices = size / sizeof(struct vertex);
g->mode = mode;
glGenVertexArrays(1, &g->vao);
glBindVertexArray(g->vao);
glGenBuffers(1, &g->attr_buffer);
glBindBuffer(GL_ARRAY_BUFFER, g->attr_buffer);
glEnableVertexAttribArray(r->shader.vertex);
glVertexAttribPointer(r->shader.vertex, 3, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)0);
glEnableVertexAttribArray(r->vertex_uv);
glVertexAttribPointer(r->vertex_uv, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (const void *)(3 * sizeof(GLfloat)));
glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return g;
}
static void geometry_free(struct geometry *g)
{
glDeleteVertexArrays(1, &g->vao);
glDeleteBuffers(1, &g->attr_buffer);
free(g);
}
struct material {
GLuint texture;
bool opaque;
};
static void init_material(struct material *m, struct cg *cg)
{
glGenTextures(1, &m->texture);
glBindTexture(GL_TEXTURE_2D, m->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cg->metrics.w, cg->metrics.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, cg->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
m->opaque = !cg->metrics.has_alpha;
}
static void delete_material(struct material *m)
{
if (m->texture)
glDeleteTextures(1, &m->texture);
}
static struct material *get_material(struct dungeon_renderer *r, int type, int index)
{
struct material *m = &r->materials[type * r->nr_dtx_columns + index];
if (!m->texture)
return NULL;
return m;
}
struct mesh {
struct geometry *geometry;
int material;
};
struct object3d {
int nr_meshes;
struct mesh *meshes;
};
static void init_object3d(struct object3d *o3d, struct dungeon_renderer *r, const struct polyobj_object *obj)
{
o3d->nr_meshes = obj->nr_materials;
o3d->meshes = xcalloc(obj->nr_materials, sizeof(struct mesh));
for (int i = 0; i < obj->nr_materials; i++)
o3d->meshes[i].material = obj->materials[i];
struct buffer *bufs = xcalloc(obj->nr_materials, sizeof(struct buffer));
for (int i = 0; i < obj->nr_parts; i++) {
const struct polyobj_part *part = &obj->parts[i];
for (int j = 0; j < part->nr_triangles; j++) {
const struct polyobj_triangle *triangle = &part->triangles[j];
for (int k = 0; k < 3; k++) {
const int ltor[] = {0, 2, 1}; // left->right handed system
const vec3 *pos = &part->vertices[triangle->index[ltor[k]]];
const vec2 *uv = &triangle->uv[ltor[k]];
buffer_write_float(&bufs[triangle->material], (*pos)[0]); // x
buffer_write_float(&bufs[triangle->material], (*pos)[1]); // y
buffer_write_float(&bufs[triangle->material], -(*pos)[2]); // z
buffer_write_float(&bufs[triangle->material], (*uv)[0]); // u
buffer_write_float(&bufs[triangle->material], (*uv)[1]); // v
}
}
}
for (int i = 0; i < obj->nr_materials; i++) {
o3d->meshes[i].geometry = geometry_create(r, (const struct vertex *)bufs[i].buf, bufs[i].index, GL_TRIANGLES);
free(bufs[i].buf);
}
free(bufs);
}
static void delete_object3d(struct object3d *o3d)
{
for (int i = 0; i < o3d->nr_meshes; i++)
geometry_free(o3d->meshes[i].geometry);
free(o3d->meshes);
}
static void init_polyobjs(struct dungeon_renderer *r, struct polyobj *po)
{
r->nr_polyobj_materials = po->nr_textures;
r->polyobj_materials = xcalloc(po->nr_textures, sizeof(struct material));
for (int i = 0; i < po->nr_textures; i++) {
struct cg *cg = polyobj_get_texture(po, i);
init_material(&r->polyobj_materials[i], cg);
cg_free(cg);
}
r->nr_polyobjs = po->nr_objects;
r->polyobjs = xcalloc(po->nr_objects, sizeof(struct object3d));
for (int i = 0; i < po->nr_objects; i++) {
struct polyobj_object *obj = polyobj_get_object(po, i);
if (!obj)
continue;
init_object3d(&r->polyobjs[i], r, obj);
polyobj_object_free(obj);
}
}
struct dungeon_renderer *dungeon_renderer_create(enum draw_dungeon_version version, struct dtx *dtx, GLuint *event_textures, int nr_event_textures, struct polyobj *po)
{
struct dungeon_renderer *r = xcalloc(1, sizeof(struct dungeon_renderer));
gfx_load_shader(&r->shader, "shaders/dungeon.v.glsl", "shaders/dungeon.f.glsl");
r->local_transform = glGetUniformLocation(r->shader.program, "local_transform");
r->proj_transform = glGetUniformLocation(r->shader.program, "proj_transform");
r->alpha_mod = glGetUniformLocation(r->shader.program, "alpha_mod");
r->vertex_uv = glGetAttribLocation(r->shader.program, "vertex_uv");
r->wall_geometry = geometry_create(r, wall_vertices, sizeof(wall_vertices), GL_TRIANGLE_STRIP);
r->door_left_geometry = geometry_create(r, door_left_vertices, sizeof(door_left_vertices), GL_TRIANGLE_STRIP);
r->door_right_geometry = geometry_create(r, door_right_vertices, sizeof(door_right_vertices), GL_TRIANGLE_STRIP);
r->roof_geometry = geometry_create(r, roof_vertices, sizeof(roof_vertices), GL_TRIANGLE_STRIP);
r->stairs_geometry = geometry_create(r, stairs_vertices, sizeof(stairs_vertices), GL_TRIANGLE_STRIP);
r->floating_marker_geometry = geometry_create(r, floating_marker_vertices, sizeof(floating_marker_vertices), GL_TRIANGLE_STRIP);
const int nr_types = DTX_DOOR + 1;
r->materials = xcalloc(nr_types * dtx->nr_columns, sizeof(struct material));
for (int type = 0; type < nr_types; type++) {
for (int i = 0; i < dtx->nr_columns; i++) {
struct cg *cg = dtx_create_cg(dtx, type, i);
if (!cg)
continue;
init_material(&r->materials[type * dtx->nr_columns + i], cg);
cg_free(cg);
}
}
r->nr_dtx_columns = dtx->nr_columns;
r->nr_materials = nr_types * dtx->nr_columns;
r->event_textures = event_textures;
r->nr_event_textures = nr_event_textures;
r->marker_table = marker_tables[version];
r->draw_event_markers = true;
if (po)
init_polyobjs(r, po);
r->skybox = skybox_create(dtx);
return r;
}
void dungeon_renderer_free(struct dungeon_renderer *r)
{
glDeleteProgram(r->shader.program);
geometry_free(r->wall_geometry);
geometry_free(r->door_left_geometry);
geometry_free(r->door_right_geometry);
geometry_free(r->roof_geometry);
geometry_free(r->stairs_geometry);
geometry_free(r->floating_marker_geometry);
for (int i = 0; i < r->nr_materials; i++)
delete_material(&r->materials[i]);
free(r->materials);
glDeleteTextures(r->nr_event_textures, r->event_textures);
free(r->event_textures);
for (int i = 0; i < r->nr_polyobjs; i++)
delete_object3d(&r->polyobjs[i]);
free(r->polyobjs);
for (int i = 0; i < r->nr_polyobj_materials; i++)
delete_material(&r->polyobj_materials[i]);
free(r->polyobj_materials);
skybox_free(r->skybox);
if (r->raster_shader.s.program)
glDeleteProgram(r->raster_shader.s.program);
free(r);
}
static void draw(struct dungeon_renderer *r, struct geometry *geometry, GLuint texture, mat4 transform)
{
glUseProgram(r->shader.program);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, transform[0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(r->shader.texture, 0);
glBindVertexArray(geometry->vao);
glDrawArrays(geometry->mode, 0, geometry->nr_vertices);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void draw_door(struct dungeon_renderer *r, GLuint texture, float angle, mat4 transform, float dx, float dz)
{
if (angle == 0.0) {
draw(r, r->wall_geometry, texture, transform);
return;
}
mat4 m;
glm_mat4_copy(transform, m);
m[3][0] += dx;
m[3][2] += dz;
glm_rotate_y(m, angle * (M_PI/180), m);
draw(r, r->door_left_geometry, texture, m);
glm_mat4_copy(transform, m);
m[3][0] -= dx;
m[3][2] -= dz;
glm_rotate_y(m, angle * -(M_PI/180), m);
draw(r, r->door_right_geometry, texture, m);
}
static void draw_floor_marker(struct dungeon_renderer *r, const struct marker_info *info, float x, float y, float z, uint32_t t)
{
float rad = t / 400.0;
mat4 rot;
glm_euler((vec3){0, 0, rad}, rot);
rot[3][3] /= 0.75; // scale
mat4 trans = MAT4(
1, 0, 0, x,
0, 0, 1, y-1,
0, -1, 0, z,
0, 0, 0, 1);
glm_mul(trans, rot, trans);
glDepthFunc(GL_LEQUAL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-1.0, -2.0);
draw(r, r->wall_geometry, r->event_textures[info->floor_marker], trans);
glDisable(GL_POLYGON_OFFSET_FILL);
glDepthFunc(GL_LESS);
}
static void draw_floating_marker(struct dungeon_renderer *r, const struct marker_info *info, float x, float y, float z, int blend_rate, uint32_t t, mat4 view_mat)
{
int texture = info->texture_index;
mat4 m = MAT4(
1, 0, 0, x,
0, 1, 0, y,
0, 0, 1, z,
0, 0, 0, 1);
if (info->texture_count) {
int frame = t / 100; // 100ms per frame
texture += frame % info->texture_count;
// Make a billboard matrix
mat3 r;
glm_mat4_pick3t(view_mat, r);
glm_mat4_ins3(r, m);
} else {
float angle = t / -(M_PI * 100.0);
glm_rotate_y(m, angle, m);
}
glUniform1f(r->alpha_mod, blend_rate / 255.0);
draw(r, r->floating_marker_geometry, r->event_textures[texture], m);
glUniform1f(r->alpha_mod, 1.0);
}
static void draw_object3d(struct dungeon_renderer *r, struct object3d *o3d, bool render_opaque, vec3 pos, float scale, float rotation_y)
{
mat4 m;
glm_translate_make(m, pos);
glm_rotate_y(m, glm_rad(rotation_y), m);
glm_scale_uni(m, scale);
for (int i = 0; i < o3d->nr_meshes; i++) {
struct mesh *mesh = &o3d->meshes[i];
struct material *material = &r->polyobj_materials[mesh->material];
if (material->opaque != render_opaque)
continue;
draw(r, mesh->geometry, material->texture, m);
}
}
static void stairs_matrix(mat4 dst, float x, float y, float z, int orientation)
{
vec3 pos = {x, y, z};
glm_translate_make(dst, pos);
const float cos[4] = {1, 0, -1, 0};
const float sin[4] = {0, 1, 0, -1};
dst[0][0] = cos[orientation % 4];
dst[0][2] = -sin[orientation % 4];
dst[2][0] = sin[orientation % 4];
dst[2][2] = cos[orientation % 4];
}
static void draw_cell(struct dungeon_renderer *r, struct dgn_cell *cell, bool render_opaque, mat4 view_transform)
{
float x = 2.0 * cell->x;
float y = 2.0 * cell->y;
float z = -2.0 * cell->z;
if (cell->floor >= 0) {
struct material *material = get_material(r, DTX_FLOOR, cell->floor);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
1, 0, 0, x,
0, 0, 1, y-1,
0, -1, 0, z,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
}
}
if (cell->ceiling >= 0) {
struct material *material = get_material(r, DTX_CEILING, cell->ceiling);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
-1, 0, 0, x,
0, 0, -1, y+1,
0, -1, 0, z,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
}
}
if (cell->north_wall >= 0) {
struct material *material = get_material(r, DTX_WALL, cell->north_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
1, 0, 0, x,
0, 1, 0, y,
0, 0, 1, z-1,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
}
}
if (cell->south_wall >= 0) {
struct material *material = get_material(r, DTX_WALL, cell->south_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
-1, 0, 0, x,
0, 1, 0, y,
0, 0, -1, z+1,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
}
}
if (cell->east_wall >= 0) {
struct material *material = get_material(r, DTX_WALL, cell->east_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
0, 0, -1, x+1,
0, 1, 0, y,
1, 0, 0, z,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
}
}
if (cell->west_wall >= 0) {
struct material *material = get_material(r, DTX_WALL, cell->west_wall);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
0, 0, 1, x-1,
0, 1, 0, y,
-1, 0, 0, z,
0, 0, 0, 1);
draw(r, r->wall_geometry, material->texture, m);
}
}
if (cell->north_door >= 0) {
struct material *material = get_material(r, DTX_DOOR, cell->north_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
1, 0, 0, x,
0, 1, 0, y,
0, 0, 1, z-1,
0, 0, 0, 1);
draw_door(r, material->texture, cell->north_door_angle, m, -1, 0);
}
}
if (cell->south_door >= 0) {
struct material *material = get_material(r, DTX_DOOR, cell->south_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
-1, 0, 0, x,
0, 1, 0, y,
0, 0, -1, z+1,
0, 0, 0, 1);
draw_door(r, material->texture, cell->south_door_angle, m, 1, 0);
}
}
if (cell->east_door >= 0) {
struct material *material = get_material(r, DTX_DOOR, cell->east_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
0, 0, -1, x+1,
0, 1, 0, y,
1, 0, 0, z,
0, 0, 0, 1);
draw_door(r, material->texture, cell->east_door_angle, m, 0, -1);
}
}
if (cell->west_door >= 0) {
struct material *material = get_material(r, DTX_DOOR, cell->west_door);
if (material && material->opaque == render_opaque) {
mat4 m = MAT4(
0, 0, 1, x-1,
0, 1, 0, y,
-1, 0, 0, z,
0, 0, 0, 1);
draw_door(r, material->texture, cell->west_door_angle, m, 0, 1);
}
}
if (cell->stairs_texture >= 0) {
struct material *material = get_material(r, DTX_STAIRS, cell->stairs_texture);
if (material && material->opaque == render_opaque) {
mat4 m;
stairs_matrix(m, x, y, z, cell->stairs_orientation);
glDisable(GL_CULL_FACE);
draw(r, r->stairs_geometry, material->texture, m);
glEnable(GL_CULL_FACE);
}
}
if (cell->roof_texture >= 0) {
struct material *material = get_material(r, DTX_STAIRS, cell->roof_texture);
if (material && material->opaque == render_opaque) {
mat4 m;
stairs_matrix(m, x, y, z, cell->roof_orientation);
draw(r, r->roof_geometry, material->texture, m);
}
}
if (cell->roof_underside_texture >= 0) {
struct material *material = get_material(r, DTX_STAIRS, cell->roof_underside_texture);
if (material && material->opaque == render_opaque) {
mat4 m;
stairs_matrix(m, x, y, z, cell->roof_orientation);
glCullFace(GL_FRONT);
draw(r, r->roof_geometry, material->texture, m);
glCullFace(GL_BACK);
}
}
if (cell->polyobj_index >= 0) {
vec3 pos = {
x + cell->polyobj_position_x,
y + cell->polyobj_position_y - 1,
z - cell->polyobj_position_z
};
draw_object3d(r, &r->polyobjs[cell->polyobj_index], render_opaque, pos, cell->polyobj_scale, cell->polyobj_rotation_y);
}
if (r->draw_event_markers && cell->floor_event && !render_opaque) {
const struct marker_info *info = get_marker_info(r, cell->floor_event);
if (info) {
uint32_t t = SDL_GetTicks();
draw_floor_marker(r, info, x, y, z, t);
draw_floating_marker(r, info, x, y, z, cell->event_blend_rate, t, view_transform);
}
}
}
void dungeon_renderer_render(struct dungeon_renderer *r, struct dgn_cell **cells, int nr_cells, mat4 view_transform, mat4 proj_transform)
{
glUseProgram(r->shader.program);
glUniformMatrix4fv(r->shader.view_transform, 1, GL_FALSE, view_transform[0]);
glUniformMatrix4fv(r->proj_transform, 1, GL_FALSE, proj_transform[0]);
glUniform1f(r->alpha_mod, 1.0);
// Render opaque objects, from near to far.
glDisable(GL_BLEND);
for (int i = 0; i < nr_cells; i++)
draw_cell(r, cells[i], true, view_transform);
// Render the skybox.
skybox_render(r->skybox, view_transform, proj_transform);
glUseProgram(r->shader.program);
// Render transparent objects, from far to near.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (int i = nr_cells - 1; i >= 0; i--)
draw_cell(r, cells[i], false, view_transform);
glUseProgram(0);
}
void dungeon_renderer_enable_event_markers(struct dungeon_renderer *r, bool enable)
{
r->draw_event_markers = enable;
}
bool dungeon_renderer_event_markers_enabled(struct dungeon_renderer *r)
{
return r->draw_event_markers;
}
bool dungeon_renderer_is_floor_opaque(struct dungeon_renderer *r, struct dgn_cell *cell)
{
if (cell->floor < 0)
return false;
struct material *material = get_material(r, DTX_FLOOR, cell->floor);
return material && material->opaque;
}
static void prepare_raster_shader(struct gfx_render_job *job, void *data)
{
struct dungeon_renderer *r = data;
float width = job->world_transform[0];
glUniform1f(r->raster_shader.amp, r->raster_amp / width);
glUniform1f(r->raster_shader.t, SDL_GetTicks() / 1000.f);
}
static void load_raster_shader(struct raster_shader *s)
{
gfx_load_shader(&s->s, "shaders/render.v.glsl", "shaders/dungeon_raster.f.glsl");
s->amp = glGetUniformLocation(s->s.program, "amp");
s->t = glGetUniformLocation(s->s.program, "t");
s->s.prepare = prepare_raster_shader;
}
void dungeon_renderer_run_post_processing(struct dungeon_renderer *r, struct texture *src, struct texture *dst)
{
// This function flips the image upside down, because the image in `src`
// (rendered by dungeon_renderer_render) is bottom-up (the first pixel is at
// the bottom-left), but sprite textures are top-down (the first pixel is at
// the top-left).
if (r->raster_scroll) {
mat4 mw_transform = MAT4(
src->w, 0, 0, 0,
0, -src->h, 0, src->h, // flip vertically
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(src->w, src->h);
struct gfx_render_job job = {
.shader = &r->raster_shader.s,
.shape = GFX_RECTANGLE,
.texture = src->handle,
.world_transform = mw_transform[0],
.view_transform = wv_transform[0],
.data = r
};
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, dst, 0, 0, dst->w, dst->h);
gfx_render(&job);
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
} else {
gfx_copy_reverse_UD(dst, 0, 0, src, 0, 0, src->w, src->h);
}
}
void dungeon_renderer_set_raster_scroll(struct dungeon_renderer *r, int type)
{
r->raster_scroll = !!type;
if (r->raster_scroll && !r->raster_shader.s.program) {
load_raster_shader(&r->raster_shader);
}
}
void dungeon_renderer_set_raster_amp(struct dungeon_renderer *r, float amp)
{
r->raster_amp = amp;
}