add is_stairs and generate trile heightmaps for triles

This commit is contained in:
Tuomas Katajisto 2026-09-19 20:09:35 +03:00
parent eeddecb63c
commit c55f26178f
5 changed files with 96 additions and 21 deletions

View File

@ -498,7 +498,10 @@ add_trile :: (name: string, x: s32, y: s32, z: s32, orientation: u8 = 0) {
array_add(*chunk.groups, group); array_add(*chunk.groups, group);
} }
invalidate_buried_around(*curworld.world, x, y, z); 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 } @Command
remove_trile :: (x: s32, y: s32, z: s32) { 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 break;
} }
if removed invalidate_buried_around(*curworld.world, x, y, z); 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 } @Command

View File

@ -64,6 +64,7 @@ ntrile :: (name: string) {
newt.name = sprint("%", name); newt.name = sprint("%", name);
set_trile(newt.name, newt); set_trile(newt.name, newt);
editor_current_trile = get_trile(newt.name); editor_current_trile = get_trile(newt.name);
trile_gen_heightmap(editor_current_trile);
} @Command } @Command
cpytrile :: (trile: string, destination: string) { cpytrile :: (trile: string, destination: string) {
@ -73,6 +74,7 @@ cpytrile :: (trile: string, destination: string) {
newt.trixels = get_trile(trile).trixels; newt.trixels = get_trile(trile).trixels;
set_trile(newt.name, newt); set_trile(newt.name, newt);
editor_current_trile = get_trile(newt.name); editor_current_trile = get_trile(newt.name);
trile_gen_heightmap(editor_current_trile);
} @Command } @Command
ltrile :: (name: string) { ltrile :: (name: string) {
@ -83,6 +85,7 @@ ltrile :: (name: string) {
return; return;
} }
editor_current_trile = nt; editor_current_trile = nt;
trile_gen_heightmap(editor_current_trile);
} @Command } @Command
#scope_file #scope_file
@ -104,6 +107,7 @@ do_undo :: () {
undo_head = (undo_head - 1 + UNDO_MAX) % UNDO_MAX; undo_head = (undo_head - 1 + UNDO_MAX) % UNDO_MAX;
undo_count -= 1; undo_count -= 1;
editor_current_trile.trixels = undo_trixels[undo_head]; editor_current_trile.trixels = undo_trixels[undo_head];
trile_gen_heightmap(editor_current_trile);
} }
do_redo :: () { do_redo :: () {
@ -114,6 +118,7 @@ do_redo :: () {
redo_head = (redo_head - 1 + UNDO_MAX) % UNDO_MAX; redo_head = (redo_head - 1 + UNDO_MAX) % UNDO_MAX;
redo_count -= 1; redo_count -= 1;
editor_current_trile.trixels = redo_trixels[redo_head]; editor_current_trile.trixels = redo_trixels[redo_head];
trile_gen_heightmap(editor_current_trile);
} }
apply_tool_to_trixel :: (x: s64, y: s64, z: s64) { apply_tool_to_trixel :: (x: s64, y: s64, z: s64) {
@ -187,7 +192,7 @@ handle_tool_click :: () {
area_start_z = hovered_trixel_z; area_start_z = hovered_trixel_z;
} }
} }
trile_gen_heightmap(editor_current_trile);
} }
#scope_export #scope_export
@ -202,6 +207,7 @@ reset_trile :: () {
} }
} }
} }
trile_gen_heightmap(editor_current_trile);
} @Command } @Command
trile_copy :: (from: string) { trile_copy :: (from: string) {
@ -213,6 +219,7 @@ trile_copy :: (from: string) {
} }
} }
} }
trile_gen_heightmap(editor_current_trile);
} @Command } @Command
tick_trile_editor :: () { tick_trile_editor :: () {
@ -577,6 +584,12 @@ draw_meta_tab :: (theme: *GR.Overall_Theme, area: GR.Rect) {
r.h = row_h; r.h = row_h;
changed := GR.base_checkbox(r, "Opaque (occludes neighbors)", editor_current_trile.is_opaque, *theme.checkbox_theme); 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; 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.y += row_h * 2;
r.h = row_h; r.h = row_h;

View File

@ -3,10 +3,12 @@ is_trile_collision :: (fx: float, fy: float, fz: float) -> bool {
y := cast(s32)floor(fy); y := cast(s32)floor(fy);
z := cast(s32)floor(fz); z := cast(s32)floor(fz);
world := get_current_world(); world := get_current_world();
if !world.valid then return false;
key := world_to_chunk_coord(x, y, z); key := world_to_chunk_coord(x, y, z);
chunk := table_find_pointer(*world.world.chunks, key); chunk := table_find_pointer(*world.world.chunks, key);
if !chunk then return false; if !chunk then return false;
lx, ly, lz := world_to_local(x, y, z); 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;
} }

View File

@ -30,6 +30,22 @@ get_trile_table_ptr :: () -> *Table(string, Trile) {
return *trile_table; 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. // @Note: Creates the gfx things if they are not yet created.
// Could be a bad idea to do this implicitly. Think about it // Could be a bad idea to do this implicitly. Think about it
// once it's more clear how this whole trile storage thing // once it's more clear how this whole trile storage thing
@ -215,23 +231,38 @@ Trile :: struct {
name : string = "test"; name : string = "test";
is_opaque : bool = true; is_opaque : bool = true;
trixels : [16][16][16] Trixel; 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; TrixelSerialize :: [5]u8;
TrileSerialize :: struct { TrileSerialize :: struct {
name : string = "test"; name : string = "test";
version : int = 0; version : int = 0;
is_opaque : u8 = 1; is_opaque : u8 = 1;
trixels : [16][16][16] TrixelSerialize; is_stairs : u8 = 0;
skip_collision : u8 = 0;
trixels : [16][16][16] TrixelSerialize;
}; };
trile_to_serialize_form :: (t: Trile) -> TrileSerialize { trile_to_serialize_form :: (t: Trile) -> TrileSerialize {
ts := TrileSerialize.{ ts := TrileSerialize.{
name = t.name, name = t.name,
version = 2, version = 3,
is_opaque = ifx t.is_opaque then cast(u8)1 else cast(u8)0, 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 i: 0..15 {
for j: 0..15 { for j: 0..15 {
for k: 0..15 { for k: 0..15 {
@ -247,6 +278,10 @@ trile_from_serialize_form :: (ts: TrileSerialize) -> Trile {
t := Trile.{ name = sprint("%", ts.name) }; t := Trile.{ name = sprint("%", ts.name) };
if ts.version >= 2 t.is_opaque = ts.is_opaque != 0; if ts.version >= 2 t.is_opaque = ts.is_opaque != 0;
else t.is_opaque = true; 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 i: 0..15 {
for j: 0..15 { for j: 0..15 {
for k: 0..15 { for k: 0..15 {
@ -270,6 +305,7 @@ trile_from_serialize_form :: (ts: TrileSerialize) -> Trile {
} }
} }
} }
trile_gen_heightmap(*t);
return t; return t;
} }

View File

@ -63,23 +63,42 @@ Chunk :: struct {
// that is useful when we're checking for ground under // that is useful when we're checking for ground under
// entities // entities
occupancy_mask : [32][32]u32; 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_probe_grid: sg_image; // 192x4096 RGBA16F 2D texture (2 probes/trile/axis)
sh_valid: bool; sh_valid: bool;
sh_dirty: bool; sh_dirty: bool;
} }
// @Note: these are local coords inside the chunk Occupancy_State :: enum {
chunk_get_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32) -> bool { FREE;
return (chunk.occupancy_mask[lx][lz] & (cast(u32)1 << ly)) != 0; OCCUPIED;
OCCUPIED_STAIRS;
} }
// @Note: these are local coords inside the chunk // @Note: these are local coords inside the chunk
chunk_set_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32, occupied: bool) { chunk_get_occupancy_mask_at :: inline (chunk: *Chunk, lx: u32, ly: u32, lz: u32) -> Occupancy_State {
if occupied { if (chunk.occupancy_mask[lx][lz] & (cast(u32)1 << ly)) != 0 {
chunk.occupancy_mask[lx][lz] |= (cast(u32)1 << ly); 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 { } else {
chunk.occupancy_mask[lx][lz] &= ~(cast(u32)1 << ly); 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 i: 0..31 {
for j: 0..31 { for j: 0..31 {
chunk.occupancy_mask[cast(u32)i][cast(u32)j] = 0; 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); chunk_clear_full_occupancy_mask(chunk);
for group: chunk.groups { for group: chunk.groups {
// @ToDo: Handle non-colliding triles by getting the trile, ok := get_trile(group.trile_name);
// trile data here and continuing if it's non-colliding. if !ok || trile.skip_collision then continue;
new_state : Occupancy_State = ifx trile.is_stairs then .OCCUPIED_STAIRS else .OCCUPIED;
for instance : group.instances { for instance : group.instances {
chunk_set_occupancy_mask_at(chunk, instance.x, instance.y, instance.z, new_state);
chunk_set_occupancy_mask_at(chunk, instance.x, instance.y, instance.z, true);
} }
} }
} }