How to Dynamically Slice a Convex Shape

http://gamedevelopment.tutsplus.com/tutorials/how-to-dynamically-slice-a-convex-shape--gamedev-14479


The ability to dynamically split a convex shape into two separate shapes is a very valuable skill or tool to have in your gamedev arsenal. This splitting allows for advanced types of simulation, such as binary space partitions for graphics or physics, dynamically destructive environments (brittle fracturing!), ocean or water simulationcollision resolution for physics engines, binary spatial partioning, and the list just goes on. One great example is the game Fruit Ninja for Kinect.

What exactly does it mean to split a convex shape? In two dimensions, we refer to a shape as a polygon; in three dimensions, we refer to a shape as a polyhedron. (Polyhedra is the word used to reference more than one polyhedron.)

Tip: In general, convex polygons and polyhedra simplify many aspects of volume or mesh manipulation and management. Due to this simplification, the entire article assumes convex polygons and convex polyhedra. Concave shapes are not apart of any discussion here. In general complicated concave shapes are simulated as a collection of joined convex shapes.

In order to make sense of the ideas presented in this article, you'll need a working knowledge of some programming language, and a simple understanding of the dot product.

One great way to split shapes in both two and three dimensions is to make use of theSutherland-Hodgman clipping routine. This routine is quite simple and very efficient, and can also be extended ever so slightly to account for numerical robustness. If you're unfamiliar with the algorithm, check out my previous article on the subject.

An understanding of planes in either two or three dimensions is also a must. It should be noted that a two dimensional plane could be thought of as a projection of a three dimensional plane into two dimensions—or, in other words, a line.

Please understand that a plane can also be thought of as a half-space. Computing the distance or intersection of points to half-spaces is a required prerequisite: see the last section of How to Create a Custom 2D Physics Engine: The Core Engine for information on this.

Please refer to the demonstration source (also on Bitbucket) that I have created as you read through this tutorial. I used this demo for creating all the GIF images in the article. The source code is in C++ (and should be cross-platform compatible), but is written in a way that can easily be ported to any programming language.

Before tackling the problem of splitting an entire polygon, let's take a look at the problem of splitting a triangle through a cutting plane. This will form the basis of understanding for moving on to a robust and generalized solution.

The nice thing about shape splitting is that, often, algorithms in 2D can be extended without much trouble directly into 3D. Here, I'll present a very simple triangle splitting algorithm for both two and three dimensions.

When a triangle intersects with a splitting plane, three new triangles should be generated. Here's an image showing a triangle before and after splitting along a plane:

TriangleSplit

Given a splitting plane, three new triangles are output during the slicing operation. Let's throw some code into the open, assuming that the three vertices of a triangle are { a, b, c } in counter-clockwise (CCW) order, and that we know that the edge ab (edge of vertices a to b) have intersected the splitting plane:

Hopefully the above code scared you a little. But fear not; all we're doing here is calculating some intersection points in order to know how to generate three new triangles. If one had examined the previous image carefully, the intersection points' locations might be obvious: they are where the dotted line meets the splitting plane, and where the edge ab intersects the splitting plane. Here's a diagram for convenience:

TriangleSplitDiagram

From this diagram, it is easy to see that output triangles should contain the vertices { a, ac, ab }{ ac, c, b }, and { ab, ac, b }. (But not necessarily in this exact format; for example, { a, b, c } would be the same triangle as { c, b, a }because vertices were simply shifted to the right.)

In order to determine which vertices contribute to which of the three new triangles, we must determine whether the vertex a and vertex c lay above or below the plane. Since we are assuming that the edge ab is known to be intersecting, we can deduce implicitly that b is on the opposite side of the clipping plane from a.

If the convention of a negative distance from a splitting plane means penetrating the plane, we can formulate a predicate to determine if a point intersects a halfspace:#define HitHalfspace( distance ) ((distance) < 0.0f). This predicate is used within each if statement to check and see whether a point is within the halfspace of the clipping plane.

There are four cases that exist of combinations of a and b hitting the halfspace of the clipping plane:

Since our function requires that both a and b be on opposite sides of the plane, two of these cases are dropped. All that is left is to see on which side c lays. From here, the orientation of all three vertices are known; intersection points and output vertices can be directly computed.

In order to use the SliceTriangle() function, we must find an intersecting edge of a triangle. The below implementation is efficient, and can be used upon all triangles in the simulation to be potentially split:

After computing the signed distance of each triangle vertex to the splitting plane, multiplication can be used to see whether two distinct points lay on opposite sides of a plane. Since the distances will be of a positive and negative float within a pair, the product of the two multiplied together must be negative. This allows for the use of a simple predicate to see if two points lay on either side of a plane: #define OnOppositeSides( distanceA, distanceB ) ((distanceA) * (distanceB) < 0.0f).

Once any edge is found to be intersecting with the splitting plane, the triangle vertices are renamed and shifted and immediately passed along to the interiorSliceTriangle function. In this way, the first intersecting edge found is renamed toab.

Here is what a final working product may look like:

Splitting triangles along cutting planes dynamically via user interaction.
Splitting triangles along cutting planes dynamically via user interaction.

Splitting triangles in this manner accounts for each triangle independently, and the algorithm presented here extends, without any additional authoring, from two to three dimensions. This form of triangle clipping is ideal when adjacency info of triangles is not required, or when clipped triangles are not stored anywhere in memory. This is often the case when computing volumes of intersections, as in buoyancy simulation.

The only problem with splitting triangles in isolation is that there is no information about triangles that are adjacent to one another. If you examine the above GIF carefully, you can see that many triangles share collinear vertices, and as a result can be collapsed into a single triangle in order to be rendered efficiently. The next section of this article addresses this problem with another, more complex, algorithm (which makes use of all the tactics present in this section).

Now for the final topic. Assuming a working understanding of the Sutherland-Hodgman algorithm, it is quite easy to extend this understanding to split a shape with a plane and output vertices on both sides of the plane. Numerical robustness can (and should) also be considered.

Let's briefly examine the old Sutherland-Hodgman clipping cases:

These three cases work decently, but don't actually take into account the thickness of the splitting plane. As a result, numerical error can drift in when objects are moving, causing low temporal frame coherence. This sort of numerical instability can result in a corner being clipped one frame and not in another frame, and this sort of jittering can be quite ugly visually, or unacceptable for physical simulation.

Another benefit of this thick plane test is that points lying very near the plane can actually be considered as being on the plane, which makes the clipped geometry slightly more useful. It is entirely possible for a computed intersection point to numerically lay on the wrong side of a plane! Thick planes avoid such weird problems.

By using thick planes for intersection tests, a new type of case can be added: a point laying directly on on a plane.

Sutherland-Hodgman should be modified like so (with a floating point EPSILON to account for thick planes):

However, this form of Sutherland-Hodgman only outputs vertices on one side of the splitting plane. These five cases can easily be extended to a set of nine to output vertices on either side of a splitting plane:

An implementation of these nine cases might look like the following (derived fromEricson's Real-Time Collision Detection):

Here is an example of Sutherland-Hodgman in action:

Splitting a polygon via Sutherland-Hodgman by user interaction. Polygons are triangulated as a triangle fan.
Splitting a polygon dynamically via Sutherland-Hodgman by user interaction. Polygons are triangulated as a triangle fan.

It's worth noting that the final polygons can be rendered as a vertex list with the format oftriangle fan.

There is one problem that you should be aware of: when computing an intersection point of ab and a splitting plane, this point suffers from numerical quantization. This means that any intersection value is an approximation of the actual intersection value. It also means that the intersection point ba is not numerically the same; tiny numerical drift will actually result in two different values!

Example of a visible crack between triangles as a result of inconsistent clipping (image inspired by Ericson's Real-Time Collision Detection book).Example of a visible crack between triangles as a result of inconsistent clipping (image inspired by Ericson's Real-Time Collision Detection book).

A naive clipping routine can make a big mistake of computing intersection points blindly, which can result in T-junctions or other gaps in geometry. To avoid such a problem, a consistent intersection orientation must be used. By convention, points should be clipped from one side of a plane to another. This strict intersection ordering ensures that the same intersection point is calculated, and will resolve potential gaps in geometry (as shown in the image above, there's a consistent clipping result on the right).

In order to actually render textures over split shapes (perhaps when splitting sprites), you'll need an understanding of UV coordinates. A complete discussion of UV coordinates and texture mapping is way beyond the scope of this article, but if you already have this understanding, it should be easy to transform intersection points into UV space.

Please understand that a transformation from world space to UV space requires achange of basis transform. I'll leave UV transformations as an exercise for the reader!

Advertisement

In this tutorial, we looked at some simple linear algebra techniques for tackling the problem of dynamically splitting generic shapes. I also addressed some numerical robustness issues. You should now be able to implement your own shape splitting code, or use the demonstration source, to achieve many advanced and interesting effects or features for general game programming.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值