first commit
This commit is contained in:
358
addons/zylann.hterrain/hterrain_mesher.gd
Executable file
358
addons/zylann.hterrain/hterrain_mesher.gd
Executable file
@@ -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
|
||||
Reference in New Issue
Block a user