let’s bring the test to a unified form of solution…
it has to be a function with at least two parameters – the spline and the fixed distance between points on the spline.
The function has to return all found points in the world space. The function can have any extra optional arguments to make calculation more accurate.
fn scatterPointsOnSpline sp dist tolerance:0.001 =
(
-- ...
points
)
here is my solution:
fn scatterPointsOnSpline sp dist tolerance:0.001 =
(
len = curveLength sp
t = 0.0
d = 0.0
current = interpCurve3D sp 1 t pathParam:off
points = #(current)
while t < 1.0 do
(
t += (dist - d)/len
t = amin t 1.0
p = interpCurve3D sp 1 t pathParam:off
d = distance current p
if d >= dist - tolerance do
(
current = p
append points current
d = 0.0
)
)
points
)
/************** TEST **************/
(
delete helpers
t0 = timestamp()
h0 = heapfree
points = scatterPointsOnSpline $ 33.0 tolerance:0.001
format "time:% heap:%" (timestamp() - t0) (h0 - heapfree)
dd = for k=1 to points.count collect
(
point pos:points[k] wirecolor:orange
if k > 1 then distance points[k-1] points[k] else dontcollect
)
format " >> %\n" #(dd.count, amin dd, amax dd)
)
PS. spline index set to 1 (of course we can pass the index to the function)
The “one step closer” solution!
Really clever. Fast and few memory. As always, you win.
I just see two problems… well, two and a half.
- For some combinations of length-dist-tolerance it freeze as Serejah’s said. It can be solved with double values to avoid freezing, but as it’s again a problem of mantissa, the upper maximum value won’t reach the tolerance. But it’s good enough in most cases (probably in all real cases).
- The “half” problem is, due to the first one, you can get a different number of points (+1 or 2) if you change the tolerance to more accurate once reached the mantissa limit. Again, I don’t think it should be a problem for real cases
- The second problem, with worse solution, is that if the spline is scaled to bigger, the given result is not the good solution, as for 3dsMax the spline length remains the same (and scaling splines is not perfect). If it’s scaled to lower it works because the script can’t find more points (takes more time but gives the good result).
Yes, that’s the solution to avoid freezing
fn scatterPointsOnSpline sp dist tolerance:0.001 =
(
tolerance = tolerance as double
len = (curveLength sp) as double
t = 0.0 as double
d = 0.0 as double
current = interpCurve3D sp 1 t pathParam:off
points = #(current)
while t < 1.0 do
(
t += (dist - d)/len
t = amin t 1.0
p = interpCurve3D sp 1 t pathParam:off
d = (distance current p) as double
if d >= dist - tolerance do
(
current = p
append points current
d = 0.0
)
)
points
)
/************** TEST **************/
(
delete helpers
t0 = timestamp()
h0 = heapfree
points = scatterPointsOnSpline $ 33 tolerance:0.000001
format "time:% heap:%" (timestamp() - t0) (h0 - heapfree)
dd = for k=1 to points.count collect
(
point pos:points[k] wirecolor:orange
if k > 1 then distance points[k-1] points[k] else dontcollect
)
format " >> %\n" #(dd.count, amin dd, amax dd)
)
It is fast.
Guess t should be double in order not to freeze max on large splines.
Here’s a mixing of the DenisT’s “one step closer” and my bisection way.
Seems not to freeze and supports scaled splines:
fn scatterPointsOnSpline sp dist tolerance:0.001 =
(
tolerance = tolerance as double
len = (curveLength sp) as double
sc = sp.scale[1]
x = len / (numSegments sp 1) / dist * 10 / sc;
step0 = (1.0/ (numSegments sp 1) / x) as double
t = 0.0 as double
d = 0.0 as double
current = interpCurve3D sp 1 t pathParam:on
points = #(current)
step = step0 as double
counter = 0
while t < 1.0 do
(
counter += 1
t += step
t = amin t 1.0
p = interpCurve3D sp 1 t pathParam:on
d = (distance current p) as double
error = (dist - d) as double
if abs error <= tolerance then
(
current = p
append points current
step = step0
counter = 0
)
else
(
if error < 0 do
(
t -= step
step *= 0.5 as double
)
if counter > 1000 do
(
current = p
append points current
step = step0
counter = 0
)
)
)
points
)
/************** TEST **************/
(
delete helpers
t0 = timestamp()
h0 = heapfree
points = scatterPointsOnSpline $ 33 tolerance:0.000001
format "time:% heap:%" (timestamp() - t0) (h0 - heapfree)
dd = for k=1 to points.count collect
(
point pos:points[k] wirecolor:orange
if k > 1 then distance points[k-1] points[k] else dontcollect
)
format " >> %\n" #(dd.count, amin dd, amax dd)
)
if scale is uniform it’s probably enough to make it work as expected
local isUniformScale = sp.scale[1] == sp.scale[2] and sp.scale[2] == sp.scale[3]
local factor = if isUniformScale then abs sp.scale[1] else 1
local len = curveLength sp * factor
and the test
for i=1 to 4 do
(
c = circle radius:20
convertToSplineShape c
c.scale = [0.5 * i, 0.5 * i, 0.5 * i]
gc();t1=timestamp();hf = heapfree
pts = scatterPointsOnSpline c 3.0
for p in pts do point pos:p centermarker:on cross:off wirecolor:yellow
format "count %\n" pts.count
format "Time: %sec. Mem: %\n\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)
)
If I remember well (I’m not in my desktop), take the second example spline and run the script with dist=300 & tolerance=0.01 (or lesser). Or the first example spline with dist=33 & tolerance=0.000001. Other combinations too.
With double precision it seems to work.
I can’t test it until tomorrow, but have you tried scaling Kostadin’s spline? Functions based on length doesn’t work fine generally when there’s a scale (check result distances).
this version doesn’t hang for me… at least i couldn’t find settings to hang:
fn scatterPointsOnSpline sp dist tolerance:0.001 =
(
dist = dist as double
len = (curveLength sp) as double
tolerance = amax tolerance (len * 0.000001d0)
t = 0.0
d = 0.0
current = interpCurve3D sp 1 t pathParam:off
points = #(current)
while t < 1.0 and not esc_ do
(
t += (dist - d)/len
t = amin t 1.0
p = interpCurve3D sp 1 t pathParam:off
d = distance current p
if d >= dist - tolerance do
(
current = p
append points current
d = 0.0
)
)
points
)
/************** TEST **************/
(
delete helpers
c = random red green
t0 = timestamp()
h0 = heapfree
points = scatterPointsOnSpline $ 30.0 tolerance:0.0001
format "time:% heap:%" (timestamp() - t0) (h0 - heapfree)
dd = for k=1 to points.count collect
(
--point pos:points[k] wirecolor:c
if k > 1 then distance points[k-1] points[k] else dontcollect
)
--format " >> %\n" dd
format " >> %\n" #(dd.count, amin dd, amax dd)
)
we have to understand that most of MAX SDK functions are made in Float precision.
so there is no sense to ask tolerance to be smaller than ‘allowable error’
because of that i’ve added tolerance correction in case of very long splines. which limits tolerance by minimum as 0.000001 of spline length
now couple words about using fixed step in the algorithm.
i wanted to avoid it because there is no absolutely accurate way to define the step. Anyway it might be a situation that making a step we pass a ‘on distance’ point or might be more than one point inside a step.
in the second case we can’t use bi-search because you can find the second point instead of the first.
as i told many times i always find something interesting and useful in every thread on this forum.
here is very interesting founding… the maxscript doesn’t provide current length for scaled splineshapes.
also it gives the right 3d points for scaled splineshapes in methods: interpCurve3D, interpBezier3D, etc.
but only for pathParam
thanks for this founding! I’ve added to my MXS extension several methods and options to take shape node scale into account. Also I’ve added snapshotAsSplineShape method