The other day I took a moment to write down how to change a horizontal slider into a vertical one in TouchDesigner. This got me thinking, and I realized that in addition to a good ol vertical slider, a 2D slider (or an XY slider) would be another handy tool to have your disposal. Why make these yourself when there are a ton of tools already made in the TUIK tool set? Good question – there are a ton of wonderful pre-made control tools at our disposal there, but if we want to take some time to better understand how those tools work and and the programming logic behind them then it’s worth taking some time to make our own versions. It’s part mental exercise, part challenge, all fun. Maybe.
For starters, why use a 2D slider anyway? There are lots of things you can do with these kinds of interface objects. These are especially handy for corner pinning objects – this control surface reports out an x and y value, which you can then scale and apply to the corner of an image. With four of these 2D sliders you now can control the four vertices of an image. More generally, these kinds of controls are useful when you want two values to come out of a single control surface. That’s all well and good, but lets get to making one, shall we?
We’re going to start this process with a regular horizontal slider. Let’s begin by adding a Slider COMP to your network.
Eventually, we’re going to make some changes to the parent object for our slider, but for now we’re going to dive inside and re-arrange the guts of our component just a little. When you get inside of the Slider you should see a familiar set-up: a Panel CHOP, an Out CHOP, and a Container called Knob.
In another post I talked about how we can decode what’ s happening in the expression in the x parameter field of our Knob. In summary we can decode the expression this way. The expression:
In English this might read – my parent’s panel u value (.5) * my parents width (100) – my panel’s width / 2. Great. Why? Well by multiplying the u value (a normalized value) with the width of the panel I get my position in terms of pixels – almost. The last thing I need to do is to take into consideration the width of the knob (divided by 2 so you split it’s value equally left and right).
This is all excellent news, but how does it help us with a 2D slider? First we need to think about what’s happening in a 1D slider vs. a 2D slider. In our regular horizontal (or vertical) slider we’re just tracking changes in a single direction – left and right, or up and down. In a 2D slider, we want to track both of those changes – up and down and left and right at the same time. To do this, we’re going to take our existing expression, and the expression that we used in our vertical slider and use both of them.
We already have an expression that allows us to track horizontal change, so how do we change that to track vertical change? In the expression above we’re going to make the following changes – first we want to use the value “v” instead of “u” that’s coming out of our panel (If you just had a moment when you said – “u, v, what now?!” take a break and read the Wikipedia page about uv mapping). We’ll also need to look at panelh (panel height) instead of panelw (panel width). This means our new expression for watching the change in a vertical dimension is:
So far so good? Okay, next we’re going to use this expression in the Y parameter field of the knob component. This means that we have the two following expressions that are in the x and y parameters of our knob component:
x = me.parent().panel.u.val*me.parent().par.panelw-me.par.panelw/2
y = me.parent().panel.v.val*me.parent().par.panelh-me.par.panelh/2
Okay, now we’re going to make one more change to our knob’s parameters while we’re here. Instead of this control object being a rectangle, let’s make it a square. We can notice that its current width and height are 5 and 20 respectively. Let’s change that to 10 and 10. You certainly don’t have to work with a square knob, but I like the way this looks for this type of control. We also need to make some changes to our Panel CHOP.
The panel chop gives us all sorts of useful information about what’s happening in a panel at any given moment. In our case we can use this CHOP to tell us the horizontal and vertical position of the knob that for our slider. If we look at how our panel is currently set up we can see that it’s selecting the u value and renaming it “v1.” We want our slider to send out both u and v information, so let’s change the select to read “u v” – separating our variables with spaces tells TouchDesigner that we’d like both to be selected. We can also rename this values while we’re here. I choose to rename them xPos and yPos (x Position, Y Position). Choose whatever name makes sense for you, and that it’s too long – if you end up needing to call these values in another expression having a shorter name is helpful.
Now let’s take a moment and head back up to our parent container. Here in our parent let’s change the width and height to be 100 and 100. We should now see a square panel with a square inside of it, that we can drag about.
If we connect a Null CHOP or a Trail CHOP we can see the values reported out of our new tool. Sweet.
Alright, this is pretty cool, and I’m mostly happy with it… but it’s missing something. One of the things you’ll see with other 2D sliders are guild lines that pass through the middle of the central knob – these help you visually maintain a sense of where your control object is pointed, and let’s face it they just look cooler.
Okay, so let’s add some guild lines, where do we start?
We can begin by diving back into our Slider component. Let’s work the easy way, and take our knob component, and make two copies of it. We can call these two new additions xWire and yWire. Instead of hard coding in the dimensions of these wires, we are instead going to use some expressions to define what they look like. Why? Well, while we certainly could do this by hard coding the numbers it also means that if we make any changes to the parent component, it also means we need to make changes several pieces inside of the component. This works just fine if you’re only making something you want to use once, but if you want to make a component that you can use and reuse then using some expressions is going to save you a ton of time – and as a bonus you’ll learn more about using python expressions in TouchDesigner. Enough of my soap box, let’s do this.
Le’ts start with our xWire component. I’d like my guild lines to be one pixel tall (or wide for the yWire), and the same width as the parent component. To make this happen let’s use the following expressions in the Width field:
In plain English this reads – what is my parent component’s panel width parameter. By using this expression we know that the width of this object will always match the width of our parent. Perfect. Before you celebrate too soon we need to add one more expression. We’d like the line that we’re drawing to stay aligned with our central knob (no really, we do want that). For this we need to keep in mind that our xWire that stretches the width of the parent component needs to move vertically – this may seem counter intuitive, but it’ll make perfect sense here in one moment. How do we do that? Well, luckily we were just practicing ways to make a knob stay aligned to a position when we made a vertical slider. We’re going to use some of the same ideas from that expression here. In the Y paramter field we’re going to use this expression:
op(‘panel1’) [ ‘yPos’ ] * me.parent().par.panelh
Say what now?! In plain English we’re saying – look at the operator called “panel1” and find the value called “xPos”, multiply that by the panel height of my parent. Now we should have a working xWire component. Before jump up to look at our 2D slider, here’s what you should have so far:
If jump out of our 2D slider and take a look at what we have so far, we should see something like this:
This is almost what we want, right? Now we just need to repeat this process to add our yWire guideline. Let’s dive back inside of our slider to finish up. For our yWire we’re going to set the width of our component to 1 and use an expression to change the height:
We’re also going to use an expression to change the X position of our component:
op(‘panel1’) [ ‘xPos’ ] * me.parent().par.panelw
These are like our other expressions just calling different values. At this point you should have something that looks like this:
Now we’re finally ready to back out of our 2D slider, and admire our hard work.
Alright, the last thing to do after you’ve done all of this hard work is to save this component as a .tox file that you can reuse. If you’ve never done that before, you can read how that works here.