Help with accurate and precise distribution of models along a line(s)?

I’m trying to accurately distribute a set of models along a line (created by user input) while ensuring that the models are evenly spaced.

What I’m trying to achieve:

  • Distributing models evenly and percisely along line(s)
  • Accurate spacing, especially for complex lines with sharp angles or few segments, respective to the amount of ‘models’.
  • I want to avoid dead spots or uneven spacing between models, regardless of the number of models or the complexity of the line.

Current:

Core part of my server side script that I use to distribute the models:

local function distributeModelsAlongLine(models, linePoints)
	if #linePoints == 0 or #models == 0 then
		warn("models == 0, or linePoints == 0")
		return
	end
	local totalDistance = 0
	local segmentLengths = {}

	if #linePoints == 1 then
		local segmentLength = (linePoints[1] - linePoints[1]).Magnitude
		totalDistance = totalDistance + segmentLength
		table.insert(segmentLengths, segmentLength)
	else
		for i = 1, #linePoints - 1 do
			local segmentLength = (linePoints[i + 1] - linePoints[i]).Magnitude
			totalDistance = totalDistance + segmentLength
			table.insert(segmentLengths, segmentLength)
		end
	end


	local accumulatedDistance = 0
	local currentSegmentIndex = 1
	local currentSegmentStart = linePoints[currentSegmentIndex]
	local currentSegmentEnd = linePoints[currentSegmentIndex + 1]
	local currentSegmentLength = segmentLengths[currentSegmentIndex]

	for i, model in ipairs(models) do
		local proportionAlongLine = (i - 1) / (#models - 1)
		local targetDistance = proportionAlongLine * totalDistance

		while targetDistance > accumulatedDistance + currentSegmentLength do
			accumulatedDistance = accumulatedDistance + currentSegmentLength
			currentSegmentIndex = currentSegmentIndex + 1

			if currentSegmentIndex >= #linePoints then
				currentSegmentStart = linePoints[#linePoints - 1]
				currentSegmentEnd = linePoints[#linePoints]
				currentSegmentLength = (currentSegmentEnd - currentSegmentStart).Magnitude
				break
			end

			currentSegmentStart = linePoints[currentSegmentIndex]
			currentSegmentEnd = linePoints[currentSegmentIndex + 1]
			currentSegmentLength = segmentLengths[currentSegmentIndex]
		end

		local remainingDistance = targetDistance - accumulatedDistance
		local alpha = remainingDistance / currentSegmentLength
		local targetPosition = currentSegmentStart:Lerp(currentSegmentEnd, alpha)

		local movementID = tick()
		movementJobs[model] = movementID
		smoothMoveLine(model, targetPosition, movementID)
		createDebugPart(targetPosition, Color3.fromRGB(0, 0, 255))
	end
end

Issues I’m facing:

  1. Precision with sharp angles or complex lines: When working with sharp turns (e.g., triangle like shapes) or complex line shapes in general, the models are not distributed precisely along the path, especially when there are very few segments and or models.
  2. Spacing Issues: While distributing the models, I’ve noticed that sometimes the spacing is not perfect, especially for more complex lines. This becomes more noticeable when there are very few line points.

Specific Questions:

  • Is there a more efficient method in general?
  • How can I make sure that models are precisely placed even with sharp turns or complex paths? Right now, the segment based traversal seems to fail with sharp angles, actually just in general complex or not.
  • How do I avoid “dead spots” in the distribution of models, making sure the models are placed and the spacing is consistent, respective to the number of models or complexity of the line.

Build a shape data structure that has 3 types of line segments, one that’s an independent line segment with 2 new vertices, one being projected from an already existing vertex, which case it only needs to start from that vertex + stride distance between models and place the models from [2, n]. And the 3rd type of line that is starts from a vertex and ends at an already established vertex, which means it does the line segment from [2 , n-1].

Your game should have the logic to determine when you are building which of the line segments one at a time or constructing shapes.

Here’s an updated formula that I’ve thought about a bit. Let me know if it works or doesn’t work. Take note that this is just a general math formula and you may need to tweak around the logic to make it work

— How a segments table would look like
local segments = {
   [1] = {
      [“Start”] = workspace.Segment1Start
      [“End”] = workspace.Segment1End
   }
   — so on
}
function DistributeModels(models : Array)
   local spacing = 3
   for si,sinfo in ipairs(segments) do
      local seglen = (sinfo.End.Position - sinfo.Start.Position).Magnitude
      local maxmodels = math.floor(seglen/spacing) + 1
      for i = 0,maxmodels - 1 do
         local model = models[1]
         if model == nil then print('All models placed!') return end
         model:PivotTo(sinfo.Start.CFrame + (sinfo.Start.CFrame.LookVector * i * spacing))
         table.remove(models,1)
      end
   end