implement global frustum culling

main
Dominik Madarász 2024-09-03 11:41:33 +02:00
parent dfcb0877ca
commit aec753e2c4
11 changed files with 29699 additions and 29284 deletions

View File

@ -1195,6 +1195,7 @@ typedef struct shadowmap_t {
int cascade_index;
unsigned shadow_technique;
float cascade_splits[4];
frustum shadow_frustum;
bool skip_render;
int lights_pushed;
handle fbo;
@ -1641,6 +1642,7 @@ typedef struct camera_t {
float yaw, pitch, roll;
float speed, fov;
float near_clip, far_clip;
float frustum_fov_multiplier;
float move_friction, move_damping;
float look_friction, look_damping;
vec3 last_look; vec3 last_move;
@ -1657,6 +1659,7 @@ typedef struct camera_t {
void camera_orbit(camera_t *cam, float yaw, float pitch, float inc_distance);
void camera_lookat(camera_t *cam, vec3 target);
void camera_enable(camera_t *cam);
frustum camera_frustum_build(camera_t *cam);
camera_t *camera_get_active();
int ui_camera(camera_t *cam);
void ddraw_camera(camera_t *cam);

View File

@ -19,7 +19,7 @@ const char *OBJ_MDLS[] = {
int main(int argc, char** argv) {
window_create(85, WINDOW_MSAA8);
// window_fps_unlock();
window_fps_unlock();
camera_t cam = camera(); {
cam.position = vec3(0, 7.5, 15);
@ -103,7 +103,7 @@ int main(int argc, char** argv) {
if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f);
vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active);
vec3 wasdec = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-input(KEY_C),input(KEY_W)-input(KEY_S)), cam.speed);
camera_moveby(&cam, wasdec);
camera_moveby(&cam, scale3(wasdec, window_delta() * 60));
camera_fps(&cam, mouse.x,mouse.y);
enum {

File diff suppressed because it is too large Load Diff

2
depot

@ -1 +1 @@
Subproject commit 132e0cc46f06576f580ea1202aea75fbf64ff13d
Subproject commit 9d9b137bf427e9144c6a47de017058d884636a27

View File

@ -17330,6 +17330,7 @@ typedef struct shadowmap_t {
int cascade_index;
unsigned shadow_technique;
float cascade_splits[NUM_SHADOW_CASCADES];
frustum shadow_frustum;
// signals
bool skip_render;
@ -17994,6 +17995,7 @@ typedef struct camera_t {
float yaw, pitch, roll; // mirror of (x,y) lookdir in deg;
float speed, fov; // fov in deg(45)
float near_clip, far_clip;
float frustum_fov_multiplier;
float move_friction, move_damping;
float look_friction, look_damping;
@ -18015,6 +18017,7 @@ API void camera_fps2(camera_t *cam, float yaw, float pitch, float roll);
API void camera_orbit(camera_t *cam, float yaw, float pitch, float inc_distance);
API void camera_lookat(camera_t *cam, vec3 target);
API void camera_enable(camera_t *cam);
API frustum camera_frustum_build(camera_t *cam);
API camera_t *camera_get_active();
API int ui_camera(camera_t *cam);
@ -383893,6 +383896,8 @@ void shadowmap_light(shadowmap_t *s, light_t *l, mat44 cam_proj, mat44 cam_view)
unsigned texture_width = s->shadow_technique == SHADOW_VSM ? s->vsm_texture_width : s->csm_texture_width;
glViewport(0, 0, texture_width, texture_width);
s->shadow_frustum = frustum_build(s->PV);
}
}
@ -387163,14 +387168,50 @@ void model_clear_frustum(model_t *m) {
m->frustum_enabled = 0;
}
#define GLOBAL_FRUSTUM_ENABLED 1
#define GLOBAL_FRUSTUM_FOV_MULTIPLIER 1.5f
static frustum global_frustum;
static mat44 global_frustum_stored_mat_proj;
static mat44 global_frustum_stored_mat_view;
static inline
bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
bool model_is_visible(model_t m, int mesh, mat44 model_mat, mat44 proj, mat44 view) {
if(!m.iqm) return false;
if(!m.frustum_enabled) return true;
bool is_enabled = m.frustum_enabled;
frustum fr = m.frustum_state;
if (active_shadowmap) {
is_enabled = true;
fr = active_shadowmap->shadow_frustum;
}
#if GLOBAL_FRUSTUM_ENABLED
if (!is_enabled) { /* custom frustum not provided, let's compute one from input call */
if (memcmp(global_frustum_stored_mat_proj, proj, sizeof(mat44)) != 0 ||
memcmp(global_frustum_stored_mat_view, view, sizeof(mat44)) != 0) {
copy44(global_frustum_stored_mat_proj, proj);
copy44(global_frustum_stored_mat_view, view);
mat44 proj_modified;
copy44(proj_modified, proj);
// Increase FOV by 1.5
float fov_scale = 1.0f / GLOBAL_FRUSTUM_FOV_MULTIPLIER;
proj_modified[0] *= fov_scale;
proj_modified[5] *= fov_scale;
mat44 projview; multiply44x2(projview, proj_modified, view);
global_frustum = frustum_build(projview);
}
fr = global_frustum;
is_enabled = true;
}
#endif
if(!is_enabled) return true;
sphere s; s.c = transform344(model_mat, m.meshcenters[mesh]); s.r = m.meshradii[mesh];
if (!frustum_test_sphere(m.frustum_state, s)) {
if (!frustum_test_sphere(fr, s)) {
return false;
}
@ -387184,7 +387225,7 @@ bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
ddraw_position(s.c, 3.0f);
#endif
if (!frustum_test_aabb(m.frustum_state, box)) {
if (!frustum_test_aabb(fr, box)) {
return false;
}
@ -387192,7 +387233,7 @@ bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
}
static
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) {
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat, mat44 proj, mat44 view) {
if(!m.iqm) return;
iqm_t *q = m.iqm;
@ -387224,13 +387265,13 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_
if (rs_idx > RENDER_PASS_OVERRIDES_BEGIN) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
array_push(drawcalls, (drawcall_t){i, -1});
}
} else {
if(pass == -1 || pass == RENDER_PASS_OPAQUE) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
// collect opaque drawcalls
if (required_rs[i] == RENDER_PASS_OPAQUE) {
@ -387247,7 +387288,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_
if(pass == -1 || pass == RENDER_PASS_TRANSPARENT) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
// collect transparent drawcalls
if (required_rs[i] == RENDER_PASS_TRANSPARENT) {
@ -387344,7 +387385,7 @@ void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* model
}
model_set_uniforms(m, shader > 0 ? shader : m.program, mv, proj, view, models[0]);
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0]);
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0], proj, view);
}
void model_render_instanced(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count) {
@ -388910,6 +388951,7 @@ camera_t camera() {
cam.position = vec3(10,10,10);
cam.updir = vec3(0,1,0);
cam.fov = 45;
cam.frustum_fov_multiplier = 1.5f;
cam.orthographic = false;
cam.distance = 3; // len3(cam.position);
cam.near_clip = 0.1f;
@ -389002,6 +389044,19 @@ void camera_enable(camera_t *cam) {
camera_fps(cam, 0, 0);
}
frustum camera_frustum_build(camera_t *cam) {
float aspect = window_width() / ((float)window_height()+!window_height());
float fov = cam->fov * cam->frustum_fov_multiplier;
mat44 proj;
if( cam->orthographic ) {
ortho44(proj, -fov * aspect, fov * aspect, -fov, fov, cam->near_clip, cam->far_clip);
} else {
perspective44(proj, fov, aspect, cam->near_clip, cam->far_clip);
}
mat44 projview; multiply44x2(projview, proj, cam->view);
return frustum_build(projview);
}
void camera_fov(camera_t *cam, float fov) {
last_camera = cam;
@ -389335,21 +389390,7 @@ void scene_render(int flags) {
sm = NULL;
}
#if 1
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum frustum_state = frustum_build(projview);
#else
static frustum frustum_state;
do_once {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
if (input_down(KEY_T)) {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
#endif
frustum frustum_state = camera_frustum_build(cam);
if(flags & SCENE_BACKGROUND) {
if(last_scene->skybox.program) {

View File

@ -2073,6 +2073,8 @@ void shadowmap_light(shadowmap_t *s, light_t *l, mat44 cam_proj, mat44 cam_view)
unsigned texture_width = s->shadow_technique == SHADOW_VSM ? s->vsm_texture_width : s->csm_texture_width;
glViewport(0, 0, texture_width, texture_width);
s->shadow_frustum = frustum_build(s->PV);
}
}
@ -5343,14 +5345,50 @@ void model_clear_frustum(model_t *m) {
m->frustum_enabled = 0;
}
#define GLOBAL_FRUSTUM_ENABLED 1
#define GLOBAL_FRUSTUM_FOV_MULTIPLIER 1.5f
static frustum global_frustum;
static mat44 global_frustum_stored_mat_proj;
static mat44 global_frustum_stored_mat_view;
static inline
bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
bool model_is_visible(model_t m, int mesh, mat44 model_mat, mat44 proj, mat44 view) {
if(!m.iqm) return false;
if(!m.frustum_enabled) return true;
bool is_enabled = m.frustum_enabled;
frustum fr = m.frustum_state;
if (active_shadowmap) {
is_enabled = true;
fr = active_shadowmap->shadow_frustum;
}
#if GLOBAL_FRUSTUM_ENABLED
if (!is_enabled) { /* custom frustum not provided, let's compute one from input call */
if (memcmp(global_frustum_stored_mat_proj, proj, sizeof(mat44)) != 0 ||
memcmp(global_frustum_stored_mat_view, view, sizeof(mat44)) != 0) {
copy44(global_frustum_stored_mat_proj, proj);
copy44(global_frustum_stored_mat_view, view);
mat44 proj_modified;
copy44(proj_modified, proj);
// Increase FOV by 1.5
float fov_scale = 1.0f / GLOBAL_FRUSTUM_FOV_MULTIPLIER;
proj_modified[0] *= fov_scale;
proj_modified[5] *= fov_scale;
mat44 projview; multiply44x2(projview, proj_modified, view);
global_frustum = frustum_build(projview);
}
fr = global_frustum;
is_enabled = true;
}
#endif
if(!is_enabled) return true;
sphere s; s.c = transform344(model_mat, m.meshcenters[mesh]); s.r = m.meshradii[mesh];
if (!frustum_test_sphere(m.frustum_state, s)) {
if (!frustum_test_sphere(fr, s)) {
return false;
}
@ -5364,7 +5402,7 @@ bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
ddraw_position(s.c, 3.0f);
#endif
if (!frustum_test_aabb(m.frustum_state, box)) {
if (!frustum_test_aabb(fr, box)) {
return false;
}
@ -5372,7 +5410,7 @@ bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
}
static
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) {
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat, mat44 proj, mat44 view) {
if(!m.iqm) return;
iqm_t *q = m.iqm;
@ -5404,13 +5442,13 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_
if (rs_idx > RENDER_PASS_OVERRIDES_BEGIN) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
array_push(drawcalls, (drawcall_t){i, -1});
}
} else {
if(pass == -1 || pass == RENDER_PASS_OPAQUE) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
// collect opaque drawcalls
if (required_rs[i] == RENDER_PASS_OPAQUE) {
@ -5427,7 +5465,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_
if(pass == -1 || pass == RENDER_PASS_TRANSPARENT) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
// collect transparent drawcalls
if (required_rs[i] == RENDER_PASS_TRANSPARENT) {
@ -5524,7 +5562,7 @@ void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* model
}
model_set_uniforms(m, shader > 0 ? shader : m.program, mv, proj, view, models[0]);
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0]);
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0], proj, view);
}
void model_render_instanced(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count) {

View File

@ -362,6 +362,7 @@ typedef struct shadowmap_t {
int cascade_index;
unsigned shadow_technique;
float cascade_splits[NUM_SHADOW_CASCADES];
frustum shadow_frustum;
// signals
bool skip_render;

View File

@ -12,6 +12,7 @@ camera_t camera() {
cam.position = vec3(10,10,10);
cam.updir = vec3(0,1,0);
cam.fov = 45;
cam.frustum_fov_multiplier = 1.5f;
cam.orthographic = false;
cam.distance = 3; // len3(cam.position);
cam.near_clip = 0.1f;
@ -104,6 +105,19 @@ void camera_enable(camera_t *cam) {
camera_fps(cam, 0, 0);
}
frustum camera_frustum_build(camera_t *cam) {
float aspect = window_width() / ((float)window_height()+!window_height());
float fov = cam->fov * cam->frustum_fov_multiplier;
mat44 proj;
if( cam->orthographic ) {
ortho44(proj, -fov * aspect, fov * aspect, -fov, fov, cam->near_clip, cam->far_clip);
} else {
perspective44(proj, fov, aspect, cam->near_clip, cam->far_clip);
}
mat44 projview; multiply44x2(projview, proj, cam->view);
return frustum_build(projview);
}
void camera_fov(camera_t *cam, float fov) {
last_camera = cam;
@ -437,21 +451,7 @@ void scene_render(int flags) {
sm = NULL;
}
#if 1
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum frustum_state = frustum_build(projview);
#else
static frustum frustum_state;
do_once {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
if (input_down(KEY_T)) {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
#endif
frustum frustum_state = camera_frustum_build(cam);
if(flags & SCENE_BACKGROUND) {
if(last_scene->skybox.program) {

View File

@ -10,6 +10,7 @@ typedef struct camera_t {
float yaw, pitch, roll; // mirror of (x,y) lookdir in deg;
float speed, fov; // fov in deg(45)
float near_clip, far_clip;
float frustum_fov_multiplier;
float move_friction, move_damping;
float look_friction, look_damping;
@ -31,6 +32,7 @@ API void camera_fps2(camera_t *cam, float yaw, float pitch, float roll);
API void camera_orbit(camera_t *cam, float yaw, float pitch, float inc_distance);
API void camera_lookat(camera_t *cam, vec3 target);
API void camera_enable(camera_t *cam);
API frustum camera_frustum_build(camera_t *cam);
API camera_t *camera_get_active();
API int ui_camera(camera_t *cam);

View File

@ -18927,6 +18927,8 @@ void shadowmap_light(shadowmap_t *s, light_t *l, mat44 cam_proj, mat44 cam_view)
unsigned texture_width = s->shadow_technique == SHADOW_VSM ? s->vsm_texture_width : s->csm_texture_width;
glViewport(0, 0, texture_width, texture_width);
s->shadow_frustum = frustum_build(s->PV);
}
}
@ -22197,14 +22199,50 @@ void model_clear_frustum(model_t *m) {
m->frustum_enabled = 0;
}
#define GLOBAL_FRUSTUM_ENABLED 1
#define GLOBAL_FRUSTUM_FOV_MULTIPLIER 1.5f
static frustum global_frustum;
static mat44 global_frustum_stored_mat_proj;
static mat44 global_frustum_stored_mat_view;
static inline
bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
bool model_is_visible(model_t m, int mesh, mat44 model_mat, mat44 proj, mat44 view) {
if(!m.iqm) return false;
if(!m.frustum_enabled) return true;
bool is_enabled = m.frustum_enabled;
frustum fr = m.frustum_state;
if (active_shadowmap) {
is_enabled = true;
fr = active_shadowmap->shadow_frustum;
}
#if GLOBAL_FRUSTUM_ENABLED
if (!is_enabled) { /* custom frustum not provided, let's compute one from input call */
if (memcmp(global_frustum_stored_mat_proj, proj, sizeof(mat44)) != 0 ||
memcmp(global_frustum_stored_mat_view, view, sizeof(mat44)) != 0) {
copy44(global_frustum_stored_mat_proj, proj);
copy44(global_frustum_stored_mat_view, view);
mat44 proj_modified;
copy44(proj_modified, proj);
// Increase FOV by 1.5
float fov_scale = 1.0f / GLOBAL_FRUSTUM_FOV_MULTIPLIER;
proj_modified[0] *= fov_scale;
proj_modified[5] *= fov_scale;
mat44 projview; multiply44x2(projview, proj_modified, view);
global_frustum = frustum_build(projview);
}
fr = global_frustum;
is_enabled = true;
}
#endif
if(!is_enabled) return true;
sphere s; s.c = transform344(model_mat, m.meshcenters[mesh]); s.r = m.meshradii[mesh];
if (!frustum_test_sphere(m.frustum_state, s)) {
if (!frustum_test_sphere(fr, s)) {
return false;
}
@ -22218,7 +22256,7 @@ bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
ddraw_position(s.c, 3.0f);
#endif
if (!frustum_test_aabb(m.frustum_state, box)) {
if (!frustum_test_aabb(fr, box)) {
return false;
}
@ -22226,7 +22264,7 @@ bool model_is_visible(model_t m, int mesh, mat44 model_mat) {
}
static
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) {
void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat, mat44 proj, mat44 view) {
if(!m.iqm) return;
iqm_t *q = m.iqm;
@ -22258,13 +22296,13 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_
if (rs_idx > RENDER_PASS_OVERRIDES_BEGIN) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
array_push(drawcalls, (drawcall_t){i, -1});
}
} else {
if(pass == -1 || pass == RENDER_PASS_OPAQUE) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
// collect opaque drawcalls
if (required_rs[i] == RENDER_PASS_OPAQUE) {
@ -22281,7 +22319,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_
if(pass == -1 || pass == RENDER_PASS_TRANSPARENT) {
for(int i = 0; i < q->nummeshes; i++) {
if (!model_is_visible(m, i, model_mat)) continue;
if (!model_is_visible(m, i, model_mat, proj, view)) continue;
// collect transparent drawcalls
if (required_rs[i] == RENDER_PASS_TRANSPARENT) {
@ -22378,7 +22416,7 @@ void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* model
}
model_set_uniforms(m, shader > 0 ? shader : m.program, mv, proj, view, models[0]);
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0]);
model_draw_call(m, shader > 0 ? shader : m.program, pass, pos44(view), models[0], proj, view);
}
void model_render_instanced(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count) {
@ -23944,6 +23982,7 @@ camera_t camera() {
cam.position = vec3(10,10,10);
cam.updir = vec3(0,1,0);
cam.fov = 45;
cam.frustum_fov_multiplier = 1.5f;
cam.orthographic = false;
cam.distance = 3; // len3(cam.position);
cam.near_clip = 0.1f;
@ -24036,6 +24075,19 @@ void camera_enable(camera_t *cam) {
camera_fps(cam, 0, 0);
}
frustum camera_frustum_build(camera_t *cam) {
float aspect = window_width() / ((float)window_height()+!window_height());
float fov = cam->fov * cam->frustum_fov_multiplier;
mat44 proj;
if( cam->orthographic ) {
ortho44(proj, -fov * aspect, fov * aspect, -fov, fov, cam->near_clip, cam->far_clip);
} else {
perspective44(proj, fov, aspect, cam->near_clip, cam->far_clip);
}
mat44 projview; multiply44x2(projview, proj, cam->view);
return frustum_build(projview);
}
void camera_fov(camera_t *cam, float fov) {
last_camera = cam;
@ -24369,21 +24421,7 @@ void scene_render(int flags) {
sm = NULL;
}
#if 1
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum frustum_state = frustum_build(projview);
#else
static frustum frustum_state;
do_once {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
if (input_down(KEY_T)) {
mat44 projview; multiply44x2(projview, cam->proj, cam->view);
frustum_state = frustum_build(projview);
}
#endif
frustum frustum_state = camera_frustum_build(cam);
if(flags & SCENE_BACKGROUND) {
if(last_scene->skybox.program) {

View File

@ -3397,6 +3397,7 @@ typedef struct shadowmap_t {
int cascade_index;
unsigned shadow_technique;
float cascade_splits[NUM_SHADOW_CASCADES];
frustum shadow_frustum;
// signals
bool skip_render;
@ -4061,6 +4062,7 @@ typedef struct camera_t {
float yaw, pitch, roll; // mirror of (x,y) lookdir in deg;
float speed, fov; // fov in deg(45)
float near_clip, far_clip;
float frustum_fov_multiplier;
float move_friction, move_damping;
float look_friction, look_damping;
@ -4082,6 +4084,7 @@ API void camera_fps2(camera_t *cam, float yaw, float pitch, float roll);
API void camera_orbit(camera_t *cam, float yaw, float pitch, float inc_distance);
API void camera_lookat(camera_t *cam, vec3 target);
API void camera_enable(camera_t *cam);
API frustum camera_frustum_build(camera_t *cam);
API camera_t *camera_get_active();
API int ui_camera(camera_t *cam);