2025-11-13 07:15:41 -06:00
|
|
|
#include "pxl8_world.h"
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
#include <stdlib.h>
|
2025-10-07 10:32:48 -05:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include "pxl8_bsp.h"
|
2025-11-28 14:41:35 -06:00
|
|
|
#include "pxl8_gen.h"
|
2025-12-02 11:02:23 -06:00
|
|
|
#include "pxl8_log.h"
|
2025-11-28 14:41:35 -06:00
|
|
|
#include "pxl8_math.h"
|
2025-10-07 10:32:48 -05:00
|
|
|
|
|
|
|
|
struct pxl8_world {
|
|
|
|
|
pxl8_bsp bsp;
|
|
|
|
|
bool loaded;
|
2025-11-09 06:30:17 -06:00
|
|
|
bool wireframe;
|
2025-10-07 10:32:48 -05:00
|
|
|
u32 wireframe_color;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pxl8_world* pxl8_world_create(void) {
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world));
|
2025-10-07 10:32:48 -05:00
|
|
|
if (!world) {
|
|
|
|
|
pxl8_error("Failed to allocate world");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
world->loaded = false;
|
|
|
|
|
world->wireframe_color = 15;
|
|
|
|
|
|
|
|
|
|
return world;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_world_destroy(pxl8_world* world) {
|
|
|
|
|
if (!world) return;
|
|
|
|
|
|
|
|
|
|
if (world->loaded) {
|
|
|
|
|
pxl8_bsp_destroy(&world->bsp);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
free(world);
|
2025-10-07 10:32:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-11 21:24:53 -06:00
|
|
|
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
|
2025-11-20 20:55:45 -06:00
|
|
|
if (!world || !gfx || !params) {
|
|
|
|
|
pxl8_error("Invalid arguments to pxl8_world_generate");
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
2025-11-09 06:30:17 -06:00
|
|
|
|
|
|
|
|
if (world->loaded) {
|
|
|
|
|
pxl8_bsp_destroy(&world->bsp);
|
|
|
|
|
world->loaded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
|
|
|
|
|
|
|
|
|
pxl8_result result = pxl8_procgen(&world->bsp, params);
|
|
|
|
|
if (result != PXL8_OK) {
|
2025-11-20 20:55:45 -06:00
|
|
|
pxl8_error("Failed to generate world: %d", result);
|
2025-11-09 06:30:17 -06:00
|
|
|
pxl8_bsp_destroy(&world->bsp);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
world->loaded = true;
|
2025-11-11 21:24:53 -06:00
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
|
|
|
|
if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
|
|
|
|
|
if (world->loaded) {
|
|
|
|
|
pxl8_bsp_destroy(&world->bsp);
|
|
|
|
|
world->loaded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
|
|
|
|
|
|
|
|
|
pxl8_result result = pxl8_bsp_load(path, &world->bsp);
|
|
|
|
|
if (result != PXL8_OK) {
|
|
|
|
|
pxl8_error("Failed to load world: %s", path);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
world->loaded = true;
|
|
|
|
|
pxl8_info("Loaded world: %s", path);
|
|
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_world_unload(pxl8_world* world) {
|
|
|
|
|
if (!world || !world->loaded) return;
|
|
|
|
|
|
|
|
|
|
pxl8_bsp_destroy(&world->bsp);
|
|
|
|
|
world->loaded = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-11 21:24:53 -06:00
|
|
|
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count) {
|
|
|
|
|
if (!world || !world->loaded || !textures || count == 0) {
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_bsp* bsp = &world->bsp;
|
|
|
|
|
|
|
|
|
|
u32 max_texinfo = count * 6;
|
|
|
|
|
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo));
|
|
|
|
|
if (!bsp->texinfo) {
|
|
|
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
bsp->num_texinfo = 0;
|
|
|
|
|
|
|
|
|
|
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
|
|
|
|
|
pxl8_bsp_face* face = &bsp->faces[face_idx];
|
|
|
|
|
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
|
|
|
|
|
|
|
|
|
u32 matched_texture_idx = count;
|
|
|
|
|
for (u32 tex_idx = 0; tex_idx < count; tex_idx++) {
|
|
|
|
|
if (textures[tex_idx].rule && textures[tex_idx].rule(&normal, face, bsp)) {
|
|
|
|
|
matched_texture_idx = tex_idx;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matched_texture_idx >= count) {
|
|
|
|
|
pxl8_warn("No texture rule matched for face %u", face_idx);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pxl8_world_texture* matched = &textures[matched_texture_idx];
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 u_axis, v_axis;
|
|
|
|
|
if (fabsf(normal.y) > 0.9f) {
|
|
|
|
|
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
|
|
|
|
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
|
|
|
|
} else if (fabsf(normal.x) > 0.7f) {
|
|
|
|
|
u_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
|
|
|
|
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
|
|
|
|
} else {
|
|
|
|
|
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
|
|
|
|
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 texinfo_idx = bsp->num_texinfo;
|
|
|
|
|
bool found_existing = false;
|
|
|
|
|
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
|
|
|
|
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 &&
|
|
|
|
|
bsp->texinfo[i].miptex == matched->texture_id &&
|
|
|
|
|
bsp->texinfo[i].u_axis.x == u_axis.x &&
|
|
|
|
|
bsp->texinfo[i].u_axis.y == u_axis.y &&
|
|
|
|
|
bsp->texinfo[i].u_axis.z == u_axis.z &&
|
|
|
|
|
bsp->texinfo[i].v_axis.x == v_axis.x &&
|
|
|
|
|
bsp->texinfo[i].v_axis.y == v_axis.y &&
|
|
|
|
|
bsp->texinfo[i].v_axis.z == v_axis.z) {
|
|
|
|
|
texinfo_idx = i;
|
|
|
|
|
found_existing = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!found_existing) {
|
|
|
|
|
if (bsp->num_texinfo >= max_texinfo) {
|
|
|
|
|
pxl8_error("Too many unique texinfo entries");
|
|
|
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name));
|
|
|
|
|
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0';
|
|
|
|
|
bsp->texinfo[texinfo_idx].miptex = matched->texture_id;
|
|
|
|
|
bsp->texinfo[texinfo_idx].u_offset = 0.0f;
|
|
|
|
|
bsp->texinfo[texinfo_idx].v_offset = 0.0f;
|
|
|
|
|
bsp->texinfo[texinfo_idx].u_axis = u_axis;
|
|
|
|
|
bsp->texinfo[texinfo_idx].v_axis = v_axis;
|
|
|
|
|
|
|
|
|
|
bsp->num_texinfo++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
face->texinfo_id = texinfo_idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries",
|
|
|
|
|
count, bsp->num_faces, bsp->num_texinfo);
|
2025-11-09 06:30:17 -06:00
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) {
|
|
|
|
|
if (!world || !world->loaded) return false;
|
2025-10-07 10:32:48 -05:00
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
const pxl8_bsp* bsp = &world->bsp;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < bsp->num_faces; i++) {
|
|
|
|
|
const pxl8_bsp_face* face = &bsp->faces[i];
|
|
|
|
|
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
|
|
|
|
|
|
|
|
|
if (fabsf(plane->normal.y) > 0.7f) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 dist = plane->normal.x * pos.x +
|
|
|
|
|
plane->normal.y * pos.y +
|
|
|
|
|
plane->normal.z * pos.z - plane->dist;
|
|
|
|
|
|
|
|
|
|
if (fabsf(dist) > radius) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 closest_point = {
|
|
|
|
|
pos.x - plane->normal.x * dist,
|
|
|
|
|
pos.y - plane->normal.y * dist,
|
|
|
|
|
pos.z - plane->normal.z * dist
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (closest_point.x < face->aabb_min.x - radius || closest_point.x > face->aabb_max.x + radius ||
|
|
|
|
|
closest_point.y < face->aabb_min.y - radius || closest_point.y > face->aabb_max.y + radius ||
|
|
|
|
|
closest_point.z < face->aabb_min.z - radius || closest_point.z > face->aabb_max.z + radius) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2025-10-07 10:32:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-10-07 10:32:48 -05:00
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
bool pxl8_world_is_loaded(const pxl8_world* world) {
|
|
|
|
|
return world && world->loaded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
|
|
|
|
if (!world || !world->loaded) return to;
|
|
|
|
|
|
|
|
|
|
const pxl8_bsp* bsp = &world->bsp;
|
|
|
|
|
pxl8_vec3 pos = to;
|
|
|
|
|
pxl8_vec3 original_velocity = {to.x - from.x, to.y - from.y, to.z - from.z};
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 clip_planes[5];
|
|
|
|
|
u32 num_planes = 0;
|
|
|
|
|
|
|
|
|
|
const f32 edge_epsilon = 1.2f;
|
|
|
|
|
const f32 radius_min = -radius + edge_epsilon;
|
|
|
|
|
const f32 radius_max = radius - edge_epsilon;
|
|
|
|
|
|
|
|
|
|
for (i32 iteration = 0; iteration < 4; iteration++) {
|
|
|
|
|
bool collided = false;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < bsp->num_faces; i++) {
|
|
|
|
|
const pxl8_bsp_face* face = &bsp->faces[i];
|
|
|
|
|
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
|
|
|
|
|
|
|
|
|
if (fabsf(plane->normal.y) > 0.7f) continue;
|
|
|
|
|
|
|
|
|
|
f32 dist = plane->normal.x * pos.x +
|
|
|
|
|
plane->normal.y * pos.y +
|
|
|
|
|
plane->normal.z * pos.z - plane->dist;
|
|
|
|
|
|
|
|
|
|
f32 abs_dist = fabsf(dist);
|
|
|
|
|
if (abs_dist > radius) continue;
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 closest_point = {
|
|
|
|
|
pos.x - plane->normal.x * dist,
|
|
|
|
|
pos.y - plane->normal.y * dist,
|
|
|
|
|
pos.z - plane->normal.z * dist
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (closest_point.x < face->aabb_min.x + radius_min || closest_point.x > face->aabb_max.x + radius_max ||
|
|
|
|
|
closest_point.y < face->aabb_min.y + radius_min || closest_point.y > face->aabb_max.y + radius_max ||
|
|
|
|
|
closest_point.z < face->aabb_min.z + radius_min || closest_point.z > face->aabb_max.z + radius_max) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 penetration = radius - abs_dist;
|
|
|
|
|
if (penetration > 0.01f) {
|
|
|
|
|
pxl8_vec3 push_dir;
|
|
|
|
|
if (dist < 0) {
|
|
|
|
|
push_dir.x = -plane->normal.x;
|
|
|
|
|
push_dir.y = -plane->normal.y;
|
|
|
|
|
push_dir.z = -plane->normal.z;
|
|
|
|
|
} else {
|
|
|
|
|
push_dir.x = plane->normal.x;
|
|
|
|
|
push_dir.y = plane->normal.y;
|
|
|
|
|
push_dir.z = plane->normal.z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_new_plane = true;
|
|
|
|
|
for (u32 p = 0; p < num_planes; p++) {
|
2025-11-28 14:41:35 -06:00
|
|
|
if (pxl8_vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
|
2025-11-21 16:44:42 -06:00
|
|
|
is_new_plane = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_new_plane && num_planes < 5) {
|
|
|
|
|
clip_planes[num_planes++] = push_dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pos.x += push_dir.x * penetration;
|
|
|
|
|
pos.y += push_dir.y * penetration;
|
|
|
|
|
pos.z += push_dir.z * penetration;
|
|
|
|
|
|
|
|
|
|
collided = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!collided) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (num_planes >= 3) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-10-07 10:32:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
if (num_planes == 2) {
|
|
|
|
|
f32 orig_vel_len_sq = original_velocity.x * original_velocity.x +
|
|
|
|
|
original_velocity.y * original_velocity.y +
|
|
|
|
|
original_velocity.z * original_velocity.z;
|
|
|
|
|
|
|
|
|
|
if (orig_vel_len_sq > 0.000001f) {
|
2025-11-28 14:41:35 -06:00
|
|
|
f32 vdot0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
|
|
|
|
f32 vdot1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
2025-11-21 16:44:42 -06:00
|
|
|
f32 dot0 = fabsf(vdot0);
|
|
|
|
|
f32 dot1 = fabsf(vdot1);
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 slide_vel;
|
|
|
|
|
if (dot0 < dot1) {
|
|
|
|
|
slide_vel.x = original_velocity.x - clip_planes[0].x * vdot0;
|
|
|
|
|
slide_vel.y = original_velocity.y - clip_planes[0].y * vdot0;
|
|
|
|
|
slide_vel.z = original_velocity.z - clip_planes[0].z * vdot0;
|
|
|
|
|
} else {
|
|
|
|
|
slide_vel.x = original_velocity.x - clip_planes[1].x * vdot1;
|
|
|
|
|
slide_vel.y = original_velocity.y - clip_planes[1].y * vdot1;
|
|
|
|
|
slide_vel.z = original_velocity.z - clip_planes[1].z * vdot1;
|
|
|
|
|
}
|
2025-10-07 10:32:48 -05:00
|
|
|
|
2025-11-21 16:44:42 -06:00
|
|
|
f32 slide_len = sqrtf(slide_vel.x * slide_vel.x + slide_vel.y * slide_vel.y + slide_vel.z * slide_vel.z);
|
|
|
|
|
|
|
|
|
|
if (slide_len > 0.01f) {
|
|
|
|
|
pos.x += slide_vel.x;
|
|
|
|
|
pos.y += slide_vel.y;
|
|
|
|
|
pos.z += slide_vel.z;
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_vec3 crease_dir = pxl8_vec3_cross(clip_planes[0], clip_planes[1]);
|
|
|
|
|
f32 crease_len = pxl8_vec3_length(crease_dir);
|
2025-11-21 16:44:42 -06:00
|
|
|
if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) {
|
2025-11-28 14:41:35 -06:00
|
|
|
f32 bias0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
|
|
|
|
f32 bias1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
2025-11-21 16:44:42 -06:00
|
|
|
|
|
|
|
|
if (bias0 < 0 && bias1 < 0) {
|
|
|
|
|
const f32 corner_push = 0.1f;
|
|
|
|
|
pxl8_vec3 push_away = {
|
|
|
|
|
clip_planes[0].x + clip_planes[1].x,
|
|
|
|
|
clip_planes[0].y + clip_planes[1].y,
|
|
|
|
|
clip_planes[0].z + clip_planes[1].z
|
|
|
|
|
};
|
|
|
|
|
f32 push_len_sq = push_away.x * push_away.x + push_away.y * push_away.y + push_away.z * push_away.z;
|
|
|
|
|
if (push_len_sq > 0.000001f) {
|
|
|
|
|
f32 inv_push_len = corner_push / sqrtf(push_len_sq);
|
|
|
|
|
pos.x += push_away.x * inv_push_len;
|
|
|
|
|
pos.y += push_away.y * inv_push_len;
|
|
|
|
|
pos.z += push_away.z * inv_push_len;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 horizontal_movement_sq = (pos.x - from.x) * (pos.x - from.x) +
|
|
|
|
|
(pos.z - from.z) * (pos.z - from.z);
|
|
|
|
|
f32 desired_horizontal_sq = (to.x - from.x) * (to.x - from.x) +
|
|
|
|
|
(to.z - from.z) * (to.z - from.z);
|
|
|
|
|
|
|
|
|
|
if (desired_horizontal_sq > 0.01f && horizontal_movement_sq < desired_horizontal_sq * 0.25f) {
|
|
|
|
|
const f32 max_step_height = 0.4f;
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 step_up = pos;
|
|
|
|
|
step_up.y += max_step_height;
|
|
|
|
|
|
|
|
|
|
if (!pxl8_world_check_collision(world, step_up, radius)) {
|
|
|
|
|
pxl8_vec3 step_forward = {
|
|
|
|
|
step_up.x + (to.x - pos.x),
|
|
|
|
|
step_up.y,
|
|
|
|
|
step_up.z + (to.z - pos.z)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!pxl8_world_check_collision(world, step_forward, radius)) {
|
|
|
|
|
pos = step_forward;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pos;
|
2025-10-07 10:32:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
refactor: reorganize pxl8 into client/src/ module structure
- core/: main entry, types, logging, I/O, RNG
- asset/: ase loader, cart, save, embed
- gfx/: graphics, animation, atlas, fonts, tilemap, transitions
- sfx/: audio
- script/: lua/fennel runtime, REPL
- hal/: platform abstraction (SDL3)
- world/: BSP, world, procedural gen
- math/: math utilities
- game/: GUI, replay
- lua/: Lua API modules
2026-01-12 21:46:31 -06:00
|
|
|
if (!world || !gfx || !world->loaded) {
|
|
|
|
|
static int count = 0;
|
|
|
|
|
if (count++ < 10) {
|
|
|
|
|
pxl8_debug("world_render: early return - world=%p, gfx=%p, loaded=%d",
|
|
|
|
|
(void*)world, (void*)gfx, world ? world->loaded : -1);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-09 06:30:17 -06:00
|
|
|
if (world->wireframe) {
|
|
|
|
|
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color);
|
|
|
|
|
} else {
|
2025-11-11 21:24:53 -06:00
|
|
|
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
|
2025-11-09 06:30:17 -06:00
|
|
|
}
|
2025-10-07 10:32:48 -05:00
|
|
|
}
|