Notifications
Clear all

[Closed] Mini-Challenge #4

 elT

OK. I’ll do my best then.

I knew about the when constructor but never had any need to use it.
Can anyone hint me on how to make it work without the handleAt attribute?
Personally I would use an expression controller to do these things purely in math because that way the rig will be max script free which gives much faster results and it will still be pure math as well.
Here goes:

(
	max create mode
	
	local numSegs = 10
	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: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]
	)
	
	when transform ctrls changes handleAt:#redrawViews do (
	with redraw off (
	with animate off (
		local pts = for o in ctrls collect o.pos
		for i = 1 to numSegs do (
			local t = (1. * i / (numSegs + 1))
			local p = getBezierInterpolation pts t
			
			ghosts[i].pos = p
			
			local x, y, z
			if i == 1 then
				x = normalize (ghosts[i].pos - start.pos)
			else
				x = normalize (ghosts[i].pos - ghosts[i - 1].pos)
			z = normalize [0,0,1]
			y = normalize (cross z x)
			z = normalize (cross x y)
			
			local m = matrix3 x y z p
			local a = start.rotation.controller.x_rotation * (1. - t) + end.rotation.controller.x_rotation * t
			preRotateX m a
			
			ghosts[i].transform = m
		)
	)))
	
	move start [0,0,0]
	forceCompleteRedraw()
)
4 Replies
(@denist)
Joined: 11 months ago

Posts: 0

Great! One thing is missed. In my rig the ghost objects distributed by length (not by path). See the path constraint’s constantVel param…

PS. there is a trick:


-- instead of 
for k=1 to 10 do d_float = 1.*k/10
-- do  
for k=1. to 10 do d_float = k/10

(@denist)
Joined: 11 months ago

Posts: 0

just remove it… where is a problem?

(@matanh)
Joined: 11 months ago

Posts: 0

It might be my home computer, it’s 6 years old now :shrug:

(@denist)
Joined: 11 months ago

Posts: 0

might be… in this case x-axis transform has to be odd. y- and z- have to work right.

bezier spline tangent might be calculated more accurate.

another thing is to try not use the start’s and end’s rotation controllers. try to use their transforms.
in this case we will not be limited by type of assigned controllers. Our control nodes (start, end, out_start, and in_end) might be driven by script controller, or orientation_constraint for example…

here is an improved version, still haven’t managed to figure out how to do the constant velocity yet. I tried Catmull-Rom and Hermite spline interpolations but it doesn’t help my situation…

(
	max create mode
	
	local numSegs = 10
	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: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]
	)
	
	when transform ctrls changes handleAt:#redrawViews do (
	with redraw off (
	with animate off (
		local pts = for o in ctrls collect o.pos
		
		for i = 1. to numSegs do (
			local t = i / (numSegs + 1)
			local p1 = getBezierInterpolation pts t
			local p2 = getBezierInterpolation pts (t + 0.0001)
			
			
			local x, y, z
			x = normalize (p2 - p1)
			z = normalize [0,0,1]
			y = normalize (cross z 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()
)

 local startXRot = (quatToEuler start.transform.rotationpart).x 		   
local endXRot = (quatToEuler end.transform.rotationpart).x
 

this is not right. Object transform is absolute (in world space coordinate system). we have to interpolate in relative (local) space. The best representation is Angle-Axis. Where the axis is object’s local space and the angle is spin (x-rotation in our case).

the tangent is accurate now.

constant velocity… it’s kinda expensive to get. but … look how to calculate bezier spline length.

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

i’ve thought a little… only thing that we need to do is to find the right angles application order.

no constant param yet, but

something very inconsistent, happens with max reference system:

the callback is getting transform changes from the tangents when these are not being transformed .
this does not happen by transforming the cont5rol trhough mxs: $ruh002.pos+=20

uncommenting line 33 to force a CTRL node update, avoids this redundancy

another problem is the undo that is unable to capture all changes depending on what node was updated last,
some of these issues may be fixed if I link all through the ref system… but it’s not clean.
I’ve had issues like this with the ref system in max, but can’t understand where is the origin…

(	--mxs curves test cgt=985724
	if ::ru_call==undefined then
	(
		::ru_call=on
		local n=9.--param

		local prop=#([-70,50,0],[-30,-80,0],[-30,50,0],[-70,-80,0],yellow,yellow,orange,orange)
		local nodes=for n in 1 to 4 collect (point name:(uniqueName "ruh") box:true size:5 pos:prop[n] wirecolor:(prop[n+4]))
		local ctrls=for o in nodes collect NodeTransformMonitor node:o forwardTransformChangeMsgs:true
		local subs=for o=1 to n collect point wirecolor:white size:5 cross:false box:true name:#ruhs
		for c=1 to 4 do custAttributes.add nodes[c] (
			attributes linkage(
				parameters main
				(
					num type:#integer
					offset type:#point3
				)
			)
		)
		for n=1 to 4 do nodes[n].num=n
		nodes[3].offset=[ 40,0,0]
		nodes[4].offset=[-40,0,0]
		
		--
		
		fn bspline subs ctrls nodes obj=
		(
-- 			print obj.num
			if obj.num<3 then
			(
				format "%: CONTROL is dragged
" (timestamp())
				ctrls[obj.num+2].node.transform=(transMatrix ctrls[obj.num+2].node.offset)*obj.transform
-- 				ctrls[obj.num].node.transform=ctrls[obj.num].node.transform -- HACK to force UNDO
			)
			else
			(
				format "%: TANGENT is dragged
" (timestamp())
				--MAX gizmo/controllers   // ref messages?
				ctrls[obj.num-2].node.offset=(obj.pos*inverse ctrls[obj.num-2].node.transform)*[1,0,0]
				ctrls[obj.num].node.transform=ctrls[obj.num-2].node.transform*(transMatrix ctrls[obj.num-2].node.offset)
				
-- 				ctrls[obj.num-2].node.transform=ctrls[obj.num-2].node.transform -- HACK to force UNDO
			)
			for s=1. to n do subs[s].pos=
			(
				t=s/(n+1)
				ctrls[1].node.pos * (1-T)^3 +
				ctrls[3].node.pos * 3*(1-T)^2*T +
				ctrls[4].node.pos * 3*(1-T)*T^2 +
				ctrls[2].node.pos * T^3
			)
		)
		bspline subs ctrls nodes nodes[1]
		when transform nodes change obj do bspline subs ctrls nodes obj id:#ru_call
	)
	else
	(
		deleteAllChangeHandlers id:#ru_call
		globalVars.remove #ru_call
		delete $ruh*
	)
)

here it is w constant length stuff added| not optimized.
I tried to modify one of the controls without triggering the callback again, but it seems there is no way, it’s not evident buy annoys me.
conding using only Change Handlers doesn’t sound like a good idea IMO…

(	--mxs curves test cgt=985724
	if ::ru_call==undefined then
	(
		local n=9.--param
		local prop=#([-70,50,0],[-30,-80,0],[-30,50,0],[-70,-80,0],yellow,yellow,orange,orange)
		::ru_call=#(
			for n in 1 to 4 collect (point name:(uniqueName "ruh") box:true size:5 pos:prop[n] wirecolor:(prop[n+4])),
			for o=1 to n collect point wirecolor:white size:5 cross:false box:true name:#ruhs
			)
		for c=1 to 4 do custAttributes.add ru_call[1][c] (
			attributes linkage(
				parameters main
				(
					num type:#integer
					offset type:#point3
				)
			)
		)
		for n=1 to 4 do ru_call[1][n].num=n
		ru_call[1][3].offset=[ 40,0,0]
		ru_call[1][4].offset=[-40,0,0]
		--
		fn bspline subs nodes obj =
		(
			--format "num:% time: %
" obj.num (timestamp())
			if obj.num<3 then nodes[obj.num+2].transform=(transMatrix nodes[obj.num+2].offset)*obj.transform
			else
			(
				nodes[obj.num].offset=(obj.pos*inverse nodes[obj.num-2].transform)*[1,0,0]
				nodes[obj.num].transform=(transMatrix nodes[obj.num].offset)*nodes[obj.num-2].transform
			)
			leng=0
			prev=ru_call[1][1].pos
			local a=nodes[1].pos,b=nodes[3].pos,c=nodes[4].pos,d=nodes[2].pos
			for t=0. to 1  by 1e-2 do
			(
				new=
				(
					a * (1-T)^3 +
					b * 3*(1-T)^2*T +
					c * 3*(1-T)*T^2 +
					d * T^3
				)
				leng+=distance prev new
				prev=new
			)
			leng/=10.
			leng2=0
			idx=1
			prev=ru_call[1][1].pos
			for t=0 to 1 by 1e-3 do
			(
				new=
				(
					a * (1-T)^3 +
					b * 3*(1-T)^2*T +
					c * 3*(1-T)*T^2 +
					d * T^3
				)
				leng2+=distance prev new
				prev=new
				if leng2 >= leng and idx<10 do
				(--format "leng: %  leng2:%
" leng leng2
					::ru_call[2][idx].pos=new
					idx+=1
					prev=new
					leng2=0
				)
			)
		)
		bspline ::ru_call[2]  ::ru_call[1] ru_call[1][1]
		when transform ::ru_call[1] change  id:#ru_call  obj do bspline ::ru_call[2] ::ru_call[1] obj
		
	)
	else
	(
		deleteAllChangeHandlers id:#ru_call
		globalVars.remove #ru_call
		delete $ruh*
	)
)
 lo1

Wow, you don’t check this forum for one day, and this is what happens.
Looks really fun, great work everyone.

TzMtn: I get weird results without handleAt:#redrawViews as well, on a new computer. I am not sure why it happens though.

2 Replies
(@matanh)
Joined: 11 months ago

Posts: 0

maybe it’s the max version, I have 2008 here

 lo1
(@lo1)
Joined: 11 months ago

Posts: 0

Seems you are right, previously tested on max 2009 and it didn’t work. On max 2012 trial it works fine.

what is the better idea? use whatever you want, and we will compare.

Page 2 / 3