new file: addons/PoissonDiscSampling/PoissonDiscSampling.gd

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.tscn
pull/4/head
Valentin Stark 3 years ago
parent 441d3050df
commit 10140bf69c
  1. 137
      addons/PoissonDiscSampling/PoissonDiscSampling.gd
  2. 571
      addons/delaunator/Delaunator.gd
  3. 7
      default_env.tres
  4. BIN
      icon.png
  5. 35
      icon.png.import
  6. 49
      project.godot
  7. 116
      ui/map/map.gd
  8. 6
      ui/map/map.tscn
  9. 7
      ui/ui.tscn
  10. 332
      utils/terrain/Terrain.gd
  11. 70
      world/game.gd
  12. 13
      world/game.tscn

@ -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 )

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -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…
Cancel
Save