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);
}
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

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}
}