aboutsummaryrefslogtreecommitdiff
path: root/game/addons/zylann.hterrain/hterrain_mesher.gd
diff options
context:
space:
mode:
Diffstat (limited to 'game/addons/zylann.hterrain/hterrain_mesher.gd')
-rw-r--r--game/addons/zylann.hterrain/hterrain_mesher.gd358
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