Final Project
Rope Bridge Generator
My final project will be a continuation of my first MEL project, the bridge generator. I want to incorporate better user controls that allow the artist to place two locators and generate a curve between them with adjustable values of weight which determine how saggy the rope bridge is. After a curve is generated the user can then adjust the number, size, and amount of spacing between each plank. Also, including hand rail ropes and random broken or missing planks would be interesting. Another feature I would like to incorporate is the ability for artists to model planks and load them into the bridge generator tool so that they are sourced into the bridge instead of using the planks created by the tool. This would allow for greater detail and variation in the planks. A texture feature would also be nice. My ultimate high reaching goal however is to incorporate nCloth dynamics to generate procedural motion on the bridge..
Step 1: Create Curves
The user must create two locaters named locater1 and locater2. The script queries the positions of the locaters
and stores each value into two arrays for each locater. The weight attribute is also set, currently at a default of 10.
Later in development the user will be able to control the weight value via a slider UI interface. The weight value is used
in calculating the amount of sag in the curve generated between the locaters. It directly affects the Y value of the new
?in-between? curve points by subtracting from the locater's original Y position.
![]() |
Next the positional difference is calculated between the locaters on both the X and Z axis (locDistX, locDistZ). That distance is then evaluated
at 25% and 75% of the distance to find the X and Z positions of the new curve points filling the middle of the curve. Now two
new in between curve points have X, Y, and Z coordinates.
The curve is constructed using the array data created from the original position of the locaters in combination with the new curve point data. This method
allows for an accurate curve to be created no matter what position the locaters are in. Originally I was restricted to only
allowing the user move the locaters along the X axis because I was not calculating the Z difference.
Step 2: Divide Curves Into Equal Segments
To plot points along the curve in equal segments I decided to take advantage of the arcLengthDimension node. Bryan Ewert has produced and excellent
tutorial on the basic implementation of the arcLengthDimension node and it can be found here. Through his tutorial I was able to place locaters along the curve
at equal lengths with any number of segments.
First I needed to find the max U value of the curve and create and arcLengthDimension node based off the maxU. In this example the max U value is 1. The
halfway point of the curve would then be U = .5. When the U value is plugged into the arcLengthDimension node it will return the length of the curve in
centimeters at the set U point. When U = 1 the node returns 28.28917cm, when U = .5 the length is 28.28917 / 2 = 14.14.
![]() |
Knowing the length of the curve allows me to calculate the size of equal length segments depending on the number of spans the user wants divide the curve into.
In this example, the curve has 10 locaters dividing the curve into 9 equal segments. When the script is called to place locaters along the curve it takes the
value 10 and subtracts one from it to get the appropriate number of equal segements. The total length of the curve is then divided by that number to get
the size of the segments. 28.28917 / 9 = 3.14.
Now I know at what distance each locater should be placed on the curve but I need still need to find the U value that correlates to the desired cm length value.
To do this I created a loop that divides the U value in half and measures the midpoint of each division checking to see if it is close to the desired value that is
driven by the increment of 3.14. The first locater to be placed next to the origin locater will be at 3.14cm, and the following locater will be at 6.28 and so on.
If the point is before the desired distance then the lesser half of the segment is divided. If the point is after the desired distance the greater half is divided.
This is continually done until a measurement is reached that is incredibly close to the desired point. The epsilon value dictates how close the value must be to be
acceptable; in this case it is set to .0001.
Step 3: Build Planks
To build planks I decided to write a new procedure that creates planks at equal segments of the curve instead of using locaters. I was able to pull this off quite
simply however the trick is now getting the planks to be created in proper orientation with the curves. Right now I have it set up for creating planks only along the X axis.
The script has no awareness of what the world orientation of the curves are so creating the planks is currently restricted to only working along the X axis. Also, the planks are oriented with
their pivot on the original curve instead of being placed in-between the two curves. Not knowing the position of the curves also ruins this portion of the program due to not
being able to calculate the midpoint between the curve offset is the curves are not perfectly in line with either the X or Z axis.
![]() |
It is important I solve the positional awareness of the curves because I already have it set up to create a curve based any configuration of the original locaters. As a tool, the artist should be able to use this script without being confined to generating a bridge strictly along the X or Z axis but should be able to create bridges within an already established 3D scene. The tool must be able work inside scene it is being implemented in instead of creating bridges in seperate files that are later imported into a master scene.
![]() |
Another issue that must be solved is the rotational orientation of the bridge planks. Currently they are oriented to be parallel with the ground instead of following the curve normal. I need to figure out a way to find the normal at any given point of the curve to accomplish this.
Step 4: Implement Positional Awareness
To solve the problem of creating planks along the curves I decided to take the original curve and align it to the X axis and then perform all my building procedures while I have it in a known coordinate space. After everything is generated I can then group the bridge and rotate it back into place.
![]() |
To do this I need to do some simple geometry to find the angle needed to rotate the original curve so it lines up with the X axis. A right triangle can be drawn from the positions of the
two locaters in the scene. By subtracting the X and Z values of the locaters from one another I am able to find the length of the opposite and adjacent side of the triangle with respect to
the angle I am trying to solve. By using those measurements I am then able to solve the inverse tangent which gives me the angle I want. The line of code I used looks like this, atand is
the MEL math function for an inverse tangent: $tanAngle = atand($sideOpp/$sideAdj);
I also had to implement some if statements that would check where the locaters are in relation to one another so that I know when to use a positive or negative rotation with my solved tangent angle.
Step 5: Duplicate Curve and Create Planks
Now that I have the curve aligned to the X axis I can begin my build procedures. The first step is to create a second curve and offset it by a certain amount. To start I just used an arbitrary value of 5 to test my results. I simply accessed Maya?s duplicate function and create a second curve from the original and translate it along the Z axis by adding the offset value to the original curve?s Z position.
![]() |
With two curves I can now build my planks. The width of the planks are determined by the offset value so they will fill the space between the two curves perfectly. To get the planks generated at the proper location I took the offset value and halved it so that the origin of the plank geometry is generated at the center point between the two curves. I also used the pointOnCurve information from the arcLengthDimension node to derive the Y position of the planks.
Step 6: Align the Planks to the Curve Normal
The planks are now generated along the curves just fine, however, the next challenge is to get them oriented with the curve normal instead of being parallel with the Y plane. To do this I used a script generated by Malcolm Kesson which solves the rotation necessary to oriented any piece of geometry with a surface normal. I was particularly interested in using the script to orient the Y-up of each of my planks with the normal of my curves. To see a complete breakdown of the script, click here, or explore Malcom Kesson?s site at www.fundza.com to learn more.
![]() |
Success! The script worked by feeding in the uParam that I derived earlier from my arcLengthDimension node procedure. Now I just need to group all the curves and geometry and get it back into the original position. I created a loop that generates a string based on the number of planks requested to be generated. It simply compiles a string that ends up looking like: select -r plankPoly plankPoly1 plankPoly2 plankPoly3 etc? and then appends curve1 and curve2 at the end. I can then use that string to activate my selection and create a group called bridgeGrp1. Upon creation of bridgeGrp1 I set its pivot to the position of locater2. That allows me to apply the inverse tangent angle to the whole group so the bridge is put back position.
Step 7: Create a User Interface
The bridge generator script is now fully functional. The final step is to create a user interface. Along the development of this script I created variables that would easily allow me to swap in lines of code that query sliders to attain their values, so building a UI was easy. I created a window that contains a generate bridge button to get things started once the user has placed two locaters into the Maya scene. There is also a delete bridge that will clear the scene of geometry and curves but will leave behind the locaters incase the user wants to continue positioning them.
![]() |
The heart of the UI is in the sliders. Weight controls the amount of sag in the curves and the offset value determines how far apart the curves are from one another and how wide the planks are.
The Plank size slider determines how thick the planks are.
A major feature I wanted incorporated with the UI is a live update system. Whenever the user makes an adjustment to the sliders I want the user to see the effect immediately
on the bridge geometry. To do this I have the bridge be completely reconstructed anytime a value change occurs. It is not the most efficient technique but it works for now. My other option was
to build another level of script awareness that tracked what has been created already and would offset their values while adding or subtracting new pieces of geometry if the number of planks is
increased or decreased. In the end that solution seemed like more trouble than it was worth so I decided to stick with the complete destruction and reconstruction of the bridge with each value
change. The script still performs well even with that inefficient technique being used.
Final Thoughts
Overall I am very pleased with the final outcome of this project. I successfully created a tool that generates a bridge in any coordinate space which was more of a challenge than I originally thought. Getting back to the basics of trigonometry was a refreshing exercise for me and forced me to approach problems with a renewed level of analysis. Sadly, I did not even come close to reaching my projected goals. I still want to incorporate a geometry loading system into the UI where the user is able to create custom planks which are then instanced where the current planks are. This will add a greater level of detail and flexibility to the tool. I also wanted to incorporate an nCloth dynamics system into the bridge so it would become a self-animated asset however I ran out of time. These are all concepts I plan on developing further to improve this tool and make it worthy of being implemented in a production setting.







