383 lines
10 KiB
GDScript
383 lines
10 KiB
GDScript
tool
|
|
|
|
extends Spatial
|
|
|
|
var data = {}
|
|
var noise = OpenSimplexNoise.new()
|
|
var mesh = MeshInstance.new()
|
|
var marker = MeshInstance.new()
|
|
var marker_small = MeshInstance.new()
|
|
var markers = Spatial.new()
|
|
var st = SurfaceTool.new()
|
|
export var update = false
|
|
export var update_delay = 5000
|
|
export var noise_seed = 42
|
|
export var noise_period = 256
|
|
export var noise_octaves = 8
|
|
export var noise_persistence = 1/3.0
|
|
export var noise_lacunarity = 3
|
|
export var noise_offset_x = 0
|
|
export var noise_offset_y = 0
|
|
export var amplitude = 800
|
|
export var width = 100
|
|
export var length = 100
|
|
export var power = 4 # higher = more flat surfaces
|
|
|
|
export var run_erosion = false
|
|
export var erosion_delay = 500
|
|
export var rain_decimation = 0.05
|
|
export var rain_decimation_dampening = 1.0
|
|
export var rain_constraint = false
|
|
export var rain_constraint_x = 84
|
|
export var rain_constraint_y = 74
|
|
export var rain_constraint_spread = 6
|
|
|
|
export var inertia_dampening = 0.1
|
|
|
|
var last_update = 0
|
|
var last_erosion = 0
|
|
|
|
export var color = Color(0.5, 1, 0)
|
|
var material = SpatialMaterial.new()
|
|
var material_marker_decimation = SpatialMaterial.new()
|
|
var material_marker_deposit = SpatialMaterial.new()
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready():
|
|
|
|
# add markers and mesh to self so they are actually rendered
|
|
|
|
self.mesh.name = "mesh"
|
|
self.markers.name = "markers"
|
|
|
|
self.add_child(self.mesh)
|
|
self.add_child(self.markers)
|
|
|
|
self.noise.seed = self.noise_seed
|
|
self.noise.set_period(self.noise_period)
|
|
self.noise.set_octaves(self.noise_octaves)
|
|
self.noise.set_persistence(self.noise_persistence)
|
|
self.noise.set_lacunarity(self.noise_lacunarity)
|
|
|
|
self.material.albedo_color = self.color
|
|
self.material.roughness = 0.8
|
|
self.material.metallic = 0.3
|
|
#self.material.set_grow_enabled(true)
|
|
#self.material.set_grow(0.5)
|
|
|
|
self.material_marker_decimation.albedo_color = Color(0, 0.5, 1)
|
|
self.material_marker_deposit.albedo_color = Color(1, 0.5, 0)
|
|
|
|
var sphere = SphereMesh.new()
|
|
sphere.set_radius(0.125)
|
|
sphere.set_height(0.25)
|
|
sphere.set_rings(3)
|
|
sphere.set_radial_segments(3)
|
|
self.marker.name = "marker"
|
|
self.marker.set_mesh(sphere)
|
|
self.marker.set_material_override(self.material_marker_decimation)
|
|
|
|
sphere = SphereMesh.new()
|
|
sphere.set_radius(0.125/2)
|
|
sphere.set_height(0.125)
|
|
sphere.set_rings(1)
|
|
sphere.set_radial_segments(1)
|
|
self.marker_small.name = "marker_small"
|
|
self.marker_small.set_mesh(sphere)
|
|
self.marker_small.set_material_override(self.material_marker_decimation)
|
|
|
|
|
|
# build terrain data
|
|
for x in range(self.width + 1):
|
|
for y in range(0, self.length + 1):
|
|
|
|
self.data[Vector2(x, y)] = self.get_noise_2d(x, y)
|
|
if x + 0.5 < self.width and y + 0.5 < self.length:
|
|
self.data[Vector2(x + 0.5, y + 0.5)] = self.get_noise_2d(x + 0.5, y + 0.5)
|
|
|
|
self.update_terrain() # render data into a mesh
|
|
|
|
|
|
func _process(delta):
|
|
|
|
var t = OS.get_system_time_msecs()
|
|
|
|
if self.update and t >= self.last_update + self.update_delay:
|
|
self.update_terrain()
|
|
self.last_update = t
|
|
|
|
if self.run_erosion and t >= self.last_erosion + self.erosion_delay:
|
|
|
|
for node in self.markers.get_children():
|
|
self.markers.remove_child(node)
|
|
node.queue_free()
|
|
|
|
var x = 0
|
|
var y = 0
|
|
if self.rain_constraint == true:
|
|
x = int(self.rain_constraint_x - self.rain_constraint_spread / 2 + randi() % (self.rain_constraint_spread + 1))
|
|
y = int(self.rain_constraint_y - self.rain_constraint_spread / 2 + randi() % (self.rain_constraint_spread + 1))
|
|
else:
|
|
x = randi() % self.width
|
|
y = randi() % self.length
|
|
|
|
if randi() % 2 and x < self.width and y < self.length:
|
|
x += 0.5
|
|
y += 0.5
|
|
self.apply_raindrop(x, y)
|
|
self.last_erosion = t
|
|
|
|
func get_noise_2d(x, y):
|
|
return pow(self.noise.get_noise_2d(self.noise_offset_x + x, self.noise_offset_y + y), self.power) * amplitude
|
|
|
|
|
|
func get_quadrant_coordinates(x, y):
|
|
|
|
# NOTE: x and y correspond to left front vertex of a quadrant
|
|
|
|
return {
|
|
'center': Vector2(x + 0.5, y + 0.5),
|
|
'left_front': Vector2(x, y),
|
|
'left_back': Vector2(x, y + 1),
|
|
'right_front': Vector2(x + 1, y),
|
|
'right_back': Vector2(x + 1, y + 1)
|
|
}
|
|
|
|
|
|
func get_connected_coordinates(x, y):
|
|
|
|
var coords = {}
|
|
|
|
if x - int(x) == 0: # means we have a coordinate NOT in the center of a quadrant
|
|
if x - 1 >= 0:
|
|
coords['left'] = Vector2(x - 1, y)
|
|
|
|
if x + 1 <= self.width:
|
|
coords['right'] = Vector2(x + 1, y)
|
|
|
|
if y - 1 >= 0:
|
|
coords['front'] = Vector2(x, y - 1)
|
|
|
|
if y + 1 <= self.length:
|
|
coords['back'] = Vector2(x, y + 1)
|
|
|
|
if x - 0.5 >= 0:
|
|
if y - 0.5 >= 0:
|
|
coords['left_front'] = Vector2(x - 0.5, y - 0.5)
|
|
|
|
if y + 0.5 <= self.length:
|
|
coords['left_back'] = Vector2(x - 0.5, y + 0.5)
|
|
|
|
if x + 0.5 <= self.width:
|
|
|
|
if y - 0.5 >= 0:
|
|
coords['right_front'] = Vector2(x + 0.5, y - 0.5)
|
|
|
|
if y + 0.5 <= self.length:
|
|
coords['right_back'] = Vector2(x + 0.5, y + 0.5)
|
|
|
|
return coords
|
|
|
|
|
|
func get_steepness_data(x, y):
|
|
|
|
var steepness = {}
|
|
var own_amplitude = self.data[Vector2(x, y)]
|
|
var connected_coordinates = self.get_connected_coordinates(x, y)
|
|
|
|
for pos in connected_coordinates:
|
|
var amplitude = self.data[connected_coordinates[pos]]
|
|
steepness[pos] = amplitude - own_amplitude
|
|
if '_' in pos:
|
|
steepness[pos] /= sqrt(0.5) # adjust for less distance between diagonal points
|
|
#steepness[pos] *= 2
|
|
|
|
return steepness
|
|
|
|
|
|
func get_flow_data(x, y, flow, decimation):
|
|
|
|
var own_coord = Vector2(x, y)
|
|
var connected = self.get_connected_coordinates(x, y)
|
|
var positions = []
|
|
var data = {}
|
|
|
|
if flow in ['left', 'right']:
|
|
positions += ['front', 'back']
|
|
|
|
elif flow in ['front', 'back']:
|
|
positions += ['left', 'right']
|
|
|
|
elif flow in ['left_front', 'right_back']:
|
|
positions += ['left_back', 'right_front']
|
|
|
|
elif flow in ['left_back', 'right_front']:
|
|
positions += ['left_front', 'right_back']
|
|
|
|
for pos in positions:
|
|
if connected.has(pos):
|
|
var flow_coord = connected[pos]
|
|
var local_steepness = self.data[flow_coord] - self.data[own_coord]
|
|
var flow_decimation = decimation * 0.5
|
|
#if flow_coord[0] - int(flow_coord[0]) == 0.5: # center vertex of a quadrant
|
|
#flow_decimation = decimation * sqrt(0.5)
|
|
#flow_decimation = flow_decimation / sqrt(0.5)
|
|
#flow_decimation = decimation * 0.75
|
|
#flow_decimation = decimation * (1 - 0.5 * sqrt(0.5))
|
|
#flow_decimation = decimation
|
|
|
|
#if local_steepness > 0:
|
|
# flow_decimation = 0
|
|
#elif flow_decimation > local_steepness * -1:
|
|
if flow_decimation > local_steepness * -1 and local_steepness < 0:
|
|
flow_decimation = local_steepness * -1
|
|
#print("flow decimation: ", flow_decimation)
|
|
data[flow_coord] = flow_decimation
|
|
|
|
# add trailing center vertices with full decimation
|
|
var trail_coords = []
|
|
if flow == 'left':
|
|
if x - 0.5 > 0:
|
|
if y - 0.5 > 0:
|
|
trail_coords.append(Vector2(x - 0.5, y - 0.5))
|
|
if y + 0.5 < self.length:
|
|
trail_coords.append(Vector2(x - 0.5, y + 0.5))
|
|
|
|
elif flow == 'right':
|
|
if x + 0.5 < self.width:
|
|
if y - 0.5 > 0:
|
|
trail_coords.append(Vector2(x + 0.5, y - 0.5))
|
|
if y + 0.5 < self.length:
|
|
trail_coords.append(Vector2(x + 0.5, y + 0.5))
|
|
|
|
elif flow == 'front':
|
|
if y - 0.5 > 0:
|
|
if x - 0.5 > 0:
|
|
trail_coords.append(Vector2(x - 0.5, y - 0.5))
|
|
if x + 0.5 < self.width:
|
|
trail_coords.append(Vector2(x + 0.5, y - 0.5))
|
|
|
|
elif flow == 'back':
|
|
if y + 0.5 < self.length:
|
|
if x - 0.5 > 0:
|
|
trail_coords.append(Vector2(x - 0.5, y + 0.5))
|
|
if x + 0.5 < self.width:
|
|
trail_coords.append(Vector2(x + 0.5, y + 0.5))
|
|
|
|
for coord in trail_coords:
|
|
var local_steepness = self.data[coord] - self.data[own_coord]
|
|
var trail_decimation = decimation
|
|
if trail_decimation > local_steepness * -1 and local_steepness < 0:
|
|
trail_decimation = local_steepness * -1
|
|
data[coord] = trail_decimation
|
|
|
|
return data
|
|
|
|
|
|
func get_deposit_coordinates(x, y):
|
|
|
|
var own_coord = Vector2(x, y)
|
|
var coords = []
|
|
for coord in self.get_connected_coordinates(x, y).values():
|
|
var steepness = self.data[coord] - self.data[own_coord]
|
|
if steepness <= 0:
|
|
coords.append(coord)
|
|
|
|
return coords
|
|
|
|
|
|
func update_terrain():
|
|
|
|
# generates a mesh from self.data
|
|
|
|
self.st.clear()
|
|
|
|
self.st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
|
self.st.set_material(self.material)
|
|
self.st.add_uv(Vector2(0,0))
|
|
for x in range(0, self.width):
|
|
for y in range(0, self.length):
|
|
|
|
var coords2d = self.get_quadrant_coordinates(x, y)
|
|
|
|
for pos in ['right_front', 'left_front', 'center',\
|
|
'left_back', 'right_back', 'center',\
|
|
'left_front', 'left_back', 'center',\
|
|
'right_back', 'right_front', 'center']:
|
|
var coord = coords2d[pos]
|
|
var amplitude = self.data[coord]
|
|
self.st.add_vertex(Vector3(coord[0], amplitude, coord[1] * -1))
|
|
|
|
self.st.add_uv(Vector2(1,1))
|
|
|
|
st.generate_normals()
|
|
st.generate_tangents()
|
|
self.mesh.set_mesh(st.commit())
|
|
|
|
|
|
func add_marker(x, y, type='decimation'):
|
|
|
|
var amplitude = self.data[Vector2(x, y)]
|
|
var m
|
|
|
|
if type == 'flow':
|
|
m = self.marker_small.duplicate()
|
|
else:
|
|
m = self.marker.duplicate()
|
|
|
|
if type == 'deposit':
|
|
m.set_material_override(self.material_marker_deposit)
|
|
|
|
m.translate(Vector3(x, amplitude, y * -1))
|
|
self.markers.add_child(m)
|
|
|
|
|
|
func apply_raindrop(x, y, level=1, deposit=0):
|
|
|
|
var own_coord = Vector2(x, y)
|
|
var own_amplitude = self.data[own_coord]
|
|
var connected_coords = self.get_connected_coordinates(x, y)
|
|
var steepness = self.get_steepness_data(x, y)
|
|
var steepest = steepness.values().min()
|
|
|
|
var flow = 'stop'
|
|
if steepest <= 0:
|
|
|
|
var steepest_key_idx = steepness.values().find(steepest)
|
|
flow = steepness.keys()[steepest_key_idx]
|
|
|
|
if flow == 'stop' or level > 255:
|
|
self.data[own_coord] += deposit / 2
|
|
self.add_marker(x, y, 'deposit')
|
|
|
|
var deposit_coords = self.get_deposit_coordinates(x, y)
|
|
for coord in deposit_coords:
|
|
|
|
self.data[coord] += deposit / (2 * len(deposit_coords))
|
|
self.add_marker(coord[0], coord[1], 'deposit')
|
|
|
|
else:
|
|
|
|
var next_coord = connected_coords[flow] # what coordinate the drop is flowing to
|
|
|
|
#var decimation = self.rain_decimation / level
|
|
var max_decimation = self.rain_decimation / (1 + self.rain_decimation_dampening * pow(level - 1, 2))
|
|
|
|
var decimation = max_decimation
|
|
if decimation > steepest * -1:
|
|
decimation = steepest * -1
|
|
|
|
var flow_data = self.get_flow_data(x, y, flow, max_decimation)
|
|
|
|
self.data[own_coord] -= decimation
|
|
deposit += decimation
|
|
self.add_marker(x, y, 'decimation')
|
|
|
|
for flow_coord in flow_data:
|
|
|
|
var flow_decimation = flow_data[flow_coord]
|
|
self.data[flow_coord] -= flow_decimation
|
|
deposit += flow_decimation
|
|
self.add_marker(flow_coord[0], flow_coord[1], 'flow')
|
|
|
|
self.apply_raindrop(next_coord[0], next_coord[1], level + 1, deposit) |