Creating Longer Trails w/ Parts & Beams

You mean one single mesh makes up the geometry and length of the entire trail?

Yes, I think that may work well for you. I’m trying it out right now.

I’m having a difficult time understanding this new feature. lol, I have not done much with standalone meshes as it is.

Its currently in beta with very little proper documentation. When I’m done testing things out I will send my code with comments, hopefully it helps. Because EditableMeshes follow the standard convention of meshes you can look at external sources on how they work.

Alright sounds good, I’ll keep messing around with them in the meantime, thanks!

Still a WIP but its working right now. I’ll probably continue working on it tomorrow

It is a module script

Planning on adding the ability to remove markers, and allow fading them out over time and maybe some other stuff
Also fixing the math so that when you look along the beam from one end the triangles dont distort

also will add comments in the final version whenever I make that

local replicated_storage = game:GetService("ReplicatedStorage")


local Trail = {}
Trail.__index = Trail

local TrailsFacingCamera = {}




game:GetService("RunService").RenderStepped:Connect(function(dt)

	local CameraLook = workspace.CurrentCamera.CFrame.LookVector



	for _,Trail in TrailsFacingCamera do
		for i,Marker in Trail.Markers do
			local Mesh:EditableMesh = Trail.EditableMesh
			local OriginalMesh:MeshPart = Trail.Mesh

			local offset

			if i == #Trail.Markers then
				offset = CameraLook:Cross(Marker.Direction).Unit * Trail.Width
			else
				offset = CameraLook:Cross((Marker.Direction+Trail.Markers[i+1].Direction)/2).Unit * Trail.Width
			end


			Mesh:SetPosition(Marker.Associated_Vertices[1],OriginalMesh.CFrame:PointToObjectSpace(Marker.Position+offset))
			Mesh:SetPosition(Marker.Associated_Vertices[2],OriginalMesh.CFrame:PointToObjectSpace(Marker.Position-offset))

		end
	end



end)


function Trail.new(Width:number,FaceCamera:boolean, StartingPoint:Vector3)

	local T = setmetatable({}, Trail)

	T.Width = Width

	T.FaceCamera = FaceCamera

	T.Mesh = Instance.new("MeshPart")
	T.Mesh.Anchored = true
	T.Mesh.CanCollide = false
	T.Mesh.CanTouch = false
	T.Mesh.CanQuery = false
	--T.Mesh.Material = Enum.Material.Neon

	T.Mesh.Size = Vector3.one



	T.Mesh.Parent = workspace:WaitForChild("Trail_Folder")


	T.EditableMesh = Instance.new("EditableMesh",T.Mesh) -- setting parent in constructor is usually bad practice, but here its fine because no properties are being modified after initialization and before parenting



	T.Markers = {}


	if StartingPoint then
		T:AddMarker(StartingPoint)
	end



	if FaceCamera then
		table.insert(TrailsFacingCamera,T)
	end

	return T
end





function Trail:AddMarker(point:Vector3)
	local Marker = {}

	local Mesh = self.EditableMesh

	local OriginalMesh = self.Mesh

	local offset = Vector3.new(0,1,0)*self.Width



	Marker.Position = point





	Marker.Associated_Vertices = {Mesh:AddVertex(OriginalMesh.CFrame:PointToObjectSpace(point+offset)),Mesh:AddVertex(OriginalMesh.CFrame:PointToObjectSpace(point-offset))} -- add two vertices for each marker


	if #self.Markers ~= 0 then




		local LastMarker = self.Markers[#self.Markers]


		Marker.Direction = (LastMarker.Position - Marker.Position)


		Mesh:AddTriangle(
			Marker.Associated_Vertices[1],
			Marker.Associated_Vertices[2],
			LastMarker.Associated_Vertices[1]
		)
		Mesh:AddTriangle(
			LastMarker.Associated_Vertices[1],
			LastMarker.Associated_Vertices[2],
			Marker.Associated_Vertices[2]
		)

		Mesh:AddTriangle(
			LastMarker.Associated_Vertices[1],
			Marker.Associated_Vertices[2],
			Marker.Associated_Vertices[1]
		)
		Mesh:AddTriangle(
			Marker.Associated_Vertices[2],
			LastMarker.Associated_Vertices[2],
			LastMarker.Associated_Vertices[1]
		)

	end

	if #self.Markers == 1 then
		self.Markers[1].Direction = Marker.Direction
	end



	table.insert(self.Markers,Marker)


	--print(self.Property)
end

return Trail

In a local script somewhere


local one = workspace:WaitForChild("1")
local two = workspace:WaitForChild("2")
local three = workspace:WaitForChild("3")

local ReplicatedStorage  = game:GetService("ReplicatedStorage")
local Trail = require(ReplicatedStorage:WaitForChild("Trail"))


local trail1 = Trail.new(2,true,one.Position)


trail1:AddMarker(two.Position)
trail1:AddMarker(three.Position)

1 Like

I appreciate you taking the time to do all of this, this should set a pretty good example at what I need to do, you don’t need to finish it or send me a final product if you don’t want to unless you deem it necessary for major things that I should know about, but I’ll mess around with what you made and all, thank you

So it’s been some time, and a few days ago I put in place what you designed. I also added a trail length based on distance from the part moving so the trail dissipates. This isn’t so bad at first, but after 30 to 45 seconds depending on how many trails, it begins to lag, but I don’t know why it would if it’s perfectly fine in the first 30 seconds and the trails are at their full length.

Are you sure the trails are the issue, and that there isn’t anything else that could be causing the lag? Sorry for the delay I haven’t been too active on the dev forum, my discord user is redevan if you want to shoot me a dm there, ill be able to reply much more quickly.

Sent a friend request, we can continue discussion there then if it’s more convenient.

For anyone seeing this after the fact, this is the module we came up with. The documentation is sparse but its pretty simple, if anyone has any questions/issues just dm me and ill see what I can do.

local RunService = game:GetService("RunService")

local Trail = {}
Trail.__index = Trail


function Trail.new(Width:number,FaceCamera:boolean,MaxLength:number) -- added MaxLength argument

	local T = setmetatable({}, Trail)



	T.Width = Width



	T.FaceCamera = FaceCamera
	--T.ThinOverTime = ThinOverTime

	T.Mesh = Instance.new("MeshPart")
	T.Mesh.Anchored = true
	T.Mesh.CanCollide = false
	T.Mesh.CanTouch = false
	T.Mesh.CanQuery = false


	T.Mesh.Size = Vector3.one

	T.MaxLength = MaxLength


	T.Mesh.Parent = workspace:WaitForChild("Trail_Folder")


	T.EditableMesh = Instance.new("EditableMesh",T.Mesh) -- setting parent in constructor is usually bad practice, but here its fine because no properties are being modified after initialization and before parenting


	T.EditableMesh:AddTriangle(T.EditableMesh:AddVertex(Vector3.zero),T.EditableMesh:AddVertex(Vector3.zero),T.EditableMesh:AddVertex(Vector3.zero)) -- triangle bug workaround



	T.Markers = {}

	





	T.StepConnection = RunService.Stepped:Connect(function(_,dt)
		local CameraCF = workspace.CurrentCamera.CFrame

		

		T.Mesh.Position = CameraCF.Position + CameraCF.LookVector*5
		local MarkersToRemove = {}
		
		
		
		for i,Marker in T.Markers do


			

			if T.MaxLength ~= 0 then
				if Marker.TotalLength >= T.MaxLength then -- if length is greater than max length
					
					MarkersToRemove[i] = Marker -- add marker to the removal buffer
					continue -- end this iteration early because marker does not exist anymore
		
				end

			end



			if T.FaceCamera then -- if trail has face camera enabled

				local offset -- get offset


				if i == #T.Markers then
					offset = (Marker.Position - CameraCF.Position).Unit:Cross(Marker.Direction).Unit * T.Width -- (1-math.clamp(Marker.Age/T.MaxAge,0,1)) -- gives 1 if age is 0 and 0 if age is at MaxAge
				else
					offset = (Marker.Position - CameraCF.Position).Unit:Cross((Marker.Direction+T.Markers[i+1].Direction)/2).Unit * T.Width --* (1-math.clamp(Marker.Age/T.MaxAge,0,1))
				end
				
				
				T.EditableMesh:SetPosition(Marker.Associated_Vertices[1],T.Mesh.CFrame:PointToObjectSpace((Marker.Position+offset)/T.Mesh.Size))
				T.EditableMesh:SetPosition(Marker.Associated_Vertices[2],T.Mesh.CFrame:PointToObjectSpace((Marker.Position-offset)/T.Mesh.Size))

			end



		end
		
		
		for _,v in MarkersToRemove do -- remove all markers inside the removal buffer
			T:RemoveMarker(v)
		end
		
	end)


	return T
end







function Trail:AddMarker(point:Vector3)
	local Marker = {}
	

	local Mesh = self.EditableMesh
	local OriginalMesh = self.Mesh
	local offset = Vector3.yAxis*self.Width


	Marker.OriginalPosition = point
	Marker.Position = point





	Marker.Length = 0
	Marker.TotalLength = 0
	
	Marker.StoredAngle = 0


	Marker.Associated_Vertices = {-- add two vertices for each marker
		Mesh:AddVertex(OriginalMesh.CFrame:PointToObjectSpace(point+offset)),
		Mesh:AddVertex(OriginalMesh.CFrame:PointToObjectSpace(point-offset))
	} 




	for i,v in Marker.Associated_Vertices do
		Mesh:SetVertexColorAlpha(v,1)
	end
	
	
	
	

	if #self.Markers ~= 0 then
		
		local LastMarker = self.Markers[#self.Markers]
		
		local DistanceVector = (LastMarker.Position - Marker.Position)

		Marker.Direction = DistanceVector.Unit
		LastMarker.Length = DistanceVector.Magnitude
		
		for _,M in self.Markers do
			M.TotalLength += DistanceVector.Magnitude
		end
		
		

		Marker.Associated_Triangles = { -- triangles are saved into table now
			Mesh:AddTriangle(
				LastMarker.Associated_Vertices[1],
				Marker.Associated_Vertices[2],
				Marker.Associated_Vertices[1]
			),
			
			Mesh:AddTriangle(
				LastMarker.Associated_Vertices[1],
				LastMarker.Associated_Vertices[2],
				Marker.Associated_Vertices[2]
			),
			
			
		}





	else
		Marker.Direction = Vector3.xAxis
	end

	if #self.Markers == 1 then
		self.Markers[1].Direction = Marker.Direction
	end



	table.insert(self.Markers,Marker)

	return Marker

end

function Trail:MoveMarker(Marker,Position)
	Marker.Position = Position
	local MarkerIndex = table.find(self.Markers,Marker)

	if MarkerIndex ~= 1 then
		local LastMarker = self.Markers[MarkerIndex-1]
		local DistanceVector = (LastMarker.Position - Marker.Position)
		Marker.Direction = DistanceVector.Unit
		
		
		for i,M in self.Markers do
			if i ~= MarkerIndex then
				M.TotalLength -= LastMarker.Length
				M.TotalLength += DistanceVector.Magnitude 
			end
		end
		
		
		
		LastMarker.Length = DistanceVector.Magnitude
	end
	
	if MarkerIndex ~= #self.Markers then
		local NextMarker = self.Markers[MarkerIndex+1]
		local DistanceVector = (Marker.Position - NextMarker.Position)
		NextMarker.Direction = DistanceVector.Unit
		Marker.TotalLength += DistanceVector.Magnitude - Marker.Length
		Marker.Length = DistanceVector.Magnitude
		
	end
	
	
	
end


function Trail:RemoveMarker(Marker) -- Remove Marker Function
	local Mesh:EditableMesh = self.EditableMesh

	local MarkerIndex = table.find(self.Markers,Marker)



	if MarkerIndex ~= #self.Markers then--the last marker does not have any triangles associated with it

		for _,TriangleId in self.Markers[MarkerIndex+1].Associated_Triangles do -- loop through every triangle connected to our two vertices
			Mesh:RemoveTriangle(TriangleId) -- remove them
		end

	end

	for _,VertexId in Marker.Associated_Vertices do -- loop through our two associated vertices
		Mesh:RemoveVertex(VertexId) -- remove them
	end



	table.clear(Marker) -- clear the marker table, may be unnecessary, if it causes issues you should remove this line

	table.remove(self.Markers,MarkerIndex) -- find the index of the marker in the markers table, and then remove it



end

function Trail:Destroy()

	self.StepConnection:Disconnect()

	while self.Markers[1] do -- cant iterate over table with loop because the table is being shifted as markers are removed
		self:RemoveMarker(self.Markers[1])
	end

	self.Mesh:Destroy()
	table.clear(self)
end



return Trail
1 Like

For anyone looking to have simple trails that last longer than 20 seconds, I encourage you use this module. Very helpful and impressive module. It was made with performance in mind, so if used properly, it should work just fine.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.