You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
422 lines
9.7 KiB
422 lines
9.7 KiB
extends Reference
|
|
|
|
# Build terrain from delaunay graph
|
|
class_name Terrain
|
|
|
|
# Triangles iterator
|
|
class Triangles:
|
|
var _terrain
|
|
var _curr
|
|
var _end
|
|
|
|
func _init(terrain):
|
|
self._terrain = terrain
|
|
self._curr = 0
|
|
self._end = _terrain._triangles.size() / 3
|
|
|
|
func _should_continue():
|
|
return (_curr < _end)
|
|
|
|
func _iter_init(arg):
|
|
_curr = 0
|
|
return _should_continue()
|
|
|
|
func _iter_next(arg):
|
|
_curr += 1
|
|
return _should_continue()
|
|
|
|
func _iter_get(arg):
|
|
var triangle = Triangle.new(_curr,_terrain)
|
|
return triangle
|
|
|
|
func size():
|
|
return _end
|
|
|
|
class Triangle:
|
|
var _idx
|
|
var _terrain
|
|
|
|
func _init(idx, terrain):
|
|
self._idx = idx
|
|
self._terrain = terrain
|
|
|
|
func get_index():
|
|
return _idx
|
|
|
|
func has_key(key):
|
|
return _terrain._triangles_data[_idx].has(key)
|
|
|
|
func set_data(key,value):
|
|
var data = _terrain._triangles_data[_idx]
|
|
data[key] = value
|
|
|
|
func get_data(key):
|
|
var data = _terrain._triangles_data[_idx]
|
|
if data.has(key):
|
|
return data[key]
|
|
|
|
func edges():
|
|
return [Edge.new(3 * _idx, _terrain), Edge.new(3 * _idx + 1, _terrain), Edge.new(3 * _idx + 2, _terrain)]
|
|
|
|
func points():
|
|
var list_points = []
|
|
for edge in edges():
|
|
list_points.append(Point.new(_terrain._triangles[edge._idx], _terrain))
|
|
return list_points
|
|
|
|
func triangles_adjacent():
|
|
var list_triangles = []
|
|
for edge in edges():
|
|
var opposite = Edge.new(_terrain._halfedges[edge._idx], _terrain)
|
|
if opposite._idx >= 0:
|
|
list_triangles.append(opposite.triangle())
|
|
return list_triangles
|
|
|
|
func center2d():
|
|
var points = points()
|
|
return (points[0].point2d() + points[1].point2d() + points[2].point2d()) / 3.0
|
|
|
|
func center3d():
|
|
var points = points()
|
|
return (points[0].point3d() + points[1].point3d() + points[2].point3d()) / 3.0
|
|
|
|
func polygon():
|
|
var polygon = []
|
|
for point in points():
|
|
polygon.append(point.point2d())
|
|
return polygon
|
|
|
|
class Points:
|
|
var _terrain
|
|
var _curr
|
|
var _end
|
|
|
|
func _init(terrain):
|
|
self._terrain = terrain
|
|
self._curr = 0
|
|
self._end = _terrain._points.size()
|
|
|
|
func _should_continue():
|
|
return (_curr < _end)
|
|
|
|
func _iter_init(arg):
|
|
_curr = 0
|
|
return _should_continue()
|
|
|
|
func _iter_next(arg):
|
|
_curr += 1
|
|
return _should_continue()
|
|
|
|
func _iter_get(arg):
|
|
var point = Point.new(_curr,_terrain)
|
|
return point
|
|
|
|
func size():
|
|
return _end
|
|
|
|
class Point:
|
|
var _idx
|
|
var _terrain
|
|
|
|
func _init(idx, terrain):
|
|
self._idx = idx
|
|
self._terrain = terrain
|
|
|
|
func get_index():
|
|
return _idx
|
|
|
|
func has_key(key):
|
|
return _terrain._points_data[_idx].has(key)
|
|
|
|
func set_data(key,value):
|
|
var data = _terrain._points_data[_idx]
|
|
data[key] = value
|
|
|
|
func get_data(key):
|
|
var data = _terrain._points_data[_idx]
|
|
if data.has(key):
|
|
return data[key]
|
|
|
|
func point3d():
|
|
return _terrain._points[_idx]
|
|
|
|
func point2d():
|
|
var point3d:Vector3 = _terrain._points[_idx]
|
|
var point2d:Vector2 = Vector2(point3d.x, point3d.z)
|
|
return(point2d)
|
|
|
|
func set_elevation(elevation:float):
|
|
_terrain._points[_idx].y = elevation
|
|
|
|
func get_elevation():
|
|
return(_terrain._points[_idx].y)
|
|
|
|
func edges_around():
|
|
var list_edges = []
|
|
var incoming_edge = Edge.new(_idx, _terrain)
|
|
var outgoing_edge
|
|
while true:
|
|
list_edges.append(incoming_edge);
|
|
outgoing_edge = incoming_edge.next_half()
|
|
incoming_edge = Edge.new(_terrain._halfedges[outgoing_edge._idx], _terrain);
|
|
if not (incoming_edge._idx != -1 and incoming_edge._idx != _idx):
|
|
break
|
|
return list_edges
|
|
|
|
func points_around():
|
|
var list_points = []
|
|
var incoming = _terrain._points_to_halfedges.get(_idx)
|
|
var incoming_edge = Point.new(incoming, _terrain)
|
|
var outgoing_edge
|
|
while true:
|
|
list_points.append(Point.new(_terrain._triangles[incoming_edge._idx], _terrain));
|
|
outgoing_edge = incoming_edge.next_half()
|
|
incoming_edge = Edge.new(_terrain._halfedges[outgoing_edge._idx], _terrain);
|
|
if not (incoming_edge._idx != -1 and incoming_edge._idx != incoming):
|
|
break
|
|
return list_points
|
|
|
|
class Edges:
|
|
var _terrain
|
|
var _curr
|
|
var _end
|
|
|
|
func _init(terrain):
|
|
self._terrain = terrain
|
|
self._curr = 0
|
|
self._end = _terrain._triangles.size()
|
|
|
|
func _should_continue():
|
|
return (_curr < _end)
|
|
|
|
func _iter_init(arg):
|
|
_curr = 0
|
|
return _should_continue()
|
|
|
|
func _iter_next(arg):
|
|
_curr += 1
|
|
return _should_continue()
|
|
|
|
func _iter_get(arg):
|
|
var edge = Edge.new(_curr,_terrain)
|
|
return edge
|
|
|
|
func size():
|
|
return _end
|
|
|
|
class Edge:
|
|
var _idx
|
|
var _terrain
|
|
|
|
func _init(idx, terrain):
|
|
self._idx = idx
|
|
self._terrain = terrain
|
|
|
|
func get_index():
|
|
return _idx
|
|
|
|
func has_key(key):
|
|
return _terrain._edges_data[_idx].has(key)
|
|
|
|
func set_data(key,value):
|
|
_terrain._edges_data[_idx][key] = value
|
|
|
|
func get_data(key):
|
|
var data = _terrain._edges_data[_idx]
|
|
if data.has(key):
|
|
return data[key]
|
|
|
|
func next_half():
|
|
return Edge.new(_idx - 2 if _idx % 3 == 2 else _idx + 1, _terrain)
|
|
|
|
func prev_half():
|
|
return Edge.new(_idx + 2 if _idx % 3 == 0 else _idx -1, _terrain)
|
|
|
|
func triangle():
|
|
return Triangle.new(floor(_idx / 3), _terrain)
|
|
|
|
func start():
|
|
return Point.new(_terrain._triangles[_idx], _terrain)
|
|
|
|
func end():
|
|
return Point.new(_terrain._triangles[next_half()._idx], _terrain)
|
|
|
|
func opposite():
|
|
return Edge.new(_terrain._halfedges[_idx], _terrain)
|
|
|
|
const terrain_file = "user://terrain.save"
|
|
|
|
var width: int
|
|
var height: int
|
|
var spacing: int
|
|
var _points = PoolVector3Array()
|
|
var _halfedges
|
|
var _triangles
|
|
var _points_to_halfedges = {}
|
|
var _data = {}
|
|
var _points_data = []
|
|
var _edges_data = []
|
|
var _triangles_data = []
|
|
var _file = File.new()
|
|
var _debug = true
|
|
|
|
"""
|
|
func general_type_of(obj):
|
|
var typ = typeof(obj)
|
|
var builtin_type_names = ["nil", "bool", "int", "real", "string", "vector2", "rect2", "vector3", "maxtrix32", "plane", "quat", "aabb", "matrix3", "transform", "color", "image", "nodepath", "rid", null, "inputevent", "dictionary", "array", "rawarray", "intarray", "realarray", "stringarray", "vector2array", "vector3array", "colorarray", "unknown"]
|
|
|
|
if(typ == TYPE_OBJECT):
|
|
return obj.type_of()
|
|
else:
|
|
return builtin_type_names[typ]
|
|
"""
|
|
|
|
func _print_debug(message):
|
|
if _debug:
|
|
print(message)
|
|
|
|
func _init(width:int=1600, height:int=800, spacing:int=30, create=false):
|
|
if _file.file_exists(terrain_file) and not create:
|
|
_print_debug("loading...")
|
|
_load()
|
|
else:
|
|
_print_debug("Creating...")
|
|
var delaunay: Delaunator
|
|
self.width = width
|
|
self.height = height
|
|
self.spacing = spacing
|
|
_create_points()
|
|
delaunay = Delaunator.new(_points)
|
|
|
|
_halfedges = PoolIntArray(delaunay.halfedges)
|
|
_triangles = PoolIntArray(delaunay.triangles)
|
|
|
|
# Initialize _points_to_halfedges
|
|
for edge_idx in edges():
|
|
var edge = get_edge(edge_idx)
|
|
var endpoint = _triangles[edge.next_half().get_index()]
|
|
if (! _points_to_halfedges.has(endpoint) or _halfedges[edge_idx] == -1):
|
|
_points_to_halfedges[endpoint] = edge_idx
|
|
|
|
# Initialise _points_data
|
|
for point_idx in points():
|
|
_points_data.append({})
|
|
|
|
# Initialise _edges_data
|
|
for edge_idx in edges():
|
|
_edges_data.append({})
|
|
|
|
# Initialise _triangle_data
|
|
for triangle_idx in triangles():
|
|
_triangles_data.append({})
|
|
|
|
_save()
|
|
|
|
func _create_points():
|
|
var rect = Rect2(Vector2(0, 0), Vector2(width, height))
|
|
var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new()
|
|
var points2d = poisson_disc_sampling.generate_points(spacing, rect, 5)
|
|
_points.resize(points2d.size())
|
|
for point_idx in points2d.size():
|
|
_points[point_idx].x = points2d[point_idx].x
|
|
_points[point_idx].z = points2d[point_idx].y
|
|
|
|
func get_triangles():
|
|
var triangles = Triangles.new(self)
|
|
return triangles
|
|
|
|
func get_edges():
|
|
var edges = Edges.new(self)
|
|
return edges
|
|
|
|
func get_points():
|
|
var points = Points.new(self)
|
|
return points
|
|
|
|
func get_point(idx):
|
|
return Point.new(idx, self)
|
|
|
|
func get_edge(idx):
|
|
return Edge.new(idx, self)
|
|
|
|
func get_triangle(idx):
|
|
return Triangle.new(idx, self)
|
|
|
|
func triangles():
|
|
return _triangles.size() / 3
|
|
|
|
func points():
|
|
return _points.size()
|
|
|
|
func edges():
|
|
return _triangles.size()
|
|
|
|
# Voronoi
|
|
|
|
func centroid(points):
|
|
return Vector3((points[0].x + points[1].x + points[2].x) / 3.0, 0.0, (points[0].z + points[1].z + points[2].z) / 3.0)
|
|
|
|
|
|
func _save():
|
|
_file.open(terrain_file, File.WRITE)
|
|
_file.store_var(width)
|
|
_file.store_var(height)
|
|
_file.store_var(spacing)
|
|
_file.store_var(_points)
|
|
_file.store_var(_halfedges)
|
|
_file.store_var(_triangles)
|
|
_file.store_var(_points_to_halfedges)
|
|
_file.store_var(_points_data)
|
|
_file.store_var(_edges_data)
|
|
_file.store_var(_triangles_data)
|
|
_file.close()
|
|
|
|
func _load():
|
|
_file.open(terrain_file, File.READ)
|
|
width = _file.get_var()
|
|
height = _file.get_var()
|
|
spacing = _file.get_var()
|
|
_points = _file.get_var()
|
|
_halfedges = _file.get_var()
|
|
_triangles = _file.get_var()
|
|
_points_to_halfedges = _file.get_var()
|
|
_points_data = _file.get_var()
|
|
_edges_data = _file.get_var()
|
|
_triangles_data = _file.get_var()
|
|
_file.close()
|
|
|
|
func get_triangles_as_polygon():
|
|
var list_polygon = []
|
|
for triangle in get_triangles():
|
|
list_polygon.append(triangle.polygon())
|
|
return list_polygon
|
|
|
|
func get_edges_as_line():
|
|
var list_lines = []
|
|
for edge in get_edges():
|
|
var line = []
|
|
line.append(edge.start().point2d())
|
|
line.append(edge.end().point2d())
|
|
list_lines.append(line)
|
|
return list_lines
|
|
|
|
func get_voronoi_edges_as_line():
|
|
var list_lines = []
|
|
for start_edge in get_edges():
|
|
var line = []
|
|
var end_edge = start_edge.opposite()
|
|
if (start_edge.get_index() < end_edge.get_index()):
|
|
line.append(start_edge.triangle().center2d())
|
|
line.append(end_edge.triangle().center2d())
|
|
list_lines.append(line)
|
|
return list_lines
|
|
|
|
func get_voronoi_cells_as_polygon():
|
|
var list_polygon = []
|
|
for point in get_points():
|
|
var polygon = []
|
|
for edge in point.edges_around():
|
|
polygon.append(edge.triangle().center2d())
|
|
list_polygon.append(polygon)
|
|
return(list_polygon)
|
|
|
|
|