diff options
Diffstat (limited to 'game/addons/zylann.hterrain/hterrain_mesher.gd')
-rw-r--r-- | game/addons/zylann.hterrain/hterrain_mesher.gd | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/game/addons/zylann.hterrain/hterrain_mesher.gd b/game/addons/zylann.hterrain/hterrain_mesher.gd new file mode 100644 index 0000000..0b371e1 --- /dev/null +++ b/game/addons/zylann.hterrain/hterrain_mesher.gd @@ -0,0 +1,358 @@ +@tool + +#const HT_Logger = preload("./util/logger.gd") +const HTerrainData = preload("./hterrain_data.gd") + +const SEAM_LEFT = 1 +const SEAM_RIGHT = 2 +const SEAM_BOTTOM = 4 +const SEAM_TOP = 8 +const SEAM_CONFIG_COUNT = 16 + + +# [seams_mask][lod] +var _mesh_cache := [] +var _chunk_size_x := 16 +var _chunk_size_y := 16 + + +func configure(chunk_size_x: int, chunk_size_y: int, lod_count: int): + assert(typeof(chunk_size_x) == TYPE_INT) + assert(typeof(chunk_size_y) == TYPE_INT) + assert(typeof(lod_count) == TYPE_INT) + + assert(chunk_size_x >= 2 or chunk_size_y >= 2) + + _mesh_cache.resize(SEAM_CONFIG_COUNT) + + if chunk_size_x == _chunk_size_x \ + and chunk_size_y == _chunk_size_y and lod_count == len(_mesh_cache): + return + + _chunk_size_x = chunk_size_x + _chunk_size_y = chunk_size_y + + # TODO Will reduce the size of this cache, but need index buffer swap feature + for seams in SEAM_CONFIG_COUNT: + var slot := [] + slot.resize(lod_count) + _mesh_cache[seams] = slot + + for lod in lod_count: + slot[lod] = make_flat_chunk(_chunk_size_x, _chunk_size_y, 1 << lod, seams) + + +func get_chunk(lod: int, seams: int) -> Mesh: + return _mesh_cache[seams][lod] as Mesh + + +static func make_flat_chunk(quad_count_x: int, quad_count_y: int, stride: int, seams: int) -> Mesh: + var positions = PackedVector3Array() + positions.resize((quad_count_x + 1) * (quad_count_y + 1)) + + var i = 0 + for y in quad_count_y + 1: + for x in quad_count_x + 1: + positions[i] = Vector3(x * stride, 0, y * stride) + i += 1 + + var indices := make_indices(quad_count_x, quad_count_y, seams) + + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX); + arrays[Mesh.ARRAY_VERTEX] = positions + arrays[Mesh.ARRAY_INDEX] = indices + + var mesh := ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + + return mesh + + +# size: chunk size in quads (there are N+1 vertices) +# seams: Bitfield for which seams are present +static func make_indices(chunk_size_x: int, chunk_size_y: int, seams: int) -> PackedInt32Array: + var output_indices := PackedInt32Array() + + if seams != 0: + # LOD seams can't be made properly on uneven chunk sizes + assert(chunk_size_x % 2 == 0 and chunk_size_y % 2 == 0) + + var reg_origin_x := 0 + var reg_origin_y := 0 + var reg_size_x := chunk_size_x + var reg_size_y := chunk_size_y + var reg_hstride := 1 + + if seams & SEAM_LEFT: + reg_origin_x += 1; + reg_size_x -= 1; + reg_hstride += 1 + + if seams & SEAM_BOTTOM: + reg_origin_y += 1 + reg_size_y -= 1 + + if seams & SEAM_RIGHT: + reg_size_x -= 1 + reg_hstride += 1 + + if seams & SEAM_TOP: + reg_size_y -= 1 + + # Regular triangles + var ii := reg_origin_x + reg_origin_y * (chunk_size_x + 1) + + for y in reg_size_y: + for x in reg_size_x: + var i00 := ii + var i10 := ii + 1 + var i01 := ii + chunk_size_x + 1 + var i11 := i01 + 1 + + # 01---11 + # | /| + # | / | + # |/ | + # 00---10 + + # This flips the pattern to make the geometry orientation-free. + # Not sure if it helps in any way though + var flip = ((x + reg_origin_x) + (y + reg_origin_y) % 2) % 2 != 0 + + if flip: + output_indices.push_back( i00 ) + output_indices.push_back( i10 ) + output_indices.push_back( i01 ) + + output_indices.push_back( i10 ) + output_indices.push_back( i11 ) + output_indices.push_back( i01 ) + + else: + output_indices.push_back( i00 ) + output_indices.push_back( i11 ) + output_indices.push_back( i01 ) + + output_indices.push_back( i00 ) + output_indices.push_back( i10 ) + output_indices.push_back( i11 ) + + ii += 1 + ii += reg_hstride + + # Left seam + if seams & SEAM_LEFT: + + # 4 . 5 + # |\ . + # | \ . + # | \. + # (2)| 3 + # | /. + # | / . + # |/ . + # 0 . 1 + + var i := 0 + var n := chunk_size_y / 2 + + for j in n: + var i0 := i + var i1 := i + 1 + var i3 := i + chunk_size_x + 2 + var i4 := i + 2 * (chunk_size_x + 1) + var i5 := i4 + 1 + + output_indices.push_back( i0 ) + output_indices.push_back( i3 ) + output_indices.push_back( i4 ) + + if j != 0 or (seams & SEAM_BOTTOM) == 0: + output_indices.push_back( i0 ) + output_indices.push_back( i1 ) + output_indices.push_back( i3 ) + + if j != n - 1 or (seams & SEAM_TOP) == 0: + output_indices.push_back( i3 ) + output_indices.push_back( i5 ) + output_indices.push_back( i4 ) + + i = i4 + + if seams & SEAM_RIGHT: + + # 4 . 5 + # . /| + # . / | + # ./ | + # 2 |(3) + # .\ | + # . \ | + # . \| + # 0 . 1 + + var i := chunk_size_x - 1 + var n := chunk_size_y / 2 + + for j in n: + + var i0 := i + var i1 := i + 1 + var i2 := i + chunk_size_x + 1 + var i4 := i + 2 * (chunk_size_x + 1) + var i5 := i4 + 1 + + output_indices.push_back( i1 ) + output_indices.push_back( i5 ) + output_indices.push_back( i2 ) + + if j != 0 or (seams & SEAM_BOTTOM) == 0: + output_indices.push_back( i0 ) + output_indices.push_back( i1 ) + output_indices.push_back( i2 ) + + if j != n - 1 or (seams & SEAM_TOP) == 0: + output_indices.push_back( i2 ) + output_indices.push_back( i5 ) + output_indices.push_back( i4 ) + + i = i4; + + if seams & SEAM_BOTTOM: + + # 3 . 4 . 5 + # . / \ . + # . / \ . + # ./ \. + # 0-------2 + # (1) + + var i := 0; + var n := chunk_size_x / 2; + + for j in n: + + var i0 := i + var i2 := i + 2 + var i3 := i + chunk_size_x + 1 + var i4 := i3 + 1 + var i5 := i4 + 1 + + output_indices.push_back( i0 ) + output_indices.push_back( i2 ) + output_indices.push_back( i4 ) + + if j != 0 or (seams & SEAM_LEFT) == 0: + output_indices.push_back( i0 ) + output_indices.push_back( i4 ) + output_indices.push_back( i3 ) + + if j != n - 1 or (seams & SEAM_RIGHT) == 0: + output_indices.push_back( i2 ) + output_indices.push_back( i5 ) + output_indices.push_back( i4 ) + + i = i2 + + if seams & SEAM_TOP: + + # (4) + # 3-------5 + # .\ /. + # . \ / . + # . \ / . + # 0 . 1 . 2 + + var i := (chunk_size_y - 1) * (chunk_size_x + 1) + var n := chunk_size_x / 2 + + for j in n: + + var i0 := i + var i1 := i + 1 + var i2 := i + 2 + var i3 := i + chunk_size_x + 1 + var i5 := i3 + 2 + + output_indices.push_back( i3 ) + output_indices.push_back( i1 ) + output_indices.push_back( i5 ) + + if j != 0 or (seams & SEAM_LEFT) == 0: + output_indices.push_back( i0 ) + output_indices.push_back( i1 ) + output_indices.push_back( i3 ) + + if j != n - 1 or (seams & SEAM_RIGHT) == 0: + output_indices.push_back( i1 ) + output_indices.push_back( i2 ) + output_indices.push_back( i5 ) + + i = i2 + + return output_indices + + +static func get_mesh_size(width: int, height: int) -> Dictionary: + return { + "vertices": width * height, + "triangles": (width - 1) * (height - 1) * 2 + } + + +# Makes a full mesh from a heightmap, without any LOD considerations. +# Using this mesh for rendering is very expensive on large terrains. +# Initially used as a workaround for Godot to use for navmesh generation. +static func make_heightmap_mesh(heightmap: Image, stride: int, scale: Vector3, + logger = null) -> Mesh: + + var size_x := heightmap.get_width() / stride + var size_z := heightmap.get_height() / stride + + assert(size_x >= 2) + assert(size_z >= 2) + + var positions := PackedVector3Array() + positions.resize(size_x * size_z) + + var i := 0 + + if heightmap.get_format() == Image.FORMAT_RH or heightmap.get_format() == Image.FORMAT_RF: + for mz in size_z: + for mx in size_x: + var x := mx * stride + var z := mz * stride + var y := heightmap.get_pixel(x, z).r + positions[i] = Vector3(x, y, z) * scale + i += 1 + + elif heightmap.get_format() == Image.FORMAT_RGB8: + for mz in size_z: + for mx in size_x: + var x := mx * stride + var z := mz * stride + var c := heightmap.get_pixel(x, z) + var y := HTerrainData.decode_height_from_rgb8_unorm(c) + positions[i] = Vector3(x, y, z) * scale + i += 1 + + else: + logger.error("Unknown heightmap format!") + return null + + var indices := make_indices(size_x - 1, size_z - 1, 0) + + var arrays := [] + arrays.resize(Mesh.ARRAY_MAX); + arrays[Mesh.ARRAY_VERTEX] = positions + arrays[Mesh.ARRAY_INDEX] = indices + + if logger != null: + logger.debug(str("Generated mesh has ", len(positions), + " vertices and ", len(indices) / 3, " triangles")) + + var mesh := ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + + return mesh |