From c55f26178f13cf009c63f75415a02c863c8bb905 Mon Sep 17 00:00:00 2001 From: Katajisto Date: Sat, 19 Sep 2026 20:09:35 +0300 Subject: [PATCH] add is_stairs and generate trile heightmaps for triles --- src/editor/level_editor.jai | 7 +++-- src/editor/trile_editor.jai | 15 ++++++++- src/pseudophysics/worldphysics.jai | 4 ++- src/trile.jai | 50 +++++++++++++++++++++++++----- src/world.jai | 41 ++++++++++++++++++------ 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/editor/level_editor.jai b/src/editor/level_editor.jai index 2b7f5f6..a73f563 100644 --- a/src/editor/level_editor.jai +++ b/src/editor/level_editor.jai @@ -498,7 +498,10 @@ add_trile :: (name: string, x: s32, y: s32, z: s32, orientation: u8 = 0) { array_add(*chunk.groups, group); } invalidate_buried_around(*curworld.world, x, y, z); - chunk_set_occupancy_mask_at(chunk, lx, ly, lz, true); + trile, ok := get_trile(name); + if !ok || trile.skip_collision then return; + new_state : Occupancy_State = ifx trile.is_stairs then .OCCUPIED_STAIRS else .OCCUPIED; + chunk_set_occupancy_mask_at(chunk, lx, ly, lz, new_state); } @Command remove_trile :: (x: s32, y: s32, z: s32) { @@ -525,7 +528,7 @@ remove_trile :: (x: s32, y: s32, z: s32) { if removed break; } if removed invalidate_buried_around(*curworld.world, x, y, z); - chunk_set_occupancy_mask_at(chunk, lx, ly, lz, false); + chunk_set_occupancy_mask_at(chunk, lx, ly, lz, .FREE); } @Command diff --git a/src/editor/trile_editor.jai b/src/editor/trile_editor.jai index a6d01c3..ecf3bc8 100644 --- a/src/editor/trile_editor.jai +++ b/src/editor/trile_editor.jai @@ -64,6 +64,7 @@ ntrile :: (name: string) { newt.name = sprint("%", name); set_trile(newt.name, newt); editor_current_trile = get_trile(newt.name); + trile_gen_heightmap(editor_current_trile); } @Command cpytrile :: (trile: string, destination: string) { @@ -73,6 +74,7 @@ cpytrile :: (trile: string, destination: string) { newt.trixels = get_trile(trile).trixels; set_trile(newt.name, newt); editor_current_trile = get_trile(newt.name); + trile_gen_heightmap(editor_current_trile); } @Command ltrile :: (name: string) { @@ -83,6 +85,7 @@ ltrile :: (name: string) { return; } editor_current_trile = nt; + trile_gen_heightmap(editor_current_trile); } @Command #scope_file @@ -104,6 +107,7 @@ do_undo :: () { undo_head = (undo_head - 1 + UNDO_MAX) % UNDO_MAX; undo_count -= 1; editor_current_trile.trixels = undo_trixels[undo_head]; + trile_gen_heightmap(editor_current_trile); } do_redo :: () { @@ -114,6 +118,7 @@ do_redo :: () { redo_head = (redo_head - 1 + UNDO_MAX) % UNDO_MAX; redo_count -= 1; editor_current_trile.trixels = redo_trixels[redo_head]; + trile_gen_heightmap(editor_current_trile); } apply_tool_to_trixel :: (x: s64, y: s64, z: s64) { @@ -187,7 +192,7 @@ handle_tool_click :: () { area_start_z = hovered_trixel_z; } } - + trile_gen_heightmap(editor_current_trile); } #scope_export @@ -202,6 +207,7 @@ reset_trile :: () { } } } + trile_gen_heightmap(editor_current_trile); } @Command trile_copy :: (from: string) { @@ -213,6 +219,7 @@ trile_copy :: (from: string) { } } } + trile_gen_heightmap(editor_current_trile); } @Command tick_trile_editor :: () { @@ -577,6 +584,12 @@ draw_meta_tab :: (theme: *GR.Overall_Theme, area: GR.Rect) { r.h = row_h; changed := GR.base_checkbox(r, "Opaque (occludes neighbors)", editor_current_trile.is_opaque, *theme.checkbox_theme); if changed editor_current_trile.is_opaque = !editor_current_trile.is_opaque; + r.y += row_h; + changed = GR.base_checkbox(r, "Skip collision", editor_current_trile.skip_collision, *theme.checkbox_theme); + if changed editor_current_trile.skip_collision = !editor_current_trile.skip_collision; + r.y += row_h; + changed = GR.base_checkbox(r, "Is stairs", editor_current_trile.is_stairs, *theme.checkbox_theme); + if changed editor_current_trile.is_stairs = !editor_current_trile.is_stairs; r.y += row_h * 2; r.h = row_h; diff --git a/src/pseudophysics/worldphysics.jai b/src/pseudophysics/worldphysics.jai index bf101db..f9375f4 100644 --- a/src/pseudophysics/worldphysics.jai +++ b/src/pseudophysics/worldphysics.jai @@ -3,10 +3,12 @@ is_trile_collision :: (fx: float, fy: float, fz: float) -> bool { y := cast(s32)floor(fy); z := cast(s32)floor(fz); world := get_current_world(); + if !world.valid then return false; + key := world_to_chunk_coord(x, y, z); chunk := table_find_pointer(*world.world.chunks, key); if !chunk then return false; lx, ly, lz := world_to_local(x, y, z); - return chunk_get_occupancy_mask_at(chunk, lx, ly, lz); + return chunk_get_occupancy_mask_at(chunk, lx, ly, lz) != .FREE; } diff --git a/src/trile.jai b/src/trile.jai index b6759a1..9669734 100644 --- a/src/trile.jai +++ b/src/trile.jai @@ -30,6 +30,22 @@ get_trile_table_ptr :: () -> *Table(string, Trile) { return *trile_table; } +trile_gen_heightmap :: (trile: *Trile) { + for x: 0..15 { + for z: 0..15 { + height : u8 = 16; + for y: 0..15 { + if trile.trixels[x][15-y][z].empty == true { + height -= 1; + } else { + break; + } + } + trile.heightmap[x][z] = height; + } + } +} + // @Note: Creates the gfx things if they are not yet created. // Could be a bad idea to do this implicitly. Think about it // once it's more clear how this whole trile storage thing @@ -215,23 +231,38 @@ Trile :: struct { name : string = "test"; is_opaque : bool = true; trixels : [16][16][16] Trixel; + + skip_collision : bool; + + // if the trile is something that should + // be able to be used so that the player + // moves in the Y direction, it needs to + // be marked as stairs and a heightmap + // in the Y axis is generated for it. + is_stairs : bool; + heightmap : [16][16]u8; }; TrixelSerialize :: [5]u8; TrileSerialize :: struct { - name : string = "test"; - version : int = 0; - is_opaque : u8 = 1; - trixels : [16][16][16] TrixelSerialize; + name : string = "test"; + version : int = 0; + is_opaque : u8 = 1; + is_stairs : u8 = 0; + skip_collision : u8 = 0; + trixels : [16][16][16] TrixelSerialize; }; trile_to_serialize_form :: (t: Trile) -> TrileSerialize { ts := TrileSerialize.{ - name = t.name, - version = 2, - is_opaque = ifx t.is_opaque then cast(u8)1 else cast(u8)0, + name = t.name, + version = 3, + is_opaque = ifx t.is_opaque then cast(u8)1 else cast(u8)0, + is_stairs = ifx t.is_stairs then cast(u8)1 else cast(u8)0, + skip_collision = ifx t.skip_collision then cast(u8)1 else cast(u8)0, }; + for i: 0..15 { for j: 0..15 { for k: 0..15 { @@ -247,6 +278,10 @@ trile_from_serialize_form :: (ts: TrileSerialize) -> Trile { t := Trile.{ name = sprint("%", ts.name) }; if ts.version >= 2 t.is_opaque = ts.is_opaque != 0; else t.is_opaque = true; + if ts.version >= 3 t.is_stairs = ts.is_stairs != 0; + else t.is_stairs = false; + if ts.version >= 3 t.skip_collision = ts.skip_collision != 0; + else t.skip_collision = false; for i: 0..15 { for j: 0..15 { for k: 0..15 { @@ -270,6 +305,7 @@ trile_from_serialize_form :: (ts: TrileSerialize) -> Trile { } } } + trile_gen_heightmap(*t); return t; } diff --git a/src/world.jai b/src/world.jai index dc8c83e..ddf5b02 100644 --- a/src/world.jai +++ b/src/world.jai @@ -63,23 +63,42 @@ Chunk :: struct { // that is useful when we're checking for ground under // entities occupancy_mask : [32][32]u32; + // We want to be easily able to identify stairs from the chunk. + stairs_mask : [32][32]u32; sh_probe_grid: sg_image; // 192x4096 RGBA16F 2D texture (2 probes/trile/axis) sh_valid: bool; sh_dirty: bool; } -// @Note: these are local coords inside the chunk -chunk_get_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32) -> bool { - return (chunk.occupancy_mask[lx][lz] & (cast(u32)1 << ly)) != 0; +Occupancy_State :: enum { + FREE; + OCCUPIED; + OCCUPIED_STAIRS; } // @Note: these are local coords inside the chunk -chunk_set_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32, occupied: bool) { - if occupied { - chunk.occupancy_mask[lx][lz] |= (cast(u32)1 << ly); +chunk_get_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32) -> Occupancy_State { + if (chunk.occupancy_mask[lx][lz] & (cast(u32)1 << ly)) != 0 { + if (chunk.stairs_mask[lx][lz] & (cast(u32)1 << ly)) != 0 then return .OCCUPIED_STAIRS; + return .OCCUPIED; + } + return .FREE; +} + +// @Note: these are local coords inside the chunk +chunk_set_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32, occupied: Occupancy_State) { + if occupied != .FREE { + if occupied == .OCCUPIED_STAIRS { + chunk.occupancy_mask[lx][lz] |= (cast(u32)1 << ly); + chunk.stairs_mask[lx][lz] |= (cast(u32)1 << ly); + } else { + chunk.occupancy_mask[lx][lz] |= (cast(u32)1 << ly); + chunk.stairs_mask[lx][lz] &= ~(cast(u32)1 << ly); + } } else { chunk.occupancy_mask[lx][lz] &= ~(cast(u32)1 << ly); + chunk.stairs_mask[lx][lz] &= ~(cast(u32)1 << ly); } } @@ -87,6 +106,7 @@ chunk_clear_full_occupancy_mask :: (chunk: *Chunk) { for i: 0..31 { for j: 0..31 { chunk.occupancy_mask[cast(u32)i][cast(u32)j] = 0; + chunk.stairs_mask[cast(u32)i][cast(u32)j] = 0; } } } @@ -128,11 +148,12 @@ calculate_and_set_chunk_occupancy_mask :: (chunk: *Chunk) { chunk_clear_full_occupancy_mask(chunk); for group: chunk.groups { - // @ToDo: Handle non-colliding triles by getting the - // trile data here and continuing if it's non-colliding. + trile, ok := get_trile(group.trile_name); + if !ok || trile.skip_collision then continue; + new_state : Occupancy_State = ifx trile.is_stairs then .OCCUPIED_STAIRS else .OCCUPIED; + for instance : group.instances { - - chunk_set_occupancy_mask_at(chunk, instance.x, instance.y, instance.z, true); + chunk_set_occupancy_mask_at(chunk, instance.x, instance.y, instance.z, new_state); } } }