There was an interesting thread recently on Odforce in which someone wanted to be able to “boolean” a curve against a polygonal mesh; essentially chopping up the curve at the intersection points between the curve and the mesh, and removing the inside parts of the curve. This ended up being a little trickier than it sounds. Of course there’s always an easier solution out there, but I stumbled onto a reasonably stable solution that seems to work for both very simple and very complex curve intersections, and it’s only a little bit weird.
The first step to making all this work is to convert the polyline into primitives; one for each line segment. This will make cutting the lines much simpler later on, because we won’t have to deal with multiple edges per primitive. Next, we use the very cool Intersection Analysis SOP to get the points of intersection between the curve and the mesh. This SOP also generates some handy attributes on each point it generates; sourceprim
and sourceprimuv
. These are both array attributes. The first index refers to the “A” side geometry on the Intersection SOP, and the second points to the “B” side. Assuming that the polygon mesh is A and the curve is B, we can use these attributes to decide how to chop up the curve via the PolySplit SOP.
The tricky bit here is that the PolySplit SOP has a very weird sort of syntax; it’s not really intended to be used procedurally, it’s an interface-driven tool. So we’ll have to use some VEX to generate that string. PolySplit syntax looks like this:
primeedge:uv
Since we used Convert Line to turn each line segment into its own primitive, we know we’re always dealing with edge 0 for each segment. So for us to cut primitive 2, edge 0 at halfway across the primitive, our cut syntax would be 2e0:0.5
. We have multiple cuts to make, so each cut string will be separated by a space.
The string for all the cuts can be generated in an Attribute Wrangle (detail mode) as follows:
int npts = npoints(1); string cuts = ""; for(int i=0; i<npts; i++) { int lineprim = point(1, "sourceprim", i)[1]; vector lineprimuv = point(1, "sourceprimuv", i)[1]; string cut = sprintf("%de0:%f", lineprim, lineprimuv.x); cuts = cuts + " " + cut; } s@cuts = cuts;
This assumes that your intersection points are in input 1, and the line to cut in input 0. First, we get the total number of intersection points using npoints()
. Next, we define a new empty string to hold all of our cuts
. In a loop, one iteration for each intersection point, we grab the sourceprim
and sourceprimuv
attributes for each of these points. Since they’re both array attributes, and we want the part of the array that corresponds to our line primitives, we choose index 1 of the arrays (since the line was connected to the second input of the Intersection SOP).
Next, we generate the string for this particular cut. The sprintf()
function allows us to generate a formatted string using special keywords like %d
and %f
, corresponding to “digit/integer” and “float” respectively. sprintf("%de0:%f", lineprim, lineprimuv.x)
will replace %d
with lineprim
, and %f
with lineprimuv.x
. Finally, we just add that string to cuts, with a space separator in between. After the loop, we create a string detail attribute s@cuts
and set it to the full string we generated in the loop.
That’s the worst of it. We can now connect the results of this wrangle to a PolySplit node, and tell it to cut the curve based on that string by using the following expression for the Split Locations parameter:
`details(opinputpath(".",0), "cuts")`
The details() function fetches a string-type detail attribute from a node. opinputpath(“.”, 0) is an easy way to point to whatever node is connected to the first input of the node the expression exists on. The whole thing is surrounded in backticks in order to force the whole expression to evaluate in-place as a string.
The rest of this operation is easy. We use another Convert Line SOP to break the new segments into their own primitives, then use a Fuse SOP in “Unique” mode to split the points connecting each segment into separate points. Next, in order to make it easy to tell which points are inside and outside the cut, we use a bit more VEX to shrink the line segments a very small amount. (Note: you could use a Primitive SOP to transform each segment as well to avoid the VEX, but this means you don’t get to be exact about how much you’re moving the points on these primitives). The Point Wrangle code looks like this:
float bias = ch("bias"); int nearpt = neighbour(0, @ptnum, 0); vector nearP = point(0, "P", nearpt); vector dP = normalize(@P - nearP); v@rest = @P; @P -= (dP * bias);
Not too bad. We define a “bias” parameter that we can fine-tune later (hit the plug button in the top corner of the wrangle node if you don’t see it). Next, for each point we find the other point connected to it via the neighbour()
function. Since each primitive only has two points, we know we’re always looking for primitive 0. Next, we grab the position nearP
from that point, subtract it from our current point’s position, and normalize that vector. Before moving the point, we stash the current position as v@rest
, and then subtract our normalized vector multiplied by our bias amount from P. This has the effect of moving the points towards their neighbor, shrinking each primitive.
Almost done. Now that our primitives are slightly biased inwards, we can easily tell what points are inside the cutting mesh by using a Group SOP in Bounding Object mode. Delete those points, then move them back to their original positions with VEX: @P = v@rest;
. Finally, Fuse any adjacent segments back together, and then use the PolyPath SOP to connect all contiguous segments back into single primitives. That’s it!
Here’s the HIP file. Have fun!