# Box Select in Render Picking

Hello all,

Has anyone seen any examples of this?

Thanks,
Tim

Hey Tim,

I’ve implemented this different ways in GeoPix, the primary idea is to take whatever objects you want to select, and generate either a centroid coordinate, or a bounding box (min and max coordinate) to represent it in some sort of 2d space that is consistent between all necessary coordinates. Then test them against your box select min and max corner coords.

I have built this using python as the intermediary, so these steps reflect that, but it wouldn’t be too tough to convert this to an all, or mostly node based approach using the new and improved matrix chop for one.

Also, not covering the drawing of the box graphic, can do that with sops n such from the coordinates in steps 2 and 3. Though you’ll need to convert them to ws or use a glsl shader to ingest ndc space coords directly.

1. Calculate 0-1 space XY coordinates for your selectable objects : I like to use the tdu.matrix class, the world matrix of the object(s) you want to select, and the camera’s view/projection matrix to calculate the NDC space coordinates of the objects. This ends up giving you a bunch of xyz coordinates, of which you can just drop Z, and keep x/y. NDC space coords are in -1:1, so you’ll want to remap them to 0-1.
It’s also a good idea to perform this step only once, at the initialization of your box select, because if you have hundreds or thousands of objects, it can drag performance down to calculate these 0:1 space coords every frame, and it’s not usually necessary since the assumption is they are not moving during box select. If they are, can ignore this optimization. Using tdu.Matrix() class, the code might look something like this:
`p = someSelectableGeoComp.computeBounds()[2] # computes in WorldSpace`
pos = `tdu.Position( p[0] , p[1] , p[2] )`
`ws2vs = op('CAMERA').worldTransform`
`ws2vs.invert()`
`pos = ws2vs * pos`
`vs2ndcs = op('CAMERA').projection( parent().width , parent().height )`
`pos = vs2ndcs * pos`
`finalX = pos.x * .5 + .5`
`finalY = pos.y * .5 + .5`

2. Record start XY mouse position on box select initialization : This should also be 0-1 space, or whatever space you use in step 1 and through out.

3. Record end XY mouse position when the box select ends : Similar as in 2), 0-1 space, and should be the coordinate of where the mouse is when you release the mouse button, or end the box select etc.

4. Calculate min and max values from start and end XY positions : Since you can drag the mouse any of 4 ways, there’s no guarantee that where the mouse ends on X and Y, will be greater or less than where it starts. so to determine the min/max bounds do something like this:
`xMin = min( x1 , x2 )`
`xMax = max( x1 , x2 )`
`yMin = min( y1 , y2 )`
`yMax = max( y1 , y2 )`

5. Test each objects XY coordinate against the box select min/max : The last step, is to iterate through all the selectable objects and their 0:1 space XY coordinates, and check to see if it lies “within” the box. Something like this:
`xTest = objX > xMin and objX < xMax`
`yTest = objY > yMin and objY < yMax`
`isInsideBox = xTest and yTest`

After 5), you can use the resulting boolean to mask your select function. At this point, if you were dealing with shift/ctrl type modifiers, you might check those, and then either overwrite the selection, add to, or subtract from etc.

I personally use python sets for this, you can read your previous selection from wherever it’s stored in TD, and convert it to a set(), and use set().union, or set().difference, or set() - set(), etc. then convert the resulting set back to a list, and then overwrite the table or chop or whatever is storing that selection data. That’s really just a preference though, lots of ways do to the last part.

Hope that helps!

1 Like

Also! If you want to implement a paint brush style paint select feature, you could do that with very little extra work since the above is 80% of what you need anyways.

The only difference with paint select, is that every frame during the selection, you are checking all points to see if they are within the radius/distance of the mouse. If they are, add them to the selection set if not already added.

Thank you so much for all this info lucas, I will dig in tomorrow morning!

1 Like

Thank you Lucas, this worked out very well and I got a nice box selection.
Uploaded a quick video of the new feature - https://youtu.be/A-mTxoTOTAc

Here are a few notes on what I did differently to fit my project -

Considering my selectable objects are instances, I ended up using my cue’s start time converted to worldspace. Technically that is just the tx position of the instance because the sop’s center is shifted, but I like thinking in time. The idea is, I only want to select a cue if the bounding box is over the start time. This of course just means not using computeBounds() and getting the position manually.

We are on the same page with sets! I didn’t have to change my original method that actually does the real selection process which includes the oldSet - newSet you mention. I use the difference here to disable the highlight on the old items. The shift modifier was already assigned to insert new values to the front of the list as well.

As for creating the box, I’d be very interested in your opinion on the most efficient approach. Here’s my current logic. My sop is a 1x1 rectangle, with a wireframe material.
On SelectStart -

• Set the tx,ty value to the clicked position
• Enable rendering

On Select -

• Set sx and sy to original position - new position

On SelectEnd -

• Set everything back to 0
• Disable rendering

Thanks again for the detailed response!
-Tim

Hey this looks great! nice work. I didn’t realize you were implementing this into your editor there.

I bet your implementation of the box drawing is one of the more efficient ways, since you are manipulating the geo comp and turning off render after. Doing it in sops is prob slowest, depending on how valuable each drop of performance is, could go a glsl route, and modify the 4 corners of a rectangle sop on the gpu, but still have to pass that data into the gpu via shader inputs or render top inputs, maybe not worth it though!

Thanks! I may consider the GLSL route, but I don’t see any performance issues so maybe I’ll come back to it. This whole system is a serious mind bender for me…

1 Like