Creating Ribbon Emitter for Godot 3.5

Godot Version

3.5

Question

`

  • I made a custom ribbon emitter for godot 3.5 because it doesn’t have one built in.

  • It works fine except for when the emitter is made a child of child. When this happens it’s coordinates start getting mirrored off global origin. I’m not a programmer by trade so I’m super confused on how my coordinates get flipped.
    *The emitter works in conjunction with an immediategeometry node. The emitter releases a breadcrumb of reference nodes and instructs the immediategeometry to draw vertex pairs on the reference nodes.
    *Is there something wrong with how I’m using global_transform to create the nodes?

`
extends Spatial
#Parent Node Reference
onready var Parent_Node = get_parent()

Ribbon Generator Tells The Immediate Geometry RibbonMesh to draw itself

RibbonMesh has code to make itself top level so as to draw at global coordinates

onready var ribbonMesh = $RibbonMesh

These reference nodes are instanced to create positions for the ribbon node’s vertices

export (PackedScene) var ref_node

#Parameters listed here and exported to inspector
export(float, 0, 9999) var segment_width := .5
export(int, 1, 9999) var segments := 16
export(float, .000001, 10) var lifetime := .05
export(Gradient) var gradient_color
export(bool) var head_faces_direction

#Player Engine Color Parameters
#var engine_color = GlobalStats.g_custom_engine_color
#Default Gradient to Prevent Errors
var defaultGradient: Gradient
#Ties lifetime and segments together to behave like a particle emitter.
#Removal Timer is based on lifetime
var referenceNodeRemovalInterval := lifetime
var referenceNodeCreationInterval := lifetime / segments
#Container to track referenceNodes
var referenceNodes: Array =
#Variables to track timers
var referenceNodeCreationTimer: Timer
var referenceNodeRemovalTimer: Timer

func _ready() → void:
#set_as_toplevel(true)
#Refresh to load gradient
gradient_color = gradient_color
#Test#
#Load lifetime values from inspecter
referenceNodeRemovalInterval = lifetime
referenceNodeCreationInterval = lifetime / segments
#Create first two constant reference points
#call_deferred(“addReferenceObject”)
#call_deferred(“addReferenceObject”)

#Create and start timers
referenceNodeCreationTimer = Timer.new()
add_child(referenceNodeCreationTimer)
referenceNodeCreationTimer.one_shot = false
referenceNodeCreationTimer.wait_time = referenceNodeCreationInterval
referenceNodeCreationTimer.connect("timeout", self, "addReferenceObject")
referenceNodeCreationTimer.start()

#Create and start deletion timer
referenceNodeRemovalTimer = Timer.new()
add_child(referenceNodeRemovalTimer)
referenceNodeRemovalTimer.one_shot = false
referenceNodeRemovalTimer.wait_time = referenceNodeRemovalInterval
referenceNodeRemovalTimer.connect("timeout", self, "removeReferenceObject")
referenceNodeRemovalTimer.start()

#Default Graident FallBack
defaultGradient = Gradient.new()
defaultGradient.add_point(0, Color(1,0,0))
defaultGradient.add_point(1, Color(0,1,0))
#Load the gradient
if gradient_color == null:
	gradient_color = defaultGradient

#This makes the reference nodes
func addReferenceObject() → void:
#Reference Node Limiter. Will determine segment numbers
if referenceNodes.size() >= segments:
return
#Creates it’s own reference node
var referenceNode = Spatial.new()

if referenceNodes.empty():
	#Insert at index 0 if the array is empty
	referenceNodes.insert(0, referenceNode)
	add_child(referenceNode)
	#get_tree().get_root().add_child(referenceNode)
	#referenceNode.transform = transform
else:
	#Insert at index 1 if array is not empty
	referenceNodes.insert(1, referenceNode)
	#Get position of parent
	referenceNode.global_transform = referenceNodes[0].global_transform
	get_tree().get_root().add_child(referenceNode)

#Command to Remove Reference Node
func removeReferenceObject() → void:
if referenceNodes.size() > 2:
var referenceNode = referenceNodes.pop_back()
referenceNode.queue_free()

func _physics_process(_delta: float) → void:
#Inherit Parent transform
#global_rotation = Parent_Node.global_rotation
if head_faces_direction:
update_reference_node()
create_plane()

func create_plane():
# Clear the ribbon mesh and begin drawing
ribbonMesh.clear()
ribbonMesh.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
var width_offset = segment_width / 2.0
var vertices =

var numReferenceNodes = referenceNodes.size()
# Check if there are at least 2 reference nodes
if numReferenceNodes >= 2:
	for i in range(numReferenceNodes):
		# Get the current reference node and its position
		var referenceNode = referenceNodes[i]
		#var position = referenceNode.transform.basis
		var position = referenceNode.global_transform.origin
		# Calculate the rotation offset
		#var rotation_offset = referenceNode.transform.basis.xform(Vector3(width_offset, 0, 0))
		var rotation_offset = referenceNode.global_transform.basis.xform(Vector3(width_offset, 0, 0))
		# Add vertices with accounting for thickness
		vertices.append(position - rotation_offset)
		vertices.append(position + rotation_offset)
	
	for i in range(0, vertices.size(), 2):
		var vertexIndex = i
		#Calculate color
		var interpolationFactor = float(vertexIndex) / float(numReferenceNodes)
		var prev_interpolationFactor = float(vertexIndex-2) / float(numReferenceNodes)
		var color1 = gradient_color.interpolate(interpolationFactor)
		var color2 = gradient_color.interpolate(prev_interpolationFactor)
		var color = color2.linear_interpolate(color1, .5)
		
		#set color and add vertices
		ribbonMesh.set_color(color)
		ribbonMesh.add_vertex(vertices[i])
		ribbonMesh.add_vertex(vertices[i + 1])
		
ribbonMesh.end()

func update_reference_node() → void:
#This makes ribbon face direction of movement
if referenceNodes.size() >= 2:
#Tracks current position and previous position
var origin = referenceNodes[0].global_transform.origin
var goal = referenceNodes[1].global_transform.origin
#Tracks if player is standing still, preventing an error.
if origin != goal:
var direction = (origin - goal).normalized()

		#Calculate rotation based on direction
		var rotation = Basis(Vector3(0, atan2(direction.x, direction.z), 0))
		#Set Rotation Directionly
		#referenceNodes[0].transform.basis = rotation
		referenceNodes[0].global_transform.basis = rotation
		
		#var dot_product = direction.dot(Vector3.UP)
		#referenceNodes[0].look_at(origin + direction, Vector3.UP)
	else:
		# Handle the case when the direction vector is too small
		referenceNodes[0].rotation_degrees = Vector3.ZERO

#func apply_player_engine_color() ->void:

Gradient.set_color (0,engine_color)

<_< also if anyone wants to let me know how to format my post correctly I’d appreciate it.

I figured this out. My immediate geometry is set to top level in order for the trail system to work. I added an additional line of code to have it inherit rotation:
rotation = get_parent().rotation