Skip to content

Commit fb5ba33

Browse files
author
Unity Technologies
committed
com.unity.physics@1.3.10
## [1.3.10] - 2025-02-17 ### Fixed * Incorrect vertex orientation during baking process in `Unity.Physics.CapsuleCollider` where vertices were unexpectedly flipped in certain conditions. * Visual artifacts in `PhysicsDebugDisplay` where capsule edges were incorrectly rendered when Height < (2 * Radius). * Capsule edge stretching in `PhysicsDebugDisplay` that occurred with non-default height-to-radius ratios.
1 parent f4ee5a3 commit fb5ba33

File tree

11 files changed

+142
-19
lines changed

11 files changed

+142
-19
lines changed

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ uid: unity-physics-changelog
44

55
# Changelog
66

7+
## [1.3.10] - 2025-02-17
8+
9+
### Fixed
10+
11+
* Incorrect vertex orientation during baking process in `Unity.Physics.CapsuleCollider` where vertices were unexpectedly flipped in certain conditions.
12+
* Visual artifacts in `PhysicsDebugDisplay` where capsule edges were incorrectly rendered when Height < (2 * Radius).
13+
* Capsule edge stretching in `PhysicsDebugDisplay` that occurred with non-default height-to-radius ratios.
14+
15+
716
## [1.3.9] - 2025-01-16
817

918
### Added
@@ -20,10 +29,8 @@ uid: unity-physics-changelog
2029
* The `Enable Integrity Checks` setting in the `Project Settings` under `Physics -> Unity Physics` now correctly reflects whether the integrity checks are enabled or disabled. Previously, the setting indicated the opposite.
2130

2231

23-
2432
## [1.3.8] - 2024-11-08
2533

26-
2734
### Removed
2835

2936
* removing various material assets within unity/physics (PhysicsDynamicDebugMaterial.mat and Material.mat (HullGeneration scene)).

Documentation~/TableOfContents.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@
3535
* [Modifying simulation behavior](simulation-modification.md)
3636
* [Simulation results](simulation-results.md)
3737
* [Glossary](glossary.md)
38+
* [Troubleshooting](troubleshooting.md)
39+
* [Ghost collision](ghost-collision.md)

Documentation~/ghost-collision.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Ghost Collision
2+
Ghost collisions are a well documented issue in game physics systems. They occur when dynamic objects moving across a surface bump into unintended collisions at the boundaries or connections between adjacent physics shapes. These collisions arise due to the discrete nature of collision detection and the way physics systems calculate the interaction between shapes.
3+
4+
Instead of moving smoothly across a surface, the object might **"catch"** on the edge of a shape as a result of this separating force, leading to unnatural behavior such as bouncing or stopping abruptly.
5+
6+
This issue typically does not occur on continuous surfaces but becomes prominent when:
7+
8+
- Shapes are composed of multiple connected elements, like triangles in a mesh.
9+
- Shapes are convex and overlap with adjacent edges or vertices.
10+
11+
A classic example of ghost collisions can be seen in a box sliding across two connected edges. As it transitions from one edge to the next, the physics system might interpret the connection as a collision and generate a response force that blocks or redirects the box's movement. As the image illustrates in the following scenario, a box falls from the top of a sloped mesh surface.
12+
13+
Instead of sliding smoothly, the box collides with every segment of the mesh. This leads to unexpected behavior, such as the box stopping or experiencing abnormal friction as it moves. The collision system incorrectly tries to stop the box while simultaneously pushing it along the surface's normal direction. During this process, contact points are registered along the mesh's surface. These points, indicated by pink lines, represent the areas where the box intersects with the surface. As a result, these contact points alter the dynamics of the collision, affecting how the box interacts with the surface.
14+
15+
![collision_ghost](images/collision-ghost-example.gif)
16+
17+
## Possible causes
18+
- **Edge Connections:** Adjacent shapes, such as triangles or line segments, share vertices or edges. The physics engine calculates interactions per **shape independently**.
19+
20+
- **Separating Planes:** In collision detection, a separating plane is defined to prevent objects from overlapping each other. When the plane is misaligned due to edge transitions, the object might collide with it unnecessarily.
21+
22+
- **Discrete Time Steps:** Physics engines operate in discrete simulation steps. If an object moves rapidly between frames, it might erroneously interact with shapes it shouldn't have encountered in a continuous simulation.
23+
24+
- **Shape Complexity:** More complex shapes, such as those with high triangle counts or multiple shared vertices, increase the likelihood of ghost collisions.
25+
26+
## To mitigate ghost collisions, try techniques such as:
27+
28+
- **Narrowphase Contact Modification:** Narrowphase modifies collision predictions by smoothing the interaction between connected shapes. It ensures that contact points across adjacent shapes are treated as part of a continuous surface rather than separate elements. [Checkout Unity Physics Samples for more details](#narrow-phase-contacts-solution).
29+
30+
- **Reducing Shape Complexity:** Simplifying collision shapes by reducing triangle counts or using convex hull approximations minimizes the potential for ghost collisions.
31+
32+
- **Smaller Time Steps:** Reducing the simulation's delta time enhances collision detection accuracy, minimizing the likelihood of ghost collisions. However, keep in mind that while a smaller time step increases precision, it can also impact performance, so it's important to balance accuracy with computational efficiency.
33+
34+
- **Voronoi Regions (Optional custom solution):** A Voronoi region defines the valid range of collision normals for an edge. If a calculated collision normal lies outside this range, it is ignored or adjusted to align with the nearest valid normal.
35+
36+
- **Shape Chains (Optional custom solution):** For static geometry to provide context about neighboring shapes, enabling the system to eliminate ghost collisions by leveraging ghost vertices for smoother transitions.
37+
38+
## Practical Examples
39+
- **Mesh Colliders:** Ghost collisions frequently appear in mesh colliders where the dynamic body interacts with individual triangles. For example, a car driving over a terrain mesh might bounce unexpectedly at triangle edges.
40+
41+
- **Vehicles:** Representing a car as a single convex hull (excluding wheels) can significantly reduce ghost collision artifacts. By avoiding multiple small shapes, the physics engine treats the vehicle as a single entity, ensuring smoother interactions.
42+
43+
- **Game Worlds:** For large, complex levels, using chain shapes or composite colliders in **"outline"** mode can create continuous surfaces that minimize ghost collisions.
44+
45+
## Narrow Phase Contacts solution
46+
47+
In a physics simulation, the narrow phase is responsible for resolving collisions between objects. Within this system, the [narrow-phase](https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/PhysicsSamples/Assets/9.%20Modify/Scripts/ModifyNarrowphaseContactsBehaviour.cs) contact solution efficiently manages collisions involving composite mesh colliders by calculating precise surface normals for each contact point and adjusting them based on the surfaces they interact with. This ensures accurate contact resolution, preventing issues such as objects improperly sticking together or interpenetrating. The method is optimized for handling complex colliders and improves performance, especially when working with large mesh colliders.
48+
49+
**ModifyNormalsJob:** This job iterates over contact points and adjusts the normal when the entity involved matches the specified surface entity. If a mesh is involved, it retrieves the surface normal from the polygon plane and adjusts the contact normal accordingly.
50+
51+
$$
52+
\text{dot product} = \mathbf{v1} \cdot \mathbf{v2} = |\mathbf{v1}| |\mathbf{v2}| \cos(\theta)
53+
$$
54+
55+
$$
56+
\text{dot product} = \mathbf{surface Normal} \cdot \mathbf{contactHeader Normal} = |\mathbf{surface Normal}| |\mathbf{contactHeader Normal}| \cos(\theta)
57+
$$
58+
59+
60+
**Job Check and Surface Evaluation**
61+
The first step is to check if either of the entities involved in the collision corresponds to the surface entity:
62+
63+
```csharp
64+
bool isBodyA = (contactHeader.EntityA == SurfaceEntity); // is the entity A the surface?
65+
bool isBodyB = (contactHeader.EntityB == SurfaceEntity); // is the entity B the surface?
66+
```
67+
68+
If either entity is the surface, the next step is to determine if the entity has any leaf or child colliders attached to the main collider. This is relevant when dealing with mesh surfaces with multiple attached leaf colliders.
69+
70+
```csharp
71+
// if we have a mesh surface we can get the surface normal from the plane of the polygon
72+
var rbIdx = CollisionWorld.GetRigidBodyIndex(SurfaceEntity);
73+
var body = CollisionWorld.Bodies[rbIdx];
74+
75+
// this is only called in case the mesh has multiple Leaf attached to the main Coillider
76+
if (body.Collider.Value.CollisionType == CollisionType.Composite)
77+
{
78+
unsafe
79+
{
80+
body.Collider.Value.GetLeaf(isBodyA ? contactHeader.ColliderKeyA : contactHeader.ColliderKeyB, out ChildCollider leafCollider);
81+
if (leafCollider.Collider->Type == ColliderType.Triangle || leafCollider.Collider->Type == ColliderType.Quad)
82+
{
83+
PolygonCollider* polygonCollider = (PolygonCollider*)leafCollider.Collider;
84+
// Potential optimization: If TransformFromChild has no rotation just use body.WorldFromBody.rot
85+
// This is likely if you only have a MeshCollider with no hierarchy.
86+
quaternion rotation = math.mul(body.WorldFromBody.rot, leafCollider.TransformFromChild.rot);
87+
float3 surfaceNormal = math.rotate(rotation, polygonCollider->Planes[0].Normal);
88+
distanceScale = math.dot(surfaceNormal, contactHeader.Normal);
89+
contactHeader.Normal = surfaceNormal;
90+
}
91+
}
92+
}
93+
```
94+
Finally, the `Distance` value is adjusted by applying the computed `distanceScale`. If the collider is not a composite collider, the result will be `0` for the distance by default.
95+
96+
```csharp
97+
contactPoint.Distance *= distanceScale;
98+
```
99+
100+
It also scales the distance based on the dot product between the first contact normal on the surface and the contact point normal, adjusting the distance used for collision calculations to ensure greater accuracy with the current position of the collider.
101+
102+
The result of applying this approach to the ghost collision issue described in the **first image** is that the box now slides more **smoothly** across the surface, even with multiple contact points (represented by the pink lines). These contact points are evaluated against the surface normal based on the first collision detection point and its associated surface normal.
103+
104+
![narrow_phase](images/narrow-phase-example.gif)
868 KB
Loading
309 KB
Loading

Documentation~/troubleshooting.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Troubleshooting
2+
3+
This section provides a comprehensive guide to common issues found when using the package and offers practical solutions to address them efficiently, ensuring a smoother workflow by equipping users with the knowledge needed to navigate challenges and maximize the effectiveness of the package.
4+
5+
## Key Features:
6+
7+
| **Known Issues** | **Solutions and Workarounds** |
8+
|----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
9+
| [**Ghost collisions**](ghost-collision.md) | This could be caused by continuous collision detection, particularly with fast moving objects or small colliders like triangles. Try [Enabling Narrowphase Contacts](https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/PhysicsSamples/Assets/9.%20Modify/Scripts/ModifyNarrowphaseContactsBehaviour.cs) |
10+

Unity.Physics.Hybrid/EntitiesBaking/BakingSystems/ColliderBakingSystem.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,11 @@ protected override ShapeComputationDataBaking GenerateComputationData(UnityEngin
323323
// the baked radius is the user-specified shape radius times the maximum of the scale of the two axes orthogonal to the capsule axis.
324324
var radius = shape.radius * math.cmax(new float3(lossyScale) { [shape.direction] = 0f });
325325

326+
// Ensure the capsule height is at least twice the radius to prevent inversion
327+
var height = math.max(shape.height * lossyScale[shape.direction], 2f * radius);
328+
326329
// the capsule vertex offset points from the center of the capsule to the top of the capsule's cylindrical center part.
327-
var vertexOffset = capsuleAxis * (0.5f * shape.height * lossyScale[shape.direction] - radius);
330+
var vertexOffset = capsuleAxis * (0.5f * height - radius);
328331

329332
// finish baking the vertex offset by rotating it with the bake matrix
330333
vertexOffset = math.rotate(bakeToShape.rotation, vertexOffset);

Unity.Physics.Hybrid/Utilities/DebugDisplay/DisplayColliderEdgesSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ void ExpandHullVertices(ref float3x2 vertices, ref ConvexHull convexHull)
149149
center = -0.5f * (vertex1 - vertex0) + vertex1;
150150
var axis = vertex1 - vertex0; //axis in wfc-space
151151
var colliderOrientation = Quaternion.FromToRotation(Vector3.up, -axis);
152-
height = 0.5f * math.length(axis) + radius;
153-
DrawColliderUtility.DrawPrimitiveCapsuleEdges(radius, height, center, colliderOrientation, worldFromConvex, ref geometries.CapsuleGeometry, uniformScale);
152+
height = math.length(axis) + 2 * radius;
153+
DrawColliderUtility.DrawPrimitiveCapsuleEdges(radius, height, center, colliderOrientation, worldFromConvex, uniformScale);
154154
break;
155155

156156
case ColliderType.Cylinder:

Unity.Physics.Hybrid/Utilities/DebugDisplay/DrawColliderUtility.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,13 @@ public static void DrawPrimitiveSphereFaces(float radius, float3 center, RigidTr
237237
}
238238
}
239239

240-
public static void DrawPrimitiveCapsuleEdges(float radius, float height, float3 center, Quaternion orientation, RigidTransform wfc, ref ColliderGeometry capsuleGeometry, float uniformScale)
240+
public static void DrawPrimitiveCapsuleEdges(float radius, float height, float3 center, Quaternion orientation, RigidTransform wfc, float uniformScale)
241241
{
242242
var edgesColor = ColorIndex.Green;
243-
var shapeScale = new float3(2.0f * radius, height, 2.0f * radius);
244-
var capsuleTransform = float4x4.TRS(center * uniformScale, orientation, shapeScale * uniformScale);
243+
var capsuleTransform = float4x4.TRS(center * uniformScale, orientation, uniformScale);
245244
var worldTransform = math.mul(new float4x4(wfc), capsuleTransform);
246245

247-
var capsuleEdges = capsuleGeometry.EdgesArray;
246+
var capsuleEdges = CreateCapsuleWireFrame(Allocator.Temp, radius, height);
248247
var lineVertices = new NativeArray<float3>(capsuleEdges.Length, Allocator.Temp);
249248
try
250249
{
@@ -396,10 +395,8 @@ private static void GetWireArcSegments(ref NativeArray<Vector3> segmentArray, in
396395
// Create a wireframe capsule with a default orientation along the y-axis. Use the general method of DrawWireArc
397396
// declared in GizmoUtil.cpp and with CapsuleBoundsHandle.DrawWireframe(). Output is an array that comprises
398397
// pairs of vertices to be used in the drawing of Capsule Collider Edges.
399-
static NativeArray<Vector3> CreateCapsuleWireFrame(Allocator allocator)
398+
static NativeArray<Vector3> CreateCapsuleWireFrame(Allocator allocator, float radius = 0.5f, float height = 2.0f)
400399
{
401-
const float radius = 0.5f;
402-
const float height = 2.0f;
403400
const int mHeightAxis = 1; //corresponds to the y-axis
404401
var center = new float3(0, 0, 0);
405402

ValidationExceptions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"ErrorExceptions": [
33
{
44
"ValidationTest": "API Validation",
5-
"PackageVersion": "1.3.9"
5+
"PackageVersion": "1.3.10"
66
}
77
],
88
"WarningExceptions": []

0 commit comments

Comments
 (0)