392 lines
11 KiB
C
392 lines
11 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "stageutils.h"
|
|
|
|
#include "global.h"
|
|
#include "resource/material.h"
|
|
#include "resource/model.h"
|
|
#include "util/glm.h"
|
|
|
|
Stage3D stage_3d_context;
|
|
|
|
void stage3d_init(Stage3D *s, uint pos_buffer_size) {
|
|
memset(s, 0, sizeof(*s));
|
|
camera3d_init(&s->cam);
|
|
dynarray_ensure_capacity(&s->positions, pos_buffer_size);
|
|
}
|
|
|
|
void camera3d_init(Camera3D *cam) {
|
|
memset(cam, 0, sizeof(*cam));
|
|
cam->fovy = STAGE3D_DEFAULT_FOVY;
|
|
cam->aspect = VIEWPORT_W/(real)VIEWPORT_H;
|
|
cam->near = STAGE3D_DEFAULT_NEAR;
|
|
cam->far = STAGE3D_DEFAULT_FAR;
|
|
}
|
|
|
|
void camera3d_update(Camera3D *cam) {
|
|
glm_vec3_add(cam->pos, cam->vel, cam->pos);
|
|
}
|
|
|
|
void stage3d_update(Stage3D *s) {
|
|
camera3d_update(&s->cam);
|
|
}
|
|
|
|
static void camera3d_apply_rotations(Camera3D *cam, mat4 mat) {
|
|
glm_rotate_x(mat, glm_rad(-cam->rot.pitch), mat);
|
|
glm_rotate_y(mat, glm_rad(-cam->rot.yaw), mat);
|
|
glm_rotate_z(mat, glm_rad(-cam->rot.roll), mat);
|
|
}
|
|
|
|
void camera3d_apply_transforms(Camera3D *cam, mat4 mat) {
|
|
camera3d_apply_rotations(cam, mat);
|
|
|
|
vec3 trans;
|
|
glm_vec3_negate_to(cam->pos, trans);
|
|
glm_translate(mat, trans);
|
|
}
|
|
|
|
void stage3d_apply_transforms(Stage3D *s, mat4 mat) {
|
|
camera3d_apply_transforms(&s->cam, mat);
|
|
}
|
|
|
|
void camera3d_apply_inverse_transforms(Camera3D *cam, mat4 mat) {
|
|
// TODO optimize this
|
|
mat4 temp;
|
|
glm_mat4_identity(temp);
|
|
camera3d_apply_transforms(cam, temp);
|
|
glm_mat4_inv_fast(temp, mat);
|
|
}
|
|
|
|
void stage3d_apply_inverse_transforms(Stage3D *s, mat4 mat) {
|
|
camera3d_apply_inverse_transforms(&s->cam, mat);
|
|
}
|
|
|
|
|
|
// The author that brought you linear3dpos and that one function
|
|
// that calculates the closest point to a line segment proudly presents:
|
|
//
|
|
// camera3d_unprojected_ray!
|
|
//
|
|
// The purpose of this function is to calculate the ray
|
|
//
|
|
// x = camera_position + dest * lambda, lambda > 0
|
|
//
|
|
// of points in world space that are shown directly behind a position in the
|
|
// 2D viewport. This function returns the normalized vector dest from which
|
|
// all points x can be constructed by the user.
|
|
// The “unprojected ray” is useful for placing 3D objects that somehow correspond
|
|
// to a thing happening in the viewport.
|
|
//
|
|
// Actually, glm implements most of what is needed for this. Nice!
|
|
//
|
|
void camera3d_unprojected_ray(Camera3D *cam, cmplx pos, vec3 dest) {
|
|
vec4 viewport = {0, VIEWPORT_H, VIEWPORT_W, -VIEWPORT_H};
|
|
vec3 p = {re(pos), im(pos),1};
|
|
|
|
mat4 mpersp;
|
|
glm_perspective(cam->fovy, cam->aspect, cam->near, cam->far, mpersp);
|
|
camera3d_apply_rotations(cam, mpersp);
|
|
|
|
glm_unproject(p, mpersp, viewport, dest);
|
|
glm_vec3_normalize(dest);
|
|
}
|
|
|
|
void camera3d_project(Camera3D *cam, vec3 pos, vec3 dest) {
|
|
vec4 viewport = {0, VIEWPORT_H, VIEWPORT_W, -VIEWPORT_H};
|
|
|
|
mat4 mpersp;
|
|
glm_perspective(cam->fovy, cam->aspect, cam->near, cam->far, mpersp);
|
|
camera3d_apply_rotations(cam, mpersp);
|
|
|
|
glm_project(pos, mpersp, viewport, dest);
|
|
}
|
|
|
|
void camera3d_fill_point_light_uniform_vectors(
|
|
Camera3D *cam,
|
|
uint num_lights,
|
|
PointLight3D lights[num_lights],
|
|
vec3 out_lpos[num_lights],
|
|
vec3 out_lrad[num_lights]
|
|
) {
|
|
assume(num_lights <= STAGE3D_MAX_LIGHTS);
|
|
|
|
// TODO: persist matrix in Camera3D struct
|
|
mat4 camera_trans;
|
|
glm_mat4_identity(camera_trans);
|
|
camera3d_apply_transforms(cam, camera_trans);
|
|
|
|
for(int i = 0; i < num_lights; ++i) {
|
|
glm_mat4_mulv3(camera_trans, lights[i].pos, 1, out_lpos[i]);
|
|
glm_vec3_copy(lights[i].radiance, out_lrad[i]);
|
|
}
|
|
}
|
|
|
|
void camera3d_set_point_light_uniforms(
|
|
Camera3D *cam,
|
|
uint num_lights,
|
|
PointLight3D lights[num_lights]
|
|
) {
|
|
if(UNLIKELY(num_lights == 0)) {
|
|
return;
|
|
}
|
|
|
|
vec3 lpos[num_lights];
|
|
vec3 lrad[num_lights];
|
|
camera3d_fill_point_light_uniform_vectors(cam, num_lights, lights, lpos, lrad);
|
|
|
|
r_uniform_vec3_array("light_positions", 0, num_lights, lpos);
|
|
r_uniform_vec3_array("light_colors", 0, num_lights, lrad);
|
|
r_uniform_int("light_count", num_lights);
|
|
}
|
|
|
|
void pbr_set_material_uniforms(const PBRMaterial *m, const PBREnvironment *env) {
|
|
int flags = 0;
|
|
|
|
if(m->diffuse_map) {
|
|
r_uniform_sampler("diffuse_map", m->diffuse_map);
|
|
flags |= PBR_FEATURE_DIFFUSE_MAP;
|
|
}
|
|
|
|
if(m->normal_map) {
|
|
r_uniform_sampler("normal_map", m->normal_map);
|
|
flags |= PBR_FEATURE_NORMAL_MAP;
|
|
}
|
|
|
|
if(m->roughness_map) {
|
|
r_uniform_sampler("roughness_map", m->roughness_map);
|
|
flags |= PBR_FEATURE_ROUGHNESS_MAP;
|
|
}
|
|
|
|
if(m->ambient_map) {
|
|
r_uniform_sampler("ambient_map", m->ambient_map);
|
|
flags |= PBR_FEATURE_AMBIENT_MAP;
|
|
}
|
|
|
|
if(m->depth_map && m->depth_scale) {
|
|
r_uniform_sampler("depth_map", m->depth_map);
|
|
flags |= PBR_FEATURE_DEPTH_MAP;
|
|
}
|
|
|
|
if(env->environment_map) {
|
|
r_uniform_sampler("ibl_brdf_lut", "ibl_brdf_lut");
|
|
r_uniform_sampler("environment_map", env->environment_map);
|
|
r_uniform_mat4("inv_camera_transform", (vec4*)env->cam_inverse_transform);
|
|
flags |= PBR_FEATURE_ENVIRONMENT_MAP;
|
|
}
|
|
|
|
if(m->ao_map) {
|
|
r_uniform_sampler("ao_map", m->ao_map);
|
|
flags |= PBR_FEATURE_AO_MAP;
|
|
}
|
|
|
|
if(!env->disable_tonemap) {
|
|
flags |= PBR_FEATURE_NEED_TONEMAP;
|
|
}
|
|
|
|
vec4 diffuseRGB_metallicA;
|
|
glm_vec3_copy((float*)m->diffuse_color, diffuseRGB_metallicA);
|
|
diffuseRGB_metallicA[3] = m->metallic_value;
|
|
r_uniform_vec4_vec("diffuseRGB_metallicA", diffuseRGB_metallicA);
|
|
|
|
vec4 ambientRGB_roughnessA;
|
|
glm_vec3_mul((float*)env->ambient_color, (float*)m->ambient_color, ambientRGB_roughnessA);
|
|
ambientRGB_roughnessA[3] = m->roughness_value;
|
|
r_uniform_vec4_vec("ambientRGB_roughnessA", ambientRGB_roughnessA);
|
|
|
|
vec4 environmentRGB_depthScale;
|
|
glm_vec3_copy((float*)env->environment_color, (float*)environmentRGB_depthScale);
|
|
environmentRGB_depthScale[3] = m->depth_scale;
|
|
r_uniform_vec4_vec("environmentRGB_depthScale", environmentRGB_depthScale);
|
|
|
|
r_uniform_int("features_mask", flags);
|
|
}
|
|
|
|
void pbr_draw_model(const PBRModel *pmdl, const PBREnvironment *env) {
|
|
pbr_set_material_uniforms(NOT_NULL(pmdl->mat), env);
|
|
r_draw_model_ptr(NOT_NULL(pmdl->mdl), 0, 0);
|
|
}
|
|
|
|
void pbr_load_model(PBRModel *pmdl, const char *model_name, const char *mat_name) {
|
|
pmdl->mdl = res_model(model_name);
|
|
pmdl->mat = res_material(mat_name);
|
|
}
|
|
|
|
void stage3d_draw_segment(Stage3D *s, SegmentPositionRule pos_rule, SegmentDrawRule draw_rule, float maxrange) {
|
|
s->positions.num_elements = 0;
|
|
|
|
// TODO maybe get rid of the return value
|
|
uint num = pos_rule(s, s->cam.pos, maxrange);
|
|
|
|
if(num < s->positions.num_elements) {
|
|
s->positions.num_elements = num;
|
|
}
|
|
|
|
dynarray_foreach_elem(&s->positions, vec3 *p, {
|
|
draw_rule(*p);
|
|
});
|
|
}
|
|
|
|
void stage3d_draw(Stage3D *s, float maxrange, uint nsegments, const Stage3DSegment segments[nsegments]) {
|
|
r_mat_mv_push();
|
|
stage3d_apply_transforms(s, *r_mat_mv_current_ptr());
|
|
r_mat_proj_push_perspective(s->cam.fovy, s->cam.aspect, s->cam.near, s->cam.far);
|
|
|
|
for(uint i = 0; i < nsegments; ++i) {
|
|
const Stage3DSegment *seg = segments + i;
|
|
stage3d_draw_segment(s, seg->pos, seg->draw, maxrange);
|
|
}
|
|
|
|
r_mat_mv_pop();
|
|
r_mat_proj_pop();
|
|
}
|
|
|
|
void stage3d_shutdown(Stage3D *s) {
|
|
dynarray_free_data(&s->positions);
|
|
}
|
|
|
|
struct pos_ray_data {
|
|
int n_closest_to_cam;
|
|
int forward_nrange;
|
|
int back_nrange;
|
|
int max_nrange;
|
|
};
|
|
|
|
static struct pos_ray_data pos_ray_init(
|
|
vec3 camera, vec3 origin, vec3 step, float forward_range, float back_range
|
|
) {
|
|
assert(forward_range >= 0);
|
|
assert(back_range >= 0);
|
|
|
|
vec3 orig_to_cam;
|
|
glm_vec3_sub(camera, origin, orig_to_cam);
|
|
|
|
const float step_len2 = glm_vec3_norm2(step);
|
|
const float step_len = sqrtf(step_len2);
|
|
const float projected_cam = glm_vec3_dot(orig_to_cam, step) / step_len2;
|
|
|
|
struct pos_ray_data rd = {
|
|
.n_closest_to_cam = roundf(projected_cam),
|
|
|
|
// This is an approximation that does not take into account the distance
|
|
// of the camera to the ray. Can be made exact though.
|
|
.forward_nrange = forward_range / step_len,
|
|
.back_nrange = back_range / step_len,
|
|
};
|
|
rd.max_nrange = max(rd.forward_nrange, rd.back_nrange);
|
|
|
|
return rd;
|
|
}
|
|
|
|
static struct pos_ray_data pos_ray_init_nsteps(
|
|
vec3 camera, vec3 origin, vec3 step, int forward_steps, int back_steps
|
|
) {
|
|
assert(forward_steps >= 0);
|
|
assert(back_steps >= 0);
|
|
|
|
vec3 orig_to_cam;
|
|
glm_vec3_sub(camera, origin, orig_to_cam);
|
|
|
|
const float step_len2 = glm_vec3_norm2(step);
|
|
const float projected_cam = glm_vec3_dot(orig_to_cam, step) / step_len2;
|
|
|
|
struct pos_ray_data rd = {
|
|
.n_closest_to_cam = roundf(projected_cam),
|
|
.forward_nrange = forward_steps,
|
|
.back_nrange = back_steps,
|
|
};
|
|
rd.max_nrange = max(rd.forward_nrange, rd.back_nrange);
|
|
|
|
return rd;
|
|
}
|
|
|
|
static void pos_ray_step(
|
|
Stage3D *s3d, struct pos_ray_data *rd, int nofs, vec3 step, vec3 origin
|
|
) {
|
|
if(nofs > rd->forward_nrange || nofs < -rd->back_nrange) {
|
|
return;
|
|
}
|
|
|
|
int n = rd->n_closest_to_cam + nofs;
|
|
|
|
if(n < 0) {
|
|
return;
|
|
}
|
|
|
|
vec3 ofs;
|
|
glm_vec3_scale(step, n, ofs);
|
|
glm_vec3_add(origin, ofs, *dynarray_append(&s3d->positions));
|
|
}
|
|
|
|
static uint pos_ray_steps_nearfirst(Stage3D *s3d, struct pos_ray_data *d, vec3 origin, vec3 step) {
|
|
const uint prev_size = s3d->positions.num_elements;
|
|
|
|
pos_ray_step(s3d, d, 0, step, origin);
|
|
|
|
for(int i = 1; i <= d->max_nrange; ++i) {
|
|
for(int dir = -1; dir <= 1; dir += 2) {
|
|
pos_ray_step(s3d, d, dir * i, step, origin);
|
|
}
|
|
}
|
|
|
|
return s3d->positions.num_elements - prev_size;
|
|
}
|
|
|
|
static uint pos_ray_steps_farfirst(Stage3D *s3d, struct pos_ray_data *d, vec3 origin, vec3 step) {
|
|
const uint prev_size = s3d->positions.num_elements;
|
|
|
|
for(int i = d->max_nrange; i > 0; --i) {
|
|
for(int dir = 1; dir >= -1; dir -= 2) {
|
|
pos_ray_step(s3d, d, dir * i, step, origin);
|
|
}
|
|
}
|
|
|
|
pos_ray_step(s3d, d, 0, step, origin);
|
|
|
|
return s3d->positions.num_elements - prev_size;
|
|
}
|
|
|
|
uint stage3d_pos_ray_nearfirst(
|
|
Stage3D *s3d, vec3 camera, vec3 origin, vec3 step, float forward_range, float back_range
|
|
) {
|
|
struct pos_ray_data d = pos_ray_init(camera, origin, step, forward_range, back_range);
|
|
return pos_ray_steps_nearfirst(s3d, &d, origin, step);
|
|
}
|
|
|
|
uint stage3d_pos_ray_nearfirst_nsteps(
|
|
Stage3D *s3d, vec3 camera, vec3 origin, vec3 step, int forward_steps, int back_steps
|
|
) {
|
|
struct pos_ray_data d = pos_ray_init_nsteps(camera, origin, step, forward_steps, back_steps);
|
|
return pos_ray_steps_nearfirst(s3d, &d, origin, step);
|
|
}
|
|
|
|
uint stage3d_pos_ray_farfirst(
|
|
Stage3D *s3d, vec3 camera, vec3 origin, vec3 step, float forward_range, float back_range
|
|
) {
|
|
struct pos_ray_data d = pos_ray_init(camera, origin, step, forward_range, back_range);
|
|
return pos_ray_steps_farfirst(s3d, &d, origin, step);
|
|
}
|
|
|
|
uint stage3d_pos_ray_farfirst_nsteps(
|
|
Stage3D *s3d, vec3 camera, vec3 origin, vec3 step, int forward_steps, int back_steps
|
|
) {
|
|
struct pos_ray_data d = pos_ray_init_nsteps(camera, origin, step, forward_steps, back_steps);
|
|
return pos_ray_steps_farfirst(s3d, &d, origin, step);
|
|
}
|
|
|
|
uint stage3d_pos_single(Stage3D *s3d, vec3 camera, vec3 origin, float maxrange) {
|
|
vec3 d;
|
|
glm_vec3_sub(origin, camera, d);
|
|
|
|
if(glm_vec3_norm2(d) > maxrange * maxrange) {
|
|
return 0;
|
|
} else {
|
|
glm_vec3_copy(origin, *dynarray_append(&s3d->positions));
|
|
return 1;
|
|
}
|
|
}
|