@tool
extends MeshInstance3D
class_name LineRenderer3D

@export var points: Array[Vector3] = [Vector3(0,0,0),Vector3(0,5,0)]:
	set(new_points): points = new_points
@export var start_thickness:float = 0.1:
	set(new_start_thickness): start_thickness = new_start_thickness
@export var end_thickness:float = 0.1:
	set(new_end_thickness): end_thickness = new_end_thickness
@export var corner_resolution:int = 5:
	set(new_corner_resolution): corner_resolution = new_corner_resolution
@export var cap_resolution:int = 5:
	set(new_cap_resolution): cap_resolution = new_cap_resolution
@export var draw_caps:bool = true:
	set(new_draw_caps): draw_caps = new_draw_caps
@export var draw_crners:bool = true:
	set(new_draw_crners): draw_crners = new_draw_crners
@export var use_global_coords:bool = true:
	set(new_use_global_coords): use_global_coords = new_use_global_coords
@export var tile_texture:bool = true:
	set(new_tile_texture): tile_texture = new_tile_texture

var camera : Camera3D
var cameraOrigin : Vector3

func _enter_tree():
	mesh = ImmediateMesh.new()

func _ready():
	pass

func _process(_delta):
	if points.size() < 2:
		return
	
	camera = get_viewport().get_camera_3d()
	if camera == null:
		return
	cameraOrigin = to_local(camera.get_global_transform().origin)
	
	var progressStep:float = 1.0 / points.size();
	var progress:float = 0;
	var thickness:float = lerp(start_thickness, end_thickness, progress);
	var nextThickness:float = lerp(start_thickness, end_thickness, progress + progressStep);
	
	var surface:SurfaceTool = SurfaceTool.new()
	surface.begin(Mesh.PRIMITIVE_TRIANGLES)
	
	for i in range(points.size() - 1):
		var A:Vector3 = points[i]
		var B:Vector3 = points[i+1]
	
		if use_global_coords:
			A = to_local(A)
			B = to_local(B)
	
		var AB:Vector3 = B - A;
		var orthogonalABStart:Vector3 = (cameraOrigin - ((A + B) / 2)).cross(AB).normalized() * thickness;
		var orthogonalABEnd:Vector3 = (cameraOrigin - ((A + B) / 2)).cross(AB).normalized() * nextThickness;
		
		var AtoABStart:Vector3 = A + orthogonalABStart
		var AfromABStart:Vector3 = A - orthogonalABStart
		var BtoABEnd:Vector3 = B + orthogonalABEnd
		var BfromABEnd:Vector3 = B - orthogonalABEnd
		
		if i == 0:
			if draw_caps:
				cap(surface, A, B, thickness, cap_resolution)
		
		if tile_texture:
			var ABLen = AB.length()
			var ABFloor = floor(ABLen)
			var ABFrac = ABLen - ABFloor
			
			surface.set_uv(Vector2(ABFloor, 0))
			surface.add_vertex(AtoABStart)
			surface.set_uv(Vector2(-ABFrac, 0))
			surface.add_vertex(BtoABEnd)
			surface.set_uv(Vector2(ABFloor, 1))
			surface.add_vertex(AfromABStart)
			surface.set_uv(Vector2(-ABFrac, 0))
			surface.add_vertex(BtoABEnd)
			surface.set_uv(Vector2(-ABFrac, 1))
			surface.add_vertex(BfromABEnd)
			surface.set_uv(Vector2(ABFloor, 1))
			surface.add_vertex(AfromABStart)
		else:
			surface.set_uv(Vector2(1, 0))
			surface.add_vertex(AtoABStart)
			surface.set_uv(Vector2(0, 0))
			surface.add_vertex(BtoABEnd)
			surface.set_uv(Vector2(1, 1))
			surface.add_vertex(AfromABStart)
			surface.set_uv(Vector2(0, 0))
			surface.add_vertex(BtoABEnd)
			surface.set_uv(Vector2(0, 1))
			surface.add_vertex(BfromABEnd)
			surface.set_uv(Vector2(1, 1))
			surface.add_vertex(AfromABStart)
		
		if i == points.size() - 2:
			if draw_caps:
				cap(surface, B, A, nextThickness, cap_resolution)
		else:
			if draw_crners:
				var C = points[i+2]
				if use_global_coords:
					C = to_local(C)
				
				var BC = C - B;
				var orthogonalBCStart = (cameraOrigin - ((B + C) / 2)).cross(BC).normalized() * nextThickness;
				
				var angleDot = AB.dot(orthogonalBCStart)
				
				if angleDot > 0 and not angleDot == 1:
					corner(surface, B, BtoABEnd, B + orthogonalBCStart, corner_resolution)
				elif angleDot < 0 and not angleDot == -1:
					corner(surface, B, B - orthogonalBCStart, BfromABEnd, corner_resolution)
		
		progress += progressStep;
		thickness = lerp(start_thickness, end_thickness, progress);
		nextThickness = lerp(start_thickness, end_thickness, progress + progressStep);
	
	surface.generate_normals()
	surface.generate_tangents()    
	mesh = surface.commit()

func cap(surface: SurfaceTool, center:Vector3, pivot:Vector3, thickness:float, cap_resolution:int):
	var orthogonal:Vector3 = (cameraOrigin - center).cross(center - pivot).normalized() * thickness;
	var axis:Vector3 = (center - cameraOrigin).normalized();
	
	var vertex_array:Array = []
	for i in range(cap_resolution + 1):
		vertex_array.append(Vector3(0,0,0))
	vertex_array[0] = center + orthogonal;
	vertex_array[cap_resolution] = center - orthogonal;
	
	for i in range(1, cap_resolution):
		vertex_array[i] = center + (orthogonal.rotated(axis, lerp(0.0, PI, float(i) / cap_resolution)));
	
	for i in range(1, cap_resolution + 1):
		surface.set_uv(Vector2(0, (i - 1) / cap_resolution))
		surface.add_vertex(vertex_array[i - 1]);
		surface.set_uv(Vector2(0, (i - 1) / cap_resolution))
		surface.add_vertex(vertex_array[i]);
		surface.set_uv(Vector2(0.5, 0.5))
		surface.add_vertex(center);
		
func corner(surface: SurfaceTool, center:Vector3, start:Vector3, end:Vector3, cap_resolution:int):
	var vertex_array:Array = []
	for i in range(cap_resolution + 1):
		vertex_array.append(Vector3(0,0,0))
	vertex_array[0] = start;
	vertex_array[cap_resolution] = end;
	
	var axis:Vector3 = start.cross(end).normalized()
	var offset:Vector3 = start - center
	var angle:float = offset.angle_to(end - center)
	
	for i in range(1, cap_resolution):
		vertex_array[i] = center + offset.rotated(axis, lerp(0.0, angle, float(i) / cap_resolution));
	
	for i in range(1, cap_resolution + 1):
		surface.set_uv(Vector2(0, (i - 1) / cap_resolution))
		surface.add_vertex(vertex_array[i - 1]);
		surface.set_uv(Vector2(0, (i - 1) / cap_resolution))
		surface.add_vertex(vertex_array[i]);
		surface.set_uv(Vector2(0.5, 0.5))
		surface.add_vertex(center);
		
