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)
