new file: addons/delaunator/Delaunator.gd new file: default_env.tres new file: icon.png new file: icon.png.import new file: project.godot new file: ui/map/map.gd new file: ui/map/map.tscn new file: ui/ui.tscn new file: utils/terrain/Terrain.gd new file: world/game.gd new file: world/game.tscnpull/4/head
parent
441d3050df
commit
10140bf69c
@ -0,0 +1,137 @@ |
|||||||
|
class_name PoissonDiscSampling |
||||||
|
|
||||||
|
var _radius: float |
||||||
|
var _sample_region_shape |
||||||
|
var _retries: int |
||||||
|
var _start_pos: Vector2 |
||||||
|
var _sample_region_rect: Rect2 |
||||||
|
var _cell_size: float |
||||||
|
var _rows: int |
||||||
|
var _cols: int |
||||||
|
var _cell_size_scaled: Vector2 |
||||||
|
var _grid: Array = [] |
||||||
|
var _points: Array = [] |
||||||
|
var _spawn_points: Array = [] |
||||||
|
var _transpose: Vector2 |
||||||
|
|
||||||
|
# radius - minimum distance between points |
||||||
|
# sample_region_shape - takes any of the following: |
||||||
|
# -a Rect2 for rectangular region |
||||||
|
# -an array of Vector2 for polygon region |
||||||
|
# -a Vector3 with x,y as the position and z as the radius of the circle |
||||||
|
# retries - maximum number of attempts to look around a sample point, reduce this value to speed up generation |
||||||
|
# start_pos - optional parameter specifying the starting point |
||||||
|
# |
||||||
|
# returns an Array of Vector2D with points in the order of their discovery |
||||||
|
func generate_points(radius: float, sample_region_shape, retries: int, start_pos := Vector2(INF, INF)) -> Array: |
||||||
|
_radius = radius |
||||||
|
_sample_region_shape = sample_region_shape |
||||||
|
_retries = retries |
||||||
|
_start_pos = start_pos |
||||||
|
_init_vars() |
||||||
|
|
||||||
|
while _spawn_points.size() > 0: |
||||||
|
var spawn_index: int = randi() % _spawn_points.size() |
||||||
|
var spawn_centre: Vector2 = _spawn_points[spawn_index] |
||||||
|
var sample_accepted: bool = false |
||||||
|
for i in retries: |
||||||
|
var angle: float = 2 * PI * randf() |
||||||
|
var sample: Vector2 = spawn_centre + Vector2(cos(angle), sin(angle)) * (radius + radius * randf()) |
||||||
|
if _is_valid_sample(sample): |
||||||
|
_grid[int((_transpose.x + sample.x) / _cell_size_scaled.x)][int((_transpose.y + sample.y) / _cell_size_scaled.y)] = _points.size() |
||||||
|
_points.append(sample) |
||||||
|
_spawn_points.append(sample) |
||||||
|
sample_accepted = true |
||||||
|
break |
||||||
|
if not sample_accepted: |
||||||
|
_spawn_points.remove(spawn_index) |
||||||
|
return _points |
||||||
|
|
||||||
|
|
||||||
|
func _is_valid_sample(sample: Vector2) -> bool: |
||||||
|
if _is_point_in_sample_region(sample): |
||||||
|
var cell := Vector2(int((_transpose.x + sample.x) / _cell_size_scaled.x), int((_transpose.y + sample.y) / _cell_size_scaled.y)) |
||||||
|
var cell_start := Vector2(max(0, cell.x - 2), max(0, cell.y - 2)) |
||||||
|
var cell_end := Vector2(min(cell.x + 2, _cols - 1), min(cell.y + 2, _rows - 1)) |
||||||
|
|
||||||
|
for i in range(cell_start.x, cell_end.x + 1): |
||||||
|
for j in range(cell_start.y, cell_end.y + 1): |
||||||
|
var search_index: int = _grid[i][j] |
||||||
|
if search_index != -1: |
||||||
|
var dist: float = _points[search_index].distance_to(sample) |
||||||
|
if dist < _radius: |
||||||
|
return false |
||||||
|
return true |
||||||
|
return false |
||||||
|
|
||||||
|
|
||||||
|
func _is_point_in_sample_region(sample: Vector2) -> bool: |
||||||
|
if _sample_region_rect.has_point(sample): |
||||||
|
match typeof(_sample_region_shape): |
||||||
|
TYPE_RECT2: |
||||||
|
return true |
||||||
|
TYPE_VECTOR2_ARRAY, TYPE_ARRAY: |
||||||
|
if Geometry.is_point_in_polygon(sample, _sample_region_shape): |
||||||
|
return true |
||||||
|
TYPE_VECTOR3: |
||||||
|
if Geometry.is_point_in_circle(sample, Vector2(_sample_region_shape.x, _sample_region_shape.y), _sample_region_shape.z): |
||||||
|
return true |
||||||
|
_: |
||||||
|
return false |
||||||
|
return false |
||||||
|
|
||||||
|
func _init_vars() -> void: |
||||||
|
randomize() |
||||||
|
|
||||||
|
# identify the type of shape and it's bounding rectangle and starting point |
||||||
|
match typeof(_sample_region_shape): |
||||||
|
TYPE_RECT2: |
||||||
|
_sample_region_rect = _sample_region_shape |
||||||
|
if _start_pos.x == INF: |
||||||
|
_start_pos.x = _sample_region_rect.position.x + _sample_region_rect.size.x * randf() |
||||||
|
_start_pos.y = _sample_region_rect.position.y + _sample_region_rect.size.y * randf() |
||||||
|
|
||||||
|
TYPE_VECTOR2_ARRAY, TYPE_ARRAY: |
||||||
|
var start: Vector2 = _sample_region_shape[0] |
||||||
|
var end: Vector2 = _sample_region_shape[0] |
||||||
|
for i in range(1, _sample_region_shape.size()): |
||||||
|
start.x = min(start.x, _sample_region_shape[i].x) |
||||||
|
start.y = min(start.y, _sample_region_shape[i].y) |
||||||
|
end.x = max(end.x, _sample_region_shape[i].x) |
||||||
|
end.y = max(end.y, _sample_region_shape[i].y) |
||||||
|
_sample_region_rect = Rect2(start, end - start) |
||||||
|
if _start_pos.x == INF: |
||||||
|
var n: int = _sample_region_shape.size() |
||||||
|
var i: int = randi() % n |
||||||
|
_start_pos = _sample_region_shape[i] + (_sample_region_shape[(i + 1) % n] - _sample_region_shape[i]) * randf() |
||||||
|
|
||||||
|
TYPE_VECTOR3: |
||||||
|
var x = _sample_region_shape.x |
||||||
|
var y = _sample_region_shape.y |
||||||
|
var r = _sample_region_shape.z |
||||||
|
_sample_region_rect = Rect2(x - r, y - r, r * 2, r * 2) |
||||||
|
if _start_pos.x == INF: |
||||||
|
var angle: float = 2 * PI * randf() |
||||||
|
_start_pos = Vector2(x, y) + Vector2(cos(angle), sin(angle)) * r * randf() |
||||||
|
_: |
||||||
|
_sample_region_shape = Rect2(0, 0, 0, 0) |
||||||
|
push_error("Unrecognized shape!!! Please input a valid shape") |
||||||
|
|
||||||
|
_cell_size = _radius / sqrt(2) |
||||||
|
_cols = max(floor(_sample_region_rect.size.x / _cell_size), 1) |
||||||
|
_rows = max(floor(_sample_region_rect.size.y / _cell_size), 1) |
||||||
|
# scale the cell size in each axis |
||||||
|
_cell_size_scaled.x = _sample_region_rect.size.x / _cols |
||||||
|
_cell_size_scaled.y = _sample_region_rect.size.y / _rows |
||||||
|
# use tranpose to map points starting from origin to calculate grid position |
||||||
|
_transpose = -_sample_region_rect.position |
||||||
|
|
||||||
|
_grid = [] |
||||||
|
for i in _cols: |
||||||
|
_grid.append([]) |
||||||
|
for j in _rows: |
||||||
|
_grid[i].append(-1) |
||||||
|
|
||||||
|
_points = [] |
||||||
|
_spawn_points = [] |
||||||
|
_spawn_points.append(_start_pos) |
||||||
@ -0,0 +1,571 @@ |
|||||||
|
extends Reference |
||||||
|
|
||||||
|
class_name Delaunator |
||||||
|
|
||||||
|
const EPSILON = pow(2, -52) |
||||||
|
const EDGE_STACK = [] |
||||||
|
|
||||||
|
var coords = [] # PoolRealArray. |
||||||
|
var halfedges = [] # PoolIntArray. |
||||||
|
var hull = [] # PoolIntArray. |
||||||
|
var triangles = [] # PoolIntArray. |
||||||
|
var triangles_len = 0 |
||||||
|
var _cx |
||||||
|
var _cy |
||||||
|
var _dists = [] # PoolRealArray. |
||||||
|
var _halfedges = [] # This array should be a PoolIntArray but we need to use the .slice() function on it. |
||||||
|
var _hash_size |
||||||
|
var _hull_hash = [] # PoolIntArray. |
||||||
|
var _hull_next = [] # PoolIntArray. |
||||||
|
var _hull_prev = [] # PoolIntArray. |
||||||
|
var _hull_start |
||||||
|
var _hull_tri = [] # PoolIntArray. |
||||||
|
var _ids = [] # PoolIntArray. |
||||||
|
var _triangles = [] # This array should be a PoolIntArray but we need to use the .slice() function on it. |
||||||
|
|
||||||
|
func _init(points): |
||||||
|
if points.size() < 3: |
||||||
|
push_error(ProjectSettings.get_setting("application/config/name") + " needs at least 3 points.") |
||||||
|
return |
||||||
|
|
||||||
|
EDGE_STACK.resize(512) |
||||||
|
|
||||||
|
var n = points.size() |
||||||
|
|
||||||
|
coords.resize(n * 2) |
||||||
|
|
||||||
|
for i in n: |
||||||
|
var point = points[i] |
||||||
|
coords[2 * i] = point.x |
||||||
|
coords[2 * i + 1] = point.z |
||||||
|
|
||||||
|
_constructor() |
||||||
|
|
||||||
|
func _constructor(): |
||||||
|
var n = coords.size() >> 1 |
||||||
|
|
||||||
|
# Arrays that will store the triangulation graph. |
||||||
|
var max_triangles = max(2 * n - 5, 0) |
||||||
|
_triangles.resize(max_triangles * 3) |
||||||
|
_halfedges.resize(max_triangles * 3) |
||||||
|
|
||||||
|
# Temporary arrays for tracking the edges of the advancing convex hull. |
||||||
|
_hash_size = ceil(sqrt(n)) |
||||||
|
_hull_prev.resize(n) # Edge to prev edge. |
||||||
|
_hull_next.resize(n) # Edge to next edge. |
||||||
|
_hull_tri.resize(n) # Edge to adjacent triangle. |
||||||
|
|
||||||
|
_hull_hash.resize(_hash_size) |
||||||
|
for i in _hash_size: |
||||||
|
_hull_hash[i] = -1 # angular edge hash |
||||||
|
|
||||||
|
# Temporary arrays for sorting points. |
||||||
|
_ids.resize(n) |
||||||
|
_dists.resize(n) |
||||||
|
|
||||||
|
update() |
||||||
|
|
||||||
|
|
||||||
|
func update(): |
||||||
|
var n = coords.size() >> 1 |
||||||
|
|
||||||
|
# Populate an array of point indices; calculate input data bbox. |
||||||
|
var min_x = INF |
||||||
|
var min_y = INF |
||||||
|
var max_x = -INF |
||||||
|
var max_y = -INF |
||||||
|
|
||||||
|
for i in n: |
||||||
|
var x = coords[2 * i] |
||||||
|
var y = coords[2 * i + 1] |
||||||
|
if x < min_x: min_x = x |
||||||
|
if y < min_y: min_y = y |
||||||
|
if x > max_x: max_x = x |
||||||
|
if y > max_y: max_y = y |
||||||
|
_ids[i] = i |
||||||
|
|
||||||
|
var cx = (min_x + max_x) / 2 |
||||||
|
var cy = (min_y + max_y) / 2 |
||||||
|
|
||||||
|
var min_dist = INF |
||||||
|
var i0 = 0 |
||||||
|
var i1 = 0 |
||||||
|
var i2 = 0 |
||||||
|
|
||||||
|
# Pick a seed point close to the center. |
||||||
|
for i in n: |
||||||
|
var d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]) |
||||||
|
if (d < min_dist): |
||||||
|
i0 = i |
||||||
|
min_dist = d |
||||||
|
var i0x = coords[2 * i0] |
||||||
|
var i0y = coords[2 * i0 + 1] |
||||||
|
|
||||||
|
min_dist = INF |
||||||
|
|
||||||
|
# Find the point closest to the seed. |
||||||
|
for i in n: |
||||||
|
if i == i0: continue |
||||||
|
var d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]) |
||||||
|
if (d < min_dist and d > 0): |
||||||
|
i1 = i |
||||||
|
min_dist = d |
||||||
|
var i1x = coords[2 * i1] |
||||||
|
var i1y = coords[2 * i1 + 1] |
||||||
|
|
||||||
|
var min_radius = INF |
||||||
|
|
||||||
|
# Find the third point which forms the smallest circumcircle with the first two. |
||||||
|
for i in n: |
||||||
|
if i == i0 or i == i1: continue |
||||||
|
var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]) |
||||||
|
if r < min_radius: |
||||||
|
i2 = i |
||||||
|
min_radius = r |
||||||
|
var i2x = coords[2 * i2] |
||||||
|
var i2y = coords[2 * i2 + 1] |
||||||
|
|
||||||
|
if min_radius == INF: |
||||||
|
# Order collinear points by dx (or dy if all x are identical) |
||||||
|
# and return the list as a hull. |
||||||
|
for i in n: |
||||||
|
var _dist_temp |
||||||
|
|
||||||
|
if coords[2 * i] - coords[0]: |
||||||
|
_dist_temp = coords[2 * i] - coords[0] |
||||||
|
elif coords[2 * i + 1] - coords[1]: |
||||||
|
_dist_temp = coords[2 * i + 1] - coords[1] |
||||||
|
else: |
||||||
|
_dist_temp = 0 |
||||||
|
|
||||||
|
_dists[i] = _dist_temp |
||||||
|
|
||||||
|
quicksort(_ids, _dists, 0, n - 1) |
||||||
|
var hull = [] |
||||||
|
hull.resize(n) |
||||||
|
var j = 0 |
||||||
|
var d0 = -INF |
||||||
|
|
||||||
|
for i in n: |
||||||
|
var id = _ids[i] |
||||||
|
if _dists[id] > d0: |
||||||
|
hull[j] = id |
||||||
|
j += 1 |
||||||
|
d0 = _dists[id] |
||||||
|
hull = hull.slice(0, j - 1) |
||||||
|
triangles = [] |
||||||
|
halfedges = [] |
||||||
|
|
||||||
|
return |
||||||
|
|
||||||
|
# Swap the order of the seed points for counter-clockwise orientation. |
||||||
|
if orient(i0x, i0y, i1x, i1y, i2x, i2y): |
||||||
|
var i = i1 |
||||||
|
var x = i1x |
||||||
|
var y = i1y |
||||||
|
i1 = i2 |
||||||
|
i1x = i2x |
||||||
|
i1y = i2y |
||||||
|
i2 = i |
||||||
|
i2x = x |
||||||
|
i2y = y |
||||||
|
|
||||||
|
var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y) |
||||||
|
_cx = center[0] |
||||||
|
_cy = center[1] |
||||||
|
|
||||||
|
for i in n: |
||||||
|
_dists[i] = dist(coords[2 * i], coords[2 * i + 1], center[0], center[1]) |
||||||
|
|
||||||
|
# Sort the points by distance from the seed triangle circumcenter. |
||||||
|
quicksort(_ids, _dists, 0, n - 1) |
||||||
|
|
||||||
|
# Set up the seed triangle as the starting hull. |
||||||
|
_hull_start = i0 |
||||||
|
var hull_size = 3 |
||||||
|
|
||||||
|
_hull_next[i0] = i1 |
||||||
|
_hull_prev[i2] = i1 |
||||||
|
_hull_next[i1] = i2 |
||||||
|
_hull_prev[i0] = i2 |
||||||
|
_hull_next[i2] = i0 |
||||||
|
_hull_prev[i1] = i0 |
||||||
|
|
||||||
|
_hull_tri[i0] = 0 |
||||||
|
_hull_tri[i1] = 1 |
||||||
|
_hull_tri[i2] = 2 |
||||||
|
|
||||||
|
for i in _hull_hash.size(): |
||||||
|
_hull_hash[i] = -1 |
||||||
|
_hull_hash[_hash_key(i0x, i0y)] = i0 |
||||||
|
_hull_hash[_hash_key(i1x, i1y)] = i1 |
||||||
|
_hull_hash[_hash_key(i2x, i2y)] = i2 |
||||||
|
|
||||||
|
# triangles_len = 0 |
||||||
|
_add_triangle(i0, i1, i2, -1, -1, -1) |
||||||
|
|
||||||
|
var xp = 0 |
||||||
|
var yp = 0 |
||||||
|
|
||||||
|
for k in _ids.size(): |
||||||
|
var i = _ids[k] |
||||||
|
var x = coords[2 * i] |
||||||
|
var y = coords[2 * i + 1] |
||||||
|
|
||||||
|
# Skip near-duplicate points. |
||||||
|
if k > 0 and abs(x - xp) <= EPSILON and abs(y - yp) <= EPSILON: continue |
||||||
|
|
||||||
|
xp = x |
||||||
|
yp = y |
||||||
|
|
||||||
|
# Skip seed triangle points. |
||||||
|
if i == i0 or i == i1 or i == i2: continue |
||||||
|
|
||||||
|
# Find a visible edge on the convex hull using edge hash. |
||||||
|
var start = 0 |
||||||
|
var key = _hash_key(x, y) |
||||||
|
|
||||||
|
for j in _hash_size: |
||||||
|
start = _hull_hash[fmod((key + j), _hash_size)] |
||||||
|
if (start != -1 and start != _hull_next[start]): break |
||||||
|
|
||||||
|
start = _hull_prev[start] |
||||||
|
var e = start |
||||||
|
|
||||||
|
while true: |
||||||
|
var q = _hull_next[e] |
||||||
|
if orient(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]): break |
||||||
|
e = q |
||||||
|
|
||||||
|
if (e == start): |
||||||
|
e = -1 |
||||||
|
break |
||||||
|
|
||||||
|
if (e == -1): continue # Likely a near-duplicate point; Skip it. |
||||||
|
|
||||||
|
# Add the first triangle from the point. |
||||||
|
var t = _add_triangle(e, i, _hull_next[e], -1, -1, _hull_tri[e]) |
||||||
|
# Recursively flip triangles from the point until they satisfy the Delaunay condition. |
||||||
|
_hull_tri[i] = _legalize(t + 2) |
||||||
|
_hull_tri[e] = t # Keep track of boundary triangles on the hull. |
||||||
|
hull_size += 1 |
||||||
|
|
||||||
|
# Walk forward through the hull, adding more triangles and flipping recursively. |
||||||
|
n = _hull_next[e] |
||||||
|
|
||||||
|
while true: |
||||||
|
var q = _hull_next[n] |
||||||
|
if not orient(x, y, coords[2 * n], coords[2 * n + 1], coords[2 * q], coords[2 * q + 1]): break |
||||||
|
t = _add_triangle(n, i, q, _hull_tri[i], -1, _hull_tri[n]) |
||||||
|
_hull_tri[i] = _legalize(t + 2) |
||||||
|
_hull_next[n] = n # Mark as removed. |
||||||
|
hull_size -= 1 |
||||||
|
n = q |
||||||
|
|
||||||
|
# Walk backward from the other side, adding more triangles and flipping. |
||||||
|
if (e == start): |
||||||
|
while true: |
||||||
|
var q = _hull_prev[e] |
||||||
|
if not orient(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]): break |
||||||
|
t = _add_triangle(q, i, e, -1, _hull_tri[e], _hull_tri[q]) |
||||||
|
_legalize(t + 2) |
||||||
|
_hull_tri[q] = t |
||||||
|
_hull_next[e] = e # Mark as removed. |
||||||
|
hull_size -= 1 |
||||||
|
e = q |
||||||
|
|
||||||
|
# Update the hull indices. |
||||||
|
_hull_start = e |
||||||
|
_hull_prev[i] = e |
||||||
|
_hull_next[e] = i |
||||||
|
_hull_prev[n] = i |
||||||
|
_hull_next[i] = n |
||||||
|
|
||||||
|
# Save the two new edges in the hash table. |
||||||
|
_hull_hash[_hash_key(x, y)] = i |
||||||
|
_hull_hash[_hash_key(coords[2 * e], coords[2 * e + 1])] = e |
||||||
|
|
||||||
|
hull.resize(hull_size) |
||||||
|
var e = _hull_start |
||||||
|
for i in hull_size: |
||||||
|
hull[i] = e |
||||||
|
e = _hull_next[e] |
||||||
|
|
||||||
|
# Trim typed triangle mesh arrays. |
||||||
|
triangles = _triangles.slice(0, triangles_len - 1) |
||||||
|
halfedges = _halfedges.slice(0, triangles_len - 1) |
||||||
|
|
||||||
|
|
||||||
|
func _hash_key(x, y): |
||||||
|
return fmod(floor(pseudo_angle(x - _cx, y - _cy) * _hash_size), _hash_size) |
||||||
|
|
||||||
|
|
||||||
|
func _legalize(a): |
||||||
|
var i = 0 |
||||||
|
var ar = 0 |
||||||
|
|
||||||
|
# Recursion eliminated with a fixed-size stack. |
||||||
|
while true: |
||||||
|
var b = _halfedges[a] |
||||||
|
|
||||||
|
# If the pair of triangles doesn't satisfy the Delaunay condition |
||||||
|
# (p1 is inside the circumcircle of [p0, pl, pr]), flip them, |
||||||
|
# then do the same check/flip recursively for the new pair of triangles |
||||||
|
# |
||||||
|
# pl pl |
||||||
|
# /||\ / \ |
||||||
|
# al/ || \bl al/ \a |
||||||
|
# / || \ / \ |
||||||
|
# / a||b \ flip /___ar___\ |
||||||
|
# p0\ || /p1 => p0\---bl---/p1 |
||||||
|
# \ || / \ / |
||||||
|
# ar\ || /br b\ /br |
||||||
|
# \||/ \ / |
||||||
|
# pr pr |
||||||
|
|
||||||
|
var a0 = a - a % 3 |
||||||
|
ar = a0 + (a + 2) % 3 |
||||||
|
|
||||||
|
if b == -1: # Convex hull edge. |
||||||
|
if i == 0: break |
||||||
|
i -= 1 |
||||||
|
a = EDGE_STACK[i] |
||||||
|
continue |
||||||
|
|
||||||
|
var b0 = b - b % 3 |
||||||
|
var al = a0 + (a + 1) % 3 |
||||||
|
var bl = b0 + (b + 2) % 3 |
||||||
|
|
||||||
|
var p0 = _triangles[ar] |
||||||
|
var pr = _triangles[a] |
||||||
|
var pl = _triangles[al] |
||||||
|
var p1 = _triangles[bl] |
||||||
|
|
||||||
|
var illegal = in_circle( |
||||||
|
coords[2 * p0], coords[2 * p0 + 1], |
||||||
|
coords[2 * pr], coords[2 * pr + 1], |
||||||
|
coords[2 * pl], coords[2 * pl + 1], |
||||||
|
coords[2 * p1], coords[2 * p1 + 1] |
||||||
|
) |
||||||
|
|
||||||
|
if illegal: |
||||||
|
_triangles[a] = p1 |
||||||
|
_triangles[b] = p0 |
||||||
|
|
||||||
|
var hbl = _halfedges[bl] |
||||||
|
|
||||||
|
# Edge swapped on the other side of the hull (rare); Fix the halfedge reference. |
||||||
|
if (hbl == -1): |
||||||
|
var e = _hull_start |
||||||
|
while true: |
||||||
|
if _hull_tri[e] == bl: |
||||||
|
_hull_tri[e] = a |
||||||
|
break |
||||||
|
|
||||||
|
e = _hull_prev[e] |
||||||
|
if e == _hull_start: break |
||||||
|
|
||||||
|
_link(a, hbl) |
||||||
|
_link(b, _halfedges[ar]) |
||||||
|
_link(ar, bl) |
||||||
|
|
||||||
|
var br = b0 + (b + 1) % 3 |
||||||
|
|
||||||
|
# Don't worry about hitting the cap: it can only happen on extremely degenerate input. |
||||||
|
if i < EDGE_STACK.size(): |
||||||
|
EDGE_STACK[i] = br |
||||||
|
i += 1 |
||||||
|
else: |
||||||
|
if i == 0: break |
||||||
|
i -= 1 |
||||||
|
a = EDGE_STACK[i] |
||||||
|
|
||||||
|
return ar |
||||||
|
|
||||||
|
|
||||||
|
func _link(a, b): |
||||||
|
_halfedges[a] = b |
||||||
|
if (b != -1): |
||||||
|
_halfedges[b] = a |
||||||
|
|
||||||
|
|
||||||
|
# Add a new triangle given vertex indices and adjacent half-edge ids. |
||||||
|
func _add_triangle(i0, i1, i2, a, b, c): |
||||||
|
var t = triangles_len |
||||||
|
|
||||||
|
_triangles[t] = i0 |
||||||
|
_triangles[t + 1] = i1 |
||||||
|
_triangles[t + 2] = i2 |
||||||
|
|
||||||
|
_link(t, a) |
||||||
|
_link(t + 1, b) |
||||||
|
_link(t + 2, c) |
||||||
|
|
||||||
|
triangles_len += 3 |
||||||
|
|
||||||
|
return t |
||||||
|
|
||||||
|
|
||||||
|
# Monotonically increases with real angle, but doesn't need expensive trigonometry. |
||||||
|
func pseudo_angle(dx, dy): |
||||||
|
var p = dx / (abs(dx) + abs(dy)) |
||||||
|
|
||||||
|
if (dy > 0): |
||||||
|
return (3 - p) / 4 # [0..1] |
||||||
|
else: |
||||||
|
return (1 + p) / 4 # [0..1] |
||||||
|
|
||||||
|
|
||||||
|
func dist(ax, ay, bx, by): |
||||||
|
var dx = ax - bx |
||||||
|
var dy = ay - by |
||||||
|
return dx * dx + dy * dy |
||||||
|
|
||||||
|
|
||||||
|
# Return 2d orientation sign if we're confident in it through J. Shewchuk's error bound check. |
||||||
|
func orient_if_sure(px, py, rx, ry, qx, qy): |
||||||
|
var l = (ry - py) * (qx - px) |
||||||
|
var r = (rx - px) * (qy - py) |
||||||
|
|
||||||
|
if (abs(l - r) >= 0.00000000000000033306690738754716 * abs(l + r)): |
||||||
|
return l - r |
||||||
|
else: |
||||||
|
return 0 |
||||||
|
|
||||||
|
|
||||||
|
# A more robust orientation test that's stable in a given triangle (to fix robustness issues). |
||||||
|
func orient(rx, ry, qx, qy, px, py): |
||||||
|
var _sign |
||||||
|
|
||||||
|
if orient_if_sure(px, py, rx, ry, qx, qy): |
||||||
|
_sign = orient_if_sure(px, py, rx, ry, qx, qy) |
||||||
|
elif orient_if_sure(rx, ry, qx, qy, px, py): |
||||||
|
_sign = orient_if_sure(rx, ry, qx, qy, px, py) |
||||||
|
elif orient_if_sure(qx, qy, px, py, rx, ry): |
||||||
|
_sign = orient_if_sure(qx, qy, px, py, rx, ry) |
||||||
|
|
||||||
|
return false if _sign == null else _sign < 0 |
||||||
|
|
||||||
|
|
||||||
|
func in_circle(ax, ay, bx, by, cx, cy, px, py): |
||||||
|
var dx = ax - px |
||||||
|
var dy = ay - py |
||||||
|
var ex = bx - px |
||||||
|
var ey = by - py |
||||||
|
var fx = cx - px |
||||||
|
var fy = cy - py |
||||||
|
|
||||||
|
var ap = dx * dx + dy * dy |
||||||
|
var bp = ex * ex + ey * ey |
||||||
|
var cp = fx * fx + fy * fy |
||||||
|
|
||||||
|
return dx * (ey * cp - bp * fy) -\ |
||||||
|
dy * (ex * cp - bp * fx) +\ |
||||||
|
ap * (ex * fy - ey * fx) < 0 |
||||||
|
|
||||||
|
|
||||||
|
func circumradius(ax, ay, bx, by, cx, cy): |
||||||
|
var dx = bx - ax |
||||||
|
var dy = by - ay |
||||||
|
var ex = cx - ax |
||||||
|
var ey = cy - ay |
||||||
|
|
||||||
|
var bl = dx * dx + dy * dy |
||||||
|
var cl = ex * ex + ey * ey |
||||||
|
|
||||||
|
# When you divide by 0 in Godot you get an error. |
||||||
|
# It should return INF (positive or negative). |
||||||
|
var d |
||||||
|
if (dx * ey - dy * ex) == 0: |
||||||
|
d = INF |
||||||
|
elif (dx * ey - dy * ex) == -0: |
||||||
|
d = -INF |
||||||
|
else: |
||||||
|
d = 0.5 / (dx * ey - dy * ex) |
||||||
|
|
||||||
|
var x = (ey * bl - dy * cl) * d |
||||||
|
var y = (dx * cl - ex * bl) * d |
||||||
|
|
||||||
|
return x * x + y * y |
||||||
|
|
||||||
|
|
||||||
|
func circumcenter(ax, ay, bx, by, cx, cy): |
||||||
|
var dx = bx - ax |
||||||
|
var dy = by - ay |
||||||
|
var ex = cx - ax |
||||||
|
var ey = cy - ay |
||||||
|
|
||||||
|
var bl = dx * dx + dy * dy |
||||||
|
var cl = ex * ex + ey * ey |
||||||
|
|
||||||
|
# When you divide by 0 in Godot you get an error. |
||||||
|
# It should return INF (positive or negative). |
||||||
|
var d |
||||||
|
if (dx * ey - dy * ex) == 0: |
||||||
|
d = INF |
||||||
|
elif (dx * ey - dy * ex) == -0: |
||||||
|
d = -INF |
||||||
|
else: |
||||||
|
d = 0.5 / (dx * ey - dy * ex) |
||||||
|
|
||||||
|
var x = ax + (ey * bl - dy * cl) * d |
||||||
|
var y = ay + (dx * cl - ex * bl) * d |
||||||
|
|
||||||
|
return [x, y] |
||||||
|
|
||||||
|
|
||||||
|
func quicksort(ids, dists, left, right): |
||||||
|
if right - left <= 20: |
||||||
|
for i in range(left + 1, right + 1): |
||||||
|
var temp = ids[i] |
||||||
|
var temp_dist = dists[temp] |
||||||
|
var j = i - 1 |
||||||
|
while j >= left and dists[ids[j]] > temp_dist: |
||||||
|
ids[j + 1] = ids[j] |
||||||
|
j -= 1 |
||||||
|
ids[j + 1] = temp |
||||||
|
else: |
||||||
|
var median = (left + right) >> 1 |
||||||
|
var i = left + 1 |
||||||
|
var j = right |
||||||
|
swap(ids, median, i) |
||||||
|
|
||||||
|
if (dists[ids[left]] > dists[ids[right]]): |
||||||
|
swap(ids, left, right) |
||||||
|
|
||||||
|
if (dists[ids[i]] > dists[ids[right]]): |
||||||
|
swap(ids, i, right) |
||||||
|
|
||||||
|
if (dists[ids[left]] > dists[ids[i]]): |
||||||
|
swap(ids, left, i) |
||||||
|
|
||||||
|
var temp = ids[i] |
||||||
|
var temp_dist = dists[temp] |
||||||
|
|
||||||
|
while true: |
||||||
|
while true: |
||||||
|
i += 1 |
||||||
|
if dists[ids[i]] >= temp_dist: break |
||||||
|
|
||||||
|
while true: |
||||||
|
j -= 1 |
||||||
|
if dists[ids[j]] <= temp_dist: break |
||||||
|
|
||||||
|
if j < i: break |
||||||
|
swap(ids, i, j) |
||||||
|
|
||||||
|
ids[left + 1] = ids[j] |
||||||
|
ids[j] = temp |
||||||
|
|
||||||
|
if right - i + 1 >= j - left: |
||||||
|
quicksort(ids, dists, i, right) |
||||||
|
quicksort(ids, dists, left, j - 1) |
||||||
|
else: |
||||||
|
quicksort(ids, dists, left, j - 1) |
||||||
|
quicksort(ids, dists, i, right) |
||||||
|
|
||||||
|
|
||||||
|
func swap(arr, i, j): |
||||||
|
var tmp = arr[i] |
||||||
|
arr[i] = arr[j] |
||||||
|
arr[j] = tmp |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
[gd_resource type="Environment" load_steps=2 format=2] |
||||||
|
|
||||||
|
[sub_resource type="ProceduralSky" id=1] |
||||||
|
|
||||||
|
[resource] |
||||||
|
background_mode = 2 |
||||||
|
background_sky = SubResource( 1 ) |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
[remap] |
||||||
|
|
||||||
|
importer="texture" |
||||||
|
type="StreamTexture" |
||||||
|
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" |
||||||
|
metadata={ |
||||||
|
"vram_texture": false |
||||||
|
} |
||||||
|
|
||||||
|
[deps] |
||||||
|
|
||||||
|
source_file="res://icon.png" |
||||||
|
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] |
||||||
|
|
||||||
|
[params] |
||||||
|
|
||||||
|
compress/mode=0 |
||||||
|
compress/lossy_quality=0.7 |
||||||
|
compress/hdr_mode=0 |
||||||
|
compress/bptc_ldr=0 |
||||||
|
compress/normal_map=0 |
||||||
|
flags/repeat=0 |
||||||
|
flags/filter=true |
||||||
|
flags/mipmaps=false |
||||||
|
flags/anisotropic=false |
||||||
|
flags/srgb=2 |
||||||
|
process/fix_alpha_border=true |
||||||
|
process/premult_alpha=false |
||||||
|
process/HDR_as_SRGB=false |
||||||
|
process/invert_color=false |
||||||
|
process/normal_map_invert_y=false |
||||||
|
stream=false |
||||||
|
size_limit=0 |
||||||
|
detect_3d=true |
||||||
|
svg/scale=1.0 |
||||||
@ -0,0 +1,49 @@ |
|||||||
|
; Engine configuration file. |
||||||
|
; It's best edited using the editor UI and not directly, |
||||||
|
; since the parameters that go here are not all obvious. |
||||||
|
; |
||||||
|
; Format: |
||||||
|
; [section] ; section goes between [] |
||||||
|
; param=value ; assign values to parameters |
||||||
|
|
||||||
|
config_version=4 |
||||||
|
|
||||||
|
_global_script_classes=[ { |
||||||
|
"base": "Reference", |
||||||
|
"class": "Delaunator", |
||||||
|
"language": "GDScript", |
||||||
|
"path": "res://addons/delaunator/Delaunator.gd" |
||||||
|
}, { |
||||||
|
"base": "Reference", |
||||||
|
"class": "PoissonDiscSampling", |
||||||
|
"language": "GDScript", |
||||||
|
"path": "res://addons/PoissonDiscSampling/PoissonDiscSampling.gd" |
||||||
|
}, { |
||||||
|
"base": "Reference", |
||||||
|
"class": "Terrain", |
||||||
|
"language": "GDScript", |
||||||
|
"path": "res://utils/terrain/Terrain.gd" |
||||||
|
} ] |
||||||
|
_global_script_class_icons={ |
||||||
|
"Delaunator": "", |
||||||
|
"PoissonDiscSampling": "", |
||||||
|
"Terrain": "" |
||||||
|
} |
||||||
|
|
||||||
|
[application] |
||||||
|
|
||||||
|
config/name="Societer" |
||||||
|
run/main_scene="res://world/game.tscn" |
||||||
|
config/icon="res://icon.png" |
||||||
|
|
||||||
|
[gui] |
||||||
|
|
||||||
|
common/drop_mouse_on_gui_input_disabled=true |
||||||
|
|
||||||
|
[physics] |
||||||
|
|
||||||
|
common/enable_pause_aware_picking=true |
||||||
|
|
||||||
|
[rendering] |
||||||
|
|
||||||
|
environment/default_environment="res://default_env.tres" |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
extends Node2D |
||||||
|
|
||||||
|
var terrain |
||||||
|
|
||||||
|
func create_map(): |
||||||
|
var river = {"size": 3, "color": "blue"} |
||||||
|
|
||||||
|
terrain.get_edge(16).set_data("river", river) |
||||||
|
|
||||||
|
var triangle_idx = 5 |
||||||
|
var triangle = terrain.get_triangle(triangle_idx) |
||||||
|
|
||||||
|
print("Triangle index : %d" % (triangle.get_index())) |
||||||
|
|
||||||
|
var edges = triangle.edges() |
||||||
|
|
||||||
|
print("Number of edges : %d" % (edges.size())) |
||||||
|
print() |
||||||
|
|
||||||
|
for edge in edges: |
||||||
|
print("Edge index : %d" % (edge.get_index())) |
||||||
|
var start_point = edge.start() |
||||||
|
var end_point = edge.end() |
||||||
|
var start = start_point.point2d() |
||||||
|
var end = end_point.point2d() |
||||||
|
|
||||||
|
print("Start point index : %d" % (start_point.get_index())) |
||||||
|
print("End point index : %d" % (end_point.get_index())) |
||||||
|
|
||||||
|
print("Start point : %s" % (start)) |
||||||
|
print("End point : %s" % (end)) |
||||||
|
|
||||||
|
if edge.has_key("river"): |
||||||
|
print("Has river") |
||||||
|
var a_river = edge.get_data("river") |
||||||
|
print("River size : %d" % (a_river["size"])) |
||||||
|
print("River color : %s" % (a_river["color"])) |
||||||
|
|
||||||
|
print() |
||||||
|
print(terrain.get_point(5).point3d()) |
||||||
|
|
||||||
|
|
||||||
|
func draw_triangles(): |
||||||
|
for polygon in terrain.get_triangles_as_polygon(): |
||||||
|
var color = Color(randf(), randf(), randf(), 1) |
||||||
|
if polygon.size() > 2: |
||||||
|
draw_polygon(polygon, PoolColorArray([color])) |
||||||
|
|
||||||
|
func draw_triangles_edges(color=Color("#000000")): |
||||||
|
for line in terrain.get_edges_as_line(): |
||||||
|
draw_line(line[0], line[1], color) |
||||||
|
|
||||||
|
func draw_voronoi_edges(color=Color("#000000")): |
||||||
|
for line in terrain.get_voronoi_edges_as_line(): |
||||||
|
draw_line(line[0], line[1], color) |
||||||
|
|
||||||
|
func draw_voronoi_cells_old(): |
||||||
|
var seen = [] |
||||||
|
for edge_idx in terrain.edges(): |
||||||
|
var triangles = [] |
||||||
|
var vertices = [] |
||||||
|
var p = terrain._triangles[terrain.next_half_edge(edge_idx)] |
||||||
|
if not seen.has(p): |
||||||
|
seen.append(p) |
||||||
|
var edges = terrain.edges_around_point(edge_idx) |
||||||
|
for edge_around_idx in edges: |
||||||
|
triangles.append(terrain.triangle_of_edge(edge_around_idx)) |
||||||
|
for triangle in triangles: |
||||||
|
vertices.append(terrain.triangle_center(triangle)) |
||||||
|
|
||||||
|
if triangles.size() > 2: |
||||||
|
var color = Color(randf(), randf(), randf(), 1) |
||||||
|
var voronoi_cell = PoolVector2Array() |
||||||
|
for vertice in vertices: |
||||||
|
voronoi_cell.append(Vector2(vertice.x, vertice.z)) |
||||||
|
draw_polygon(voronoi_cell, PoolColorArray([color])) |
||||||
|
func draw_voronoi_cells(): |
||||||
|
for polygon in terrain.get_voronoi_cells_as_polygon(): |
||||||
|
var color = Color(randf(), randf(), randf(), 1) |
||||||
|
if polygon.size() > 2: |
||||||
|
draw_polygon(polygon, PoolColorArray([color])) |
||||||
|
|
||||||
|
func draw_voronoi_cells_convex_hull(): |
||||||
|
for point_idx in terrain.points(): |
||||||
|
var triangles = [] |
||||||
|
var vertices = [] |
||||||
|
var incoming = terrain._points_to_half_edges.get(point_idx) |
||||||
|
|
||||||
|
if incoming == null: |
||||||
|
triangles.append(0) |
||||||
|
else: |
||||||
|
var edges = terrain.edges_around_point(incoming) |
||||||
|
for edge_idx in edges: |
||||||
|
triangles.append(terrain.triangle_of_edge(edge_idx)) |
||||||
|
|
||||||
|
for triangle_idx in triangles: |
||||||
|
vertices.append(terrain.triangle_center(triangle_idx)) |
||||||
|
|
||||||
|
if triangles.size() > 2: |
||||||
|
var color = Color(randf(), randf(), randf(), 1) |
||||||
|
var voronoi_cell = PoolVector2Array() |
||||||
|
for vertice in vertices: |
||||||
|
voronoi_cell.append(Vector2(vertice[0], vertice[1])) |
||||||
|
draw_polygon(voronoi_cell, PoolColorArray([color])) |
||||||
|
|
||||||
|
func _draw(): |
||||||
|
print("before drawing") |
||||||
|
draw_triangles() |
||||||
|
# draw_voronoi_cells() |
||||||
|
# draw_triangles_edges() |
||||||
|
# draw_voronoi_cells_convex_hull() |
||||||
|
# draw_voronoi_edges(Color("#ff0000")) |
||||||
|
|
||||||
|
func _on_Game_world_loaded(game_terrain): |
||||||
|
terrain = game_terrain |
||||||
|
create_map() |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
[gd_scene load_steps=2 format=2] |
||||||
|
|
||||||
|
[ext_resource path="res://ui/map/map.gd" type="Script" id=1] |
||||||
|
|
||||||
|
[node name="Map" type="Node2D"] |
||||||
|
script = ExtResource( 1 ) |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
[gd_scene load_steps=2 format=2] |
||||||
|
|
||||||
|
[ext_resource path="res://ui/map/map.tscn" type="PackedScene" id=1] |
||||||
|
|
||||||
|
[node name="UI" type="Node2D"] |
||||||
|
|
||||||
|
[node name="Map" parent="." instance=ExtResource( 1 )] |
||||||
@ -0,0 +1,332 @@ |
|||||||
|
extends Reference |
||||||
|
|
||||||
|
class_name Terrain |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
|
||||||
|
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 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) |
||||||
|
|
||||||
|
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(): |
||||||
|
return _triangles |
||||||
|
|
||||||
|
func get_halfedges(): |
||||||
|
return _halfedges |
||||||
|
|
||||||
|
# return que les id ? |
||||||
|
func get_points(): |
||||||
|
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_idx in triangles(): |
||||||
|
var polygon = [] |
||||||
|
for point in get_triangle(triangle_idx).points(): |
||||||
|
polygon.append(point.point2d()) |
||||||
|
list_polygon.append(polygon) |
||||||
|
return list_polygon |
||||||
|
|
||||||
|
func get_edges_as_line(): |
||||||
|
var list_lines = [] |
||||||
|
for edge_idx in edges(): |
||||||
|
var line = [] |
||||||
|
var edge = get_edge(edge_idx) |
||||||
|
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 edge_idx in edges(): |
||||||
|
var line = [] |
||||||
|
var start_edge = get_edge(edge_idx) |
||||||
|
var end_edge = get_edge(_halfedges[edge_idx]) |
||||||
|
if (edge_idx < _halfedges[edge_idx]): |
||||||
|
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_idx in points(): |
||||||
|
var point = get_point(point_idx) |
||||||
|
var polygon = [] |
||||||
|
for edge in point.edges_around(): |
||||||
|
polygon.append(edge.triangle().center2d()) |
||||||
|
list_polygon.append(polygon) |
||||||
|
return(list_polygon) |
||||||
|
|
||||||
@ -0,0 +1,70 @@ |
|||||||
|
extends Spatial |
||||||
|
|
||||||
|
signal world_loaded |
||||||
|
|
||||||
|
export(int) var width = 2000 |
||||||
|
export(int) var height = 2000 |
||||||
|
export(int) var spacing = 20 |
||||||
|
export(int, 1, 9) var octaves = 5 |
||||||
|
export(int, 1, 30) var wavelength = 8 |
||||||
|
export(int) var border_width = 200 |
||||||
|
export(int) var terraces = 24 |
||||||
|
export(int) var terrace_height = 5 |
||||||
|
export(int) var mountain_height = 6 |
||||||
|
export(int) var river_proba = 200 |
||||||
|
|
||||||
|
var rng = RandomNumberGenerator.new() |
||||||
|
var noise = OpenSimplexNoise.new() |
||||||
|
|
||||||
|
var terrain |
||||||
|
|
||||||
|
func _ready(): |
||||||
|
rng.randomize() |
||||||
|
noise.seed = rng.randi() |
||||||
|
noise.octaves = octaves |
||||||
|
terrain = Terrain.new(width,height,spacing,true) |
||||||
|
init_points_data() |
||||||
|
print(terrain.get_point(3)) |
||||||
|
emit_signal("world_loaded", terrain) |
||||||
|
|
||||||
|
func init_points_data(): |
||||||
|
for index in terrain.get_points().size(): |
||||||
|
terrain.get_point(index).set_elevation(find_elevation(terrain.get_point(index).point2d())) |
||||||
|
# points_data.append({ |
||||||
|
# "elevation": 0, |
||||||
|
# "used": false, |
||||||
|
# "water": false, |
||||||
|
# "ocean": false, |
||||||
|
# "coast": false, |
||||||
|
# "mountain": false, |
||||||
|
# "river": false |
||||||
|
# }) |
||||||
|
|
||||||
|
func find_elevation(point): |
||||||
|
var border = border_width + rng.randf_range(-20.0, 20.0) |
||||||
|
var elevation = noise.get_noise_2d(point.x / wavelength, point.y / wavelength) |
||||||
|
|
||||||
|
if point.x < border: |
||||||
|
elevation -= ((border - point.x) / border) / 2.0 |
||||||
|
if point.y < border: |
||||||
|
elevation -= (border - point.y) / border |
||||||
|
if point.x > width - border: |
||||||
|
elevation -= (border - (width - point.x)) / border |
||||||
|
if point.y > height - border: |
||||||
|
elevation -= (border - (height - point.y)) / border |
||||||
|
|
||||||
|
elevation = max(elevation, -1) |
||||||
|
|
||||||
|
if elevation > 0.1: |
||||||
|
elevation = max(pow((elevation) * 1.2, 1.5), 0.1) |
||||||
|
|
||||||
|
elevation = min(elevation, 1) |
||||||
|
|
||||||
|
elevation = elevation * terraces |
||||||
|
return elevation |
||||||
|
# |
||||||
|
# if points_data[point_id].elevation <= 0: |
||||||
|
# points_data[point_id].water = true |
||||||
|
# |
||||||
|
# if points_data[point_id].elevation >= mountain_height: |
||||||
|
# points_data[point_id].mountain = true |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
[gd_scene load_steps=3 format=2] |
||||||
|
|
||||||
|
[ext_resource path="res://ui/ui.tscn" type="PackedScene" id=1] |
||||||
|
[ext_resource path="res://world/game.gd" type="Script" id=2] |
||||||
|
|
||||||
|
[node name="Game" type="Spatial"] |
||||||
|
script = ExtResource( 2 ) |
||||||
|
|
||||||
|
[node name="UI" parent="." instance=ExtResource( 1 )] |
||||||
|
|
||||||
|
[connection signal="world_loaded" from="." to="UI/Map" method="_on_Game_world_loaded"] |
||||||
|
|
||||||
|
[editable path="UI"] |
||||||
Loading…
Reference in new issue