-
-
Save axilirate/96a3e77d597c2527582dbc79aecbab70 to your computer and use it in GitHub Desktop.
class_name Trail3D extends MeshInstance3D | |
""" | |
Original Author: Oussama BOUKHELF | |
License: MIT | |
Version: 0.1 | |
Email: [email protected] | |
Description: Advanced 2D/3D Trail system. | |
""" | |
enum {VIEW, NORMAL, OBJECT} | |
enum {X, Y, Z} | |
@export var emit: bool = true | |
@export var distance: float= 0.1 | |
@export_range(0, 99999) var segments: int = 20 | |
@export var lifetime: float= 0.5 | |
@export_range(0, 99999) var base_width: float = 0.5 | |
@export var tiled_texture: bool = false | |
@export var tiling: int = 0 | |
@export var width_profile: Curve | |
@export_range(0, 3) var smoothing_iterations: int= 0 | |
@export_range(0, 0.5) var smoothing_ratio: float = 0.25 | |
@export_enum(VIEW, NORMAL, OBJECT) var alignment: int = VIEW | |
@export_enum(X, Y, Z) var axe: int = Y | |
var points := [] | |
var color := Color(1, 1, 1, 1) | |
var always_update = false | |
var _target: Node3D | |
var _A: Point | |
var _B: Point | |
var _C: Point | |
var _temp_segment := [] | |
var _points := [] | |
class Point: | |
var transform: Transform3D | |
var age: float = 0.0 | |
func _init(transform :Transform3D, age :float) -> void: | |
transform = transform | |
age = age | |
func update(delta: float, points: Array) -> void: | |
age -= delta | |
if age <= 0: | |
points.erase(self) | |
func add_point(transform :Transform3D) -> void: | |
var point = Point.new(transform, lifetime) | |
points.push_back(point) | |
func clear_points() -> void: | |
points.clear() | |
func _prepare_geometry(point_prev :Point, point :Point, half_width :float, factor :float) -> Array: | |
var normal := Vector3() | |
if alignment == VIEW: | |
if get_viewport().get_camera_3d(): | |
var cam_pos = get_viewport().get_camera_3d().get_global_transform().origin | |
var path_direction :Vector3 = (point.transform.origin - point_prev.transform.origin).normalized() | |
normal = (cam_pos - (point.transform.origin + point_prev.transform.origin)/2).cross(path_direction).normalized() | |
else: | |
print("There is no camera in the scene") | |
elif alignment == NORMAL: | |
if axe == X: | |
normal = point.transform.basis.x.normalized() | |
elif axe == Y: | |
normal = point.transform.basis.y.normalized() | |
else: | |
normal = point.transform.basis.z.normalized() | |
else: | |
if axe == X: | |
normal = _target.global_transform.basis.x.normalized() | |
elif axe == Y: | |
normal = _target.global_transform.basis.y.normalized() | |
else: | |
normal = _target.global_transform.basis.z.normalized() | |
var width = half_width | |
if width_profile: | |
width = half_width * width_profile.interpolate(factor) | |
var p1 = point.transform.origin-normal*width | |
var p2 = point.transform.origin+normal*width | |
return [p1, p2] | |
func render(update := false) -> void: | |
if update: | |
always_update = update | |
else: | |
_render_geometry(points) | |
func _render_realtime() -> void: | |
var render_points = _points+_temp_segment+[_C] | |
_render_geometry(render_points) | |
func _render_geometry(source: Array) -> void: | |
var points_count = source.size() | |
if points_count < 2: | |
return | |
# The following section is a hack to make orientation "view" work. | |
# but it may cause an artifact at the end of the trail. | |
# You can use transparency in the gradient to hide it for now. | |
var _d :Vector3 = source[0].transform.origin - source[1].transform.origin | |
var _t :Transform3D = source[0].transform | |
_t.origin = _t.origin + _d | |
var point = Point.new(_t, source[0].age) | |
var to_be_rendered = [point]+source | |
points_count += 1 | |
var half_width :float = base_width/2.0 | |
var u := 0.0 | |
mesh.clear_surfaces() | |
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, null) | |
for i in range(1, points_count): | |
var factor :float = float(i)/(points_count-1) | |
var vertices = _prepare_geometry(to_be_rendered[i-1], to_be_rendered[i], half_width, 1.0-factor) | |
if tiled_texture: | |
if tiling > 0: | |
factor *= tiling | |
else: | |
var travel = (to_be_rendered[i-1].transform.origin - to_be_rendered[i].transform.origin).length() | |
u += travel/base_width | |
factor = u | |
mesh.surface_set_uv(Vector2(factor, 0)) | |
mesh.surface_add_vertex(vertices[0]) | |
mesh.surface_set_uv(Vector2(factor, 1)) | |
mesh.surface_add_vertex(vertices[1]) | |
mesh.surface_end() | |
func _update_points() -> void: | |
var delta = get_process_delta_time() | |
_A.update(delta, _points) | |
_B.update(delta, _points) | |
_C.update(delta, _points) | |
for point in _points: | |
point.update(delta, _points) | |
var size_multiplier = [1, 2, 4, 6][smoothing_iterations] | |
var max_points_count :int = segments * size_multiplier | |
if _points.size() > max_points_count: | |
_points.reverse() | |
_points.resize(max_points_count) | |
_points.reverse() | |
func smooth() -> void: | |
if points.size() < 3: | |
return | |
var output := [points[0]] | |
for i in range(1, points.size()-1): | |
output += _chaikin(points[i-1], points[i], points[i+1]) | |
output.push_back(points[-1]) | |
points = output | |
func _chaikin(A, B, C) -> Array: | |
if smoothing_iterations == 0: | |
return [B] | |
var out := [] | |
var x :float = smoothing_ratio | |
# Pre-calculate some parameters to improve performance | |
var xi :float = (1-x) | |
var xpa :float = (x*x-2*x+1) | |
var xpb :float = (-x*x+2*x) | |
# transforms | |
var A1_t :Transform3D = A.transform.interpolate_with(B.transform, xi) | |
var B1_t :Transform3D = B.transform.interpolate_with(C.transform, x) | |
# ages | |
var A1_a :float = lerp(A.age, B.age, xi) | |
var B1_a :float = lerp(B.age, C.age, x) | |
if smoothing_iterations == 1: | |
out = [Point.new(A1_t, A1_a), Point.new(B1_t, B1_a)] | |
else: | |
# transforms | |
var A2_t :Transform3D = A.transform.interpolate_with(B.transform, xpa) | |
var B2_t :Transform3D = B.transform.interpolate_with(C.transform, xpb) | |
var A11_t :Transform3D = A1_t.interpolate_with(B1_t, x) | |
var B11_t :Transform3D = A1_t.interpolate_with(B1_t, xi) | |
# ages | |
var A2_a :float = lerp(A.age, B.age, xpa) | |
var B2_a :float = lerp(B.age, C.age, xpb) | |
var A11_a :float = lerp(A1_a, B1_a, x) | |
var B11_a :float = lerp(A1_a, B1_a, xi) | |
if smoothing_iterations == 2: | |
out += [Point.new(A2_t, A2_a), Point.new(A11_t, A11_a), | |
Point.new(B11_t, B11_a), Point.new(B2_t, B2_a)] | |
elif smoothing_iterations == 3: | |
# transforms | |
var A12_t :Transform3D = A1_t.interpolate_with(B1_t, xpb) | |
var B12_t :Transform3D = A1_t.interpolate_with(B1_t, xpa) | |
var A121_t :Transform3D = A11_t.interpolate_with(A2_t, x) | |
var B121_t :Transform3D = B11_t.interpolate_with(B2_t, x) | |
# ages | |
var A12_a :float = lerp(A1_a, B1_a, xpb) | |
var B12_a :float = lerp(A1_a, B1_a, xpa) | |
var A121_a :float = lerp(A11_a, A2_a, x) | |
var B121_a :float = lerp(B11_a, B2_a, x) | |
out += [Point.new(A2_t, A2_a), Point.new(A121_t, A121_a), Point.new(A12_t, A12_a), | |
Point.new(B12_t, B12_a), Point.new(B121_t, B121_a), Point.new(B2_t, B2_a)] | |
return out | |
func _emit(delta) -> void: | |
var _transform :Transform3D = _target.global_transform | |
var point = Point.new(_transform, lifetime) | |
if not _A: | |
_A = point | |
return | |
elif not _B: | |
_A.update(delta, _points) | |
_B = point | |
return | |
if _B.transform.origin.distance_squared_to(_transform.origin) >= distance*distance: | |
_A = _B | |
_B = point | |
_points += _temp_segment | |
_C = point | |
_update_points() | |
_temp_segment = _chaikin(_A, _B, _C) | |
_render_realtime() | |
func _ready() -> void: | |
mesh = ImmediateMesh.new() | |
_target = get_parent() | |
top_level = true | |
func _process(delta) -> void: | |
if emit: | |
_emit(delta) | |
return | |
if always_update: | |
# This is needed for alignment == view, so it can be updated every frame. | |
_render_geometry(points) |
Hi,
I have a spaceship CharacterBody3D which I can fly in space... but the trail is out of place and I can't get it to the right position. In the scene the trail is at the engine exhaust, but when I run the trail starts somewhere in space and doesn't follow the movement of the ship. Any idea how to fix this? I use Godot 4 Beta 7 Mono.
Try to put the trail inside a Node3D, move the Node3D to your desired position and make sure the position of the trail is 0, 0, 0.
heya! this looks super promising, but doesn't work on release version of godot4. for example, .interpolate()
was changed into .sample()
, and @export_enum
should now be done with strings, like so:
@export_enum("VIEW", "NORMAL", "OBJECT") var alignment: int = 0
@export_enum("X", "Y", "Z") var axe: int = 1
other than that, i've replaced .interpolate()
with .sample()
and now am struggling to make it work. your update to this would be of tremendous value.
Hi there! Just wondering if you have any plans to update this for the release version of Godot 4 (Echoing the points @DISN-kolo has made).
@DISN-kolo - did you end up getting this to work? Otherwise, did you find a seperate solution for a trail in 3D?
Cheers! 😄
@Krydan nope, I gave up XD
I've sort of done it with manually tracking an object through space and passing those coordinates to a subdivided path so that it trails behind. Not very intuitive or reliable, but seems to work OK mostly...
I'd also really love to figure out how to make this work. If anyone finds a solution - please post it here!
Here's an updated version that works with Godot 4.1.1
https://gist.github.com/geegaz/8dfd61f600828c02acbbdaa749a2bbd5
heya! this looks super promising, but doesn't work on release version of godot4. for example,
.interpolate()
was changed into.sample()
, and@export_enum
should now be done with strings, like so:@export_enum("VIEW", "NORMAL", "OBJECT") var alignment: int = 0 @export_enum("X", "Y", "Z") var axe: int = 1
I added these, and the last issues was apparently in the Point class where the transform
and age
parameters of the _init()
method were shadowing the transform
and age
properties of Point.
Legend! Thank you!
Hi,
I have a spaceship CharacterBody3D which I can fly in space... but the trail is out of place and I can't get it to the right position. In the scene the trail is at the engine exhaust, but when I run the trail starts somewhere in space and doesn't follow the movement of the ship. Any idea how to fix this? I use Godot 4 Beta 7 Mono.
![]()
Try to put the trail inside a Node3D, move the Node3D to your desired position and make sure the position of the trail is 0, 0, 0.
I had a similar issue
func _ready() -> void:
global_transform = Transform3D.IDENTITY
mesh = ImmediateMesh.new()
_target = get_parent()
top_level = true
add global_transform = Transform3D.IDENTITY seems to fix it, I don't really know why
Hi,
I have a spaceship CharacterBody3D which I can fly in space... but the trail is out of place and I can't get it to the right position. In the scene the trail is at the engine exhaust, but when I run the trail starts somewhere in space and doesn't follow the movement of the ship. Any idea how to fix this? I use Godot 4 Beta 7 Mono.
![]()
Try to put the trail inside a Node3D, move the Node3D to your desired position and make sure the position of the trail is 0, 0, 0.
I had a similar issue
func _ready() -> void: global_transform = Transform3D.IDENTITY mesh = ImmediateMesh.new() _target = get_parent() top_level = true
add global_transform = Transform3D.IDENTITY seems to fix it, I don't really know why
You are a bloody life saver!
I ported this code to Rust here; developed and running it on Godot 4.3 dev 6 build.
It works just like a normal native addon, and it is compatible with GDScript and C#.
On top of the performance benefits of using Rust, I've also optimized a lot of the original code to avoid unnecessary array copying.
A small part of the trail mesh does not disappear when the trail3D node stops moving. Anyone else having this issue?
Hi,
I have a spaceship CharacterBody3D which I can fly in space... but the trail is out of place and I can't get it to the right position. In the scene the trail is at the engine exhaust, but when I run the trail starts somewhere in space and doesn't follow the movement of the ship. Any idea how to fix this? I use Godot 4 Beta 7 Mono.
![]()
Try to put the trail inside a Node3D, move the Node3D to your desired position and make sure the position of the trail is 0, 0, 0.
I had a similar issue
func _ready() -> void: global_transform = Transform3D.IDENTITY mesh = ImmediateMesh.new() _target = get_parent() top_level = true
add global_transform = Transform3D.IDENTITY seems to fix it, I don't really know why
Works perfectly, thanks.
I assume it is cuz top_level is set to true and then we are accessing space positions in it without ever setting it's global_position
Hi,
I have a spaceship CharacterBody3D which I can fly in space... but the trail is out of place and I can't get it to the right position. In the scene the trail is at the engine exhaust, but when I run the trail starts somewhere in space and doesn't follow the movement of the ship. Any idea how to fix this? I use Godot 4 Beta 7 Mono.