extends Reference # test # Build terrain from delaunay graph class_name Terrain # Voronoi Center iterator class VoronoiCenters: 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 center = VoronoiCenter.new(_curr,_terrain) return center func size(): return _end # Voronoi Center object class VoronoiCenter: var _idx var _terrain func _init(idx, terrain): self._idx = idx self._terrain = terrain func to_point(): return Point.new(_idx, _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 has_temp_key(key): return _terrain._points_temp_data[_idx].has(key) func set_temp_data(key,value): var data = _terrain._points_temp_data[_idx] data[key] = value func get_temp_data(key): var data = _terrain._points_temp_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 neighbors(): var list_centers = [] for point in to_point().points_around(): list_centers.append(point.to_center()) return list_centers func borders(): var list_edges = [] for edge in to_point().edges_around(): var center_start = edge.start().to_center() var center_end = edge.end().to_center() var corner_start = VoronoiCorner.new(edge.triangle()) var corner_end = VoronoiCorner.new(edge.opposite().triangle()) list_edges.append(VoronoiEdge.new(corner_start,corner_end,center_start,center_end)) return list_edges func corners(): var list_corners = [] var a_point = to_point() for triangle in a_point.triangles_around(): var corner = VoronoiCorner.new(triangle) list_corners.append(corner) return list_corners func corner(idx): var triangle = to_point().triangles_around()[idx] return VoronoiCorner.new(triangle) func corner_count(): return to_point().triangles_around().size() func polygon(): var polygon = [] for corner in corners(): polygon.append(corner.point2d()) return polygon # Voronoi Corner object class VoronoiCorner: var _triangle func _init(triangle): self._triangle = triangle func point3d(): return _triangle.center3d() func point2d(): return _triangle.center2d() func touches(): var list_centers = [] for point in _triangle.points(): list_centers.append(point.to_center()) return list_centers func protudes(): var list_edges = [] for edge in _triangle.edges(): var center_start = edge.start().to_center() var center_end = edge.end().to_center() var corner_start = VoronoiCorner.new(edge.triangle()) var corner_end = VoronoiCorner.new(edge.opposite().triangle()) list_edges.append(VoronoiEdge.new(corner_start,corner_end,center_start,center_end)) return list_edges func adjacents(): var list_corners = [] for triangle in _triangle.adjacents(): list_corners.append(VoronoiCorner.new(triangle)) return list_corners # Voronoi Edge object class VoronoiEdge: var _start_corner var _end_corner var _start_center var _end_center func _init(start_corner, end_corner, start_center, end_center): self._start_corner = end_corner self._end_corner = start_corner self._start_center = end_center self._end_center = start_center func start_corner(): return _start_corner func end_corner(): return _end_corner func start_center(): return _start_center func end_center(): return _end_center func line(): var line = [] line.append(start_corner().point2d()) line.append(end_corner().point2d()) return line # 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 # Triangle object 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 has_temp_key(key): return _terrain._triangles_temp_data[_idx].has(key) func set_temp_data(key,value): var data = _terrain._triangles_temp_data[_idx] data[key] = value func get_temp_data(key): var data = _terrain._triangles_temp_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)) list_points.invert() return list_points func adjacents(): 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 circumcenter = get_data("center2d") if not circumcenter: var points = points() var a = points[0].point2d() var b = points[1].point2d() var c = points[2].point2d() var ad = a[0] * a[0] + a[1] * a[1] var bd = b[0] * b[0] + b[1] * b[1] var cd = c[0] * c[0] + c[1] * c[1] var D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])) circumcenter = Vector2( 1 / D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1])), 1 / D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])) ) set_data("center2d", circumcenter) return circumcenter # return (points[0].point2d() + points[1].point2d() + points[2].point2d()) / 3.0 func center3d(): var circumcenter = get_data("center3d") if not circumcenter: var points = points() var a = points[0].point2d() var b = points[1].point2d() var c = points[2].point2d() var ad = a[0] * a[0] + a[1] * a[1] var bd = b[0] * b[0] + b[1] * b[1] var cd = c[0] * c[0] + c[1] * c[1] var D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])) circumcenter = Vector3( 1 / D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1])), points[0].get_elevation(), 1 / D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])) ) set_data("center3d", circumcenter) return circumcenter # var center2d = center2d() # return Vector3(center2d.x, ) # var points = points() # return (points[0].point3d() + points[1].point3d() + points[2].point3d()) / 3.0 func set_elevation(elevation:float): for point in points(): point.set_elevation(elevation) func get_elevation(): return center3d().y func polygon(): var polygon = [] for point in points(): polygon.append(point.point2d()) return polygon func is_water(): if get_elevation() <= 0: return true return false # Points iterator 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 # Point object class Point: var _idx var _terrain func _init(idx, terrain): self._idx = idx self._terrain = terrain func to_center(): return VoronoiCenter.new(_idx, _terrain) func find_index(): var vect=point2d() return int(vect[0] / 64.0) * 32 + int(vect[1] / 64.0) func get_index(): return _idx func distance(vect): return(point2d().distance_to(vect)) 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 has_temp_key(key): return _terrain._points_temp_data[_idx].has(key) func set_temp_data(key,value): var data = _terrain._points_temp_data[_idx] data[key] = value func get_temp_data(key): var data = _terrain._points_temp_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 = [] if _terrain._points_to_halfedges.has(_idx): var incoming = _terrain._points_to_halfedges.get(_idx) var incoming_edge = Edge.new(incoming, _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 != incoming): break return list_edges func points_around(): var list_points = [] var incoming = _terrain._points_to_halfedges.get(_idx) var incoming_edge = Edge.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 func triangles_around(): var list_triangles = get_temp_data("triangles_around") if not list_triangles: list_triangles = [] for edge in edges_around(): list_triangles.append(edge.triangle()) # list_triangles.append(edge.opposite().triangle()) set_temp_data("triangles_around", list_triangles) return list_triangles # Edges iterator 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 # Edge object class Edge: var _idx var _terrain func _init(idx:int, 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 has_temp_key(key): return _terrain._edges_temp_data[_idx].has(key) func set_temp_data(key,value): _terrain._edges_temp_data[_idx][key] = value func get_temp_data(key): var data = _terrain._edges_temp_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 opposite_triangle(): return opposite().triangle() 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) func line(): var line = [] line.append(start().point2d()) line.append(end().point2d()) return line # Terrain instance variables var _width: int var _height: int var _spacing: int var _name: String var _points = PoolVector3Array() var _halfedges var _triangles var _points_to_halfedges = {} var _data = {} var _points_data = [] var _edges_data = [] var _triangles_data = [] var _temp_data = {} var _points_temp_data = [] var _edges_temp_data = [] var _triangles_temp_data = [] var _created = false var _loaded = false var _path = "" var _list = [] # Terrain constructor func _init(): var directory = Directory.new() var file = File.new() var file_name = "" var directory_name = "" var path = "" var parameter = {} var parameter_file_name = "" var graph_file_name = "" var data_file_name = "" # Get list terrain directory.open("user://") if not directory.dir_exists("terrain"): directory.make_dir("terrain") directory.change_dir("terrain") directory.list_dir_begin() directory_name = directory.get_next() while directory_name != "": if directory.dir_exists(directory_name): # Ok terrain path found path = "user://terrain/%s" % (directory_name) # Get terrain parameters file_name = "%s/param.save" % path if file.file_exists(file_name): parameter = {} file.open(file_name, File.READ) parameter["width"] = file.get_var() parameter["height"] = file.get_var() parameter["spacing"] = file.get_var() parameter["name"] = file.get_var() file.close() _list.append(parameter) directory_name = directory.get_next() directory.list_dir_end() func create(width:int, height:int, spacing:int, name:String): Global.print_debug("Creating : %s ..." % (name)) var delaunay: Delaunator _width = width _height = height _spacing = spacing _name = name Global.loadings["world_creation"].new_phase("Generation des points...", 1) _create_points() delaunay = Delaunator.new(_points) _halfedges = PoolIntArray(delaunay.halfedges) _triangles = PoolIntArray(delaunay.triangles) Global.loadings["world_creation"].new_phase("Initialisation du terrain...", get_points().size() + get_edges().size()) # Initialize _points_to_halfedges for edge in get_edges(): var endpoint = _triangles[edge.next_half().get_index()] if (! _points_to_halfedges.has(endpoint) or _halfedges[edge.get_index()] == -1): _points_to_halfedges[endpoint] = edge.get_index() Global.loadings["world_creation"].increment_step() _create_find_point_table() # Initialise _points_data for point_idx in self.get_points().size(): _points_data.append({}) _points_temp_data.append({}) # Initialise _edges_data for edge_idx in self.get_edges().size(): _edges_data.append({}) _edges_temp_data.append({}) # Initialise _triangle_data for triangle_idx in self.get_triangles().size(): _triangles_data.append({}) _triangles_temp_data.append({}) _created = true save() # Create a table to find a point from vector2 func _create_find_point_table(): var find_point = [] # var corners = {} # var edges = {} find_point.resize(1024) for idx in 1024: find_point[idx]=[] for point in get_points(): var point_index = point.get_index() find_point[point.find_index()].append(point_index) var center = point.to_center() set_data("find_point",find_point) # Create points on the terrain 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, 2) _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 # Terrain methodes func set_data(key,value): _data[key] = value func set_temp_data(key, value): _temp_data[key] = value func get_data(key): if _data.has(key): return _data[key] func get_name(): return _name func get_temp_data(key): if _temp_data.has(key): return _temp_data[key] func reset_temp_data(): _temp_data = {} # Reset _points_data _points_temp_data = [] for point_idx in self.get_points().size(): _points_temp_data.append({}) # Reset _edges_data _edges_temp_data = [] for edge_idx in self.get_edges().size(): _edges_temp_data.append({}) # Reset _triangle_data _triangles_temp_data = [] for triangle_idx in self.get_triangles().size(): _triangles_temp_data.append({}) 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 find_point(vect): var selected_point var minimum = 999999.99 for idx in _data["find_point"][int(vect[0] / 64.0) * 32 + int(vect[1] / 64.0)]: var point=get_point(idx) var distance = point.distance(vect) if(distance < minimum): selected_point = point minimum=distance for point_around in selected_point.points_around(): var distance = point_around.distance(vect) if(distance < minimum): selected_point = point_around minimum=distance return selected_point func get_centers(): var centers = VoronoiCenters.new(self) return centers func get_center(idx): return VoronoiCenter.new(idx, self) func get_edge(idx): return Edge.new(idx, self) func get_triangle(idx): return Triangle.new(idx, self) func save(): var directory = Directory.new() Global.print_debug("Save terrain : %s" %(_name)) # Goto terrain directory directory.open("user://") if not directory.dir_exists("terrain"): directory.make_dir("terrain") directory.change_dir("terrain") if not directory.dir_exists(_name): directory.make_dir(_name) directory.change_dir(_name) # Save terrain save_parameter() save_graph() save_data() func delete(name): var directory = Directory.new() Global.print_debug("Delete terrain : %s" %(name)) # Goto terrain directory directory.open("user://") if not directory.dir_exists("terrain"): directory.make_dir("terrain") directory.change_dir("terrain") if directory.dir_exists(name): directory.change_dir(name) directory.list_dir_begin() var filename = directory.get_next() while filename != "": if( directory.file_exists(filename)): Global.print_debug("Found file: " + filename) directory.remove(filename) filename = directory.get_next() directory.list_dir_end() directory.change_dir("..") var result = directory.remove(name) if(result != OK): Global.print_debug(result) func save_parameter(): var file = File.new() var file_name = "user://terrain/%s/param.save" % (_name) Global.print_debug("Save parameter terrain : %s" % (_name)) file.open(file_name, File.WRITE) file.store_var(_width) file.store_var(_height) file.store_var(_spacing) file.store_var(_name) file.close() func save_graph(): var file = File.new() var file_name = "user://terrain/%s/graph.save" % (_name) Global.print_debug("Save graph terrain : %s" % (_name)) file.open(file_name, File.WRITE) file.store_var(_points) file.store_var(_halfedges) file.store_var(_triangles) file.store_var(_points_to_halfedges) file.close() func save_data(): var file = File.new() var file_name = "user://terrain/%s/data.save" % (_name) Global.print_debug("Save data terrain : %s" % (_name)) file.open(file_name, File.WRITE) file.store_var(_data) file.store_var(_points_data) file.store_var(_edges_data) file.store_var(_triangles_data) file.close() func load(name): # Goto terrain directory var directory = Directory.new() directory.open("user://") if not directory.dir_exists("terrain"): directory.make_dir("terrain") directory.change_dir("terrain") if not directory.dir_exists(name): directory.make_dir(name) directory.change_dir(name) # Load parameter if directory.file_exists("param.save"): load_parameter(name) # Load graph if directory.file_exists("graph.save"): load_graph(name) # Load data if directory.file_exists("data.save"): load_data(name) # Initialise _points_temp_data for point_idx in self.get_points().size(): _points_temp_data.append({}) # Initialise _edges_temp_data for edge_idx in self.get_edges().size(): _edges_temp_data.append({}) # Initialise _triangle_temp_data for triangle_idx in self.get_triangles().size(): _triangles_temp_data.append({}) _loaded = true func load_parameter(name): var file = File.new() var file_name = "user://terrain/%s/param.save" % (name) Global.print_debug("Load parameter file : %s" % (file_name)) if file.file_exists(file_name): file.open(file_name, File.READ) _width = file.get_var() _height = file.get_var() _spacing = file.get_var() _name = file.get_var() file.close() else: Global.print_debug("The parameter file : %s does not exist" % (file_name)) func load_graph(name): var file = File.new() var file_name = "user://terrain/%s/graph.save" % (name) Global.print_debug("Load graph file : %s" % (file_name)) if file.file_exists(file_name): file.open(file_name, File.READ) _points = file.get_var() _halfedges = file.get_var() _triangles = file.get_var() _points_to_halfedges = file.get_var() file.close() else: Global.print_debug("The graph file : %s does not exist" % (file_name)) func load_data(name): var file = File.new() var file_name = "user://terrain/%s/data.save" % (name) Global.print_debug("Load data file : %s" % (file_name)) if file.file_exists(file_name): file.open(file_name, File.READ) _data = file.get_var() _points_data = file.get_var() _edges_data = file.get_var() _triangles_data = file.get_var() file.close() else: Global.print_debug("The data file : %s does not exist" % (file_name)) func list(): return _list func is_created(): return _created func is_loaded(): return _loaded func exists(name): for terrain in _list: if name == terrain["name"]: return true return false 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 center in get_centers(): list_polygon.append(center.polygon()) return(list_polygon) func get_chunk(vect): var centers = [] for idx in _data["find_point"][int(vect[0] / 64.0) * 32 + int(vect[1] / 64.0)]: # print(idx) centers.append(get_center(idx)) return centers func get_parameters(): return { "name": _name, "width": _width, "height": _height, "spacing": _spacing }