extends Spatial export(int) var planet_radius = 128 export(int) var face_resolution = 16 export(Material) var face_material = null export(Material) var atmosphere_material = null export(float) var planet_terrain_seed = 0.0 export(int) var terrain_octaves = 3 export(float) var terrain_period = 64.0 export(float) var terrain_persistence = 0.5 export(float) var terrain_lacunarity = 2.0 const LOD_MAX = 5 const LOD_ENABLE = false const WIREFRAME = false class PlanetThreading: var planet func _init(planet): self.planet = planet func _recursive_generate(face): var generated = false for child in face.get_children(): if generated: break if child.is_leaf() and not child.generated: child.generate_mesh() generated = true break elif not child.is_leaf(): generated = self._recursive_generate(child) return generated func _recursive_tick(face, camera): for child in face.get_children(): child.process_lod(camera) if not child.is_leaf(): self._recursive_tick(child, camera) func _tick(): var generated = false for i in self.planet.get_children(): if generated: continue generated = self._recursive_generate(self.planet) if generated or not LOD_ENABLE: return var camera = self.planet.get_viewport().get_camera() self._recursive_tick(self.planet, camera) class PlanetMetadata: var resolution var radius var material var lods var noise func _init(resolution, radius, material, lods, noise): self.resolution = resolution self.radius = radius self.material = material self.lods = lods self.noise = noise func get_radius(): return self.radius func get_resolution(): return self.resolution func get_material(): return self.material func get_detail_levels(): return self.lods func get_noise(): return self.noise class CubeFace extends MeshInstance: var position var normal var left var forward var resolution var radius var level var meta var center var generated = false func _init(level, pos, normal, meta): self.meta = meta self.normal = normal self.resolution = meta.get_resolution() self.radius = meta.get_radius() self.level = level self.generated = false # Calculate left (x) and forward (z) vectors from the normal (y) self.left = Vector3(normal.y, normal.z, normal.x) self.forward = normal.cross(self.left) self.position = pos # Center the first face if level == 0: self.position = self.position - (self.left * (self.radius / max(2.0, pow(2, self.level)))) self.position = self.position - (self.forward * (self.radius / max(2.0, pow(2, self.level)))) func process_lod(camera): if not self.center: return var camera_pos_local = self.to_local(camera.get_translation()) var camera_dist_center = (camera_pos_local - self.center).length() var divisionLevel = pow(2, self.level) var split_distance = float(float(self.radius) / divisionLevel) if camera_dist_center < split_distance * 1.5 and self.is_leaf(): self.subdivide() elif camera_dist_center > split_distance * 2 and not self.is_leaf(): self.merge() func is_leaf(): return self.level == self.meta.get_detail_levels() or self.get_child_count() == 0 func generate_mesh(): if (self.generated): return var vertices = self.resolution var noise = self.meta.get_noise() var st = SurfaceTool.new() var mode = Mesh.PRIMITIVE_TRIANGLES if WIREFRAME: mode = Mesh.PRIMITIVE_LINES st.begin(mode) st.set_material(self.meta.get_material()) var divisionLevel = pow(2, self.level) for i in range(0.0, vertices): for j in range (0.0, vertices): # Vertex index (0 to 1) var iindex = i / (vertices - 1.0) var jindex = j / (vertices - 1.0) # From the left and forward vectors, we can calculate an oriented vertex var iv = ((self.left * iindex) * self.radius) / divisionLevel var jv = ((self.forward * jindex) * self.radius) / divisionLevel # Add the scaled left and forward to the centered origin var vertex = self.position + (iv + jv) # Normalize and multiply by radius to create a spherical mesh var vertNormal = vertex.normalized() #var pos = vertNormal * (noise.get_noise_3dv(vertex) + self.radius) var pos = vertNormal * self.radius # Add to ST st.add_uv(Vector2(j * (1 / vertices), i * (1 / vertices))) st.add_vertex(pos) # Set centerpoint if (i == int(vertices / 2) and j == int(vertices / 2)): self.center = pos # Arrange planet face triangles for gz in range(0, vertices - 1): for gx in range(0, vertices - 1): var topLeft = (gz * vertices) + gx var topRight = topLeft + 1 var bottomLeft = ((gz + 1) * vertices) + gx var bottomRight = bottomLeft + 1 #st.add_index(topRight) #st.add_index(bottomRight) #st.add_index(topLeft) #st.add_index(topLeft) #st.add_index(bottomRight) #st.add_index(bottomLeft) st.add_index(topLeft) st.add_index(topRight) st.add_index(bottomLeft) st.add_index(bottomLeft) st.add_index(topRight) st.add_index(bottomRight) if not WIREFRAME: st.generate_normals() self.mesh = st.commit() self.generated = true func dispose(): if self.mesh: self.mesh = null self.generated = false func merge(): if self.level == 0: return if self.get_child_count() == 0: return for child in self.get_children(): child.merge() child.dispose() child.queue_free() func subdivide(): if self.level == self.meta.get_detail_levels(): return var lv = self.level + 1 var stepLeft = self.left * (self.radius / pow(2, lv)) var stepForward = self.forward * (self.radius / pow(2, lv)) self.add_child(CubeFace.new(lv, self.position + stepForward, self.normal, self.meta)) self.add_child(CubeFace.new(lv, (self.position + stepLeft) + stepForward, self.normal, self.meta)) self.add_child(CubeFace.new(lv, self.position, self.normal, self.meta)) self.add_child(CubeFace.new(lv, (self.position + stepLeft), self.normal, self.meta)) self.dispose() class CubePlanet extends Spatial: var faces = [] var meta func _init(meta): self.meta = meta var hs = meta.get_radius() / 2.0 self.set_name("CubePlanet") self.add_child(CubeFace.new(0, Vector3(0,0,-hs), Vector3(0,0,-1), self.meta)) # front self.add_child(CubeFace.new(0, Vector3(0,0, hs), Vector3(0,0,1), self.meta)) # back self.add_child(CubeFace.new(0, Vector3(-hs,0, 0), Vector3(-1,0,0), self.meta)) # left self.add_child(CubeFace.new(0, Vector3(hs,0, 0), Vector3(1,0,0), self.meta)) # right self.add_child(CubeFace.new(0, Vector3(0,hs,0), Vector3(0,1,0), self.meta)) # top self.add_child(CubeFace.new(0, Vector3(0,-hs,0), Vector3(0,-1,0), self.meta)) # bottom #self.get_child(0).subdivide() var thread # Called when the node enters the scene tree for the first time. func _ready(): var noise = OpenSimplexNoise.new() noise.seed = planet_terrain_seed noise.octaves = terrain_octaves noise.period = terrain_period noise.persistence = terrain_period noise.lacunarity = terrain_lacunarity #var atmo = MeshInstance.new() #var atmo_sphere = SphereMesh.new() #var atmo_radius = planet_radius + 10 #atmo_sphere.set_radius(-atmo_radius) #atmo_sphere.set_height(-atmo_radius * 2) #atmo_sphere.material = atmosphere_material #atmo_sphere.material.set_shader_param("ground_radius", atmo_radius) #atmo_sphere.material.set_shader_param("atmosphere_radius", atmo_radius + .15) #atmo.mesh = atmo_sphere #add_child(atmo) var planet = CubePlanet.new(PlanetMetadata.new(face_resolution, planet_radius, face_material, LOD_MAX, noise)) thread = PlanetThreading.new(planet) add_child(planet) func _process(delta): if thread: thread._tick()