Apply clipping planes to only specific area of object

I am using Three.js (R3F) to apply clipping planes to a model. For clarity, let’s say that my model is shaped like a barbell, and that I only want to apply my clipping planes to one end of said barbell. Some planes I want don’t cause any issue, but others directly interfere with the other end of the barbell. How would I limit the clipping plane’s reach to only the desired end?

My current solution is to split the model into two separate meshes so that I can apply the clipping planes in isolation. This works, although since my models are slightly transparent, there is a visual bug where the other portion of the model cannot be seen through the inside of the other. Messing with the rendering order between the two meshes only seems to flip which side this happens on:

I found a similar question asked a few years back. The accepted solution of setting clipIntersection to true gives the desired behavior, but as OP described in the comments, it only works for two planes. I tried setting a more custom mix of union and intersection clipping planes via global and local clipping planes (briefly described in further comments), but I couldn’t get anything to work the way I want it to.

1 Like

i’ve wasted weeks on trying to figure out clipping (with caps) and it seems way too messy to do. i think you might like csg. yes it’s overblown but so is capped clip, maybe more so.

here are two examples with a movable runtime subtract

1 Like

Ah yes, I was messing with that the other day. Really like the updates in v2. I was trying to boil it’s usage down to something as simple as I could, so I tried doing some operations with plane geometry but ended up running into errors.

If using plane geometry isn’t viable, my next idea is to create rectangles as my “clipping planes” and resize them to reach the end of my model (either arbitrarily or via some bounding box calculations etc.). From this stack overflow post, it looks like scaling my rectangle in a single direction will take a little extra work as well, but it seems promising.

Maybe something like:

<mesh>
  <Geometry>
    <Base>
      <myBarbellModel />
    </Base>
    <Subtraction>
      <myClippingRectangle />
    </Subtraction>
  </Geometry>
</mesh>

yes, a rectangular cutter would make sense, you can make it really big to save you from the hassle of having to always update its size.

like this condescending-bas-tyqw6h - CodeSandbox

@drcmda I played around with this idea, but have come across another problem.

The main concept works:

  1. Create my rectangular cutters (box geometry)
  2. Since i’m going off of plane data for cutter placement, I use this method to properly orient my cutters
    a. Also doing an additional transformation to ensure that the applied signed distance is with respect to the edge of my cutter, not the middle of it
  3. Add each cutter as its own CSG operation (in my case, subtraction)

This produces my desired result. The problem comes with load time. On load, I am bombarded with thousands of console warnings:

ExtendedTriangle.intersectsTriangle: Triangles are coplanar which does not support an output edge. Setting edge to 0, 0, 0.

After some digging, I think I know what’s going on. Back to the barbell:

I think these coplanar intersections are what’s causing the problem. It doesn’t like multiple geometries trying to do the same operation on the same triangle(s). I’m not sure if that bottom coplanar point is an issue since it’s not over the base geometry, but I digress. Now take this issue with the context of the cutters being 3D geometries:

Even just looking within the base geometry, there are multiple areas of overlap. This is what I believe contributes to the warnings appearing in such a large quantity, as it’s emitting for each triangle within an overlap.

Although these are just warnings, the operation that they describe (Setting edge to 0, 0, 0) to fix the conflict causes considerable load times (~10s wait time on my machine) and severe browser lag. I tried to avoid the issue by first combining all of my cutters into a single geometry via BufferGeometryUtils.mergeBufferGeometries(), but that ended up messing up the stencil caps as it looked like they had no context of the other cuts around them.

I’ve since been struggling on where to go from here. Is there a way to prevent the warning by doing some operations before render? I was also thinking of possibly creating some logic to prevent these geometries from ever overlapping, but I can’t think of any relatively clean way to do so.

I’m sorry to interfere in your interesting discussion. I just want to slip in a question: are the three planes fixed (so you need a solution for a predetermined set of planes) or not (so they can be arranged in different ways for different use cases)?

1 Like

In my case, the planes are fixed and predetermined.

1 Like

@gkjohnson is here, maybe he can help you with the coplanar problem. do you have a sandbox that demonstrates the issue?

Fixed and predetermined planes might provide more options for solutions. I will have a look tomorrow, if there is no answer by then.

Meanwhile I had similar experience in the past: a mesh with complex geometry, created via CSG required about 15 seconds to be done. So I created the object once, exported it into GLTF, and then in the actual program I just loaded the already ready object. Loading was much faster than CSGing.

Make sure you’re using the latest versions of three-mesh-bvh and three-bvh-csg. This log has been suppressed in the latest releases.

should be good, just checked and it’s all @ latest. :pray:

in the official complex geometry demo individual geometries are not merged before processing the csg but actually use the libraries csgEvaluator.evaluate function to additively combine brushes with ADDITION resulting in a finalBrush to be subtracted like so…

	let finalBrush = brushes[ 0 ];
	csgEvaluator.useGroups = false;
	for ( let i = 1, l = brushes.length; i < l; i ++ ) {

		const b = brushes[ i ];
		finalBrush = csgEvaluator.evaluate( finalBrush, b, ADDITION );
		finalBrush.material = material;

	}

whereby brushes is an array of brushes generated by each geometry needed to be subtracted, i’m not certain but this should be taken care of in the internals of dreis fork…

it’s best if you provide

I did end up having an older version of drei installed. Updating it removed the console messages, but I observed little to no improvement in the load times of my scene (unsurprisingly).

I was afraid of this, but it looks like I have a similar story to @PavelBoytchev , where my base mesh is just too complex to be done with any decent speed. I’ve recreated my environment with everything but my base mesh (using simple geometry instead) with no performance issues:

Going back to what I said previously about combining my cutters via BufferGeometryUtils.mergeBufferGeometries(), I did take note that it runs slightly faster, but still slow overall with the added issue of the weird stencil caps. Here’s a sandbox that demonstrates this (still simple geometry for base):

If I can’t figure out this performance issue, I’ll probably end up having to go back to trying clipping planes again :face_vomiting:

Thinking back, it may have been misleading to say that the planes are both fixed and predetermined. Although I will always know what the initial positions of these planes will be, I do want to allow users to move these planes or “cutters” around in the future. I wish I had the luxury of making the cuts prior in blender or something haha. Apologies for any possible confusion!

1 Like

with load time do you mean the fade in? that is a css delay

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

canvas {
  opacity: 0;
  touch-action: none;
  animation: fade-in 1s ease 0.5s forwards;
}

it loads instantly for me otherwise. i don’t know why it would not, these are still very simple operations that should be realtime … all cuts together shouldn’t be more than a microsecond. the speed of bvh-csg is one of its sellings points. :slight_smile:

I tried slightly different approach and there is no visible problem where the two halves are attached, although there are transparency issues in other parts of the shape, but they are not related to the cutting and stitching. Here is the result (left is stitched object, middle and right are both halves):

https://codepen.io/boytchev/full/ExeoeOJ

image

I do the following:

  • Cutting is done by an additional horizontal cutting plane through the middle of the object. So, there are two identical obejcts: one shows only its top half, the other – its bottom.
  • To resolve transparency at the point of object cutting, each of the two identical objects are additionally split into two – with FrontSides and BackSides.
  • Overall I have 4 identical objects – top-front, top-back, bottom-front, bottom-back. Render order ensures the back objects are drawn before the front objects.
  • Obviously there are no caps.

The sandboxes load fine for me, its just when I apply the cuts to my more complex model instead of the simple rectangle, the significant load times occur (10-15s). Sounds like this might have been experienced before, too:

After seeing the old log messages and thinking about the coplanar calculations, it makes some sense to me from a high level, although I don’t have many ideas on how I could try and mitigate the time aspect.

Oh interesting

This might be the missing piece for me. I’ll try it out later this weekend. Thanks!

This works! Thank you so much for your solution and in-depth showcase of it! On a side note, would you be able to explain to me why this works and/or how you arrived at it? I’m having a hard time connecting the dots as to why separate front and back side meshes can hide where the object was cut, but a singular double side mesh can’t. I know that we split it so that we can define the render order between the two, but trying to figure out why this is required. Again, thank you! :slight_smile: