Download src/render/textures.rs from EHS-Studio/hieroxel: direct link, hf CLI and curl.
- Browser
- Download file 12.9 kB
-
https://huggingface.co/EHS-Studio/hieroxel/resolve/main/src/render/textures.rs
- Command line
-
hf download hf://EHS-Studio/hieroxel/src/render/textures.rs
-
curl -L -o textures.rs https://huggingface.co/EHS-Studio/hieroxel/resolve/main/src/render/textures.rs
12.9 kB
| //! Procedural 16x16 block textures, painted at startup into one mipmapped texture array. | |
| use bevy::asset::RenderAssetUsages; | |
| use bevy::image::{Image, ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor}; | |
| use bevy::render::render_resource::{ | |
| Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension, | |
| }; | |
| use crate::world::block::tex; | |
| pub const SIZE: usize = 16; | |
| const MIPS: u32 = 5; | |
| type Px = [u8; 4]; | |
| fn h(layer: u8, x: usize, y: usize, salt: u32) -> f32 { | |
| crate::world::terrain::hashf(0xB10C ^ salt, x as i32, y as i32, layer as i32) | |
| } | |
| fn rgb(c: [i32; 3], jitter: f32) -> Px { | |
| let j = |v: i32| (v as f32 + jitter).clamp(0.0, 255.0) as u8; | |
| [j(c[0]), j(c[1]), j(c[2]), 255] | |
| } | |
| fn noisy(layer: u8, x: usize, y: usize, base: [i32; 3], amount: f32) -> Px { | |
| rgb(base, (h(layer, x, y, 1) - 0.5) * 2.0 * amount) | |
| } | |
| const CLEAR: Px = [0, 0, 0, 0]; | |
| const GLYPHS: [&str; 16] = [ | |
| "################", | |
| "#..............#", | |
| "#.###....#.#...#", | |
| "#.#.#....###...#", | |
| "#.###.....#....#", | |
| "#..#.....###...#", | |
| "#.###.....#....#", | |
| "#..............#", | |
| "#.#...#..####..#", | |
| "#.##.##..#..#..#", | |
| "#.#.#.#..####..#", | |
| "#.#...#....#...#", | |
| "#........###...#", | |
| "#..#####.......#", | |
| "#..............#", | |
| "################", | |
| ]; | |
| /// Paints one texture layer, row-major, top row first. | |
| pub fn paint(layer: u8) -> Vec<Px> { | |
| let mut px = vec![CLEAR; SIZE * SIZE]; | |
| for y in 0..SIZE { | |
| for x in 0..SIZE { | |
| let r = h(layer, x, y, 2); | |
| px[y * SIZE + x] = match layer { | |
| tex::STONE => { | |
| let base = if r < 0.12 { [104, 104, 108] } else { [128, 128, 131] }; | |
| noisy(layer, x, y, base, 12.0) | |
| } | |
| tex::DIRT => dirt(layer, x, y, r), | |
| tex::GRASS_TOP => { | |
| let base = if r < 0.2 { [78, 140, 44] } else { [96, 162, 56] }; | |
| noisy(layer, x, y, base, 16.0) | |
| } | |
| tex::GRASS_SIDE => { | |
| let depth = 3 + (h(layer, x, 0, 3) * 3.0) as usize; | |
| if y < depth { | |
| noisy(layer, x, y, [92, 158, 52], 14.0) | |
| } else { | |
| dirt(layer, x, y, r) | |
| } | |
| } | |
| tex::SAND => noisy(layer, x, y, [222, 208, 160], 9.0), | |
| tex::GRAVEL => { | |
| let pebble = h(layer, x / 3, y / 3, 4); | |
| let base = if pebble < 0.33 { | |
| [110, 104, 100] | |
| } else if pebble < 0.66 { | |
| [140, 134, 128] | |
| } else { | |
| [92, 88, 90] | |
| }; | |
| noisy(layer, x, y, base, 10.0) | |
| } | |
| tex::WATER => { | |
| let wave = ((x as f32 * 0.8 + y as f32 * 0.4).sin() * 0.5 + 0.5) * 18.0; | |
| rgb([38, 92, 196], wave + (r - 0.5) * 10.0) | |
| } | |
| tex::LOG_SIDE => { | |
| let stripe = h(layer, x, 0, 5); | |
| let base = if stripe < 0.3 { [82, 60, 36] } else { [106, 79, 48] }; | |
| noisy(layer, x, y, base, 8.0) | |
| } | |
| tex::LOG_TOP => { | |
| let (dx, dy) = (x as f32 - 7.5, y as f32 - 7.5); | |
| let d = dx.abs().max(dy.abs()); | |
| if d > 6.5 { | |
| noisy(layer, x, y, [96, 70, 42], 8.0) | |
| } else if (d as i32) % 2 == 0 { | |
| noisy(layer, x, y, [176, 140, 88], 6.0) | |
| } else { | |
| noisy(layer, x, y, [150, 116, 70], 6.0) | |
| } | |
| } | |
| tex::LEAVES => { | |
| if r < 0.22 { | |
| CLEAR | |
| } else { | |
| let base = if h(layer, x, y, 6) < 0.3 { [46, 104, 34] } else { [62, 128, 44] }; | |
| noisy(layer, x, y, base, 14.0) | |
| } | |
| } | |
| tex::PLANKS => { | |
| let seam = y % 4 == 3; | |
| let offset = if (y / 4) % 2 == 0 { 0 } else { 8 }; | |
| let joint = (x + offset) % 16 == 0; | |
| if seam || joint { | |
| [120, 90, 54, 255] | |
| } else { | |
| noisy(layer, x, y, [180, 141, 88], 7.0 + h(layer, x, y / 4, 7) * 8.0) | |
| } | |
| } | |
| tex::GLASS => { | |
| let edge = x == 0 || y == 0 || x == SIZE - 1 || y == SIZE - 1; | |
| let glint = (x + y == 9 || x + y == 11) && (3..9).contains(&x); | |
| if edge { | |
| [205, 228, 240, 255] | |
| } else if glint { | |
| [235, 245, 255, 200] | |
| } else { | |
| CLEAR | |
| } | |
| } | |
| tex::MOSS => { | |
| let base = if r < 0.25 { [88, 132, 58] } else { [66, 106, 46] }; | |
| noisy(layer, x, y, base, 12.0) | |
| } | |
| tex::VINE => { | |
| let strand = [3usize, 8, 12]; | |
| let on = strand.iter().any(|&s| { | |
| let wobble = ((y as f32 * 0.9 + s as f32).sin() * 1.2).round() as i32; | |
| (s as i32 + wobble) == x as i32 | |
| }); | |
| let leaf = r < 0.12; | |
| if on || leaf { | |
| noisy(layer, x, y, [52, 116, 38], 14.0) | |
| } else { | |
| CLEAR | |
| } | |
| } | |
| tex::GLYPHSTONE => { | |
| let carved = GLYPHS[y].as_bytes()[x] == b'#'; | |
| if carved { | |
| noisy(layer, x, y, [150, 118, 70], 6.0) | |
| } else { | |
| noisy(layer, x, y, [214, 186, 124], 7.0) | |
| } | |
| } | |
| tex::LUMEN => { | |
| let (dx, dy) = (x as f32 - 7.5, y as f32 - 7.5); | |
| let facet = ((dx * 0.7 + dy).abs() < 1.5) || ((dx - dy * 0.6).abs() < 1.2); | |
| let edge = x == 0 || y == 0 || x == SIZE - 1 || y == SIZE - 1; | |
| if edge { | |
| [60, 140, 150, 255] | |
| } else if facet { | |
| [210, 255, 245, 255] | |
| } else { | |
| noisy(layer, x, y, [110, 228, 214], 16.0) | |
| } | |
| } | |
| tex::SNOW => noisy(layer, x, y, [238, 244, 250], 6.0), | |
| tex::SNOW_SIDE => { | |
| let depth = 3 + (h(layer, x, 0, 8) * 3.0) as usize; | |
| if y < depth { | |
| noisy(layer, x, y, [236, 242, 248], 6.0) | |
| } else { | |
| dirt(layer, x, y, r) | |
| } | |
| } | |
| tex::CLAY => noisy(layer, x, y, [156, 162, 178], 6.0), | |
| tex::BASALT => { | |
| let band = h(layer, x, 0, 9); | |
| let base = if band < 0.4 { [46, 46, 54] } else { [62, 62, 70] }; | |
| noisy(layer, x, y, base, 7.0) | |
| } | |
| tex::BLOOM => { | |
| let (dx, dy) = (x as i32 - 8, y as i32 - 5); | |
| if dx * dx + dy * dy <= 5 { | |
| if dx * dx + dy * dy <= 1 { | |
| [250, 220, 90, 255] | |
| } else { | |
| noisy(layer, x, y, [222, 70, 150], 14.0) | |
| } | |
| } else if (x == 8 || (x == 7 && y == 11)) && y > 7 { | |
| [56, 128, 44, 255] | |
| } else if (y == 10 && (x == 9 || x == 10)) || (y == 12 && (x == 6 || x == 5)) { | |
| [70, 146, 52, 255] | |
| } else { | |
| CLEAR | |
| } | |
| } | |
| tex::SAPLING => { | |
| let (dx, dy) = (x as i32 - 8, y as i32 - 6); | |
| if dx * dx + dy * dy * 2 <= 18 && r > 0.15 { | |
| noisy(layer, x, y, [62, 136, 44], 14.0) | |
| } else if (x == 8 || x == 7) && y >= 8 { | |
| [96, 70, 40, 255] | |
| } else { | |
| CLEAR | |
| } | |
| } | |
| tex::DEEPCORE => { | |
| let base = if r < 0.1 { [70, 40, 90] } else { [30, 28, 36] }; | |
| noisy(layer, x, y, base, 6.0) | |
| } | |
| tex::TUFT => { | |
| let blade = h(layer, x, 0, 10); | |
| let height = 4 + (blade * 10.0) as usize; | |
| if x % 2 == (blade * 2.0) as usize % 2 && y >= SIZE - height { | |
| noisy(layer, x, y, [92, 160, 54], 18.0) | |
| } else { | |
| CLEAR | |
| } | |
| } | |
| tex::BRICK => { | |
| let row = y / 4; | |
| let mortar_y = y % 4 == 3; | |
| let mortar_x = (x + if row % 2 == 0 { 0 } else { 4 }) % 8 == 7; | |
| if mortar_y || mortar_x { | |
| noisy(layer, x, y, [188, 180, 168], 6.0) | |
| } else { | |
| noisy(layer, x, y, [154, 72, 56], 12.0) | |
| } | |
| } | |
| tex::SANDSTONE => { | |
| let band = (y / 5) % 2 == 0; | |
| let base = if band { [214, 190, 132] } else { [200, 174, 116] }; | |
| noisy(layer, x, y, base, 6.0) | |
| } | |
| _ => [255, 0, 255, 255], | |
| }; | |
| } | |
| } | |
| px | |
| } | |
| fn dirt(layer: u8, x: usize, y: usize, r: f32) -> Px { | |
| let base = if r < 0.15 { [100, 70, 46] } else if r > 0.92 { [140, 104, 72] } else { [122, 87, 59] }; | |
| noisy(layer, x, y, base, 10.0) | |
| } | |
| /// Halves a layer, averaging colour weighted by alpha so cut-out edges do not darken. | |
| fn downsample(src: &[Px], size: usize) -> Vec<Px> { | |
| let half = size / 2; | |
| let mut out = vec![CLEAR; half * half]; | |
| for y in 0..half { | |
| for x in 0..half { | |
| let mut c = [0f32; 3]; | |
| let mut a = 0f32; | |
| for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { | |
| let p = src[(y * 2 + dy) * size + x * 2 + dx]; | |
| let w = p[3] as f32; | |
| for i in 0..3 { | |
| c[i] += p[i] as f32 * w; | |
| } | |
| a += w; | |
| } | |
| out[y * half + x] = if a > 0.0 { | |
| [(c[0] / a) as u8, (c[1] / a) as u8, (c[2] / a) as u8, (a / 4.0) as u8] | |
| } else { | |
| CLEAR | |
| }; | |
| } | |
| } | |
| out | |
| } | |
| pub fn build_array() -> Image { | |
| let layers = tex::COUNT; | |
| let mut data = Vec::new(); | |
| for layer in 0..layers as u8 { | |
| let mut level = paint(layer); | |
| let mut size = SIZE; | |
| for _ in 0..MIPS { | |
| data.extend(level.iter().flatten()); | |
| if size > 1 { | |
| level = downsample(&level, size); | |
| size /= 2; | |
| } | |
| } | |
| } | |
| let mut image = Image::new( | |
| Extent3d { | |
| width: SIZE as u32, | |
| height: SIZE as u32, | |
| depth_or_array_layers: layers, | |
| }, | |
| TextureDimension::D2, | |
| // Sized for mip 0 to satisfy the constructor; replaced by the full mip chain below. | |
| vec![0; SIZE * SIZE * 4 * layers as usize], | |
| TextureFormat::Rgba8UnormSrgb, | |
| RenderAssetUsages::RENDER_WORLD, | |
| ); | |
| image.data = Some(data); | |
| image.texture_descriptor.mip_level_count = MIPS; | |
| image.texture_view_descriptor = Some(TextureViewDescriptor { | |
| dimension: Some(TextureViewDimension::D2Array), | |
| ..Default::default() | |
| }); | |
| image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor { | |
| address_mode_u: ImageAddressMode::Repeat, | |
| address_mode_v: ImageAddressMode::Repeat, | |
| mag_filter: ImageFilterMode::Nearest, | |
| min_filter: ImageFilterMode::Nearest, | |
| mipmap_filter: ImageFilterMode::Linear, | |
| ..Default::default() | |
| }); | |
| image | |
| } | |
| /// A standalone 2D copy of one layer, for the hotbar. | |
| pub fn icon(layer: u8) -> Image { | |
| let data: Vec<u8> = paint(layer).into_iter().flatten().collect(); | |
| let mut image = Image::new( | |
| Extent3d { | |
| width: SIZE as u32, | |
| height: SIZE as u32, | |
| depth_or_array_layers: 1, | |
| }, | |
| TextureDimension::D2, | |
| data, | |
| TextureFormat::Rgba8UnormSrgb, | |
| RenderAssetUsages::RENDER_WORLD, | |
| ); | |
| image.sampler = ImageSampler::nearest(); | |
| image | |
| } | |