[Closed] Mini-challenge #7: Divide bezier spline
ok, Only delete…
the problem I see,
is that if you delete a knot, you are changing the length already…
and all the knots added are useless, if you were looking for some accuracy.
somehow one has to figure the future new shape.
That means, not using MAX mxs functions
…
delete the knots:
(
fn divideEven o s segs steps: =
(
convertToSplineShape o
if steps==unsupplied do steps = curveLength o s * 5 as integer
local prefknot = 0
for n = 1. to segs - 1 do
(
pa = lengthToPathParam o s (n/segs) steps:steps
prevseg = (FindPathSegAndParam o s pa)[1] - 1
--prevseg = (ceil (pa / (1./(numSegments o s)))) - 1
pa = pa /(1.0/(numSegments o s)) - prevseg
newvert = refineSegment o s (prevseg + 1) pa
if newvert != (n+1) do
for k = n+1 to newvert-1 do deleteKnot o s (n+1)
resetLengthInterp o
)
updateShape o
)
divideEven $ 1 22 steps:20000
)
If sum of lengths of two surrounding segment’s are equal or close enough to (spline length / (divisions +1)),
or just, if previous segment is smaller then (spline length / (divisions +1)).
Or another way is to store indexes of new knots in an array, and then delete the rest except the first and the last knot.
(
global rollDivideSpline
try(destroyDialog rollDivideSpline)catch("")
local index = 1
local newKnots
fn extendLine p0 p1 k =((normalize (p1-p0))*(distance p1 p0) * k +p0)
rollout rollDivideSpline "Divide Spline" width:250 height:190
(
group "Make Spline "
(
spinner spnFirstDiv "Segments: " range:[1,30,3] type:#integer fieldwidth:27 align:#left across:2 offset:[0,4]
button btnMakeSpline "Make Spline" width:95 height:20 align:#left offset:[0,2]
)
group "Subdivideivide Spline Evenly "
(
spinner spnSegs "Segments: " range:[1,50,7] type:#integer fieldwidth:27 align:#left across:2 offset:[0,4]
button btnDivideSpline "Subdivide Spline" width:95 height:20 align:#left offset:[0,2]
)
group "Remove Old Knots "
(
button btnRemoveKnots1 "Remove Knots 1" width:95 height:20 align:#center offset:[0,2]
button btnRemoveKnots2 "Remove Knots 2" width:95 height:20 align:#center offset:[0,2]
)
fn makeSpline =
(
global sp = splineShape name:"sp" wirecolor:orange
sp.vertexticks = on
addnewSpline sp
addKnot sp 1 #corner #curve [0,0,0]
addKnot sp 1 #corner #curve [100,0,0]
setknottype sp 1 1 #bezierCorner
setoutvec sp 1 1 [0,40,0]
updateShape sp
)
/* the subdivide function */ --by denisT
fn subdivideSplineEvenly sp index segments:1 steps:100 = if iskindof sp SplineShape do
(
converttosplineshape sp
step = 1./segments
newKnots = #()
for k=1 to (segments-1) do
(
param = k*step
p = lengthToPathParam sp index param steps:steps
segs = numSegments sp index
seg = floor(p*segs) + 1
ps = p*segs - seg + 1
k = refineSegment sp index seg ps
append newKnots (seg+1)
resetLengthInterp()
)
updateshape sp
)
fn removeKnot sp index k steps:10000=
(
local segs = numSegments sp index
local segLengths = getSegLengths sp index numArcSteps:steps
segFract = segLengths[segs + (k-1)] / (segLengths[segs + (k-1)] + segLengths[segs + k])
p1 = getKnotPoint sp index (k-1)
p2 = getKnotPoint sp index (k+1)
h1 = getOutVec sp index (k-1)
h2 = getInVec sp index (k+1)
setOutVec sp index (k - 1) (extendLine p1 h1 (1/segFract))
setInVec sp index (k + 1) (extendLine p2 h2 (1/(1-segFract)))
deleteKnot sp index k
)
/* function that removes all knots exept first and last and the knots which indexes are stored in newKnots array */
fn removeOldKnots1 sp index segments:spnSegs.value steps:10000 = if (numSegments sp index) > segments do
(
for k = (numSegments sp index) to 2 by -1 do if findItem newKnots k == 0 then removeKnot sp index k steps:10000
updateshape sp
)
/* function that removes knot if previous segment is smaller then (spline length / (divisions +1)) */
fn removeOldKnots2 sp index segments:spnSegs.value steps:10000 = if (numSegments sp index) > segments do
(
refSegLength = (curveLength sp index) / segments
for k = (numSegments sp index) to 2 by -1 do
(
segs = numSegments sp index
segLengths = getSegLengths sp index numArcSteps:steps
if not close_enough segLengths[(numSegments sp index) + k] refSegLength 1000000 then
(
removeKnot sp index k
resetLengthInterp()
)
)
updateshape sp
)
on btnMakeSpline pressed do
(
makeSpline()
if spnFirstDiv.value > 1 then subdivideSplineEvenly sp index segments:spnFirstDiv.value steps:10000
)
on btnDivideSpline pressed do if IsValidNode sp and sp != undefined then subdivideSplineEvenly sp index segments:spnSegs.value steps:10000
on btnRemoveKnots1 pressed do removeOldKnots1 sp index steps:10000
on btnRemoveKnots2 pressed do removeOldKnots2 sp index steps:10000
)
createDialog rollDivideSpline pos:[400,400]
)
I’ve stumbled across this interesting thread today and it seems like it is an unfinished business type of deal.
My theory is this for the second imperfection in Denis’ snippet…
Once the subdivision is complete of “X” number of knots the user then needs to take the “complete length of the spline” and then divide it by the number of segments getting “segment length” if correct that segment length should be the same for every segment in the spline otherwise the knots are not evenly spaced out. So to test this and eliminate the invalid knots we need to loop through each segment and compare it to the “segment length” and if it is false then delete the second knot that makes up the segment and then test that segment length again and you continue testing it until it equals the right length, then move on to the next segment. The user will eventually loop through the entire spline. The problem here is that every time the user deletes a knot the segment count changes, so to fix this problem you will have to loop through the segments in reverse order.
This should leave the user with a spline length which is equal to (the number of desired number of segments * segment 1’s length).