godot-planet/scripts/Planet.gd

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()