Notifications
Clear all

[Closed] Placing points along spline at even distance between them

I am sure that the title of the topic is not very accurate but this is my fault.
What I need is to place points on a spline and the points to be at even distance between them. I do not want the spline to be divided.
For example the first point to be at the same position of the first knot of the spline. The second point to lay on the spline but the straight distance to the first point to be 33 units. The third point to lay on the spline and the straight distance to the second point to be 33 units and so on.

On the image below you can see what I mean:
https://drive.google.com/open?id=1ocbdZMAdPJlaua3rlup65T19fdgujCBn

The distance between the knots of the spline is 33 units(shown with green markers).

The code that I use to place the yellow points is:

curSpline = selection[1]
	dist = 33.0
	curvLength = curveLength curSpline ns
	numSpl = numSplines curSpline
	for ns = 1 to numSpl do
	(
		cnt = (floor (curvLength / dist)) as integer
		for i = 0 to cnt  do
		(			
			point pos:(lengthInterp curSpline ns (i*dist/curvLength)) size:10
		)
	)

And the result can be seen in the same image with yellow markers. As you see the distance between the points is not 33 units.

Is there are any easy way to place the points so the distance between them to be 33 units and all points to lays on the spline? Mathematical way. Or I have to use some dirty hacks to find the proper position of the points? What comes to my mind as a dirty hack is this:

(	
	curSpline = selection[1]
	
	dist = 33.0
	step = 1.0 / 1000000
	stepPointsArr = for i = 0.0 to 1.0 by step collect (in coordsys world (interpCurve3D curSpline 1 i pathParam:false))
		
	pointsPosArr = #()	
	stopLoop = false
	firstPosIdx = 1
	while stopLoop == false do
	(		
		for j = firstPosIdx+1 to stepPointsArr.count do
		(
			if j != stepPointsArr.count then
			(
				curDist = distance stepPointsArr[firstPosIdx] stepPointsArr[j]
				if abs (dist - curDist) < 0.001 do
				(
					append pointsPosArr stepPointsArr[j]
					firstPosIdx = j
				)
			)
			else
			(
				stopLoop = true
			)
		)
	)
	
	if pointsPosArr.count != 0 do
	(
		for p in pointsPosArr do
		(
			point pos:p wirecolor:yellow
		)
	)
)

If you use the spline in this max(2014) file: https://drive.google.com/open?id=1FqZCp8VLfs8teEY1GHccltLpdR5PM1MR

the code above will create points almost at the same position where the knots of the splines are.

37 Replies

The only “Math” solution is intersecting the spline with a circle with the given radius and the last point as the center.
I’ve never used the shape intersect option in 3dsMax. You can try and see if it’s ok in performance.
I will think a little more about optimization… or by searching the math result.

Hi, aaandres.
Using a circle was my first idea, but finding the intersection between a circle and a “bezier” spline… I have a dirty solution for this task too.
I can test any new way of solving the problem on sunday evening. I have to go to the bed now. Tomorrow I am at work(24h shift).

This is definitely not a way to go if you want pure math solution, but anyway.

(

delete objects
delete helpers
	
shp = Ngon radius:50 cornerRadius:0 nsides:33 circular:on scribe:1
shp.steps = 50
noisemod = Noisemodifier strength:[30,30,0] scale:10.0
addModifier shp noisemod
convertToSplineShape shp

len = curveLength shp 1

step = 20.0 as double

h = for i=1 to 10 collect point centermarker:on cross:off wirecolor:yellow

param = step / len
format "PARAM: %\n" param

params = #()
gc();t1=timestamp();hf = heapfree

current = 0.0 as double
for i=1 to h.count - 1 do
(	
	
	p0 = lengthInterp shp 1 current steps:4444
	p1 = lengthInterp shp 1 (current + param) steps:4444

	while not keyboard.escPressed do
	(
		d = distance p0 p1
		
		if (abs (d - step)) < 0.01 do
		(
			format "%: param:%\n" i current			
			append params current
			exit
		)
		
		if d < step do
		(
			current = current + param * (mod (step / d) 1.0)
			p1 = lengthInterp shp 1 current steps:44444
		)
		
		if d > step do
		(
			current = current - param * (1.0 - step / d)
			p1 = lengthInterp shp 1 current steps:44444
		)
				
	)

)

format "Time: %sec. Mem: %\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)

h[1].pos = lengthInterp shp 1 0 steps:44444
for i=1 to params.count do
(
	h[i+1].pos = lengthInterp shp 1 params[i] steps:44444
)

)

Wonder how could we do it similar to this approach
rDycwICCt0

Pure Math solution is a sixth degree equation… thus we have to go through numerical solution.
The quickest should be Newton-Raphson, but we’ll have first to calculate the spline equation and then it’s derivative.
So, I think the best solution is just find the segment where the next point lies and then search it by bisection. We can use the more accurate function ‘interpBezier3D’ and, of course, use pathParam instead of lenghParam (faster and more accurate).
For your spline, Kostadin, I get 1 msec face to the 4 sec of the original solution with one more decimal precision. Not bad!

(	
	t1 = timeStamp()
	curSpline = selection[1]
	
	fixedDist = 33.0
	accuracy = 0.0001
	
	-- PathParam of each knot
	numK = numknots curSpline
	if isClosed curSpline 1 do numK +=1
	invNumK = 1.0 / (numK - 1)
	knotsParam = for i = 1 to numK collect invNumK * (i-1)
	knotsArray = for i = 1 to numK collect (in coordsys world (interpCurve3D curSpline 1 knotsParam[i] pathParam:true))
		
	pointsPosArr = #(knotsArray[1])	
	stopLoop = false
	currentKnotIdx = 2
		
	while stopLoop == false do
	(
		-- Find segment where the next point lies
		lastPoint = pointsPosArr[pointsPosArr.count]
		morePoints = false
		for i = currentKnotIdx to numK while not morePoints do
		(
			dist = distance lastPoint  knotsArray[i]
			currentKnotIdx = i

			if dist >= fixedDist do
			(
				morePoints = true
			)
		)
		
		if morePoints then
		(
			-- Let's find the point by bisection in the segment found (max 1000 iterations)
			pt
			error = 1e6
			a = 0.0
			b = 1.0
			iterations = 0
			while error > accuracy and iterations < 1000 do
			(
				iterations += 1
				param = (a+b) * 0.5
				pt = interpBezier3D curSpline 1 (currentKnotIdx - 1) param pathParam:true
				dist = distance lastPoint pt
				if dist > fixedDist then b = param else a = param
				error = abs(dist - fixedDist)
			)
			append pointsPosArr pt
		)
		else
		(
			stopLoop = true
		)
	)
	
	t2 = timeStamp()
	format "Time= %ms\n" (t2-t1)
	
	for p in pointsPosArr do
	(
		point pos:p wirecolor:yellow
	)
	for i = 2 to pointsPosArr.count do
	(
		format "dist[%:%]= %\n" (i-1) i (distance pointsPosArr[i] pointsPosArr[i-1])
	)
)

That’s bisection!

Note: I don’t really understand well your method, but my cursor icon gets mad when the script finish. And if I save the scene and reload it, the cursor’s still mad. I’m with 3dsMax 2016. !!??

Upps! My solution is only valid for splines with nearly-straight segments or distance smaller than knots distances. If not, we have to subdivide segments.
I’ll write the code later…

It’s an INTERESTING CHALLENGE !

as soon as i have a time i will try to give my solution.

so, the challenge’s rule is the same: DO FAST AND SAVE MEMORY

Here’s the solution with subSegments for splines with very closed segments. I don’t think you’ll need more than 10 subsegments for really extrange splines.
This solution is coded with a struct, so more memory and time than with arrays, but more clear. If you want the array solution, I have it.
I get 2ms for your spline (memory 28680L) with 5 subsegments (they could be just 1 for this case) and an accuracy of 0.0001.

(	
	gc()
	t1 = timeStamp()
	m1 = heapfree
	
	-- Catched functions
	local interpCurve = interpCurve3D
	local interpBezier = interpBezier3D

	-- Input Data
	curSpline = selection[1]
	fixedDist = 33
	numSubSeg = 5	--	Caution!!! Minimum 1
	accuracy = 0.0001
	
	-- Basic spline data for calculation
	numK0 = numknots curSpline 1
	closed = isClosed curSpline 1
	if closed do numK0 +=1
	numK = (numK0 - 1) * numSubSeg + 1
	numSeg = numSegments curSpline 1
	invNumSeg = 1.0 / numSeg
	invNumSubSeg = 1.0 / numSubSeg
	relStep = invNumSubSeg * invNumSeg
	
	---------------------------------------
	-- Data for each knot
	struct Knot (coord, segment, relParam)
	local knots = #()
	local coord
	local segment
	local absParam
	local relParam
		
	ptsCounter = 0
	for i = 1 to numK0 - 1 do
	(
		ptsCounter += 1

		coord = getKnotPoint curSpline 1 i
		segment = i - 1
		absParam = invNumSeg * (i-1)
		relParam = 0.0
		knots[ptsCounter] = Knot coord segment relParam
		
		for j = 1 to numSubSeg - 1 do
		(
			ptsCounter += 1

			segment = i
			absParam += relStep
			relParam += invNumSubSeg
			coord = (in coordsys world (interpCurve curSpline 1 absParam pathParam:true))
			knots[ptsCounter] = Knot coord segment relParam
		)
	)
	ptsCounter += 1

	if closed then
	(
		coord = getKnotPoint curSpline 1 1
	)
	else
	(
		coord = getKnotPoint curSpline 1 numK0
	)
	knots[ptsCounter] = Knot coord numSeg 1.0
	--------------------------------------
	
	-- Calculation
	pointsPosArr = #(knots[1].coord)	
	stopLoop = false
	currentKnotIdx = 2
		
	while stopLoop == false do
	(
		-- Get index of knot farthest than fixedDist from previous one
		lastPoint = pointsPosArr[pointsPosArr.count]
		morePoints = false
		for i = currentKnotIdx to numK while not morePoints do
		(
			dist = distance lastPoint  knots[i].coord
			currentKnotIdx = i

			if dist >= fixedDist do
			(
				morePoints = true
			)
		)
		
		-- Calculation of the point by bisection (max 1000 iterations)
		if morePoints then
		(
			segmentID = knots[currentKnotIdx].segment
			
			pt
			error = 1e6
			a = knots[currentKnotIdx - 1].relParam
			b = knots[currentKnotIdx].relParam
			if b == 0.0 do b = 1.0
			iterations = 0
			while error > accuracy and iterations < 1000 do
			(
				iterations += 1
				param = (a+b) * 0.5
				pt = interpBezier curSpline 1 segmentID param pathParam:true
				dist = distance lastPoint pt
				if dist > fixedDist then b = param else a = param
				error = abs(dist - fixedDist)
			)
			append pointsPosArr pt
		)
		else
		(
			stopLoop = true
		)
	)
	
	t2 = timeStamp()
	m2 = heapfree
	format "Time: %ms; Mem: %\n" (t2-t1) (m1-m2)
	
	-- Print and Plot results
	for p in pointsPosArr do
	(
		point pos:p wirecolor:yellow
	)
	for i = 2 to pointsPosArr.count do
	(
		format "dist[%:%]= %\n" (i-1) i (distance pointsPosArr[i] pointsPosArr[i-1])
	)
)

Hope it helps.

As a picture is worth a thousand words, here you have an image of the kind of spline you can have problems if you don’t use subdivisions:


Page 1 / 4