Terrain/ComplexTerrain.gd

650 lines
21 KiB
GDScript

tool
extends Spatial
#export var material = preload('res://ErosionMaterial.gd')
#export(OpenSimplexNoise) var noise
export var width = 10
export var length = 10
export var amplitude = 800
export var power = 4
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 deposit_min = 0.01
var data = {}
var st = SurfaceTool.new()
var noise = OpenSimplexNoise.new()
var debuginstances = Spatial.new()
var meshinstances = Spatial.new()
#var material = ErosionMaterial.new()
var materials = {
'granite': ErosionMaterial.new(),
'clay': ErosionMaterial.new(),
'air': ErosionMaterial.new()
}
class Vector2Sort:
static func sort(a, b):
if a[0] < b[0]:
return true
elif a[0] == b[0] and a[1] < b[1]:
return true
return false
func dict_equal(a, b):
if len(a.keys()) != len(b.keys()):
return false
for key in a:
if not key in b.keys():
return false
print(typeof(a[key]))
if typeof(a[key]) != typeof(b[key]):
print("typeof failure")
return false
#if typeof(a[key]) == TYPE_DICTIONARY:
# print("recursive dict equal")
# if not dict_equal(a[key], b[key]):
# return false
if typeof(a[key]) == TYPE_ARRAY:
if len(a[key]) != len(b[key]):
return false
for index in range(0, len(a[key])):
var item_a = a[key][index]
var item_b = b[key][index]
if typeof(item_a) != typeof(item_b):
return false
if typeof(item_a) == TYPE_DICTIONARY:
if not dict_equal(item_a, item_b):
return false
elif typeof(item_a) == TYPE_ARRAY:
print("NOOOOOOOOOOOOOOOOOOOOOOOOIN")
else:
print("else thingamabob")
if item_a != item_b:
return false
elif a[key] != b[key]:
#print([a[key], b[key]])
return false
return true
func list_contains_dict(list, dict):
if dict == null:
print(">>>>>>>>>>>>>>>>>>> FNORD!")
for item in list:
if self.dict_equal(item, dict):
#if item.hash() == dict.hash():
return true
return false
func deposit_equal(a, b):
if a['material'] != b['material']:
return false
if a['bounds'] != b['bounds']:
return false
if len(a['points']) != len(b['points']):
return false
for point in a['points']:
if not point in b['points']:
return false
return true
func deposit_in_array(list, deposit):
for item in list:
if self.deposit_equal(item, deposit):
return true
return false
# Called when the node enters the scene tree for the first time.
func _ready():
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.materials['granite'].albedo_color = Color(1.0, 0.0, 0.0)
#self.materials['clay'].albedo_color = Color(0, 1, 0)
self.materials['granite'].albedo_color = Color(0.1, 0.1, 0.1)
self.materials['clay'].albedo_color = Color(0.7, 0.4, 0)
self.materials['air'].albedo_color = Color(0.8,1,0.9, 0.025)
for material in self.materials.values():
material.roughness = 0.8
material.metallic = 0.3
material.set_flag(SpatialMaterial.FLAG_USE_POINT_SIZE, true)
material.set_point_size(10)
material.set_line_width(10)
self.materials['air'].set_feature(SpatialMaterial.FEATURE_TRANSPARENT, true)
self.add_child(self.meshinstances)
self.add_child(self.debuginstances)
self.seed_layers()
self.generate_model()
self.render_debug()
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func seed_layers():
for x in range(0, width + 1):
for y in range(0, length + 1):
self.data[Vector2(x, y)] = self.generate_stack(x, y)
func render_debug():
var vertices = {}
for x in range(0, width + 1):
for y in range(0, length + 1):
var stack = self.data[Vector2(x, y)]
var bottom = 0
for layer in stack:
var material = layer['material']
if not vertices.has(material):
vertices[material] = []
vertices[material].append(Vector3(x, bottom, y * -1))
vertices[material].append(Vector3(x, bottom + layer['height'], y * -1))
bottom += layer['height']
for material_name in vertices:
self.st.clear()
self.st.begin(Mesh.PRIMITIVE_LINES)
self.st.set_material(self.materials[material_name])
for vertex in vertices[material_name]:
self.st.add_vertex(vertex)
var m = MeshInstance.new()
m.set_mesh(st.commit())
self.debuginstances.add_child(m)
func generate_deposit_mesh(deposit):
#print("deposit")
#print(deposit['material'])
#print(len(deposit))
self.st.clear()
self.st.begin(Mesh.PRIMITIVE_TRIANGLES)
self.st.set_material(self.materials[deposit['material']])
self.st.add_uv(Vector2(0,0))
#for index in Geometry.triangulate_polygon(coords):
# print("vertex")
# self.st.add_vertex(deposit[index][0][0], deposit[index]['bottom'], deposit[index][0][1] * -1)
var vertices_top = []
var vertices_bottom = []
var by_coordinates = {}
for key in deposit['points']:
if not by_coordinates.has(key[0]):
by_coordinates[key[0]] = []
by_coordinates[key[0]].append(key)
for x in range(deposit['bounds'][0][0], deposit['bounds'][1][0]):
var x_odd = x % 2 == 1
for y in range(deposit['bounds'][0][1], deposit['bounds'][1][1]):
var y_odd = y % 2 == 1
var coord = Vector2(x, y)
if coord in by_coordinates.keys():
var right = coord + Vector2(1, 0)
var back = coord + Vector2(0, 1)
var right_back = coord + Vector2(1, 1)
for key in by_coordinates[coord]:
var info = deposit['points'][key]
#if info['height'] > 0.01:
if true:
var neighbors_by_coordinates = {}
for neighbor_key in info['neighbors']:
neighbors_by_coordinates[neighbor_key[0]] = deposit['points'][neighbor_key]
#if x_odd and not y_odd or not x_odd and y_odd:
if x_odd != y_odd:
if right in neighbors_by_coordinates and right_back in neighbors_by_coordinates:
var right_info = neighbors_by_coordinates[right]
var right_back_info = neighbors_by_coordinates[right_back]
#bottom side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
# top side
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
# front side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
# diagonal side
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
# right side
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
if back in neighbors_by_coordinates and right_back in neighbors_by_coordinates:
var back_info = neighbors_by_coordinates[back]
var right_back_info = neighbors_by_coordinates[right_back]
# bottom side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
# top side
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
# diagonal side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
# back side
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
# left side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
else:
if right in neighbors_by_coordinates and back in neighbors_by_coordinates:
var right_info = neighbors_by_coordinates[right]
var back_info = neighbors_by_coordinates[back]
# bottom side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
# top side
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
# diagonal side
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
# front side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
# left side
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(coord[0], info['top'], coord[1] * -1))
st.add_vertex(Vector3(coord[0], info['bottom'], coord[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
if back in neighbors_by_coordinates and right in neighbors_by_coordinates and right_back in neighbors_by_coordinates:
var right_info = neighbors_by_coordinates[right]
var back_info = neighbors_by_coordinates[back]
var right_back_info = neighbors_by_coordinates[right_back]
# bottom side
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
# top side
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
# diagonal side
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
# right side
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
st.add_vertex(Vector3(right[0], right_info['bottom'], right[1] * -1))
st.add_vertex(Vector3(right[0], right_info['top'], right[1] * -1))
# back side
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['bottom'], back[1] * -1))
st.add_vertex(Vector3(back[0], back_info['top'], back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['bottom'], right_back[1] * -1))
st.add_vertex(Vector3(right_back[0], right_back_info['top'], right_back[1] * -1))
self.st.add_uv(Vector2(1,1))
self.st.generate_normals()
return st.commit()
func generate_model():
for meshinstance in self.meshinstances.get_children():
self.meshinstances.remove_child(meshinstance)
meshinstance.queue_free()
var deposits = []
for coord in self.data:
#print("dat coord: ", coord)
var stack = self.data[coord]
for layer_index in range(0, len(stack)):
var deposit = self.get_deposit(coord, layer_index)
#if not deposit in deposits:
#if not self.list_contains_dict(deposits, deposit):
if not self.deposit_in_array(deposits, deposit):
deposits.append(deposit)
print("number of deposits: ", len(deposits))
for deposit in deposits:
var meshinstance = MeshInstance.new()
meshinstance.set_mesh(self.generate_deposit_mesh(deposit))
self.meshinstances.add_child(meshinstance)
func get_deposit(coord, layer_index, deposit_data=null):
var stack = self.data[coord]
#for layer in stack:
var own_layer = stack[layer_index]
var own_material = own_layer['material']
var own_height = own_layer['height']
var own_bottom = self.get_layer_bottom(coord, layer_index)
var own_top = own_bottom + own_height
if deposit_data == null:
deposit_data = {
'material': own_material,
'points': {},
'bounds': [coord, coord]
}
var key = [coord, layer_index]
if key in deposit_data['points'].keys():
return deposit_data # avoid infinite recursion
if own_height > self.deposit_min:
#deposit_data['points'].append(datapoint)
deposit_data['points'][key] = {}
deposit_data['points'][key]['bottom'] = own_bottom
deposit_data['points'][key]['height'] = own_height
deposit_data['points'][key]['top'] = own_top
deposit_data['points'][key]['neighbors'] = []
for next_coord in self.get_connected_points(coord):
#for layer in self.data[next_coord]:
for lindex in range(0, len(self.data[next_coord])):
var layer = self.data[next_coord][lindex]
if layer['material'] == own_material:
var layer_bottom = self.get_layer_bottom(next_coord, lindex)
var layer_height = layer['height']
var layer_top = layer_bottom + layer_height
#var valid_neighbor = false
#if own_bottom <= layer_bottom and own_top >= layer_top: # we span entire neighbor
# valid_neighbor = true
#elif own_bottom >= layer_bottom and own_top <= layer_top: # neighbor spans us entirely
# valid_neighbor = true
#elif own_bottom >= layer_bottom and own_bottom <= layer_top: # our bottom in contact with neighbor
# valid_neighbor = true
#elif own_top >= layer_bottom and own_top <= layer_top: # our top in contact with neighbor
# valid_neighbor = true
#if valid_neighbor:
#if lindex == layer_index:
if lindex == layer_index and layer_height > self.deposit_min:
if next_coord[0] < deposit_data['bounds'][0][0]:
deposit_data['bounds'][0][0] = next_coord[0]
elif next_coord[0] > deposit_data['bounds'][1][0]:
deposit_data['bounds'][1][0] = next_coord[0]
if next_coord[1] < deposit_data['bounds'][0][1]:
deposit_data['bounds'][0][1] = next_coord[1]
elif next_coord[1] > deposit_data['bounds'][1][1]:
deposit_data['bounds'][1][1] = next_coord[1]
deposit_data = self.get_deposit(next_coord, lindex, deposit_data)
#if not is_rim:
# deposit_data = self.get_deposit(next_coord, lindex, deposit_data)
#else:
# deposit_data['points'][[next_coord, lindex]] = {
# 'bottom': layer_bottom,
# 'height': layer_height,
# 'top': layer_top,
# 'neighbors': [key]
# }
deposit_data['points'][key]['neighbors'].append([next_coord, lindex])
return deposit_data
func get_layer_bottom(coord, layer_index):
var offset = 0
for idx in range(0, layer_index): # iterates up to the element *before* the one deonoted by layer_index
var layer_info = self.data[coord][idx]
offset += layer_info['height']
return offset
func get_connected_points(coord):
var coords = []
for x_offset in [-1, 0, 1]:
for y_offset in [-1, 0, 1]:
if [x_offset, y_offset] != [0, 0]:
var x = coord[0] + x_offset
var y = coord[1] + y_offset
if self.in_range(0, self.width, x) and self.in_range(0, self.length, y):
coords.append(Vector2(x, y))
return coords
func get_connected_points_in_deposit(deposit, coord):
var coords = self.get_connected_points(coord)
var deposit_coords = []
for info in deposit['points']:
deposit_coords.append(info[0])
var valid_coords = []
for coord in coords:
if coord in deposit_coords:
valid_coords.append(coord)
return valid_coords
func in_range(minimum, maximum, value):
return value >= minimum and value <= maximum
func generate_stack(x, y):
#var layers = ['granite', self.granite_bottom]
var stack = []
#for layer_info in self.layers:
# var generator = layer_info[1]
# stack.append([layer_info[0], generator(x, y)])
var height = self.granite_bottom(x, y)
stack.append({
'material': 'granite',
'height': height
})
height = self.clay_bottom(x, y)
stack.append({
'material': 'clay',
'height': height
})
height = self.air(x, y)
stack.append({
'material': 'air',
'height': height
})
height = self.granite_top(x, y)
stack.append({
'material': 'granite',
'height': height
})
return stack
func granite_top(x, y):
return pow(self.noise.get_noise_2d(self.noise_offset_x + x, self.noise_offset_x + y), self.power) * self.amplitude
func air(x, y):
#return pow(self.noise.get_noise_2d(self.noise_offset_x + x, self.noise_offset_x + y), self.power) * self.amplitude
return pow(self.noise.get_noise_2d((self.noise_offset_x + x) * -1, (self.noise_offset_y + y) * -1), self.power) * self.amplitude
func clay_bottom(x, y):
#return pow(self.noise.get_noise_2d(self.noise_offset_x + x, self.noise_offset_x + y), self.power) * self.amplitude
return pow(self.noise.get_noise_2d(self.noise_offset_x + x + 1000, self.noise_offset_y + y + 1000), self.power * 2) * self.amplitude * 2
func granite_bottom(x, y):
#return pow(self.noise.get_noise_2d(self.noise_offset_x + x, self.noise_offset_x + y), self.power) * self.amplitude
return 0.1 + pow(self.noise.get_noise_2d(self.noise_offset_x + x * 2, self.noise_offset_y + y * 2), self.power) * self.amplitude