291 lines
7.6 KiB
GDScript
291 lines
7.6 KiB
GDScript
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()
|