diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-31 00:03:32 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-31 00:03:32 -0700 |
| commit | d68cf09f7b8e5cb783dc495097c17eaf2a4d5427 (patch) | |
| tree | 59216cae8c466ad63fa6ff47dc5c7f35c64c23d2 | |
| parent | 8e128d3ca24fa10dc7d871f7daf6c43eaddc385c (diff) | |
cleanupkai/herringbone
| -rw-r--r-- | assets/tilesets/tileset1/img.png | bin | 860 -> 1237 bytes | |||
| -rw-r--r-- | src/camera.rs | 2 | ||||
| -rw-r--r-- | src/content/world/mines.rs | 152 | ||||
| -rw-r--r-- | src/main.rs | 8 | ||||
| -rw-r--r-- | src/proc_gen/herringbone.rs | 113 | ||||
| -rw-r--r-- | src/proc_gen/mod.rs | 101 | ||||
| -rw-r--r-- | src/proc_gen/tileset_loader.rs | 53 | ||||
| -rw-r--r-- | src/sim/cell_manager/chunk.rs | 2 | ||||
| -rw-r--r-- | src/sim/cell_manager/manager.rs | 2 | ||||
| -rw-r--r-- | src/sim/entity/mod.rs | 2 | ||||
| -rw-r--r-- | src/sim/lib/marching_squares.rs | 12 | ||||
| -rw-r--r-- | src/sim/rb_manager/mod.rs | 2 |
12 files changed, 279 insertions, 170 deletions
diff --git a/assets/tilesets/tileset1/img.png b/assets/tilesets/tileset1/img.png Binary files differindex e730faf..08df35d 100644 --- a/assets/tilesets/tileset1/img.png +++ b/assets/tilesets/tileset1/img.png diff --git a/src/camera.rs b/src/camera.rs index f5e7c1c..8d1fe78 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -85,7 +85,7 @@ impl Camera { pub fn new(screen_size: (i32, i32)) -> Self { Camera { - zoom: 0.20, + zoom: 0.12, centre: (0.0, 0.0), screen_size, } diff --git a/src/content/world/mines.rs b/src/content/world/mines.rs index ae859bc..6ed4451 100644 --- a/src/content/world/mines.rs +++ b/src/content/world/mines.rs @@ -1,41 +1,145 @@ +use glam::{IVec2, Vec2}; + use crate::{ config::TILESET_SCALING, content::materials::MaterialId, proc_gen::{ - Biome, - tileset_loader::{Tile, TileOrientation, TilesetPixelType}, + Biome, TileCells, + tileset_loader::{Tile, TilesetPixelType}, }, - sim::cell::Cell, + sim::{cell::Cell, lib::marching_squares::compute_types}, }; -pub struct MinesBiome; +const FULL: [bool; (TILESET_SCALING * TILESET_SCALING) as usize] = + [true; (TILESET_SCALING * TILESET_SCALING) as usize]; + +const EMPTY: [bool; (TILESET_SCALING * TILESET_SCALING) as usize] = + [false; (TILESET_SCALING * TILESET_SCALING) as usize]; + +pub struct MinesBiome { + pixel_expansion_by_type: [[bool; (TILESET_SCALING * TILESET_SCALING) as usize]; 16], +} + +fn circular_pixel_expansion( + c: Vec2, + r: f32, +) -> [bool; (TILESET_SCALING * TILESET_SCALING) as usize] { + let rs = r.powi(2); + let mut ret = [false; (TILESET_SCALING * TILESET_SCALING) as usize]; + + for y in 0..TILESET_SCALING { + for x in 0..TILESET_SCALING { + // definitely can be optimized a lot + let dist = Vec2::new(x as f32, y as f32).distance_squared(c); + if dist <= rs { + ret[(x + y * TILESET_SCALING) as usize] = true; + } + } + } + + ret +} + +impl MinesBiome { + pub fn new() -> Self { + MinesBiome { + pixel_expansion_by_type: [ + // empty + EMPTY, + // BL + circular_pixel_expansion( + Vec2::new(0.0, TILESET_SCALING as f32 - 1.0), + TILESET_SCALING as f32, + ), + // BR + circular_pixel_expansion( + Vec2::new(TILESET_SCALING as f32 - 1.0, TILESET_SCALING as f32 - 1.0), + TILESET_SCALING as f32, + ), + // BL BR + FULL, + // TR + circular_pixel_expansion( + Vec2::new(TILESET_SCALING as f32 - 1.0, 0.0), + TILESET_SCALING as f32, + ), + // TR BL + FULL, + // TR BR + FULL, + // TR BR BL + FULL, + // TL + circular_pixel_expansion(Vec2::new(0.0, 0.0), TILESET_SCALING as f32), + // TL BL + FULL, + // TL BR + FULL, + // TL BR BL + FULL, + // TL TR + FULL, + // TL TR BL + FULL, + // TL TR BR + FULL, + // surrounded + FULL, + ], + } + } +} impl Biome for MinesBiome { - fn derive_world_from_tile(&self, tile: &Tile) -> Vec<Cell> { - let tp = TILESET_SCALING.pow(2) as u32; - let mut cells = - vec![Cell::void(); (tile.dimensions.short * tile.dimensions.long * tp) as usize]; - let ey = match tile.orientation { - TileOrientation::Horizontal => tile.dimensions.short, - TileOrientation::Vertical => tile.dimensions.long, - }; - let ex = match tile.orientation { - TileOrientation::Horizontal => tile.dimensions.long, - TileOrientation::Vertical => tile.dimensions.short, - }; + fn derive_world_from_tile(&self, tile: &Tile) -> TileCells { + let pixels = tile.size(); + let marched_pixels = compute_types(tile); + let size = pixels * TILESET_SCALING; + let mut cells = vec![Cell::void(); (size.x * size.y) as usize]; - for y in 0..ey { - for x in 0..ex { - let idx = (x + y * ex) * tp; - for i in 0..tp { - cells[(idx + i) as usize] = match tile.pixels[(x + y * ex) as usize] { - TilesetPixelType::Void => Cell::void(), - TilesetPixelType::Terrain => Cell::from_material(MaterialId::Dirt), + for py in 0..pixels.y { + for px in 0..pixels.x { + let pixel = tile.pixel_at(IVec2::new(px, py)); + let variant = marched_pixels[(px + py * (pixels.x + 1)) as usize]; + + // let pix = marched_pixels[(px + py * pixels.x) as usize]; + // let expansion_cells = [Cell::from_material(MaterialId::from_index( + // pix % MaterialId::ALL.len() as u8, + // )); + // (TILESET_SCALING * TILESET_SCALING) as usize]; + + // let origin = IVec2::new(px, py) * TILESET_SCALING; + // for i in 0..TILESET_SCALING { + // let y = i + origin.y; + // let row = (y * size.x + origin.x) as usize; + // cells[row..row + TILESET_SCALING as usize].copy_from_slice( + // &expansion_cells[i as usize..i as usize + TILESET_SCALING as usize], + // ); + // } + + let expansion = self.pixel_expansion_by_type[variant as usize]; + // TODO optimize + let expansion_cells = expansion.map(|c| { + if c { + Cell::from_material(MaterialId::Dirt) + } else { + Cell::void() } + }); + + let origin = IVec2::new(px, py) * TILESET_SCALING; + for i in 0..TILESET_SCALING { + let dst_y = i + origin.y; + let dst_row = (dst_y * size.x + origin.x) as usize; + let src_row = (i * TILESET_SCALING) as usize; + + cells[dst_row..dst_row + TILESET_SCALING as usize].copy_from_slice( + &expansion_cells[src_row..src_row + TILESET_SCALING as usize], + ); } } } - cells + TileCells { size, cells } } } diff --git a/src/main.rs b/src/main.rs index 118f2a4..e9cae2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,7 +150,7 @@ impl Default for App { sim_manager: Some(SimManager::new()), world_generator: TilesetWorldGenerator::new( "assets/tilesets/tileset1", - Box::new(MinesBiome), + &MinesBiome::new(), ), last_render: Instant::now(), @@ -168,7 +168,11 @@ impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = Arc::new( event_loop - .create_window(Window::default_attributes().with_title(WINDOW_TITLE)) + .create_window( + Window::default_attributes() + .with_title(WINDOW_TITLE) + .with_maximized(true), + ) .unwrap(), ); diff --git a/src/proc_gen/herringbone.rs b/src/proc_gen/herringbone.rs index 271b7a1..e8569c8 100644 --- a/src/proc_gen/herringbone.rs +++ b/src/proc_gen/herringbone.rs @@ -1,111 +1,30 @@ use fxhash::hash32; use glam::IVec2; -use crate::{ - config::TILESET_SCALING, - proc_gen::tileset_loader::{Tile, TileOrientation, Tileset, TilesetPixelType}, -}; +use crate::proc_gen::tileset_loader::TileOrientation; -pub fn split_position(world: IVec2, tileset: &Tileset) -> (IVec2, IVec2, TileOrientation) { - let s = tileset.dimensions.short as i32; - let tx = world.x.div_euclid(s); - let ty = world.y.div_euclid(s); - let fx = world.x.rem_euclid(s); - let fy = world.y.rem_euclid(s); - - match (tx - ty).rem_euclid(4) { - // left half of horizontal tile - 0 => ( - IVec2::new(tx, ty), - IVec2::new(fx, fy), - TileOrientation::Horizontal, - ), - // right half of horizontal tile - 1 => ( - IVec2::new(tx - 1, ty), - IVec2::new(fx + s, fy), - TileOrientation::Horizontal, - ), - // top half of vertical tile - 3 => ( - IVec2::new(tx, ty), - IVec2::new(fx, fy), - TileOrientation::Vertical, - ), - // bottom half of vertical tile - 2 => ( - IVec2::new(tx, ty - 1), - IVec2::new(fx, fy + s), - TileOrientation::Vertical, - ), - _ => unreachable!(), +fn tile_starting_at(grid: IVec2) -> Option<TileOrientation> { + match (grid.x - grid.y).rem_euclid(4) { + // 1 is the right half of the horizontal tile starting at 0, + // 2 is the bottom half of the vertical tile starting at 3 + 0 => Some(TileOrientation::Horizontal), + 3 => Some(TileOrientation::Vertical), + _ => None, } } +pub fn tiles_overlapping(min: IVec2, max: IVec2) -> impl Iterator<Item = (IVec2, TileOrientation)> { + ((min.y - 1)..max.y) + .flat_map(move |y| ((min.x - 1)..max.x).map(move |x| IVec2::new(x, y))) + .filter_map(|grid| tile_starting_at(grid).map(|orientation| (grid, orientation))) +} + // better "%" for hashes since it prefers the high bits #[inline] fn reduce(h: u32, n: usize) -> usize { (((h as u128) * (n as u128)) >> 32) as usize } -fn pick_variant(tile_pos: IVec2, orientation: TileOrientation, tileset: &Tileset) -> &Tile { - let h = hash32(&tile_pos); - match orientation { - TileOrientation::Horizontal => { - &tileset.horizontal_tiles[reduce(h, tileset.horizontal_tiles.len())] - } - TileOrientation::Vertical => { - &tileset.vertical_tiles[reduce(h, tileset.vertical_tiles.len())] - } - } -} - -// TODO: optimize -fn get_pixel_at_position(world: IVec2, tileset: &Tileset) -> TilesetPixelType { - let scaled_world = world.div_euclid(IVec2::new(TILESET_SCALING, TILESET_SCALING)); - let (tile_pos, local, orientation) = split_position(scaled_world, tileset); - let tile = pick_variant(tile_pos, orientation, tileset); - let width = match orientation { - TileOrientation::Horizontal => tileset.dimensions.long as i32, - TileOrientation::Vertical => tileset.dimensions.short as i32, - }; - tile.pixels[(local.x + local.y * width) as usize] +pub fn variant_index(grid: IVec2, variants: usize) -> usize { + reduce(hash32(&grid), variants) } - -pub fn tiles_overlapping( - a: IVec2, - b: IVec2, - tileset: &Tileset, -) -> impl Iterator<Item = (IVec2, &Tile, TileOrientation)> { - let s = IVec2::new( - tileset.dimensions.short as i32, - tileset.dimensions.short as i32, - ); - - let ta = a.div_euclid(s) - 1; - let tb = (b - 1).div_euclid(s); - - (ta.y..=tb.y) - .flat_map(move |ty| (ta.x..=tb.x).map(move |tx| (tx, ty))) - .filter_map(move |(tx, ty)| match (tx - ty).rem_euclid(4) { - 0 => Some(( - IVec2::new(tx, ty), - pick_variant(IVec2::new(tx, ty), TileOrientation::Horizontal, tileset), - TileOrientation::Horizontal, - )), - 3 => Some(( - IVec2::new(tx, ty), - pick_variant(IVec2::new(tx, ty), TileOrientation::Vertical, tileset), - TileOrientation::Vertical, - )), - _ => None, - }) -} - -// pub fn get_cell_at_position(world: IVec2, tileset: &Tileset) -> Cell { -// let pixel = get_pixel_at_position(world, tileset); -// match pixel { -// TilesetPixelType::Void => Cell::void(), -// TilesetPixelType::Terrain => Cell::from_material(MaterialId::Dirt), -// } -// } diff --git a/src/proc_gen/mod.rs b/src/proc_gen/mod.rs index 049ad1a..caec241 100644 --- a/src/proc_gen/mod.rs +++ b/src/proc_gen/mod.rs @@ -6,66 +6,97 @@ use glam::IVec2; use crate::{ config::{CHUNK_SIZE, TILESET_SCALING}, proc_gen::{ - herringbone::tiles_overlapping, + herringbone::{tiles_overlapping, variant_index}, tileset_loader::{Tile, TileOrientation, Tileset, load_tileset}, }, sim::{cell::Cell, cell_manager::chunk::Chunk, entity::EntityDef}, }; +pub struct TileCells { + pub size: IVec2, + pub cells: Vec<Cell>, +} + pub trait Biome { - fn derive_world_from_tile(&self, _tile: &Tile) -> Vec<Cell> { - Vec::new() - } - fn derive_entities_from_tile(&self, _tile: &Tile) -> Vec<EntityDef> { + fn derive_world_from_tile(&self, tile: &Tile) -> TileCells; + fn derive_entities_from_tile(&self, tile: &Tile) -> Vec<EntityDef> { Vec::new() } } pub struct TilesetWorldGenerator { tileset: Tileset, - biome: Box<dyn Biome>, + horizontal_tiles: Vec<TileCells>, + vertical_tiles: Vec<TileCells>, } impl TilesetWorldGenerator { + pub fn new(tileset_path: &str, biome: &dyn Biome) -> Self { + let tileset = load_tileset(tileset_path); + + let horizontal_tiles = tileset + .horizontal_tiles + .iter() + .map(|tile| biome.derive_world_from_tile(tile)) + .collect(); + let vertical_tiles = tileset + .vertical_tiles + .iter() + .map(|tile| biome.derive_world_from_tile(tile)) + .collect(); + + TilesetWorldGenerator { + tileset, + horizontal_tiles, + vertical_tiles, + } + } + + fn grid_size(&self) -> IVec2 { + IVec2::splat(self.tileset.dimensions.short as i32 * TILESET_SCALING) + } + + fn tile_at(&self, grid: IVec2, orientation: TileOrientation) -> &TileCells { + let tiles = match orientation { + TileOrientation::Horizontal => &self.horizontal_tiles, + TileOrientation::Vertical => &self.vertical_tiles, + }; + &tiles[variant_index(grid, tiles.len())] + } + pub fn generate_chunk(&self, chunk_position: IVec2) -> Chunk { let mut chunk = Chunk::void(); - let a = chunk_position * CHUNK_SIZE; - let b = (chunk_position + 1) * CHUNK_SIZE; - let tiles = tiles_overlapping(a * TILESET_SCALING, b * TILESET_SCALING, &self.tileset); - let world_tiles = tiles.map(|(p, t, o)| (p, self.biome.derive_world_from_tile(t), o)); + let chunk_min = chunk_position * CHUNK_SIZE; + let chunk_max = chunk_min + CHUNK_SIZE; + + let grid = self.grid_size(); + let tiles = tiles_overlapping( + chunk_min.div_euclid(grid), + (chunk_max - 1).div_euclid(grid) + 1, + ); + + for (grid_position, orientation) in tiles { + let tile = self.tile_at(grid_position, orientation); + let tile_min = grid_position * grid; - for (tp, t, o) in world_tiles { - let p = tp * self.tileset.dimensions.short as i32 * TILESET_SCALING; - let ta = p.max(a); - let tb = p.min(b); - if ta.x >= tb.x || ta.y >= tb.y { + // subset of tile in chunk + let min = tile_min.max(chunk_min); + let max = (tile_min + tile.size).min(chunk_max); + if min.x >= max.x { continue; } + let width = (max.x - min.x) as usize; - for y in ta.y..tb.y { - for x in ta.x..tb.x { - let sw = match o { - TileOrientation::Horizontal => { - self.tileset.dimensions.long as i32 * TILESET_SCALING - } - TileOrientation::Vertical => { - self.tileset.dimensions.short as i32 * TILESET_SCALING - } - }; - - let src = (y - p.y) * sw + (x - p.x); - let dst = (y - a.y) * CHUNK_SIZE + (x - a.x); - chunk.cells[dst as usize] = t[src as usize]; - } + for y in min.y..max.y { + let src = ((y - tile_min.y) * tile.size.x + (min.x - tile_min.x)) as usize; + let dst = ((y - chunk_min.y) * CHUNK_SIZE + (min.x - chunk_min.x)) as usize; + chunk.cells[dst..dst + width].copy_from_slice(&tile.cells[src..src + width]); } } - chunk - } + chunk.mark_collider_dirty(0); - pub fn new(tileset_path: &str, biome: Box<dyn Biome>) -> Self { - let tileset = load_tileset(tileset_path); - TilesetWorldGenerator { tileset, biome } + chunk } } diff --git a/src/proc_gen/tileset_loader.rs b/src/proc_gen/tileset_loader.rs index acb4d65..e0d9d18 100644 --- a/src/proc_gen/tileset_loader.rs +++ b/src/proc_gen/tileset_loader.rs @@ -1,6 +1,9 @@ +use glam::IVec2; use serde::Deserialize; use std::{collections::HashMap, error::Error, fs::File, io::BufReader, path::Path}; +use crate::sim::lib::marching_squares::Marchable; + fn load_img(path: &Path) -> Result<(Vec<u8>, u32, u32), Box<dyn Error>> { let mut decoder = png::Decoder::new(BufReader::new(File::open(path)?)); decoder.set_transformations(png::Transformations::IDENTITY); @@ -32,7 +35,7 @@ pub struct TileDimensions { pub long: u32, } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)] pub enum TilesetPixelType { Void = 0, Terrain, @@ -55,6 +58,54 @@ pub struct Tile { pub pixels: Vec<TilesetPixelType>, } +// TODO might want to use newtypes to hoist to biome +impl Marchable for Tile { + fn occupied(&self, pos: IVec2) -> bool { + // TODO this will break for tileset dimensions other than 22x44 + if pos.x < 0 || pos.x >= self.size().x || pos.y < 0 || pos.y >= self.size().y { + match self.orientation { + TileOrientation::Horizontal => { + let a = (pos.x == -1 || pos.x == (self.dimensions.long as i32)) + && pos.y >= 8 + && pos.y <= 13; + let b = (pos.y == -1 || pos.y == (self.dimensions.short as i32)) + && ((pos.x >= 8 && pos.x <= 13) || (pos.x >= 30 && pos.x <= 35)); + !(a || b) + } + TileOrientation::Vertical => { + let a = (pos.y == -1 || pos.y == (self.dimensions.long as i32)) + && pos.x >= 8 + && pos.x <= 13; + let b = (pos.x == -1 || pos.x == (self.dimensions.short as i32)) + && ((pos.y >= 8 && pos.y <= 13) || (pos.y >= 30 && pos.y <= 35)); + !(a || b) + } + } + } else { + self.pixel_at(pos) == TilesetPixelType::Terrain + } + } + fn marchable_size(&self) -> IVec2 { + self.size() + } +} + +impl Tile { + pub fn size(&self) -> IVec2 { + let short = self.dimensions.short as i32; + let long = self.dimensions.long as i32; + match self.orientation { + TileOrientation::Horizontal => IVec2::new(long, short), + TileOrientation::Vertical => IVec2::new(short, long), + } + } + + #[inline] + pub fn pixel_at(&self, position: IVec2) -> TilesetPixelType { + self.pixels[(position.x + position.y * self.size().x) as usize] + } +} + #[derive(Debug)] pub struct Tileset { pub dimensions: TileDimensions, diff --git a/src/sim/cell_manager/chunk.rs b/src/sim/cell_manager/chunk.rs index 48e9d16..2021b97 100644 --- a/src/sim/cell_manager/chunk.rs +++ b/src/sim/cell_manager/chunk.rs @@ -72,7 +72,7 @@ impl Marchable for Chunk { || material.form == MaterialForm::Powder && cell.settled() >= SETTLED_THRESOHLD } } - fn size(&self) -> IVec2 { + fn marchable_size(&self) -> IVec2 { IVec2::new(CHUNK_SIZE, CHUNK_SIZE) } } diff --git a/src/sim/cell_manager/manager.rs b/src/sim/cell_manager/manager.rs index 157499d..7478995 100644 --- a/src/sim/cell_manager/manager.rs +++ b/src/sim/cell_manager/manager.rs @@ -43,7 +43,7 @@ impl CellManager { if !sleeping { self.chunks[idx].sleeping = false; // remove this - self.chunks[idx].collider_dirty_seqno = Some(0); + self.chunks[idx].mark_collider_dirty(0); } self.chunks[idx].needs_texture_update = true; } diff --git a/src/sim/entity/mod.rs b/src/sim/entity/mod.rs index 2f4034b..9425f9b 100644 --- a/src/sim/entity/mod.rs +++ b/src/sim/entity/mod.rs @@ -44,7 +44,7 @@ impl Marchable for EntityCells { self.get_cell_at_local_position(pos).material != MaterialId::Void } } - fn size(&self) -> IVec2 { + fn marchable_size(&self) -> IVec2 { self.size } } diff --git a/src/sim/lib/marching_squares.rs b/src/sim/lib/marching_squares.rs index ff48fd5..438b16c 100644 --- a/src/sim/lib/marching_squares.rs +++ b/src/sim/lib/marching_squares.rs @@ -43,15 +43,15 @@ fn derive_type(tl: bool, tr: bool, br: bool, bl: bool) -> usize { pub trait Marchable { fn occupied(&self, pos: IVec2) -> bool; - fn size(&self) -> IVec2; + fn marchable_size(&self) -> IVec2; } -pub fn compute_types(marchable: &impl Marchable, w: i32, h: i32) -> Vec<u8> { +pub fn compute_types(marchable: &impl Marchable) -> Vec<u8> { // TODO could pre-allocate size let mut types = Vec::new(); - for y in -1..h { + for y in -1..marchable.marchable_size().y { // TODO precompute per column - for x in -1..w { + for x in -1..marchable.marchable_size().x { let tl = marchable.occupied(IVec2::new(x, y)); let tr = marchable.occupied(IVec2::new(x + 1, y)); let br = marchable.occupied(IVec2::new(x + 1, y + 1)); @@ -133,11 +133,11 @@ fn toward_side(x: i32, y: i32, exit: Side) -> (i32, i32, Side) { // outer is cw, inner ccw pub fn marching_squares_vertex_trace(marchable: &impl Marchable) -> Vec<Vec<Vec2>> { - let [w, h] = marchable.size().to_array(); + let [w, h] = marchable.marchable_size().to_array(); let mut visited = vec![0u8; ((w + 1) * (h + 1)) as usize]; let idx = |cx: i32, cy: i32| (((cy + 1) * (w + 1)) + (cx + 1)) as usize; - let types = compute_types(marchable, w, h); + let types = compute_types(marchable); let mut polys = Vec::new(); for y in -1..h { diff --git a/src/sim/rb_manager/mod.rs b/src/sim/rb_manager/mod.rs index 9d183db..2b476d5 100644 --- a/src/sim/rb_manager/mod.rs +++ b/src/sim/rb_manager/mod.rs @@ -110,7 +110,7 @@ impl RbManager { let minimum_verts = minimum_verts_per_poly.unwrap_or(3); - let centre_offset = (marchable.size() / 2).as_vec2() - 0.5; + let centre_offset = (marchable.marchable_size() / 2).as_vec2() - 0.5; for poly in polys { let v = vertices.len() as u32; |
