diff --git a/assets/textures/block_atlas.png b/assets/textures/block_atlas.png new file mode 100644 index 0000000..1ee3aac Binary files /dev/null and b/assets/textures/block_atlas.png differ diff --git a/assets/textures/gen_atlas.py b/assets/textures/gen_atlas.py new file mode 100644 index 0000000..f8dcc6f --- /dev/null +++ b/assets/textures/gen_atlas.py @@ -0,0 +1,181 @@ +""" +Generate block_atlas.png — 4x4 atlas (256x256), 64x64 tiles +Row 0: SAND, ROCK, CORAL_RED, CORAL_BLUE +Row 1: KELP, WRECK_WOOD, ICE, BEDROCK +Row 2-3: unused (blank) +""" +from PIL import Image, ImageDraw +import random + +TILE = 64 +ATLAS_COLS = 4 +ATLAS_ROWS = 4 +W = TILE * ATLAS_COLS +H = TILE * ATLAS_ROWS + +atlas = Image.new("RGBA", (W, H), (0, 0, 0, 255)) + + +def noise_tile(base_color, variation=20, seed=0): + r = random.Random(seed) + img = Image.new("RGBA", (TILE, TILE)) + px = img.load() + br, bg, bb = base_color + for y in range(TILE): + for x in range(TILE): + v = r.randint(-variation, variation) + px[x, y] = ( + max(0, min(255, br + v)), + max(0, min(255, bg + v)), + max(0, min(255, bb + v)), + 255 + ) + return img + + +def sand_tile(): + img = noise_tile((194, 178, 128), variation=18, seed=2) + draw = ImageDraw.Draw(img) + r = random.Random(2) + for _ in range(60): + x, y = r.randint(0, TILE-1), r.randint(0, TILE-1) + c = r.randint(160, 210) + draw.point((x, y), fill=(c, c-10, c-30, 255)) + return img + + +def rock_tile(): + img = noise_tile((115, 115, 115), variation=25, seed=3) + draw = ImageDraw.Draw(img) + r = random.Random(3) + for _ in range(4): + y = r.randint(5, TILE-5) + x0 = r.randint(0, 10) + x1 = r.randint(TILE-10, TILE-1) + draw.line([(x0, y), (x1, y + r.randint(-2, 2))], fill=(80, 80, 80, 255), width=1) + return img + + +def coral_red_tile(): + img = noise_tile((230, 64, 52), variation=15, seed=4) + draw = ImageDraw.Draw(img) + r = random.Random(4) + for _ in range(20): + x, y = r.randint(0, TILE-3), r.randint(0, TILE-3) + draw.ellipse([x, y, x+2, y+2], fill=(255, 200, 180, 255)) + return img + + +def coral_blue_tile(): + img = noise_tile((51, 127, 242), variation=15, seed=5) + draw = ImageDraw.Draw(img) + r = random.Random(5) + for _ in range(20): + x, y = r.randint(0, TILE-3), r.randint(0, TILE-3) + draw.ellipse([x, y, x+2, y+2], fill=(180, 220, 255, 255)) + return img + + +def kelp_tile(): + img = noise_tile((38, 153, 51), variation=12, seed=6) + draw = ImageDraw.Draw(img) + r = random.Random(6) + for i in range(0, TILE, 8): + x = i + r.randint(-2, 2) + draw.line([(x, 0), (x, TILE-1)], fill=(20, 100, 30, 255), width=2) + return img + + +def wood_tile(): + img = noise_tile((89, 56, 31), variation=12, seed=7) + draw = ImageDraw.Draw(img) + r = random.Random(7) + for i in range(0, TILE, 6): + y = i + r.randint(-1, 1) + shade = r.randint(50, 90) + draw.line([(0, y), (TILE-1, y + r.randint(-1, 1))], fill=(shade, shade//2, shade//4, 255), width=1) + return img + + +def ice_tile(): + img = noise_tile((191, 230, 255), variation=10, seed=8) + draw = ImageDraw.Draw(img) + r = random.Random(8) + for _ in range(5): + x0, y0 = r.randint(0, TILE-1), r.randint(0, TILE-1) + x1 = x0 + r.randint(-15, 15) + y1 = y0 + r.randint(-15, 15) + draw.line([(x0, y0), (x1, y1)], fill=(140, 200, 240, 255), width=1) + return img + + +def bedrock_tile(): + img = noise_tile((38, 38, 38), variation=10, seed=9) + draw = ImageDraw.Draw(img) + r = random.Random(9) + for _ in range(12): + x, y = r.randint(0, TILE-5), r.randint(0, TILE-5) + c = r.randint(20, 50) + draw.ellipse([x, y, x+4, y+4], fill=(c, c, c, 255)) + return img + + +def glow_coral_cyan_tile(): + img = noise_tile((51, 204, 255), variation=12, seed=10) + draw = ImageDraw.Draw(img) + r = random.Random(10) + for _ in range(25): + x, y = r.randint(0, TILE-3), r.randint(0, TILE-3) + draw.ellipse([x, y, x+3, y+3], fill=(180, 255, 255, 255)) + return img + + +def glow_coral_violet_tile(): + img = noise_tile((178, 77, 230), variation=12, seed=11) + draw = ImageDraw.Draw(img) + r = random.Random(11) + for _ in range(25): + x, y = r.randint(0, TILE-3), r.randint(0, TILE-3) + draw.ellipse([x, y, x+3, y+3], fill=(230, 180, 255, 255)) + return img + + +def lava_vent_tile(): + img = noise_tile((60, 30, 10), variation=10, seed=12) + draw = ImageDraw.Draw(img) + r = random.Random(12) + # glowing cracks in orange/red + for _ in range(8): + x0, y0 = r.randint(10, TILE-10), r.randint(10, TILE-10) + x1 = x0 + r.randint(-12, 12) + y1 = y0 + r.randint(-12, 12) + draw.line([(x0, y0), (x1, y1)], fill=(255, 120, 20, 255), width=2) + # hot center glow + for _ in range(6): + x, y = r.randint(20, TILE-20), r.randint(20, TILE-20) + draw.ellipse([x, y, x+5, y+5], fill=(255, 200, 50, 255)) + return img + + +tiles = [ + (0, 0, sand_tile()), + (1, 0, rock_tile()), + (2, 0, coral_red_tile()), + (3, 0, coral_blue_tile()), + (0, 1, kelp_tile()), + (1, 1, wood_tile()), + (2, 1, ice_tile()), + (3, 1, bedrock_tile()), + (0, 2, glow_coral_cyan_tile()), + (1, 2, glow_coral_violet_tile()), + (2, 2, lava_vent_tile()), +] + +for col, row, tile in tiles: + atlas.paste(tile, (col * TILE, row * TILE)) + +out = "block_atlas.png" +atlas.save(out) +import os +size = os.path.getsize(out) +print(f"Atlas saved: {out} ({W}x{H}) — {size} bytes ({size//1024} Ko)") diff --git a/scripts/world/BlockDatabase.gd b/scripts/world/BlockDatabase.gd index ce68c2c..16f11c0 100644 --- a/scripts/world/BlockDatabase.gd +++ b/scripts/world/BlockDatabase.gd @@ -10,72 +10,120 @@ enum BlockType { KELP = 6, WRECK_WOOD = 7, ICE = 8, - BEDROCK = 9 + BEDROCK = 9, + GLOW_CORAL_CYAN = 10, + GLOW_CORAL_VIOLET = 11, + LAVA_VENT = 12 } +# Atlas is 4x4 tiles, each tile = 0.25 of atlas width/height +const ATLAS_TILE_SIZE: Vector2 = Vector2(0.25, 0.25) + const _BLOCKS: Dictionary = { BlockType.AIR: { "name": "Air", "color": Color(0.0, 0.0, 0.0, 0.0), "hardness": 0.0, - "drops": [] + "drops": [], + "atlas_uv": Vector2(0.0, 0.0) # unused, AIR not rendered }, BlockType.WATER: { "name": "Eau", "color": Color(0.1, 0.4, 0.8, 0.6), "hardness": 0.0, - "drops": [] + "drops": [], + "atlas_uv": Vector2(0.0, 0.0) # unused, WATER not solid }, BlockType.SAND: { "name": "Sable", "color": Color(0.76, 0.70, 0.50, 1.0), "hardness": 0.5, - "drops": [BlockType.SAND] + "drops": [BlockType.SAND], + "atlas_uv": Vector2(0.0, 0.0) # row 0, col 0 }, BlockType.ROCK: { "name": "Roche", "color": Color(0.45, 0.45, 0.45, 1.0), "hardness": 2.0, - "drops": [BlockType.ROCK] + "drops": [BlockType.ROCK], + "atlas_uv": Vector2(0.25, 0.0) # row 0, col 1 }, BlockType.CORAL_RED: { "name": "Corail Rouge", "color": Color(0.90, 0.25, 0.20, 1.0), "hardness": 0.3, - "drops": [BlockType.CORAL_RED] + "drops": [BlockType.CORAL_RED], + "atlas_uv": Vector2(0.5, 0.0) # row 0, col 2 }, BlockType.CORAL_BLUE: { "name": "Corail Bleu", "color": Color(0.20, 0.50, 0.95, 1.0), "hardness": 0.3, - "drops": [BlockType.CORAL_BLUE] + "drops": [BlockType.CORAL_BLUE], + "atlas_uv": Vector2(0.75, 0.0) # row 0, col 3 }, BlockType.KELP: { "name": "Algue", "color": Color(0.15, 0.60, 0.20, 1.0), "hardness": 0.1, - "drops": [BlockType.KELP] + "drops": [BlockType.KELP], + "atlas_uv": Vector2(0.0, 0.25) # row 1, col 0 }, BlockType.WRECK_WOOD: { "name": "Bois d'Épave", "color": Color(0.35, 0.22, 0.12, 1.0), "hardness": 1.0, - "drops": [BlockType.WRECK_WOOD] + "drops": [BlockType.WRECK_WOOD], + "atlas_uv": Vector2(0.25, 0.25) # row 1, col 1 }, BlockType.ICE: { "name": "Glace", "color": Color(0.75, 0.90, 1.0, 0.85), "hardness": 0.8, - "drops": [] + "drops": [], + "atlas_uv": Vector2(0.5, 0.25) # row 1, col 2 }, BlockType.BEDROCK: { "name": "Bedrock", "color": Color(0.15, 0.15, 0.15, 1.0), "hardness": -1.0, - "drops": [] + "drops": [], + "atlas_uv": Vector2(0.75, 0.25) # row 1, col 3 + }, + BlockType.GLOW_CORAL_CYAN: { + "name": "Corail Bio Cyan", + "color": Color(0.2, 0.8, 1.0, 1.0), + "hardness": 0.3, + "drops": [BlockType.GLOW_CORAL_CYAN], + "atlas_uv": Vector2(0.0, 0.5), # row 2, col 0 + "emission": Color(0.2, 0.8, 1.0), + "emission_energy": 3.0 + }, + BlockType.GLOW_CORAL_VIOLET: { + "name": "Corail Bio Violet", + "color": Color(0.7, 0.3, 0.9, 1.0), + "hardness": 0.3, + "drops": [BlockType.GLOW_CORAL_VIOLET], + "atlas_uv": Vector2(0.25, 0.5), # row 2, col 1 + "emission": Color(0.7, 0.3, 0.9), + "emission_energy": 3.0 + }, + BlockType.LAVA_VENT: { + "name": "Cheminée Hydrothermale", + "color": Color(1.0, 0.5, 0.1, 1.0), + "hardness": 3.0, + "drops": [BlockType.LAVA_VENT], + "atlas_uv": Vector2(0.5, 0.5), # row 2, col 2 + "emission": Color(1.0, 0.5, 0.1), + "emission_energy": 3.0 } } +func get_atlas_uv(id: int) -> Vector2: + if _BLOCKS.has(id): + return _BLOCKS[id]["atlas_uv"] + return Vector2(0.0, 0.0) + func is_solid(id: int) -> bool: if id == BlockType.AIR or id == BlockType.WATER or id == BlockType.KELP: return false @@ -100,3 +148,18 @@ func get_drops(id: int) -> Array: if _BLOCKS.has(id): return _BLOCKS[id]["drops"] return [] + +func is_emissive(id: int) -> bool: + if _BLOCKS.has(id): + return _BLOCKS[id].has("emission") + return false + +func get_emission_color(id: int) -> Color: + if _BLOCKS.has(id) and _BLOCKS[id].has("emission"): + return _BLOCKS[id]["emission"] + return Color(0.0, 0.0, 0.0) + +func get_emission_energy(id: int) -> float: + if _BLOCKS.has(id) and _BLOCKS[id].has("emission_energy"): + return _BLOCKS[id]["emission_energy"] + return 1.0 diff --git a/scripts/world/Chunk.gd b/scripts/world/Chunk.gd index 7a7c4c7..a286442 100644 --- a/scripts/world/Chunk.gd +++ b/scripts/world/Chunk.gd @@ -7,6 +7,16 @@ var _blocks: PackedInt32Array = PackedInt32Array() var _mesh_instance: MeshInstance3D = null var _static_body: StaticBody3D = null +static var shared_material: StandardMaterial3D = null + +static func _init_material() -> void: + if shared_material == null: + shared_material = StandardMaterial3D.new() + shared_material.albedo_texture = load("res://assets/textures/block_atlas.png") + shared_material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS + shared_material.vertex_color_use_as_albedo = false + shared_material.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX + func _ready() -> void: if _blocks.size() == 0: _blocks.resize(4096) @@ -32,11 +42,16 @@ func generate_mesh() -> void: _static_body.queue_free() _static_body = null + _init_material() + var st: SurfaceTool = SurfaceTool.new() st.begin(Mesh.PRIMITIVE_TRIANGLES) var has_faces: bool = false + var tile_w: float = BlockDatabase.ATLAS_TILE_SIZE.x + var tile_h: float = BlockDatabase.ATLAS_TILE_SIZE.y + for x: int in range(CHUNK_SIZE): for y: int in range(CHUNK_SIZE): for z: int in range(CHUNK_SIZE): @@ -44,25 +59,25 @@ func generate_mesh() -> void: if not BlockDatabase.is_solid(block_id): continue - var color: Color = BlockDatabase.get_color(block_id) + var uv_tl: Vector2 = BlockDatabase.get_atlas_uv(block_id) if not _is_opaque_at(x, y + 1, z): - _add_face_top(st, x, y, z, color) + _add_face_top(st, x, y, z, uv_tl, tile_w, tile_h) has_faces = true if not _is_opaque_at(x, y - 1, z): - _add_face_bottom(st, x, y, z, color) + _add_face_bottom(st, x, y, z, uv_tl, tile_w, tile_h) has_faces = true if not _is_opaque_at(x + 1, y, z): - _add_face_right(st, x, y, z, color) + _add_face_right(st, x, y, z, uv_tl, tile_w, tile_h) has_faces = true if not _is_opaque_at(x - 1, y, z): - _add_face_left(st, x, y, z, color) + _add_face_left(st, x, y, z, uv_tl, tile_w, tile_h) has_faces = true if not _is_opaque_at(x, y, z + 1): - _add_face_front(st, x, y, z, color) + _add_face_front(st, x, y, z, uv_tl, tile_w, tile_h) has_faces = true if not _is_opaque_at(x, y, z - 1): - _add_face_back(st, x, y, z, color) + _add_face_back(st, x, y, z, uv_tl, tile_w, tile_h) has_faces = true if not has_faces: @@ -70,15 +85,11 @@ func generate_mesh() -> void: st.generate_normals() - var mat: StandardMaterial3D = StandardMaterial3D.new() - mat.vertex_color_use_as_albedo = true - mat.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX - st.set_material(mat) - var mesh: ArrayMesh = st.commit() _mesh_instance = MeshInstance3D.new() _mesh_instance.mesh = mesh + _mesh_instance.material_override = shared_material add_child(_mesh_instance) var shape: ConcavePolygonShape3D = ConcavePolygonShape3D.new() @@ -96,80 +107,109 @@ func _is_opaque_at(x: int, y: int, z: int) -> bool: return false return BlockDatabase.is_solid(get_block(x, y, z)) -func _add_face_top(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void: +# UV layout for a quad: TL, TR, BR, BL +# Face vertices are added as two triangles: TL-TR-BR and TL-BR-BL + +func _add_face_top(st: SurfaceTool, x: int, y: int, z: int, uv_tl: Vector2, tw: float, th: float) -> void: var fx: float = float(x) var fy: float = float(y) var fz: float = float(z) - st.set_color(color) + # tri 1 + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy + 1.0, fz)) + st.set_uv(uv_tl + Vector2(tw, 0.0)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0)) - st.set_color(color) + # tri 2 + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy + 1.0, fz)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0)) + st.set_uv(uv_tl + Vector2(0.0, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0)) -func _add_face_bottom(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void: +func _add_face_bottom(st: SurfaceTool, x: int, y: int, z: int, uv_tl: Vector2, tw: float, th: float) -> void: var fx: float = float(x) var fy: float = float(y) var fz: float = float(z) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, 0.0)) st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy, fz)) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy, fz)) + st.set_uv(uv_tl + Vector2(0.0, th)) st.add_vertex(Vector3(fx, fy, fz)) -func _add_face_right(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void: +func _add_face_right(st: SurfaceTool, x: int, y: int, z: int, uv_tl: Vector2, tw: float, th: float) -> void: var fx: float = float(x) var fy: float = float(y) var fz: float = float(z) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx + 1.0, fy, fz)) + st.set_uv(uv_tl + Vector2(tw, 0.0)) st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0)) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx + 1.0, fy, fz)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0)) + st.set_uv(uv_tl + Vector2(0.0, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz)) -func _add_face_left(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void: +func _add_face_left(st: SurfaceTool, x: int, y: int, z: int, uv_tl: Vector2, tw: float, th: float) -> void: var fx: float = float(x) var fy: float = float(y) var fz: float = float(z) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, 0.0)) st.add_vertex(Vector3(fx, fy, fz)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz)) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz)) + st.set_uv(uv_tl + Vector2(0.0, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0)) -func _add_face_front(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void: +func _add_face_front(st: SurfaceTool, x: int, y: int, z: int, uv_tl: Vector2, tw: float, th: float) -> void: var fx: float = float(x) var fy: float = float(y) var fz: float = float(z) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, 0.0)) st.add_vertex(Vector3(fx, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0)) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0)) + st.set_uv(uv_tl + Vector2(0.0, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0)) -func _add_face_back(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void: +func _add_face_back(st: SurfaceTool, x: int, y: int, z: int, uv_tl: Vector2, tw: float, th: float) -> void: var fx: float = float(x) var fy: float = float(y) var fz: float = float(z) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy, fz)) + st.set_uv(uv_tl + Vector2(tw, 0.0)) st.add_vertex(Vector3(fx + 1.0, fy, fz)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz)) - st.set_color(color) + st.set_uv(uv_tl) st.add_vertex(Vector3(fx, fy, fz)) + st.set_uv(uv_tl + Vector2(tw, th)) st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz)) + st.set_uv(uv_tl + Vector2(0.0, th)) st.add_vertex(Vector3(fx, fy + 1.0, fz))