# This file is part of open-fpsz. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . class_name Trail3D extends MeshInstance3D @export var _trail_enabled : bool = true @export var _start_width : float = 0.5 @export var _end_width : float = 0.0 @export_range(0.5, 1.5) var _scaling_acceleration : float = 0.5 @export var _motion_delta : float = 0.1 # Smoothness of trail @export var _lifespan : float = 1.0 # Duration of trail @export var _start_color : Color = Color(1.0, 1.0, 1.0, 1.0) # Start color @export var _end_color : Color = Color(1.0, 1.0, 1.0, 1.0) # End color var _previous_position : Vector3 var _points : Array class PointData: var position : Vector3 var width_0 : Vector3 var width_1 : Vector3 var lifespan : float = 0.0 func _append_point() -> void: var point_data : PointData = PointData.new() point_data.position = global_position point_data.width_0 = global_basis.x * _start_width point_data.width_1 = global_basis.x * _start_width - global_basis.x * _end_width _points.append(point_data) func _ready() -> void: _previous_position = global_position mesh = ImmediateMesh.new() func _process(delta : float) -> void: if(_previous_position - global_position).length() > _motion_delta and _trail_enabled: _append_point() _previous_position = global_position _update_lifespans(delta) _render_trail() # Update all lifespans and use a mark-and-sweep algorithm to remove "expired" points func _update_lifespans(delta : float) -> void: var points_to_remove : Array = [] for point_data : PointData in _points: point_data.lifespan += delta if point_data.lifespan > _lifespan: points_to_remove.append(point_data) for point_data : PointData in points_to_remove: _points.erase(point_data) func _render_trail() -> void: mesh.clear_surfaces() var number_of_points : int = _points.size() if number_of_points < 2: return # Render new mesh for each point, triangle strips draw 2 triangles each 4 points # which is perfect for a trail mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) for point_index in range(number_of_points): var point_data : PointData = _points[point_index] var t : float = float(point_index) / (number_of_points - 1.0) var current_color : Color = _start_color.lerp(_end_color, 1 - t) mesh.surface_set_color(current_color) var current_width : Vector3 = point_data.width_0 - pow(1 - t, _scaling_acceleration) * point_data.width_1 var t0 : float = float(point_index) / float(number_of_points) var t1 : float = t mesh.surface_set_uv(Vector2(t0, 0)) mesh.surface_add_vertex(to_local(point_data.position + current_width)) mesh.surface_set_uv(Vector2(t1, 1)) mesh.surface_add_vertex(to_local(point_data.position - current_width)) mesh.surface_end()