Notifications
Clear all

[Closed] Mini-Challenge #4

here is a my version with constant velocity.
but there must be a better way to distribute points along a parametric bezier spline without an incremental loop.

(
	max create mode
	
	local numSegs = 10
	local step = 0.001
	local up = [0,0,1]
	local start = point name:"start" pos:[0,0,0] size:10 box:true cross:false wirecolor:green
	local end = point name:"end" pos:[100,0,0] size:10 box:true cross:false wirecolor:green
	local outStart = point name:"out-start" pos:[100.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
	local inEnd = point name:"in-end" pos:[200.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
	local ctrls = #(start, outStart, inEnd, end)
	
	setTransformLockFlags #(outStart, inEnd) #{2..9}
	outStart.parent = start
	inEnd.parent = end
	
	local ghosts = for i = 1 to numSegs collect
		point name:("ghost" + i as string) axistripod:on box:on cross:off size:7 wirecolor:red
	setTransformLockFlags ghosts #all
	
	fn getBezierInterpolation p t =
	(
		(1. - t) ^ 3 * p[1] + 3 * (1. - t) ^ 2 * t * p[2] + 3 * (1. - t) * t ^ 2 * p[3] + t ^ 3 * p[4]
	)
	
	fn bezierSplineLength p start end step =
	(
		local lastPt = getBezierInterpolation p start
		local result = 0
		
		for t = start + step to end by step do (
			local newPt = getBezierInterpolation p t
			result += distance newPt lastPt
			lastPt = newPt
		)
		
		result
	)
	
	when transform ctrls changes handleAt:#redrawViews do (
	if not mouse.buttonStates[1] then (
	with redraw off (
	with animate off (
		local pts = for o in ctrls collect o.pos
		local d1 = distance pts[1] pts[2]
		local d2 = distance pts[3] pts[4]
		
		local s = (bezierSplineLength pts 0.0 1.0 step) / numSegs
		local last = 0.0
		
		for i = 1. to numSegs do (
			local t = last + step
			
			while ((bezierSplineLength pts last t step) < s) do
				t += step
			t = last = amin t 1.0
			
			local p1 = getBezierInterpolation pts t
			local p2 = getBezierInterpolation pts (t + step)
			
			local x, y, z
			x = normalize (p2 - p1)
			y = normalize (cross up x)
			z = normalize (cross x y)
			
			local m = matrix3 x y z p1
			local startXRot = (quatToEuler start.transform.rotationpart).x
			local endXRot = (quatToEuler end.transform.rotationpart).x
			local a = startXRot * (1. - t) + endXRot * t
			preRotateX m a
			
			ghosts[i].transform = m
		)
	))))
	
	move start [0,0,0]
	forceCompleteRedraw()
)

Now I need to look into what Denis said about the local rotation…

let’s forget about constant velocity for a while…

i made some modification in TzMtN’s code to add a little more Math in it


 (
 	max create mode
 	delete objects 
 	
 	local numSegs = 10
 	local start = point name:"start" pos:[0,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
 	local end = point name:"end" pos:[100,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
 	local outStart = point name:"out-start" pos:[100.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
 	local inEnd = point name:"in-end" pos:[200.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
 	local ctrls = #(start, outStart, inEnd, end)
 	
 	setTransformLockFlags #(outStart, inEnd) #{2..9}
 	outStart.parent = start
 	inEnd.parent = end
 	
 	local ghosts = for i = 1 to numSegs collect
 		point name:("ghost" + i as string) axistripod:on box:on cross:off size:10 wirecolor:red
 	setTransformLockFlags ghosts #all
 	
 	fn getBezierInterpolation p t =
 	(
 		(1. - t)^3 * p[1] + 3 * (1. - t)^2 * t * p[2] + 3 * (1. - t) * t^2 * p[3] + t^3 * p[4]
 	)
 	fn getBezierDerivative p t =
 	(
 		-3 * (1. - t)^2 * p[1] + (3 * (1. - t)^2 - 6 * (1. - t)*t) * p[2] + (6 * (1. - t)*t - 3 * t^2) * p[3] + 3 * t^2 * p[4]
 	)
 
 	fn updateGhosts = 
 	(
 		local pts = for o in ctrls collect o.pos
 		
 		for i = 1. to numSegs do 
 		(
 			local t = i / (numSegs + 1)
 			local p = getBezierInterpolation pts t
 			local q = slerp start.transform.rotation end.transform.rotation t 
 			
 			local x, y, z
 			x = normalize (getBezierDerivative pts t) -- normalized tangent
 			z = (q as matrix3).row3
 			y = normalize (cross z x)
 			z = normalize (cross x y)
 			
 			local m = matrix3 x y z p
 			with animate off ghosts[i].transform = m
 		)
 	)
 	updateGhosts()
 	when transform ctrls changes do updateGhosts()
 )
 

I use TzMtN’s code because it’s very similar to my (TzMtN, do you mind?).

  1. I added mathematically correct method of the tangent calculation. It’s the first derivative of the parametric equation of the Cubic Bézier Curve.
    2) I use slerp function to interpolate between start and end rotations.
    3) Final matrix is built on tangent and Z of interpolated quaternion (as matrix)

there is flipping in the local X axis
slerp,matrix quats, the usual problem, especially for rigging not limited to 180º
the best is to manipulate the value directly somehow…

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

it’s a known problem. i give a time to anyone to find a solution…

ps. you are talking about Z – flip, aren’t you?

x axis flip, would be. well, the others too, but are less evident.
I think it will always happen with slerp or any math based on world coordinates

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

x axis is a tangent. does it flip? give me start and end transforms please?

mm, reset/execute the code:
select the ‘end’ object and rotate it’s local X axis:
more than 240º == flip
less than -120 == flip

in the original example with path constraint, you create rotation list to blend the rotation x axis.
in your code, you’re using slerp. that must be… derivatives can’t be the problem…

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

that’s expected. X-axis stays right. Y and Z change. so we have to find a way to fix it… or find another way how to calculate X-rotation

Could you give us a hint, this is really interesting to me but it looks like the discussion is dead

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

well, there is a hint.
I’m using slerp function to get quaternion interpolation. It gives me spherical linear interpolation. But do I really need it if later I use only one component of the resulting quaternion? Could we do anything simpler?

Because the discussion is dying I’m leaving the correct method of constant velocity calculation open.

So here is a version using a point3 slerp function, but it still obviously flips after 180 deg.
I think we must work with the angles themselves somehow, but how do we do that independent of the rotation controller?

(
 	max create mode
 	delete objects 
 	
 	local numSegs = 10
 	local start = point name:"start" pos:[0,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
 	local end = point name:"end" pos:[100,0,0] axistripod:on box:on cross:off size:15 wirecolor:green
 	local outStart = point name:"out-start" pos:[100.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
 	local inEnd = point name:"in-end" pos:[200.0 / 3,0,0] size:2.5 box:true cross:false wirecolor:green
 	local ctrls = #(start, outStart, inEnd, end)
 	
 	setTransformLockFlags #(outStart, inEnd) #{2..9}
 	outStart.parent = start
 	inEnd.parent = end
 	
 	local ghosts = for i = 1 to numSegs collect
 		point name:("ghost" + i as string) axistripod:on box:on cross:off size:10 wirecolor:red
 	setTransformLockFlags ghosts #all
 	
 	fn getBezierInterpolation p t =
 	(
 		(1. - t)^3 * p[1] + 3 * (1. - t)^2 * t * p[2] + 3 * (1. - t) * t^2 * p[3] + t^3 * p[4]
 	)
	
 	fn getBezierDerivative p t =
 	(
 		-3 * (1. - t)^2 * p[1] + (3 * (1. - t)^2 - 6 * (1. - t)*t) * p[2] + (6 * (1. - t)*t - 3 * t^2) * p[3] + 3 * t^2 * p[4]
 	)
	
	fn point3Slerp p t =
	(
		if distance p[1] p[2] < 0.0001 then
			p[1]
		else (
			local omega = acos (dot p[1] p[2])
			(sin ((1 - t) * omega)) / (sin omega) * p[1] + (sin (t * omega)) / (sin omega) * p[2]
		)
	)
 
 	fn updateGhosts = 
 	(
 		local pts = for o in ctrls collect o.pos
 		
 		for i = 1. to numSegs do 
 		(
 			local t = i / (numSegs + 1)
 			local p = getBezierInterpolation pts t
 			
 			local x, y, z
 			x = normalize (getBezierDerivative pts t)
 			z = point3Slerp #(start.dir, end.dir) t
 			y = normalize (cross z x)
 			z = normalize (cross x y)
 			
 			local m = matrix3 x y z p
 			with animate off ghosts[i].transform = m
 		)
 	)
 	
	updateGhosts()
 	when transform ctrls changes do 
		updateGhosts()
 )
 

Page 3 / 3