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)