improve star projection

This commit is contained in:
asrael 2026-01-23 11:04:05 -06:00
parent c771fa665d
commit 28c3e7ee04
22 changed files with 612 additions and 660 deletions

View file

@ -496,7 +496,7 @@
(pxl8.end_frame_3d)) (pxl8.end_frame_3d))
(sky.render-stars cam-yaw cam-pitch 1.0 last-dt) (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt)
(let [cx (/ (pxl8.get_width) 2) (let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2) cy (/ (pxl8.get_height) 2)

View file

@ -40,20 +40,20 @@
(pxl8.gui_window 200 100 240 200 "pxl8 demo") (pxl8.gui_window 200 100 240 200 "pxl8 demo")
(when (gui:button 1 215 140 210 30 "Resume") (when (gui:button 1 215 147 210 30 "Resume")
(hide)) (hide))
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")] (let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
(when (gui:button 3 215 175 210 30 music-label) (when (gui:button 3 215 182 210 30 music-label)
(if (music.is-playing) (if (music.is-playing)
(music.stop) (music.stop)
(music.start)))) (music.start))))
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
(when (gui:button 4 215 210 210 30 wire-label) (when (gui:button 4 215 217 210 30 wire-label)
(set wireframe (not wireframe)))) (set wireframe (not wireframe))))
(when (gui:button 2 215 245 210 30 "Quit") (when (gui:button 2 215 252 210 30 "Quit")
(pxl8.quit)) (pxl8.quit))
(if (gui:is_hovering) (if (gui:is_hovering)

View file

@ -1,4 +1,3 @@
(local ffi (require :ffi))
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local effects (require :pxl8.effects)) (local effects (require :pxl8.effects))
@ -21,10 +20,13 @@
;; Blue/magic: indices 176-178 (purples - brightest of the range) ;; Blue/magic: indices 176-178 (purples - brightest of the range)
(local IDX_MAGIC 176) (local IDX_MAGIC 176)
(var sky-mesh nil)
(var star-time 0)
(var last-gradient-key nil) (var last-gradient-key nil)
(var random-stars []) (var random-stars [])
(var sky-mesh nil)
(var star-count 0)
(var star-directions nil)
(var star-projected nil)
(var star-time 0)
(var tiny-stars []) (var tiny-stars [])
(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b] (fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
@ -161,99 +163,88 @@
(when (> dy -0.1) (when (> dy -0.1)
(table.insert tiny-stars {:dx dx :dy dy :dz dz (table.insert tiny-stars {:dx dx :dy dy :dz dz
:brightness brightness :brightness brightness
:color color})))))) :color color})))))
(fn project-direction [dir-x dir-y dir-z yaw pitch cos-rot sin-rot width height] (set star-count (+ (length tiny-stars) (length random-stars)))
(let [rot-x (- (* dir-x cos-rot) (* dir-z sin-rot)) (set star-directions (pxl8.create_vec3_array star-count))
rot-z (+ (* dir-x sin-rot) (* dir-z cos-rot)) (set star-projected (pxl8.create_vec3_array star-count))
cos-yaw (math.cos yaw)
sin-yaw (math.sin yaw)
cos-pitch (math.cos pitch)
sin-pitch (math.sin pitch)
rotated-x (+ (* rot-x cos-yaw) (* rot-z sin-yaw))
rotated-z (+ (* (- rot-x) sin-yaw) (* rot-z cos-yaw))
rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch))
final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))]
(when (> final-z 0.01)
(let [fov 1.047
aspect (/ width height)
half-fov-tan (math.tan (* fov 0.5))
ndc-x (/ rotated-x (* final-z half-fov-tan aspect))
ndc-y (/ rotated-y (* final-z half-fov-tan))]
(when (and (>= ndc-x -1) (<= ndc-x 1) (>= ndc-y -1) (<= ndc-y 1))
{:x (math.floor (* (+ 1 ndc-x) 0.5 width))
:y (math.floor (* (- 1 ndc-y) 0.5 height))})))))
(fn render-stars [yaw pitch intensity dt] (var idx 0)
(each [_ star (ipairs tiny-stars)]
(let [dir (. star-directions idx)]
(set dir.x star.dx)
(set dir.y star.dy)
(set dir.z star.dz))
(set idx (+ idx 1)))
(each [_ star (ipairs random-stars)]
(let [dir (. star-directions idx)]
(set dir.x star.dx)
(set dir.y star.dy)
(set dir.z star.dz))
(set idx (+ idx 1))))
(fn render-stars [cam-x cam-y cam-z intensity dt]
(set star-time (+ star-time (or dt 0))) (set star-time (+ star-time (or dt 0)))
(when (> intensity 0) (when (and (> intensity 0) (> star-count 0))
(let [width (pxl8.get_width) (let [glows []
height (pxl8.get_height)
glows []
fade-in (* intensity intensity) fade-in (* intensity intensity)
time-factor (/ star-time 60) time-factor (/ star-time 60)
star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD) star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD)
cos-rot (math.cos star-rotation) t (pxl8.mat4_translate cam-x cam-y cam-z)
sin-rot (math.sin star-rotation)] r (pxl8.mat4_rotate_y star-rotation)
s (pxl8.mat4_scale sky-radius sky-radius sky-radius)
transform (pxl8.mat4_multiply t (pxl8.mat4_multiply r s))
tiny-count (length tiny-stars)]
(each [i star (ipairs tiny-stars)] (pxl8.project_points star-directions star-projected star-count transform)
(let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
(when screen (for [i 0 (- tiny-count 1)]
(let [int (math.floor (* star.brightness fade-in))] (let [screen (. star-projected i)]
(when (> screen.z 0)
(let [star (. tiny-stars (+ i 1))
int (math.floor (* star.brightness fade-in))]
(when (> int 8) (when (> int 8)
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x (math.floor screen.x) :y (math.floor screen.y)
:radius 1 :radius 1
:intensity int :intensity int
:color star.color :color star.color
:shape effects.GLOW_CIRCLE})))))) :shape effects.GLOW_CIRCLE}))))))
(each [i star (ipairs random-stars)] (for [i 0 (- (length random-stars) 1)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)] (let [screen (. star-projected (+ tiny-count i))]
(when screen (when (> screen.z 0)
(let [phase (+ (* i 2.137) (* time-factor 3.0)) (let [star (. random-stars (+ i 1))
phase (+ (* (+ i 1) 2.137) (* time-factor 3.0))
twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28)))) twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
int (math.floor (* star.brightness fade-in twinkle))] int (math.floor (* star.brightness fade-in twinkle))
sx (math.floor screen.x)
sy (math.floor screen.y)]
(if (> star.brightness 220) (if (> star.brightness 220)
(do (do
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x sx :y sy :radius 3
:radius 3
:intensity (math.floor (* int 1.5)) :intensity (math.floor (* int 1.5))
:color star.color :color star.color :shape effects.GLOW_DIAMOND})
:shape effects.GLOW_DIAMOND}) (table.insert glows {:x sx :y sy :radius 5
(table.insert glows {:x screen.x :y screen.y
:radius 5
:intensity (math.floor (/ int 2)) :intensity (math.floor (/ int 2))
:color star.color :color star.color :shape effects.GLOW_CIRCLE}))
:shape effects.GLOW_CIRCLE}))
(> star.brightness 180) (> star.brightness 180)
(do (do
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x sx :y sy :radius 2 :intensity int
:radius 2 :color star.color :shape effects.GLOW_DIAMOND})
:intensity int (table.insert glows {:x sx :y sy :radius 4
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 4
:intensity (math.floor (/ int 3)) :intensity (math.floor (/ int 3))
:color star.color :color star.color :shape effects.GLOW_CIRCLE}))
:shape effects.GLOW_CIRCLE}))
(> star.brightness 120) (> star.brightness 120)
(do (do
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x sx :y sy :radius 2
:radius 2
:intensity (math.floor (* int 0.67)) :intensity (math.floor (* int 0.67))
:color star.color :color star.color :shape effects.GLOW_DIAMOND})
:shape effects.GLOW_DIAMOND}) (table.insert glows {:x sx :y sy :radius 3
(table.insert glows {:x screen.x :y screen.y
:radius 3
:intensity (math.floor (/ int 4)) :intensity (math.floor (/ int 4))
:color star.color :color star.color :shape effects.GLOW_CIRCLE}))
:shape effects.GLOW_CIRCLE})) (table.insert glows {:x sx :y sy :radius 2
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity (math.floor (* int 0.5)) :intensity (math.floor (* int 0.5))
:color star.color :color star.color :shape effects.GLOW_CIRCLE}))))))
:shape effects.GLOW_CIRCLE}))))))
(when (> (length glows) 0) (when (> (length glows) 0)
(effects.glows glows))))) (effects.glows glows)))))

5
demo/profile_3d.fnl Normal file
View file

@ -0,0 +1,5 @@
(local first_person3d (require :mod.first_person3d))
(global init first_person3d.init)
(global update first_person3d.update)
(global frame first_person3d.frame)

View file

@ -121,7 +121,7 @@ pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) {
if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) { if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) {
return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far); return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far);
} else { } else {
return pxl8_mat4_ortho( return pxl8_mat4_orthographic(
cam->ortho_left, cam->ortho_right, cam->ortho_left, cam->ortho_right,
cam->ortho_bottom, cam->ortho_top, cam->ortho_bottom, cam->ortho_top,
cam->near, cam->far cam->near, cam->far
@ -219,9 +219,9 @@ pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, p
pxl8_mat4 view = pxl8_3d_camera_get_view(cam); pxl8_mat4 view = pxl8_3d_camera_get_view(cam);
pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam); pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam);
pxl8_mat4 vp = pxl8_mat4_mul(proj, view); pxl8_mat4 vp = pxl8_mat4_multiply(proj, view);
pxl8_vec4 clip = pxl8_mat4_mul_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f}); pxl8_vec4 clip = pxl8_mat4_multiply_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f});
if (clip.w <= 0.0f) return result; if (clip.w <= 0.0f) return result;

View file

@ -215,7 +215,7 @@ void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) {
void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) { void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) {
if (!cpu || !frame) return; if (!cpu || !frame) return;
cpu->frame = *frame; cpu->frame = *frame;
cpu->mvp = pxl8_mat4_mul(frame->projection, frame->view); cpu->mvp = pxl8_mat4_multiply(frame->projection, frame->view);
} }
void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) { void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) {
@ -368,8 +368,8 @@ void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8
if (!cpu || !cpu->current_target) return; if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target; pxl8_cpu_render_target* render_target = cpu->current_target;
pxl8_vec4 c0 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f}); pxl8_vec4 c0 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f}); pxl8_vec4 c1 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f});
if (c0.w <= 0.0f || c1.w <= 0.0f) return; if (c0.w <= 0.0f || c1.w <= 0.0f) return;
@ -1068,8 +1068,7 @@ static void dispatch_triangle(
const pxl8_atlas* textures, const pxl8_gfx_material* material const pxl8_atlas* textures, const pxl8_gfx_material* material
) { ) {
if (material->wireframe) { if (material->wireframe) {
u8 color = (material->texture_id > 0) ? (u8)material->texture_id : 15; rasterize_triangle_wireframe(cpu, vo0, vo1, vo2, 15, material->double_sided);
rasterize_triangle_wireframe(cpu, vo0, vo1, vo2, color, material->double_sided);
return; return;
} }
@ -1100,8 +1099,8 @@ void pxl8_cpu_draw_mesh(
) { ) {
if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return; if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return;
pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, *model); pxl8_mat4 mv = pxl8_mat4_multiply(cpu->frame.view, *model);
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, mv); pxl8_mat4 mvp = pxl8_mat4_multiply(cpu->frame.projection, mv);
f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f; f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f;
@ -1120,13 +1119,13 @@ void pxl8_cpu_draw_mesh(
pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->position.z, 1.0f}; pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->position.z, 1.0f};
pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f}; pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f};
vo0.clip_pos = pxl8_mat4_mul_vec4(mvp, p0); vo0.clip_pos = pxl8_mat4_multiply_vec4(mvp, p0);
vo1.clip_pos = pxl8_mat4_mul_vec4(mvp, p1); vo1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1);
vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2); vo2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2);
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(*model, p0); pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(*model, p0);
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(*model, p1); pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(*model, p1);
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(*model, p2); pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(*model, p2);
vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z}; vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z};
vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z}; vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z};
vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z}; vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z};
@ -1144,9 +1143,9 @@ void pxl8_cpu_draw_mesh(
vo2.color = v2->color; vo2.color = v2->color;
if (material->dynamic_lighting) { if (material->dynamic_lighting) {
pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v0->normal)); pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v0->normal));
pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v1->normal)); pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v1->normal));
pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v2->normal)); pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v2->normal));
pxl8_light_result lr0 = calc_vertex_light(vo0.world_pos, n0, &cpu->frame); pxl8_light_result lr0 = calc_vertex_light(vo0.world_pos, n0, &cpu->frame);
pxl8_light_result lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame); pxl8_light_result lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame);
@ -1177,44 +1176,6 @@ void pxl8_cpu_draw_mesh(
} }
} }
void pxl8_cpu_draw_mesh_wireframe(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
u8 color
) {
if (!cpu || !cpu->current_target || !mesh || mesh->index_count < 3) return;
pxl8_cpu_render_target* render_target = cpu->current_target;
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, pxl8_mat4_mul(cpu->frame.view, model));
for (u32 i = 0; i < mesh->index_count; i += 3) {
const pxl8_vertex* v0 = &mesh->vertices[mesh->indices[i]];
const pxl8_vertex* v1 = &mesh->vertices[mesh->indices[i + 1]];
const pxl8_vertex* v2 = &mesh->vertices[mesh->indices[i + 2]];
pxl8_vec4 c0 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v0->position.x, v0->position.y, v0->position.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v1->position.x, v1->position.y, v1->position.z, 1.0f});
pxl8_vec4 c2 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v2->position.x, v2->position.y, v2->position.z, 1.0f});
if (c0.w <= 0.0f || c1.w <= 0.0f || c2.w <= 0.0f) continue;
f32 hw = (f32)render_target->width * 0.5f;
f32 hh = (f32)render_target->height * 0.5f;
i32 x0 = (i32)(hw + c0.x / c0.w * hw);
i32 y0 = (i32)(hh - c0.y / c0.w * hh);
i32 x1 = (i32)(hw + c1.x / c1.w * hw);
i32 y1 = (i32)(hh - c1.y / c1.w * hh);
i32 x2 = (i32)(hw + c2.x / c2.w * hw);
i32 y2 = (i32)(hh - c2.y / c2.w * hh);
pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color);
pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color);
pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color);
}
}
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) { u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) {
if (!cpu || cpu->target_stack_depth == 0) return NULL; if (!cpu || cpu->target_stack_depth == 0) return NULL;
return cpu->target_stack[0]->framebuffer; return cpu->target_stack[0]->framebuffer;

View file

@ -57,13 +57,6 @@ void pxl8_cpu_draw_mesh(
const pxl8_atlas* textures const pxl8_atlas* textures
); );
void pxl8_cpu_draw_mesh_wireframe(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
u8 color
);
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu);
u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu); u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu);
u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu);

View file

@ -41,6 +41,7 @@ struct pxl8_gfx {
u32 sprite_cache_capacity; u32 sprite_cache_capacity;
u32 sprite_cache_count; u32 sprite_cache_count;
pxl8_viewport viewport; pxl8_viewport viewport;
pxl8_mat4 view_proj;
}; };
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
@ -611,8 +612,10 @@ void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.view); pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view);
gfx->frustum = pxl8_frustum_from_matrix(vp); gfx->frustum = pxl8_frustum_from_matrix(vp);
gfx->view_proj = vp;
switch (gfx->backend.type) { switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: case PXL8_GFX_BACKEND_CPU:
@ -628,6 +631,38 @@ const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) {
return &gfx->frustum; return &gfx->frustum;
} }
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return &gfx->view_proj;
}
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform) {
if (!gfx || !in || !out) return 0;
pxl8_mat4 mvp = transform ? pxl8_mat4_multiply(gfx->view_proj, *transform) : gfx->view_proj;
f32 hw = (f32)gfx->framebuffer_width * 0.5f;
f32 hh = (f32)gfx->framebuffer_height * 0.5f;
u32 visible = 0;
for (u32 i = 0; i < count; i++) {
pxl8_vec4 clip = pxl8_mat4_multiply_vec4(mvp, (pxl8_vec4){in[i].x, in[i].y, in[i].z, 1.0f});
if (clip.w <= 0.0f) {
out[i].z = -1.0f;
continue;
}
f32 inv_w = 1.0f / clip.w;
out[i].x = hw + clip.x * inv_w * hw;
out[i].y = hh - clip.y * inv_w * hh;
out[i].z = clip.w;
visible++;
}
return visible;
}
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) { void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) {
if (!gfx) return; if (!gfx) return;
switch (gfx->backend.type) { switch (gfx->backend.type) {
@ -672,17 +707,6 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
} }
} }
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color) {
if (!gfx || !mesh) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_mesh_wireframe(gfx->backend.cpu, mesh, model, color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
}
void pxl8_3d_end_frame(pxl8_gfx* gfx) { void pxl8_3d_end_frame(pxl8_gfx* gfx) {
if (!gfx) return; if (!gfx) return;
switch (gfx->backend.type) { switch (gfx->backend.type) {

View file

@ -60,10 +60,11 @@ void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx); void pxl8_3d_clear_depth(pxl8_gfx* gfx);
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material); void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
void pxl8_3d_end_frame(pxl8_gfx* gfx); void pxl8_3d_end_frame(pxl8_gfx* gfx);
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx); u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx); const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx);
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms); pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);

View file

@ -18,6 +18,12 @@ typedef enum pxl8_blend_mode {
} pxl8_blend_mode; } pxl8_blend_mode;
typedef struct pxl8_gfx_material { typedef struct pxl8_gfx_material {
char name[16];
pxl8_vec3 u_axis;
pxl8_vec3 v_axis;
f32 u_offset;
f32 v_offset;
u32 texture_id; u32 texture_id;
u32 lightmap_id; u32 lightmap_id;
u8 alpha; u8 alpha;

View file

@ -86,15 +86,17 @@ pxl8.create_anim_from_ase = anim.Anim.from_ase
pxl8.bounds = math.bounds pxl8.bounds = math.bounds
pxl8.Camera3D = gfx3d.Camera3D pxl8.Camera3D = gfx3d.Camera3D
pxl8.create_camera_3d = gfx3d.Camera3D.new pxl8.Mesh = gfx3d.Mesh
pxl8.begin_frame_3d = gfx3d.begin_frame pxl8.begin_frame_3d = gfx3d.begin_frame
pxl8.clear_3d = gfx3d.clear pxl8.clear_3d = gfx3d.clear
pxl8.clear_depth = gfx3d.clear_depth pxl8.clear_depth = gfx3d.clear_depth
pxl8.create_camera_3d = gfx3d.Camera3D.new
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.create_vec3_array = gfx3d.create_vec3_array
pxl8.draw_line_3d = gfx3d.draw_line pxl8.draw_line_3d = gfx3d.draw_line
pxl8.draw_mesh = gfx3d.draw_mesh pxl8.draw_mesh = gfx3d.draw_mesh
pxl8.end_frame_3d = gfx3d.end_frame pxl8.end_frame_3d = gfx3d.end_frame
pxl8.Mesh = gfx3d.Mesh pxl8.project_points = gfx3d.project_points
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.Compressor = sfx.Compressor pxl8.Compressor = sfx.Compressor
pxl8.create_compressor = sfx.Compressor.new pxl8.create_compressor = sfx.Compressor.new
@ -111,7 +113,9 @@ pxl8.gui_window = gui.window
pxl8.mat4_identity = math.mat4_identity pxl8.mat4_identity = math.mat4_identity
pxl8.mat4_lookat = math.mat4_lookat pxl8.mat4_lookat = math.mat4_lookat
pxl8.mat4_multiply = math.mat4_multiply pxl8.mat4_multiply = math.mat4_multiply
pxl8.mat4_ortho = math.mat4_ortho pxl8.mat4_multiply_vec3 = math.mat4_multiply_vec3
pxl8.mat4_multiply_vec4 = math.mat4_multiply_vec4
pxl8.mat4_orthographic = math.mat4_orthographic
pxl8.mat4_perspective = math.mat4_perspective pxl8.mat4_perspective = math.mat4_perspective
pxl8.mat4_rotate_x = math.mat4_rotate_x pxl8.mat4_rotate_x = math.mat4_rotate_x
pxl8.mat4_rotate_y = math.mat4_rotate_y pxl8.mat4_rotate_y = math.mat4_rotate_y

View file

@ -216,4 +216,12 @@ function gfx3d.end_frame()
C.pxl8_3d_end_frame(core.gfx) C.pxl8_3d_end_frame(core.gfx)
end end
function gfx3d.project_points(input, output, count, transform)
C.pxl8_3d_project_points(core.gfx, input, output, count, transform)
end
function gfx3d.create_vec3_array(count)
return ffi.new("pxl8_vec3[?]", count)
end
return gfx3d return gfx3d

View file

@ -11,8 +11,16 @@ function math.mat4_identity()
return C.pxl8_mat4_identity() return C.pxl8_mat4_identity()
end end
function math.mat4_mul(a, b) function math.mat4_multiply(a, b)
return C.pxl8_mat4_mul(a, b) return C.pxl8_mat4_multiply(a, b)
end
function math.mat4_multiply_vec3(m, v)
return C.pxl8_mat4_multiply_vec3(m, v)
end
function math.mat4_multiply_vec4(m, v)
return C.pxl8_mat4_multiply_vec4(m, v)
end end
function math.mat4_translate(x, y, z) function math.mat4_translate(x, y, z)
@ -35,8 +43,8 @@ function math.mat4_scale(x, y, z)
return C.pxl8_mat4_scale(x, y, z) return C.pxl8_mat4_scale(x, y, z)
end end
function math.mat4_ortho(left, right, bottom, top, near, far) function math.mat4_orthographic(left, right, bottom, top, near, far)
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far) return C.pxl8_mat4_orthographic(left, right, bottom, top, near, far)
end end
function math.mat4_perspective(fov, aspect, near, far) function math.mat4_perspective(fov, aspect, near, far)

View file

@ -85,8 +85,8 @@ function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radiu
return result.x, result.y, result.z return result.x, result.y, result.z
end end
function World:set_wireframe(enabled, color) function World:set_wireframe(enabled)
C.pxl8_world_set_wireframe(self._ptr, enabled, color or 15) C.pxl8_world_set_wireframe(self._ptr, enabled)
end end
function World:unload() function World:unload()

View file

@ -110,7 +110,7 @@ pxl8_mat4 pxl8_mat4_identity(void) {
return mat; return mat;
} }
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) { pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) {
pxl8_mat4 mat = {0}; pxl8_mat4 mat = {0};
for (i32 col = 0; col < 4; col++) { for (i32 col = 0; col < 4; col++) {
@ -126,7 +126,7 @@ pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) {
return mat; return mat;
} }
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) { pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) {
return (pxl8_vec4){ return (pxl8_vec4){
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w, .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w,
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w, .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w,
@ -135,7 +135,7 @@ pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) {
}; };
} }
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v) { pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v) {
return (pxl8_vec3){ return (pxl8_vec3){
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z, .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z,
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z, .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z,
@ -202,7 +202,7 @@ pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z) {
return mat; return mat;
} }
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) { pxl8_mat4 pxl8_mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
pxl8_mat4 mat = {0}; pxl8_mat4 mat = {0};
mat.m[0] = 2.0f / (right - left); mat.m[0] = 2.0f / (right - left);
@ -255,15 +255,15 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
pxl8_frustum frustum; pxl8_frustum frustum;
const f32* m = vp.m; const f32* m = vp.m;
frustum.planes[0].normal.x = m[3] - m[0]; frustum.planes[0].normal.x = m[3] + m[0];
frustum.planes[0].normal.y = m[7] - m[4]; frustum.planes[0].normal.y = m[7] + m[4];
frustum.planes[0].normal.z = m[11] - m[8]; frustum.planes[0].normal.z = m[11] + m[8];
frustum.planes[0].distance = m[15] - m[12]; frustum.planes[0].distance = m[15] + m[12];
frustum.planes[1].normal.x = m[3] + m[0]; frustum.planes[1].normal.x = m[3] - m[0];
frustum.planes[1].normal.y = m[7] + m[4]; frustum.planes[1].normal.y = m[7] - m[4];
frustum.planes[1].normal.z = m[11] + m[8]; frustum.planes[1].normal.z = m[11] - m[8];
frustum.planes[1].distance = m[15] + m[12]; frustum.planes[1].distance = m[15] - m[12];
frustum.planes[2].normal.x = m[3] + m[1]; frustum.planes[2].normal.x = m[3] + m[1];
frustum.planes[2].normal.y = m[7] + m[5]; frustum.planes[2].normal.y = m[7] + m[5];
@ -275,15 +275,15 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
frustum.planes[3].normal.z = m[11] - m[9]; frustum.planes[3].normal.z = m[11] - m[9];
frustum.planes[3].distance = m[15] - m[13]; frustum.planes[3].distance = m[15] - m[13];
frustum.planes[4].normal.x = m[3] - m[2]; frustum.planes[4].normal.x = m[3] + m[2];
frustum.planes[4].normal.y = m[7] - m[6]; frustum.planes[4].normal.y = m[7] + m[6];
frustum.planes[4].normal.z = m[11] - m[10]; frustum.planes[4].normal.z = m[11] + m[10];
frustum.planes[4].distance = m[15] - m[14]; frustum.planes[4].distance = m[15] + m[14];
frustum.planes[5].normal.x = m[3] + m[2]; frustum.planes[5].normal.x = m[3] - m[2];
frustum.planes[5].normal.y = m[7] + m[6]; frustum.planes[5].normal.y = m[7] - m[6];
frustum.planes[5].normal.z = m[11] + m[10]; frustum.planes[5].normal.z = m[11] - m[10];
frustum.planes[5].distance = m[15] + m[14]; frustum.planes[5].distance = m[15] - m[14];
for (i32 i = 0; i < 6; i++) { for (i32 i = 0; i < 6; i++) {
f32 len = pxl8_vec3_length(frustum.planes[i].normal); f32 len = pxl8_vec3_length(frustum.planes[i].normal);
@ -298,19 +298,21 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
} }
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) { bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) {
const f32 FRUSTUM_EPSILON = -75.0f;
for (i32 i = 0; i < 6; i++) { for (i32 i = 0; i < 6; i++) {
pxl8_vec3 normal = frustum->planes[i].normal; pxl8_vec3 normal = frustum->planes[i].normal;
f32 d = frustum->planes[i].distance; f32 d = frustum->planes[i].distance;
pxl8_vec3 p_vertex = { pxl8_vec3 p_vertex = {
(normal.x >= 0.0f) ? max.x : min.x, (normal.x > 0.0f) ? max.x : min.x,
(normal.y >= 0.0f) ? max.y : min.y, (normal.y > 0.0f) ? max.y : min.y,
(normal.z >= 0.0f) ? max.z : min.z (normal.z > 0.0f) ? max.z : min.z
}; };
f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d; f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d;
if (p_dist < 0.0f) { if (p_dist < FRUSTUM_EPSILON) {
return false; return false;
} }
} }

View file

@ -105,10 +105,10 @@ pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b);
pxl8_mat4 pxl8_mat4_identity(void); pxl8_mat4 pxl8_mat4_identity(void);
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up); pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b); pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v); pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v);
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v); pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far); pxl8_mat4 pxl8_mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far); pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle); pxl8_mat4 pxl8_mat4_rotate_x(f32 angle);
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle); pxl8_mat4 pxl8_mat4_rotate_y(f32 angle);

View file

@ -256,10 +256,16 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n" "void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n" "void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n" "void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
"\n" "\n"
"typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n" "typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n"
"\n" "\n"
"typedef struct pxl8_gfx_material {\n" "typedef struct pxl8_gfx_material {\n"
" char name[16];\n"
" pxl8_vec3 u_axis;\n"
" pxl8_vec3 v_axis;\n"
" f32 u_offset;\n"
" f32 v_offset;\n"
" u32 texture_id;\n" " u32 texture_id;\n"
" u32 lightmap_id;\n" " u32 lightmap_id;\n"
" u8 alpha;\n" " u8 alpha;\n"
@ -298,14 +304,15 @@ static const char* pxl8_ffi_cdefs =
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n" "u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n" "void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n" "void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
"void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);\n"
"\n" "\n"
"u32 pxl8_hash32(u32 x);\n" "u32 pxl8_hash32(u32 x);\n"
"\n" "\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n" "pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n" "pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
"pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);\n" "pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);\n"
"pxl8_mat4 pxl8_mat4_ortho(float left, float right, float bottom, float top, float near, float far);\n" "pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v);\n"
"pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);\n"
"pxl8_mat4 pxl8_mat4_orthographic(float left, float right, float bottom, float top, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n" "pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n" "pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n"
"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n" "pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n"
@ -421,7 +428,7 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_world_is_loaded(const pxl8_world* world);\n" "bool pxl8_world_is_loaded(const pxl8_world* world);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" "pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled, u8 color);\n" "void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n"
"\n" "\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" "typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
"pxl8_gui_state* pxl8_gui_state_create(void);\n" "pxl8_gui_state* pxl8_gui_state_create(void);\n"

View file

@ -40,12 +40,20 @@ typedef struct {
pxl8_bsp_chunk chunks[CHUNK_COUNT]; pxl8_bsp_chunk chunks[CHUNK_COUNT];
} pxl8_bsp_header; } pxl8_bsp_header;
typedef struct {
f32 x0, y0, x1, y1;
} screen_rect;
typedef struct {
u32 leaf;
screen_rect window;
} portal_queue_entry;
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) { static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
pxl8_vec3 v; f32 x = pxl8_read_f32(stream);
v.x = pxl8_read_f32(stream); f32 y = pxl8_read_f32(stream);
v.y = pxl8_read_f32(stream); f32 z = pxl8_read_f32(stream);
v.z = pxl8_read_f32(stream); return (pxl8_vec3){x, z, y};
return v;
} }
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) { static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
@ -75,25 +83,6 @@ static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_id
return *out_vert_idx < bsp->num_vertices; return *out_vert_idx < bsp->num_vertices;
} }
static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) {
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
i32 edge_idx = bsp->surfedges[surfedge_idx];
if (edge_idx >= 0) {
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[0];
*out_v1_idx = bsp->edges[edge_idx].vertex[1];
} else {
edge_idx = -edge_idx;
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[1];
*out_v1_idx = bsp->edges[edge_idx].vertex[0];
}
return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices;
}
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT; if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
@ -179,16 +168,20 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
chunk = &header.chunks[CHUNK_TEXINFO]; chunk = &header.chunks[CHUNK_TEXINFO];
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
bsp->num_texinfo = chunk->size / 40; bsp->num_materials = chunk->size / 40;
if (bsp->num_texinfo > 0) { if (bsp->num_materials > 0) {
bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo)); bsp->materials = calloc(bsp->num_materials, sizeof(pxl8_gfx_material));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_texinfo; i++) { for (u32 i = 0; i < bsp->num_materials; i++) {
bsp->texinfo[i].u_axis = read_vec3(&stream); bsp->materials[i].u_axis = read_vec3(&stream);
bsp->texinfo[i].u_offset = pxl8_read_f32(&stream); bsp->materials[i].u_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].v_axis = read_vec3(&stream); bsp->materials[i].v_axis = read_vec3(&stream);
bsp->texinfo[i].v_offset = pxl8_read_f32(&stream); bsp->materials[i].v_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].miptex = pxl8_read_u32(&stream); bsp->materials[i].texture_id = pxl8_read_u32(&stream);
bsp->materials[i].alpha = 255;
bsp->materials[i].dither = true;
bsp->materials[i].dynamic_lighting = true;
bsp->materials[i].double_sided = true;
} }
} }
@ -203,7 +196,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
bsp->faces[i].side = pxl8_read_u16(&stream); bsp->faces[i].side = pxl8_read_u16(&stream);
bsp->faces[i].first_edge = pxl8_read_u32(&stream); bsp->faces[i].first_edge = pxl8_read_u32(&stream);
bsp->faces[i].num_edges = pxl8_read_u16(&stream); bsp->faces[i].num_edges = pxl8_read_u16(&stream);
bsp->faces[i].texinfo_id = pxl8_read_u16(&stream); bsp->faces[i].material_id = pxl8_read_u16(&stream);
bsp->faces[i].styles[0] = pxl8_read_u8(&stream); bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
bsp->faces[i].styles[1] = pxl8_read_u8(&stream); bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
bsp->faces[i].styles[2] = pxl8_read_u8(&stream); bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
@ -225,8 +218,18 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
bsp->nodes[i].plane_id = pxl8_read_u32(&stream); bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
bsp->nodes[i].children[0] = pxl8_read_i16(&stream); bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
bsp->nodes[i].children[1] = pxl8_read_i16(&stream); bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream); i16 nx = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream); i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->nodes[i].mins[0] = nx;
bsp->nodes[i].mins[1] = nz;
bsp->nodes[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->nodes[i].maxs[0] = mx;
bsp->nodes[i].maxs[1] = mz;
bsp->nodes[i].maxs[2] = my;
bsp->nodes[i].first_face = pxl8_read_u16(&stream); bsp->nodes[i].first_face = pxl8_read_u16(&stream);
bsp->nodes[i].num_faces = pxl8_read_u16(&stream); bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
} }
@ -241,8 +244,18 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
for (u32 i = 0; i < bsp->num_leafs; i++) { for (u32 i = 0; i < bsp->num_leafs; i++) {
bsp->leafs[i].contents = pxl8_read_i32(&stream); bsp->leafs[i].contents = pxl8_read_i32(&stream);
bsp->leafs[i].visofs = pxl8_read_i32(&stream); bsp->leafs[i].visofs = pxl8_read_i32(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream); i16 nx = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream); i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->leafs[i].mins[0] = nx;
bsp->leafs[i].mins[1] = nz;
bsp->leafs[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->leafs[i].maxs[0] = mx;
bsp->leafs[i].maxs[1] = mz;
bsp->leafs[i].maxs[2] = my;
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream); bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream); bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream); for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
@ -267,8 +280,18 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model)); bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_models; i++) { for (u32 i = 0; i < bsp->num_models; i++) {
for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream); f32 minx = pxl8_read_f32(&stream);
for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream); f32 miny = pxl8_read_f32(&stream);
f32 minz = pxl8_read_f32(&stream);
bsp->models[i].mins[0] = minx;
bsp->models[i].mins[1] = minz;
bsp->models[i].mins[2] = miny;
f32 maxx = pxl8_read_f32(&stream);
f32 maxy = pxl8_read_f32(&stream);
f32 maxz = pxl8_read_f32(&stream);
bsp->models[i].maxs[0] = maxx;
bsp->models[i].maxs[1] = maxz;
bsp->models[i].maxs[2] = maxy;
bsp->models[i].origin = read_vec3(&stream); bsp->models[i].origin = read_vec3(&stream);
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream); for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
bsp->models[i].visleafs = pxl8_read_i32(&stream); bsp->models[i].visleafs = pxl8_read_i32(&stream);
@ -335,16 +358,18 @@ error_cleanup:
void pxl8_bsp_destroy(pxl8_bsp* bsp) { void pxl8_bsp_destroy(pxl8_bsp* bsp) {
if (!bsp) return; if (!bsp) return;
free(bsp->cell_portals);
free(bsp->edges); free(bsp->edges);
free(bsp->faces); free(bsp->faces);
free(bsp->leafs); free(bsp->leafs);
free(bsp->lightdata); free(bsp->lightdata);
free(bsp->marksurfaces); free(bsp->marksurfaces);
free(bsp->materials);
free(bsp->models); free(bsp->models);
free(bsp->nodes); free(bsp->nodes);
free(bsp->planes); free(bsp->planes);
free(bsp->render_face_flags);
free(bsp->surfedges); free(bsp->surfedges);
free(bsp->texinfo);
free(bsp->vertex_lights); free(bsp->vertex_lights);
free(bsp->vertices); free(bsp->vertices);
free(bsp->visdata); free(bsp->visdata);
@ -376,66 +401,71 @@ bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
i32 visofs = bsp->leafs[leaf_from].visofs; i32 visofs = bsp->leafs[leaf_from].visofs;
if (visofs < 0) return true; if (visofs < 0) return true;
u32 target_byte = leaf_to >> 3; u32 row_size = (bsp->num_leafs + 7) >> 3;
u32 target_bit = leaf_to & 7; u32 byte_idx = (u32)leaf_to >> 3;
u32 bit_idx = (u32)leaf_to & 7;
u32 pos = (u32)visofs; u8* vis = bsp->visdata + visofs;
u32 out_pos = 0; u8* vis_end = bsp->visdata + bsp->visdata_size;
u32 out = 0;
while (out_pos <= target_byte && pos < bsp->visdata_size) { while (out < row_size && vis < vis_end) {
u8 b = bsp->visdata[pos++]; if (*vis) {
if (b != 0) { if (out == byte_idx) {
if (out_pos == target_byte) { return (*vis & (1 << bit_idx)) != 0;
return (b & (1 << target_bit)) != 0;
} }
out_pos++; out++;
vis++;
} else { } else {
if (pos >= bsp->visdata_size) break; vis++;
u32 count = bsp->visdata[pos++]; if (vis >= vis_end) break;
if (out_pos + count > target_byte) { u32 count = *vis++;
if (out + count > byte_idx && byte_idx >= out) {
return false; return false;
} }
out_pos += count; out += count;
} }
} }
return false; return out > byte_idx ? false : true;
} }
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) { pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) {
pxl8_bsp_pvs pvs = {0}; pxl8_bsp_pvs pvs = {0};
u32 pvs_size = (bsp->num_leafs + 7) / 8; u32 row = (bsp->num_leafs + 7) >> 3;
pvs.data = calloc(pvs_size, 1); pvs.data = malloc(row);
pvs.size = pvs_size; pvs.size = row;
if (!pvs.data) return pvs; if (!pvs.data) return pvs;
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) { if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
memset(pvs.data, 0xFF, pvs_size); memset(pvs.data, 0xFF, row);
return pvs; return pvs;
} }
i32 visofs = bsp->leafs[leaf].visofs; i32 visofs = bsp->leafs[leaf].visofs;
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) { if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
memset(pvs.data, 0xFF, pvs_size); memset(pvs.data, 0xFF, row);
return pvs; return pvs;
} }
u32 pos = (u32)visofs; u8* in = bsp->visdata + visofs;
u32 out = 0; u8* out = pvs.data;
u8* out_end = pvs.data + row;
while (out < pvs_size && pos < bsp->visdata_size) { do {
u8 b = bsp->visdata[pos++]; if (*in) {
*out++ = *in++;
if (b != 0) {
pvs.data[out++] = b;
} else { } else {
if (pos >= bsp->visdata_size) break; in++;
u32 count = bsp->visdata[pos++]; i32 c = *in++;
out += count; while (c > 0 && out < out_end) {
*out++ = 0;
c--;
} }
} }
} while (out < out_end);
return pvs; return pvs;
} }
@ -449,10 +479,10 @@ void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) {
} }
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) { bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) {
if (!pvs || !pvs->data || leaf < 0) return false; if (!pvs || !pvs->data || leaf < 0) return true;
u32 byte_idx = leaf >> 3; u32 byte_idx = (u32)leaf >> 3;
u32 bit_idx = leaf & 7; u32 bit_idx = (u32)leaf & 7;
if (byte_idx >= pvs->size) return false; if (byte_idx >= pvs->size) return true;
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0; return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
} }
@ -551,6 +581,83 @@ static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum
return pxl8_frustum_test_aabb(frustum, mins, maxs); return pxl8_frustum_test_aabb(frustum, mins, maxs);
} }
static inline bool screen_rect_valid(screen_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
return (screen_rect){
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
};
}
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
if (nx < r->x0) r->x0 = nx;
if (nx > r->x1) r->x1 = nx;
if (ny < r->y0) r->y0 = ny;
if (ny > r->y1) r->y1 = ny;
}
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
pxl8_vec3 world_corners[4] = {
{portal->x0, 0, portal->z0},
{portal->x1, 0, portal->z1},
{portal->x1, wall_height, portal->z1},
{portal->x0, wall_height, portal->z0},
};
pxl8_vec4 clip[4];
bool in_front[4];
i32 front_count = 0;
const f32 NEAR_W = 0.001f;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
in_front[i] = clip[i].w > NEAR_W;
if (in_front[i]) front_count++;
}
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
i32 j = (i + 1) % 4;
pxl8_vec4 a = clip[i];
pxl8_vec4 b = clip[j];
bool a_in = in_front[i];
bool b_in = in_front[j];
if (a_in) {
f32 inv_w = 1.0f / a.w;
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
}
if (a_in != b_in) {
f32 t = (NEAR_W - a.w) / (b.w - a.w);
pxl8_vec4 intersection = {
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
a.z + t * (b.z - a.z),
NEAR_W
};
f32 inv_w = 1.0f / intersection.w;
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
}
}
if (result.x0 < -1.0f) result.x0 = -1.0f;
if (result.y0 < -1.0f) result.y0 = -1.0f;
if (result.x1 > 1.0f) result.x1 = 1.0f;
if (result.y1 > 1.0f) result.y1 = 1.0f;
return result;
}
static void collect_face_to_mesh( static void collect_face_to_mesh(
const pxl8_bsp* bsp, const pxl8_bsp* bsp,
u32 face_id, u32 face_id,
@ -569,17 +676,18 @@ static void collect_face_to_mesh(
} }
} }
const pxl8_bsp_texinfo* texinfo = NULL; const pxl8_gfx_material* material = NULL;
f32 tex_scale = 64.0f; f32 tex_scale = 64.0f;
if (face->texinfo_id < bsp->num_texinfo) { if (face->material_id < bsp->num_materials) {
texinfo = &bsp->texinfo[face->texinfo_id]; material = &bsp->materials[face->material_id];
} }
u16 base_idx = (u16)mesh->vertex_count; u16 base_idx = (u16)mesh->vertex_count;
u32 num_verts = 0; u32 num_verts = 0;
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
i32 surfedge_idx = face->first_edge + i; u32 edge_i = face->side ? (face->num_edges - 1 - i) : i;
i32 surfedge_idx = face->first_edge + edge_i;
u32 vert_idx; u32 vert_idx;
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) { if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
@ -589,9 +697,9 @@ static void collect_face_to_mesh(
pxl8_vec3 pos = bsp->vertices[vert_idx].position; pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 u = 0.0f, v = 0.0f; f32 u = 0.0f, v = 0.0f;
if (texinfo) { if (material) {
u = (pxl8_vec3_dot(pos, texinfo->u_axis) + texinfo->u_offset) / tex_scale; u = (pxl8_vec3_dot(pos, material->u_axis) + material->u_offset) / tex_scale;
v = (pxl8_vec3_dot(pos, texinfo->v_axis) + texinfo->v_offset) / tex_scale; v = (pxl8_vec3_dot(pos, material->v_axis) + material->v_offset) / tex_scale;
} }
u8 light = 255; u8 light = 255;
@ -618,8 +726,8 @@ static void collect_face_to_mesh(
} }
} }
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) { void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
if (!gfx || !bsp || face_id >= bsp->num_faces) return; if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
pxl8_mesh* mesh = pxl8_mesh_create(64, 192); pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return; if (!mesh) return;
@ -628,172 +736,115 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t
if (mesh->index_count > 0) { if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity(); pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_gfx_material mat = { pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
.texture_id = texture_id,
.alpha = 255,
.dither = true,
.dynamic_lighting = true,
};
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
} }
pxl8_mesh_destroy(mesh); pxl8_mesh_destroy(mesh);
} }
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) { void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
static int call_count = 0; if (!gfx || !bsp || bsp->num_faces == 0) return;
if (!gfx || !bsp || bsp->num_faces == 0) { if (!bsp->cell_portals || bsp->num_cell_portals == 0) return;
if (call_count++ < 5) { if (!bsp->materials || bsp->num_materials == 0) return;
pxl8_debug("bsp_render_textured: early return - gfx=%p, bsp=%p, num_faces=%u",
(void*)gfx, (void*)bsp, bsp ? bsp->num_faces : 0);
}
return;
}
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) { const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
if (call_count++ < 5) { if (!frustum || !vp) return;
pxl8_debug("bsp_render_textured: frustum is NULL!");
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_cell_portals) return;
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
if (!bsp_mut->render_face_flags) {
bsp_mut->render_face_flags = calloc(bsp->num_faces, 1);
if (!bsp_mut->render_face_flags) return;
} }
memset(bsp_mut->render_face_flags, 0, bsp->num_faces);
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
u8* visited = calloc(visited_bytes, 1);
screen_rect* cell_windows = calloc(bsp->num_leafs, sizeof(screen_rect));
portal_queue_entry* queue = malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
if (!visited || !cell_windows || !queue) {
free(visited);
free(cell_windows);
free(queue);
pxl8_bsp_pvs_destroy(&pvs);
return; return;
} }
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
static u32 debug_counter = 0; visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
bool do_debug = (debug_counter++ % 300 == 0); cell_windows[camera_leaf] = full_screen;
u32 visible_leafs = 0; queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
u32 visible_faces = 0;
u32 total_faces_in_visible_leafs = 0; f32 wall_height = 128.0f;
static bool dumped_camera_leaf = false;
if (do_debug) { while (head < tail) {
bool self_visible = (camera_leaf < 0) || pxl8_bsp_is_leaf_visible(bsp, camera_leaf, camera_leaf); portal_queue_entry entry = queue[head++];
if (!self_visible) { u32 leaf_id = entry.leaf;
pxl8_debug("WARNING: Camera leaf %d is NOT visible from itself!", camera_leaf); screen_rect window = entry.window;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) {
screen_rect existing = cell_windows[target];
bool expanded = false;
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
if (expanded && tail < bsp->num_leafs * 4) {
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
} }
if (!dumped_camera_leaf && camera_leaf >= 0) {
const pxl8_bsp_leaf* cl = &bsp->leafs[camera_leaf];
pxl8_debug("Camera leaf %d: contents=%d, marksurfaces=%u-%u (%u faces)",
camera_leaf, cl->contents, cl->first_marksurface,
cl->first_marksurface + cl->num_marksurfaces, cl->num_marksurfaces);
dumped_camera_leaf = true;
}
for (u32 i = 0; i < bsp->num_leafs; i++) {
if (camera_leaf < 0 || pxl8_bsp_is_leaf_visible(bsp, camera_leaf, i)) {
visible_leafs++;
total_faces_in_visible_leafs += bsp->leafs[i].num_marksurfaces;
}
}
}
static u8* rendered_faces = NULL;
static u32 rendered_faces_capacity = 0;
if (rendered_faces_capacity < bsp->num_faces) {
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
if (!new_buffer) return;
rendered_faces = new_buffer;
rendered_faces_capacity = bsp->num_faces;
}
memset(rendered_faces, 0, bsp->num_faces);
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
u32 current_texture = 0xFFFFFFFF;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
bool is_camera_leaf = ((i32)leaf_id == camera_leaf);
f32 cx = ((f32)leaf->mins[0] + (f32)leaf->maxs[0]) * 0.5f;
f32 cy = ((f32)leaf->mins[1] + (f32)leaf->maxs[1]) * 0.5f;
f32 cz = ((f32)leaf->mins[2] + (f32)leaf->maxs[2]) * 0.5f;
f32 dx = camera_pos.x - cx;
f32 dy = camera_pos.y - cy;
f32 dz = camera_pos.z - cz;
f32 dist_sq = dx*dx + dy*dy + dz*dz;
bool camera_near_leaf = dist_sq < (512.0f * 512.0f);
bool skip_frustum = is_camera_leaf || camera_near_leaf;
if (!skip_frustum && !leaf_in_frustum(leaf, frustum)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue;
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (rendered_faces[face_id]) continue;
rendered_faces[face_id] = 1;
if (do_debug) visible_faces++;
if (!skip_frustum && !face_in_frustum(bsp, face_id, frustum)) {
continue; continue;
} }
const pxl8_bsp_face* face = &bsp->faces[face_id]; visited[byte] |= (1 << bit);
u32 texture_id = 0; cell_windows[target] = new_window;
if (face->texinfo_id < bsp->num_texinfo) { queue[tail++] = (portal_queue_entry){target, new_window};
texture_id = bsp->texinfo[face->texinfo_id].miptex;
}
if (texture_id != current_texture && mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_gfx_material mat = {
.texture_id = current_texture,
.alpha = 255,
.dither = true,
.dynamic_lighting = true,
};
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh_clear(mesh);
}
current_texture = texture_id;
collect_face_to_mesh(bsp, face_id, mesh);
} }
} }
if (mesh->index_count > 0) { pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
pxl8_mat4 identity = pxl8_mat4_identity(); if (!mesh) {
pxl8_gfx_material mat = { free(visited);
.texture_id = current_texture, free(cell_windows);
.alpha = 255, free(queue);
.dither = true, return;
.dynamic_lighting = true,
};
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
} }
if (do_debug) { u32 current_material = 0xFFFFFFFF;
pxl8_debug("Camera at (%.1f, %.1f, %.1f) -> leaf %d, %u/%u leafs, %u/%u faces (expected %u)", u32 total_faces = 0;
camera_pos.x, camera_pos.y, camera_pos.z,
camera_leaf, visible_leafs, bsp->num_leafs,
visible_faces, bsp->num_faces, total_faces_in_visible_leafs);
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color) {
if (!gfx || !bsp) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
u8 line_color = (u8)(color & 0xFF);
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue; u32 byte = leaf_id >> 3;
u32 bit = leaf_id & 7;
if (!(visited[byte] & (1 << bit))) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
if (!leaf_in_frustum(leaf, frustum)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) { for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i; u32 surf_idx = leaf->first_marksurface + i;
@ -802,23 +853,45 @@ void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 cam
u32 face_id = bsp->marksurfaces[surf_idx]; u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue; if (face_id >= bsp->num_faces) continue;
if (bsp_mut->render_face_flags[face_id]) continue;
bsp_mut->render_face_flags[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) continue; if (!face_in_frustum(bsp, face_id, frustum)) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id]; const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= bsp->num_materials) continue;
total_faces++;
for (u32 e = 0; e < face->num_edges; e++) { if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
i32 surfedge_idx = face->first_edge + e; pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
pxl8_mesh_clear(mesh);
}
if (surfedge_idx >= (i32)bsp->num_surfedges) continue; current_material = material_id;
collect_face_to_mesh(bsp, face_id, mesh);
u32 v0_idx, v1_idx;
if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue;
pxl8_vec3 p0 = bsp->vertices[v0_idx].position;
pxl8_vec3 p1 = bsp->vertices[v1_idx].position;
pxl8_3d_draw_line(gfx, p0, p1, line_color);
} }
} }
if (mesh->index_count > 0 && current_material < bsp->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
}
static u32 debug_count = 0;
if (debug_count++ < 5) {
pxl8_debug("BSP render: %u faces, mesh verts=%u indices=%u", total_faces, mesh->vertex_count, mesh->index_count);
if (mesh->vertex_count > 0) {
pxl8_vertex* v = &mesh->vertices[0];
pxl8_debug(" vert[0]: pos=(%.1f,%.1f,%.1f) uv=(%.2f,%.2f)",
v->position.x, v->position.y, v->position.z, v->u, v->v);
} }
} }
pxl8_bsp_pvs_destroy(&pvs);
free(visited);
free(cell_windows);
free(queue);
pxl8_mesh_destroy(mesh);
}

View file

@ -17,7 +17,7 @@ typedef struct pxl8_bsp_face {
u16 side; u16 side;
u8 styles[4]; u8 styles[4];
u16 texinfo_id; u16 material_id;
pxl8_vec3 aabb_min; pxl8_vec3 aabb_min;
pxl8_vec3 aabb_max; pxl8_vec3 aabb_max;
@ -63,16 +63,6 @@ typedef struct pxl8_bsp_plane {
i32 type; i32 type;
} pxl8_bsp_plane; } pxl8_bsp_plane;
typedef struct pxl8_bsp_texinfo {
u32 miptex;
char name[16];
f32 u_offset;
pxl8_vec3 u_axis;
f32 v_offset;
pxl8_vec3 v_axis;
} pxl8_bsp_texinfo;
typedef struct pxl8_bsp_vertex { typedef struct pxl8_bsp_vertex {
pxl8_vec3 position; pxl8_vec3 position;
@ -91,47 +81,52 @@ typedef struct pxl8_bsp_lightmap_sample {
u8 r; u8 r;
} pxl8_bsp_lightmap_sample; } pxl8_bsp_lightmap_sample;
typedef struct pxl8_bsp_material_batch {
u16* face_indices;
u32 face_count;
u8 material_id;
pxl8_mesh* mesh;
} pxl8_bsp_material_batch;
typedef struct pxl8_bsp_pvs { typedef struct pxl8_bsp_pvs {
u8* data; u8* data;
u32 size; u32 size;
} pxl8_bsp_pvs; } pxl8_bsp_pvs;
typedef struct pxl8_bsp_portal {
f32 x0, z0;
f32 x1, z1;
u32 target_leaf;
} pxl8_bsp_portal;
typedef struct pxl8_bsp_cell_portals {
pxl8_bsp_portal portals[4];
u8 num_portals;
} pxl8_bsp_cell_portals;
typedef struct pxl8_bsp { typedef struct pxl8_bsp {
pxl8_bsp_cell_portals* cell_portals;
pxl8_bsp_edge* edges; pxl8_bsp_edge* edges;
pxl8_bsp_face* faces; pxl8_bsp_face* faces;
pxl8_bsp_leaf* leafs; pxl8_bsp_leaf* leafs;
u8* lightdata; u8* lightdata;
pxl8_bsp_lightmap* lightmaps; pxl8_bsp_lightmap* lightmaps;
u16* marksurfaces; u16* marksurfaces;
pxl8_bsp_material_batch* material_batches; pxl8_gfx_material* materials;
pxl8_bsp_model* models; pxl8_bsp_model* models;
pxl8_bsp_node* nodes; pxl8_bsp_node* nodes;
pxl8_bsp_plane* planes; pxl8_bsp_plane* planes;
u8* render_face_flags;
i32* surfedges; i32* surfedges;
pxl8_bsp_texinfo* texinfo;
u32* vertex_lights; u32* vertex_lights;
pxl8_bsp_vertex* vertices; pxl8_bsp_vertex* vertices;
u8* visdata; u8* visdata;
u32 lightdata_size; u32 lightdata_size;
u32 num_cell_portals;
u32 num_edges; u32 num_edges;
u32 num_faces; u32 num_faces;
u32 num_leafs; u32 num_leafs;
u32 num_lightmaps; u32 num_lightmaps;
u32 num_marksurfaces; u32 num_marksurfaces;
u32 num_material_batches; u32 num_materials;
u32 num_models; u32 num_models;
u32 num_nodes; u32 num_nodes;
u32 num_planes; u32 num_planes;
u32 num_surfedges; u32 num_surfedges;
u32 num_texinfo;
u32 num_vertex_lights; u32 num_vertex_lights;
u32 num_vertices; u32 num_vertices;
u32 visdata_size; u32 visdata_size;
@ -155,9 +150,8 @@ pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset); pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v); pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id); void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos); void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -8,7 +8,7 @@
#include "pxl8_rng.h" #include "pxl8_rng.h"
#define CELL_SIZE 64.0f #define CELL_SIZE 64.0f
#define PVS_MAX_DEPTH 40 #define PVS_MAX_DEPTH 32
#define WALL_HEIGHT 128.0f #define WALL_HEIGHT 128.0f
typedef struct room_grid { typedef struct room_grid {
@ -24,22 +24,6 @@ typedef struct bsp_build_context {
u32 plane_offset; u32 plane_offset;
} bsp_build_context; } bsp_build_context;
typedef struct portal {
f32 x0, z0;
f32 x1, z1;
u32 target_leaf;
} portal;
typedef struct cell_portals {
portal portals[4];
u8 num_portals;
} cell_portals;
typedef struct vis_window {
f32 left_x, left_z;
f32 right_x, right_z;
} vis_window;
typedef struct light_source { typedef struct light_source {
pxl8_vec3 position; pxl8_vec3 position;
f32 intensity; f32 intensity;
@ -183,9 +167,9 @@ static void compute_bsp_vertex_lighting(
pxl8_debug("Computed vertex lighting: %u vertices, %u lights", bsp->num_vertices, num_lights); pxl8_debug("Computed vertex lighting: %u vertices, %u lights", bsp->num_vertices, num_lights);
} }
static cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) { static pxl8_bsp_cell_portals* build_pxl8_bsp_cell_portals(const room_grid* grid, f32 cell_size) {
i32 total_cells = grid->width * grid->height; i32 total_cells = grid->width * grid->height;
cell_portals* portals = calloc(total_cells, sizeof(cell_portals)); pxl8_bsp_cell_portals* portals = calloc(total_cells, sizeof(pxl8_bsp_cell_portals));
if (!portals) return NULL; if (!portals) return NULL;
for (i32 y = 0; y < grid->height; y++) { for (i32 y = 0; y < grid->height; y++) {
@ -197,7 +181,7 @@ static cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) {
f32 cz = y * cell_size; f32 cz = y * cell_size;
if (x > 0 && room_grid_get(grid, x - 1, y) == 0) { if (x > 0 && room_grid_get(grid, x - 1, y) == 0) {
portal* p = &portals[c].portals[portals[c].num_portals++]; pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx; p->x0 = cx;
p->z0 = cz; p->z0 = cz;
p->x1 = cx; p->x1 = cx;
@ -205,7 +189,7 @@ static cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) {
p->target_leaf = y * grid->width + (x - 1); p->target_leaf = y * grid->width + (x - 1);
} }
if (x < grid->width - 1 && room_grid_get(grid, x + 1, y) == 0) { if (x < grid->width - 1 && room_grid_get(grid, x + 1, y) == 0) {
portal* p = &portals[c].portals[portals[c].num_portals++]; pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx + cell_size; p->x0 = cx + cell_size;
p->z0 = cz + cell_size; p->z0 = cz + cell_size;
p->x1 = cx + cell_size; p->x1 = cx + cell_size;
@ -213,7 +197,7 @@ static cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) {
p->target_leaf = y * grid->width + (x + 1); p->target_leaf = y * grid->width + (x + 1);
} }
if (y > 0 && room_grid_get(grid, x, y - 1) == 0) { if (y > 0 && room_grid_get(grid, x, y - 1) == 0) {
portal* p = &portals[c].portals[portals[c].num_portals++]; pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx + cell_size; p->x0 = cx + cell_size;
p->z0 = cz; p->z0 = cz;
p->x1 = cx; p->x1 = cx;
@ -221,7 +205,7 @@ static cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) {
p->target_leaf = (y - 1) * grid->width + x; p->target_leaf = (y - 1) * grid->width + x;
} }
if (y < grid->height - 1 && room_grid_get(grid, x, y + 1) == 0) { if (y < grid->height - 1 && room_grid_get(grid, x, y + 1) == 0) {
portal* p = &portals[c].portals[portals[c].num_portals++]; pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx; p->x0 = cx;
p->z0 = cz + cell_size; p->z0 = cz + cell_size;
p->x1 = cx + cell_size; p->x1 = cx + cell_size;
@ -233,168 +217,57 @@ static cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) {
return portals; return portals;
} }
static inline f32 cross_2d(f32 ax, f32 az, f32 bx, f32 bz) { typedef struct flood_entry {
return ax * bz - az * bx;
}
static bool clip_window_through_portal(
f32 src_x, f32 src_z,
const vis_window* window,
const portal* p,
vis_window* clipped
) {
f32 wl_x = window->left_x - src_x;
f32 wl_z = window->left_z - src_z;
f32 wr_x = window->right_x - src_x;
f32 wr_z = window->right_z - src_z;
f32 p0_x = p->x0 - src_x;
f32 p0_z = p->z0 - src_z;
f32 p1_x = p->x1 - src_x;
f32 p1_z = p->z1 - src_z;
f32 p0_vs_wl = cross_2d(p0_x, p0_z, wl_x, wl_z);
f32 p1_vs_wl = cross_2d(p1_x, p1_z, wl_x, wl_z);
f32 p0_vs_wr = cross_2d(p0_x, p0_z, wr_x, wr_z);
f32 p1_vs_wr = cross_2d(p1_x, p1_z, wr_x, wr_z);
if (p1_vs_wl <= 0.0f || p0_vs_wr >= 0.0f) {
return false;
}
if (p0_vs_wl > 0.0f) {
clipped->left_x = p->x0;
clipped->left_z = p->z0;
} else {
clipped->left_x = window->left_x;
clipped->left_z = window->left_z;
}
if (p1_vs_wr < 0.0f) {
clipped->right_x = p->x1;
clipped->right_z = p->z1;
} else {
clipped->right_x = window->right_x;
clipped->right_z = window->right_z;
}
f32 cl_x = clipped->left_x - src_x;
f32 cl_z = clipped->left_z - src_z;
f32 cr_x = clipped->right_x - src_x;
f32 cr_z = clipped->right_z - src_z;
if (cross_2d(cl_x, cl_z, cr_x, cr_z) >= -0.0001f) {
return false;
}
return true;
}
typedef struct portal_vis_context {
u8* pvs;
u8* visited;
const cell_portals* portals;
const pxl8_bsp_leaf* leafs;
f32 src_x;
f32 src_z;
u32 num_leafs;
u32 max_depth;
} portal_vis_context;
static void portal_flood_recursive(
portal_vis_context* ctx,
u32 current_leaf,
const vis_window* window,
u32 depth
) {
if (depth > ctx->max_depth) return;
u32 byte = current_leaf >> 3;
u32 bit = current_leaf & 7;
if (ctx->visited[byte] & (1 << bit)) return;
ctx->visited[byte] |= (1 << bit);
if (ctx->leafs[current_leaf].contents == -1) return;
ctx->pvs[byte] |= (1 << bit);
const cell_portals* cp = &ctx->portals[current_leaf];
for (u8 i = 0; i < cp->num_portals; i++) {
const portal* p = &cp->portals[i];
u32 target_byte = p->target_leaf >> 3;
u32 target_bit = p->target_leaf & 7;
if (ctx->visited[target_byte] & (1 << target_bit)) continue;
vis_window clipped;
if (clip_window_through_portal(ctx->src_x, ctx->src_z, window, p, &clipped)) {
portal_flood_recursive(ctx, p->target_leaf, &clipped, depth + 1);
}
}
}
typedef struct vis_entry {
u32 leaf; u32 leaf;
vis_window window; u32 depth;
} vis_entry; } flood_entry;
static void portal_flood_bfs( static void portal_flood_bfs(
u32 start_leaf, u32 start_leaf,
const cell_portals* portals, const pxl8_bsp_cell_portals* portals,
const pxl8_bsp_leaf* leafs, const pxl8_bsp_leaf* leafs,
u8* pvs, u8* pvs,
u32 num_leafs, u32 num_leafs,
f32 cell_size, f32 cell_size,
i32 grid_width i32 grid_width
) { ) {
(void)cell_size;
(void)grid_width;
u32 pvs_bytes = (num_leafs + 7) / 8; u32 pvs_bytes = (num_leafs + 7) / 8;
u8* visited = calloc(pvs_bytes, 1); u8* visited = calloc(pvs_bytes, 1);
vis_entry* queue = malloc(num_leafs * 4 * sizeof(vis_entry)); flood_entry* queue = malloc(num_leafs * sizeof(flood_entry));
if (!visited || !queue) { if (!visited || !queue) {
free(visited); free(visited);
free(queue); free(queue);
return; return;
} }
i32 start_x = start_leaf % grid_width;
i32 start_y = start_leaf / grid_width;
f32 src_x = (start_x + 0.5f) * cell_size;
f32 src_z = (start_y + 0.5f) * cell_size;
u32 head = 0, tail = 0; u32 head = 0, tail = 0;
pvs[start_leaf >> 3] |= (1 << (start_leaf & 7)); pvs[start_leaf >> 3] |= (1 << (start_leaf & 7));
visited[start_leaf >> 3] |= (1 << (start_leaf & 7)); visited[start_leaf >> 3] |= (1 << (start_leaf & 7));
queue[tail++] = (flood_entry){start_leaf, 0};
const cell_portals* start_cp = &portals[start_leaf];
for (u8 i = 0; i < start_cp->num_portals; i++) {
const portal* p = &start_cp->portals[i];
vis_window initial = {p->x0, p->z0, p->x1, p->z1};
queue[tail++] = (vis_entry){p->target_leaf, initial};
}
while (head < tail) { while (head < tail) {
vis_entry e = queue[head++]; flood_entry e = queue[head++];
if (e.depth >= PVS_MAX_DEPTH) continue;
if (leafs[e.leaf].contents == -1) continue; if (leafs[e.leaf].contents == -1) continue;
u32 byte = e.leaf >> 3; const pxl8_bsp_cell_portals* cp = &portals[e.leaf];
u32 bit = e.leaf & 7; for (u8 i = 0; i < cp->num_portals; i++) {
u32 target = cp->portals[i].target_leaf;
pvs[byte] |= (1 << bit); u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) continue; if (visited[byte] & (1 << bit)) continue;
visited[byte] |= (1 << bit); visited[byte] |= (1 << bit);
const cell_portals* cp = &portals[e.leaf]; if (leafs[target].contents == -1) continue;
for (u8 i = 0; i < cp->num_portals; i++) {
const portal* p = &cp->portals[i];
vis_window clipped; pvs[byte] |= (1 << bit);
if (clip_window_through_portal(src_x, src_z, &e.window, p, &clipped)) { queue[tail++] = (flood_entry){target, e.depth + 1};
queue[tail++] = (vis_entry){p->target_leaf, clipped};
}
} }
} }
@ -402,7 +275,7 @@ static void portal_flood_bfs(
free(queue); free(queue);
} }
static u8* compute_leaf_pvs(u32 start_leaf, const cell_portals* portals, static u8* compute_leaf_pvs(u32 start_leaf, const pxl8_bsp_cell_portals* portals,
u32 num_leafs, const pxl8_bsp_leaf* leafs, u32 num_leafs, const pxl8_bsp_leaf* leafs,
const room_grid* grid, f32 cell_size) { const room_grid* grid, f32 cell_size) {
u32 pvs_bytes = (num_leafs + 7) / 8; u32 pvs_bytes = (num_leafs + 7) / 8;
@ -434,7 +307,7 @@ static u32 rle_compress_pvs(const u8* pvs, u32 pvs_bytes, u8* out) {
return out_pos; return out_pos;
} }
static pxl8_result build_pvs_data(pxl8_bsp* bsp, const cell_portals* portals, static pxl8_result build_pvs_data(pxl8_bsp* bsp, const pxl8_bsp_cell_portals* portals,
const room_grid* grid, f32 cell_size) { const room_grid* grid, f32 cell_size) {
u32 num_leafs = bsp->num_leafs; u32 num_leafs = bsp->num_leafs;
u32 pvs_bytes = (num_leafs + 7) / 8; u32 pvs_bytes = (num_leafs + 7) / 8;
@ -571,8 +444,8 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
bsp->texinfo = NULL; bsp->materials = NULL;
bsp->num_texinfo = 0; bsp->num_materials = 0;
i32 vert_idx = 0; i32 vert_idx = 0;
i32 face_idx = 0; i32 face_idx = 0;
@ -600,7 +473,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -628,7 +501,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -656,7 +529,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -684,7 +557,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -721,7 +594,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -829,7 +702,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
pxl8_debug("Built BSP tree: %u nodes, %u leafs, %u planes", pxl8_debug("Built BSP tree: %u nodes, %u leafs, %u planes",
bsp->num_nodes, bsp->num_leafs, bsp->num_planes); bsp->num_nodes, bsp->num_leafs, bsp->num_planes);
cell_portals* portals = build_cell_portals(grid, cell_size); pxl8_bsp_cell_portals* portals = build_pxl8_bsp_cell_portals(grid, cell_size);
if (!portals) { if (!portals) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
@ -874,12 +747,14 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size); pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size);
free(portals);
if (pvs_result != PXL8_OK) { if (pvs_result != PXL8_OK) {
free(portals);
return pvs_result; return pvs_result;
} }
bsp->cell_portals = portals;
bsp->num_cell_portals = total_cells;
return PXL8_OK; return PXL8_OK;
} }

View file

@ -11,8 +11,6 @@
struct pxl8_world { struct pxl8_world {
pxl8_bsp bsp; pxl8_bsp bsp;
bool loaded; bool loaded;
bool wireframe;
u32 wireframe_color;
}; };
pxl8_world* pxl8_world_create(void) { pxl8_world* pxl8_world_create(void) {
@ -23,7 +21,6 @@ pxl8_world* pxl8_world_create(void) {
} }
world->loaded = false; world->loaded = false;
world->wireframe_color = 15;
return world; return world;
} }
@ -98,12 +95,12 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
pxl8_bsp* bsp = &world->bsp; pxl8_bsp* bsp = &world->bsp;
u32 max_texinfo = count * 6; u32 max_materials = count * 6;
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo)); bsp->materials = calloc(max_materials, sizeof(pxl8_gfx_material));
if (!bsp->texinfo) { if (!bsp->materials) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
bsp->num_texinfo = 0; bsp->num_materials = 0;
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) { for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
pxl8_bsp_face* face = &bsp->faces[face_idx]; pxl8_bsp_face* face = &bsp->faces[face_idx];
@ -136,45 +133,50 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f}; v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
} }
u32 texinfo_idx = bsp->num_texinfo; u32 material_idx = bsp->num_materials;
bool found_existing = false; bool found_existing = false;
for (u32 i = 0; i < bsp->num_texinfo; i++) { for (u32 i = 0; i < bsp->num_materials; i++) {
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 && if (strcmp(bsp->materials[i].name, matched->name) == 0 &&
bsp->texinfo[i].miptex == matched->texture_id && bsp->materials[i].texture_id == matched->texture_id &&
bsp->texinfo[i].u_axis.x == u_axis.x && bsp->materials[i].u_axis.x == u_axis.x &&
bsp->texinfo[i].u_axis.y == u_axis.y && bsp->materials[i].u_axis.y == u_axis.y &&
bsp->texinfo[i].u_axis.z == u_axis.z && bsp->materials[i].u_axis.z == u_axis.z &&
bsp->texinfo[i].v_axis.x == v_axis.x && bsp->materials[i].v_axis.x == v_axis.x &&
bsp->texinfo[i].v_axis.y == v_axis.y && bsp->materials[i].v_axis.y == v_axis.y &&
bsp->texinfo[i].v_axis.z == v_axis.z) { bsp->materials[i].v_axis.z == v_axis.z) {
texinfo_idx = i; material_idx = i;
found_existing = true; found_existing = true;
break; break;
} }
} }
if (!found_existing) { if (!found_existing) {
if (bsp->num_texinfo >= max_texinfo) { if (bsp->num_materials >= max_materials) {
pxl8_error("Too many unique texinfo entries"); pxl8_error("Too many unique material entries");
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name)); pxl8_gfx_material* mat = &bsp->materials[material_idx];
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0'; memcpy(mat->name, matched->name, sizeof(mat->name));
bsp->texinfo[texinfo_idx].miptex = matched->texture_id; mat->name[sizeof(mat->name) - 1] = '\0';
bsp->texinfo[texinfo_idx].u_offset = 0.0f; mat->texture_id = matched->texture_id;
bsp->texinfo[texinfo_idx].v_offset = 0.0f; mat->u_offset = 0.0f;
bsp->texinfo[texinfo_idx].u_axis = u_axis; mat->v_offset = 0.0f;
bsp->texinfo[texinfo_idx].v_axis = v_axis; mat->u_axis = u_axis;
mat->v_axis = v_axis;
mat->alpha = 255;
mat->dither = true;
mat->double_sided = true;
mat->dynamic_lighting = true;
bsp->num_texinfo++; bsp->num_materials++;
} }
face->texinfo_id = texinfo_idx; face->material_id = material_idx;
} }
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries", pxl8_info("Applied %u textures to %u faces, created %u materials",
count, bsp->num_faces, bsp->num_texinfo); count, bsp->num_faces, bsp->num_materials);
return PXL8_OK; return PXL8_OK;
} }
@ -398,15 +400,13 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
return; return;
} }
if (world->wireframe) { pxl8_bsp_render(gfx, &world->bsp, camera_pos);
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color);
} else {
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
}
} }
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled, u8 color) { void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
if (!world) return; if (!world || !world->loaded) return;
world->wireframe = enabled;
world->wireframe_color = color; for (u32 i = 0; i < world->bsp.num_materials; i++) {
world->bsp.materials[i].wireframe = enabled;
}
} }

View file

@ -32,7 +32,7 @@ bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radi
bool pxl8_world_is_loaded(const pxl8_world* world); bool pxl8_world_is_loaded(const pxl8_world* world);
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos); void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled, u8 color); void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
#ifdef __cplusplus #ifdef __cplusplus
} }