Point on Path World coordinates for several values of percent

Point on Path World coordinates for several values of percent

leeminardi
Mentor Mentor
1,054 Views
13 Replies
Message 1 of 14

Point on Path World coordinates for several values of percent

leeminardi
Mentor
Mentor

I am trying to create a Script for a point with a Path constraint that determines the coordinates of the point for several different values of percent.  The script should be capable of changing the percent value as a function of its world location.

For example, given pointB with a Path constraint to line1   three points locations are to be output then the value of s (percent) is determined by whichever distance is greater p0 to p1, or p1 to p2.

 

For this code:

self.position.controller.percent = 0.0
p0 = self.position
self.position.controller.percent = 0.15
p1 = self.position
self.position.controller.percent = 0.25
p2 = self.position
format ("\n\n p0 = %,  p1 = %,  p2 = %") p0 p1 p2 
d1 = length(p1 - p0)
d2 = length(p2 - p1)
format ("\n d1 = %, d2 = % ") d1 d2

if (d1 < d2) then
(s = 0.15)
else
(s = 0.25)
s

The output to the Max Listener is:

p0 = [0,0,0],  p1 = [0,0,0],  p2 = [0,0,0]
 d1 = 0.0, d2 = 0.0 

 p0 = [1244.75,1807.58,758.997],  p1 = [1244.75,1807.58,758.997],  p2 = [1244.75,1807.58,758.997]
 d1 = 0.0, d2 = 0.0 

 p0 = [24.895,36.1515,15.1799],  p1 = [24.895,36.1515,15.1799],  p2 = [24.895,36.1515,15.1799]
 d1 = 0.0, d2 = 0.0 

 p0 = [0,0,0],  p1 = [0,0,0],  p2 = [0,0,0]
 d1 = 0.0, d2 = 0.0 

 p0 = [1244.75,1807.58,758.997],  p1 = [1244.75,1807.58,758.997],  p2 = [1244.75,1807.58,758.997]
 d1 = 0.0, d2 = 0.0 

 p0 = [0,0,0],  p1 = [0,0,0],  p2 = [0,0,0]
 d1 = 0.0, d2 = 0.0 

 p0 = [1244.75,1807.58,758.997],  p1 = [1244.75,1807.58,758.997],  p2 = [1244.75,1807.58,758.997]
 d1 = 0.0, d2 = 0.0 

 p0 = [24.895,36.1515,15.1799],  p1 = [24.895,36.1515,15.1799],  p2 = [24.895,36.1515,15.1799]
 d1 = 0.0, d2 = 0.0 

 p0 = [0,0,0],  p1 = [0,0,0],  p2 = [0,0,0]
 d1 = 0.0, d2 = 0.0 

 p0 = [1244.75,1807.58,758.997],  p1 = [1244.75,1807.58,758.997],  p2 = [1244.75,1807.58,758.997]
 d1 = 0.0, d2 = 0.0 

 p0 = [24.895,36.1515,15.1799],  p1 = [24.895,36.1515,15.1799],  p2 = [24.895,36.1515,15.1799]

First,  why are there 10 sets of output?  I would expect only 1 set.

 

The values output for p0, p1, p2 are all over and not consistent.  Why?

 

How can I captures the world location of PointB for multiple values of percent?

leeminardi_0-1643832643385.png

 

lee.minardi
0 Likes
Accepted solutions (1)
1,055 Views
13 Replies
Replies (13)
Message 2 of 14

denisT.MaxDoctor
Advisor
Advisor

I don't really understand the task...

Let's first play with this snippet, and then we will discuss other options and specify the task better:

 

 

delete objects
gc()

h = helix name:#path radius1:50 radius2:30 height:100 turns:1 bias:0 direction:0 wirecolor:orange
p0 = point name:#p0 size:20 wirecolor:red
p1 = point name:#p1 size:20 wirecolor:red
x = point name:#ruler size:15 box:on wirecolor:yellow
t = dummy name:#target

p = p0.position.controller = path_constraint path:h percent:40
p = p1.position.controller = path_constraint path:h percent:70
p = x.position.controller = path_constraint path:h

s = t.position.controller = position_script()
s.addnode #p0 p0
s.addnode #p1 p1
s.addnode #ruler x
s.setexpression @"if (distance p0 ruler < distance p1 ruler) then p0.pos else p1.pos"

select x
 

 

 

play with  p0, p1, and x object percents...

0 Likes
Message 3 of 14

leeminardi
Mentor
Mentor

@denisT.MaxDoctor Thank you for your response.

The questions in my original post were aimed to hopefully help me better understand the syntax and operation of script controller.  I had hoped to embed the script in a controller that positions a point constrained to a path as a function of its position.  I'll try another approach and explain my larger goal. 

 

In you helix model, let's say I have a point A

PointA = [-60,25,0]

 

I would like to position another point that is path constrained to the helix that is a distance 100 from PointA.  I plan to use numerical methods to guess a location and then adjust its position as a function of the distance from the most recent guess to pointA.   So if my first guess for s (percent) is 0.1 (10 percent) d = 113.273. Additional guesses for s would be made until d <100 at which point s would be adjusted to halfway between its current value and the last value and the two intervals would be examine for where the solution lies.  The process would continue until an acceptable tolerance is reached.  If convergence didn't occur within n iteration the process would stop with the point at the last calculated value of s.

 

lee.minardi
0 Likes
Message 4 of 14

denisT.MaxDoctor
Advisor
Advisor

Sorry, but again I don't understand anything. I can definitely say that we have three points and one path. Let's name two points #controls and the third point #target.
The position of the #target depends on the distance to the #controls.
Maybe it will be clearer if you draw how the #target should be located depending on the position of the #controls and the shape of the #path in different combinations?

 

The main thing is to understand the task and the essence of the problem. Everything else is technical details.

0 Likes
Message 5 of 14

leeminardi
Mentor
Mentor

Here' the big picture.  I am trying to create a solution to a problem someone posted on how to rig a trolley that runs via electricity supplied by an overhead cable.  The approach that I am taking to do the rigging is to adjust the location of a point  object (PtCable) that has a path constraint to the cable.  The length of the boom is L.  The pivot of the boom is located at the end on the boom on top of the  bus and its local z axis is  collinear with the boom. PointBus is coincident with this end of the boom and both the boom and the point are linkied to the bus.  A LookAt constraint pointing to PtCable controls the boom's rotation.

 

My strategy in solving this is to create, in effect, a Solver that finds a point on the path that is a distance L away from PointBus.  To do so requires the determination in world coordinates of PtCable's position for an initial guess of the "percent along" parameter (s) and then it's coordinates for modified values of s (a new guess) based on the previous guess.

 

Where I am having difficulty is getting the valid PtCable coordinates for the successive guesses of s.  The new guesses for s are based on previous s values and the corresponding location of PtCable.   

 

Here's my rough code so far.

d = length(BusPt * BusT - self.position)
L= 20  -- length of Boom
flag = true -- set to false when solution found
delta = 0.02 -- initial guess increment
s = 0.0
format("\n\n1  s = %, self pos = %") s self.position
for i = 1 to 5 while flag Do
(
if (abs(d-L) > (L/100.)) then 
  (
    s = s + delta
   self.position.controller.percent = s 
   format("\n2  s = %, self pos = %") s self.position
   d = length(BusPt * BusT - self.position)
  format ("\nIF TRUE i = %,  d = %,   s = %") i d s
  )
else 
( -- handle this later,  Get working for 
  -- sucessive values od d > L first
  flag = false
)
 
self.position.controller.percent = s
d = length(BusPt * BusT - self.position)
format ("\ni = %,  d2 = %,   s = %") i d s

i = i + 1

) -- end for
format ("\n\nself posC = %") self.position
format ("\nNOTHING")
s

image.png

Before going much further with this approach I need to be sure I can get valid coordinates for PointCable when the value of s is modified.

When this code is evaluated the first few lines of output look as follows.

NOTHING

1  s = 0.0, self pos = [-7.99266,72.2269,15.1799]

1  s = 0.0, self pos = [0,0,0]
2  s = 0.02, self pos = [0,0,0]
IF TRUE i = 1,  d = 44.5881,   s = 0.02
i = 1,  d2 = 44.5881,   s = 0.02
2  s = 0.04, self pos = [0,0,0]
IF TRUE i = 2,  d = 44.5881,   s = 0.04
i = 2,  d2 = 44.5881,   s = 0.04
2  s = 0.06, self pos = [0,0,0]
IF TRUE i = 3,  d = 44.5881,   s = 0.06
i = 3,  d2 = 44.5881,   s = 0.06
2  s = 0.08, self pos = [0,0,0]
IF TRUE i = 4,  d = 44.5881,   s = 0.08
i = 4,  d2 = 44.5881,   s = 0.08
2  s = 0.1, self pos = [0,0,0]
IF TRUE i = 5,  d = 44.5881,   s = 0.1
i = 5,  d2 = 44.5881,   s = 0.1

The coordinates for self.position are incorrect except for the final output when s = 0.1 ( 5 * 0.02).  The correct coordinates for PointCable when s = 0 are [-7.99266,72.2269,15.1799] but the coordinates are [0,0,0] for all values of s.

 

Thank you for taking a look at this.  My knowledge of Max Script is very limited. I usually try to control an object through a script controller (e.g., transform script) for the object rather than a standalone Max script.  Is there a reason why I should not use the controller approach?

 

Thank you,

 

Lee 

lee.minardi
0 Likes
Message 6 of 14

leeminardi
Mentor
Mentor

@denisT.MaxDoctor   I've been studying the code you posted for points on a helical path.

 

Using that as a base I created a Point001 and defined its location on the helix as halfway between points p0 and p1 in parametric space via a Script Controller.  I included a few format statements to help me in the debugging.

u0 = p0.position.controller.percent
u1 = p1.position.controller.percent
u = (u0 + u1)/2.
format ("\n\nu = % ") u
L = 100.0
p = self.position
format ("\np = % ") p

u = u/100. 

The output upon evaluation looks like this.

p = [-37.3163,11.3641,54.7073] 

u = 55.0 
p = [-37.3163,11.3641,54.7073] 

u = 55.0 
p = [0,0,0] 

u = 55.0 
p = [-2000.01,-43.8716,2482.69] 

u = 55.0 

Why are there multiple occurrences of the format statement and why the different values for p?  The first value of p is correct.

leeminardi_0-1643905204174.png

 

What I would like to do from here is to determine if the distance between this location for point001 and some other location (for example, teapot) is less than L and if so modify the percent parameter for point001 and then determine the distance from its new location to teapot and if necessary modify the percent parameter again. 

lee.minardi
0 Likes
Message 7 of 14

denisT.MaxDoctor
Advisor
Advisor

I've been busy the last few weeks and haven't had time to play around with this theme...

 

try my version of "trolley" rig:

 

fn bestSplineParam sp pos len step:0.001 threshold:0.01 = 
(
	best_dist = 1e9
	best_delta = 1e9
	best_len = 1e9
	best_param = -1
	
	good_enough = len * threshold
	local out = off
	
	max_param = nearestpathparam sp pos
	
	for t = 0.0 to max_param by step while not out do 
	(
		x = interpCurve3D sp 1 t 
		dist = distance x pos
		delta = abs (dist - len)
		if (delta < best_delta) do
		(
			best_param = t
			best_dist = dist
			best_delta = delta
		)
		out = (delta <= good_enough)
	)
	--#(best_dist, best_param, best_delta, out)
	best_param
)


/***************** scene setup ***********************/

delete objects
gc()

with redraw off
(
	dest_len = 50

	sp = helix name:#wire radius1:150 radius2:90 height:4 turns:1 bias:0 direction:0 pos:[0,0,30] wirecolor:blue
	converttosplineshape sp

	bus = dummy name:#bus boxsize:[20,20,20] pos:[96,-100,0] isselected:on
	pt = point name:#knot size:10 box:on cross:on wirecolor:orange
	c = pt.pos.controller = path path:sp

	pole = cylinder name:#pole radius:0.5 sides:6 height:dest_len parent:bus pos:bus.pos wirecolor:yellow
	lockedtracksman.setlocks on pole.position.controller pole.position.controller 0 off
	--p = pole.position.controller = position_constraint()
	--p.appendtarget bus 100
	
	r = pole.rotation.controller = lookat_constraint()
	r.appendtarget pt 100
	r.viewline_length_abs = on
	r.target_axis = 2
	r.upnode_axis = 1
	r.stoup_axis = 1

	-- val = bestSplineParam sp bus.pos dest_len
	-- distance bus pt

	s = c.percent.controller = float_script()
	s.addnode #path sp
	s.addnode #bus bus
	s.addconstant #length dest_len
	s.setexpression "bestSplineParam path bus.pos length"
)

 

to move the "bus" in the opposite direction you can use the same function but loop path parameter from 1 to 0

0 Likes
Message 8 of 14

denisT.MaxDoctor
Advisor
Advisor

this is just the basic idea... and of course there are many ways to optimize.

0 Likes
Message 9 of 14

leeminardi
Mentor
Mentor

@denisT.MaxDoctor  Thank you for taking another look at my question.  Since your last post I decided to approach the problem by directly access point coordinates of  a spline via the interpCurve3D spline method.  You can see the results here.  I'm sorry I didn't post that I had a solution.  I will look at your new code tomorrow (it is late here).

 

My script finds the point for a single position of the bus. I could not figure out how to answer the  OP's additional request to generate key frames from my script.  I posted an additional question here.

 

Thanks again for taking the time to look into my question.

lee.minardi
0 Likes
Message 10 of 14

denisT.MaxDoctor
Advisor
Advisor

I don't think we need to generate any animation keys. The rig is a "constraint or/and expression" driven system. The main goal is to rig without keying.

 

I will try to find time to look at your latest solution more closely. Maybe I'm missing some point...

0 Likes
Message 11 of 14

leeminardi
Mentor
Mentor

@denisT.MaxDoctor wrote:

I don't think we need to generate any animation keys. The rig is a "constraint or/and expression" driven system. The main goal is to rig without keying.

 


@denisT.MaxDoctor  After studying your code in post #7 and the resulting Max file I agree that generating animation key frames is not necessary. Your file showed me how to call a script from a Script Controller.  Note, the pole will pull away from the wire when the bus move towards the left. I found the problem was with:

max_param = nearestpathparam sp pos

 

Setting max_param to 1.0 seemed to fix the problem. Was the goal of the statement to limit the number of iterations?  I think the bisection search I use may be more efficient. 

 

With some minor modifications my CaculateCablePoint function can be adapted to be used by a script controller for PointCable in my Max file.    What's not clear to me is how a custom Maxscript (e.g., CaculateCablePoint or your bestSplineParam) is loaded.  How does Max know where to look to access it?

 

I'll post the final version when I have it successfully debugged and tested.

 

Having a sample script and file was very helpful.  Thanks again.

 

Lee

 

lee.minardi
0 Likes
Message 12 of 14

leeminardi
Mentor
Mentor
Accepted solution

For those who may be interested a final (hopefully) version of the program can be found here in post #5.

 

lee.minardi
0 Likes
Message 13 of 14

denisT.MaxDoctor
Advisor
Advisor

@leeminardi wrote:


@denisT.MaxDoctor  After studying your code in post #7 and the resulting Max file I agree that generating animation key frames is not necessary. Your file showed me how to call a script from a Script Controller.  Note, the pole will pull away from the wire when the bus move towards the left. I found the problem was with:

 

max_param = nearestpathparam sp pos

 

 

Setting max_param to 1.0 seemed to fix the problem. Was the goal of the statement to limit the number of iterations?  I think the bisection search I use may be more efficient. 

 

With some minor modifications my CaculateCablePoint function can be adapted to be used by a script controller for PointCable in my Max file.    What's not clear to me is how a custom Maxscript (e.g., CaculateCablePoint or your bestSplineParam) is loaded.  How does Max know where to look to access it?

 


No. 1
I'm using the nearestpathParam parameter to limit the loop when calculating the forward movement of the bus (in the direction from 0 to 1). I want the "pole" to look back all the time. So as we go forward, I calculate from 0 to the nearest parameter. If we need to use the same "wire" to return, we will cycle from 1 to the nearest parameter.

No. 2

Using the Bisection method is the right direction for optimization. It's how I would finally do it too.

No. 3

Usually, all rig setups are distributed with a specially created rig tool (in the plugins or macro-scripts directory).

But I always recommend moving all functions used in script controllers out from the "global" scope. The only global thing should be the script controller itself. So I typically put all the calculation methods in a custom attribute added to the script controller.

 

 

0 Likes
Message 14 of 14

denisT.MaxDoctor
Advisor
Advisor

#3

example:

 

 

delete objects 
gc()

(
	t = dummy name:#target boxsize:[20,20,20] isselected:on
	p = converttosplineshape (circle name:#path radius:100 wirecolor:orange) 
	s = point name:#source size:25 cross:on box:on wirecolor:yellow
		
	pc = t.pos.controller = path path:p
	sc = s.pos.controller = position_script()

	ca = attributes "StepPercent" attribID:#(0x23425100, 0x15416122)
	(
		parameters params
		(
			step type:#float default:0.1 
		)
		fn modulaPercent percent step = (percent - (mod percent step))
		fn getParamByTarget sp target = 
		(
			nearestpathparam sp target.transform.pos
		)
		fn getPointByParam sp target step:0.1 = 
		(
			param = getParamByTarget sp target
			interpCurve3D sp 1 (modulaPercent param step)
		)
	)

	custattributes.add sc ca

	sc.addnode #path p
	sc.addnode #target t 
	sc.setexpression "this.steppercent.getPointByParam path target step:this.steppercent.step"
)

 

as you can see in this example all calculation methods will be distributed with the rig (file)

 

 

0 Likes