@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): if (axis.length() < 0.1): continue #catch null vectors axis = axis.normalized() 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);