extends Node class_name QuadTreeNode var is_root : bool = false: get: return is_root set(value): is_root = value var is_leaf : bool = true: get: return is_leaf set(value): is_leaf = value var level : int var radius : float = 1.0 var children : Array var mesh_instance : MeshInstance3D = MeshInstance3D.new() var material = load("res://PlanetColoring.tres") @export var normal : Vector3 @export var center : Vector2 var center_of_mass : Vector3 const THRESHOLDS = [INF, 4096*4096, 2048*2048, 1024*1024, 512*512, 256*256, 128*128] const RESOLUTION = 17 func split(planet_data : PlanetData): remove_child(mesh_instance) mesh_instance = null children[0] = QuadTreeNode.new() children[0].center = center + Vector2(radius/2, radius/2) children[1] = QuadTreeNode.new() children[1].center = center + Vector2(-radius/2, radius/2) children[2] = QuadTreeNode.new() children[2].center = center + Vector2(radius/2, -radius/2) children[3] = QuadTreeNode.new() children[3].center = center + Vector2(-radius/2, -radius/2) for child in children: call_deferred("add_child", child) child.level = level + 1 child.radius = radius/2 child.normal = normal child.material = material child.mesh_instance = MeshInstance3D.new() child.regenerate_mesh(planet_data) child.call_deferred("add_child", child.mesh_instance) self.is_leaf = false func join(): for child in children: if (child != null): child.join() child.free() child = null self.is_leaf = true mesh_instance = MeshInstance3D.new() call_deferred("add_child", mesh_instance) func update_from_camera_pos(planet_data : PlanetData, camera : Camera3D = null): if (camera == null): camera = get_viewport().get_camera_3d() if (camera == null): return var camera_pos = camera.global_position var dist = (camera_pos - center_of_mass).length_squared() if (self.level < (THRESHOLDS.size() - 1) and dist < THRESHOLDS[self.level] and self.is_leaf): split(planet_data) elif (dist > THRESHOLDS[self.level] + THRESHOLDS[self.level]/2 and not self.is_leaf): join() regenerate_mesh(planet_data) if (children[0] != null): for child in children: child.update_from_camera_pos(planet_data, camera) func regenerate_mesh(planet_data): var arrays := [] arrays.resize(Mesh.ARRAY_MAX) var vertex_array := PackedVector3Array() var uv_array := PackedVector2Array() var normal_array := PackedVector3Array() var index_array := PackedInt32Array() var num_vertices : int = RESOLUTION * RESOLUTION var num_indices : int = (RESOLUTION-1) * (RESOLUTION-1) * 6 vertex_array.resize(num_vertices) normal_array.resize(num_vertices) uv_array.resize(num_vertices) index_array.resize(num_indices) var tri_index : int = 0 var a_axis := Vector3(normal.y, normal.z, normal.x) var b_axis : Vector3 = normal.cross(a_axis) for y in range(RESOLUTION): for x in range(RESOLUTION): var i : int = x + y*RESOLUTION # Relative position of the current point inside the tile var percent := Vector2(x,y) / (RESOLUTION-1) # Calculate the point on a cube, normalize it to a sphere, and map it to a point on the planet's surface. var point_on_cube : Vector3 = normal + (center.x + (percent.x - 0.5)*2.0*radius)*a_axis + (center.y + (percent.y - 0.5)*2.0*radius)*b_axis var point_on_sphere = point_on_cube.normalized() var point_on_planet = planet_data.point_on_planet(point_on_sphere) # Update the max and min elevations of the planet's surface. var elevation = point_on_planet.length() if (elevation < planet_data.min_elevation): planet_data.min_elevation = elevation if (elevation > planet_data.max_elevation): planet_data.max_elevation = elevation # Calculate center of mass of current tile. if (percent.x == 0.5 and percent.y == 0.5): center_of_mass = point_on_planet # Generate surface objects if (level == THRESHOLDS.size()-1 and elevation > planet_data.radius and randf() > 0.98 and elevation < planet_data.radius*1.02): # Create a new instance of a tree var test_tree = load("res://EvergreenTree.tscn").instantiate() var tree_axis_a = Vector3(point_on_sphere.y, point_on_sphere.z, point_on_sphere.x).normalized() var tree_axis_b = point_on_sphere.cross(tree_axis_a).normalized() test_tree.transform = Transform3D(tree_axis_b/2, point_on_sphere/2, tree_axis_a/2, point_on_planet) call_deferred("add_child", test_tree) vertex_array[i] = point_on_planet if x != RESOLUTION-1 and y != RESOLUTION-1: index_array[tri_index + 2] = i index_array[tri_index + 1] = i+RESOLUTION+1 index_array[tri_index] = i+RESOLUTION index_array[tri_index + 5] = i index_array[tri_index + 4] = i+1 index_array[tri_index + 3] = i+RESOLUTION+1 tri_index += 6 for a in range(0, index_array.size(), 3): var b : int = a + 1 var c : int = a + 2 var ab : Vector3 = vertex_array[index_array[b]] - vertex_array[index_array[a]] var bc : Vector3 = vertex_array[index_array[c]] - vertex_array[index_array[b]] var ca : Vector3 = vertex_array[index_array[a]] - vertex_array[index_array[c]] var cross_ab_bc : Vector3 = bc.cross(ab) var cross_bc_ca : Vector3 = ca.cross(bc) var cross_ca_ab : Vector3 = ab.cross(ca) normal_array[index_array[a]] += cross_ab_bc + cross_bc_ca + cross_ca_ab normal_array[index_array[b]] += cross_ab_bc + cross_bc_ca + cross_ca_ab normal_array[index_array[c]] += cross_ab_bc + cross_bc_ca + cross_ca_ab for i in range(normal_array.size()): normal_array[i] = normal_array[i].normalized() arrays[Mesh.ARRAY_VERTEX] = vertex_array arrays[Mesh.ARRAY_NORMAL] = normal_array arrays[Mesh.ARRAY_TEX_UV] = uv_array arrays[Mesh.ARRAY_INDEX] = index_array call_deferred("_update_mesh", arrays, planet_data) func _update_mesh(arrays : Array, planet_data): var _mesh := ArrayMesh.new() _mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) if (mesh_instance == null): return mesh_instance.mesh = _mesh if (level == THRESHOLDS.size()-1): mesh_instance.create_trimesh_collision() # DEBUG SHADING #mesh_instance.material_override = StandardMaterial3D.new() #mesh_instance.material_override.albedo_color = Color.from_hsv(float(level)/(THRESHOLDS.size()-1), 1, 1) mesh_instance.material_override = material mesh_instance.material_override.set_shader_parameter("min_elevation", planet_data.min_elevation - 0.2) mesh_instance.material_override.set_shader_parameter("max_elevation", planet_data.max_elevation) mesh_instance.material_override.set_shader_parameter("elevation_color", planet_data.planet_color) func _init(): children.resize(4)