Skip to content

Commit e7c10f9

Browse files
fireStevenGeens
andcommitted
Fix CSG edge case causing intersection line to hit on common edge of 2 triangles.
The previous implementation assumed that the intersection entered or exited a shape when it hit right on the common edge of 2 triangles. However, there is also a case where it just "skirts" the other shape on the outside. To fix this, we added code to check the intersection distance and if the normals of the faces are pointed in the same direction as the intersection or not (e.g. inner product > 0). This handles the case where the intersection line hits the common edge of 2 triangles and skirts the other shape on the outside. Extended code to cover a third case. Fixes godotengine#58637. Co-authored-by: OldBelge <StevenGeens@users.noreply.github.com>
1 parent fdf66b3 commit e7c10f9

File tree

4 files changed

+80
-17
lines changed

4 files changed

+80
-17
lines changed

modules/csg/csg.cpp

+66-14
Original file line numberDiff line numberDiff line change
@@ -526,17 +526,19 @@ int CSGBrushOperation::MeshMerge::_create_bvh(FaceBVH *facebvhptr, FaceBVH **fac
526526
return index;
527527
}
528528

529-
void CSGBrushOperation::MeshMerge::_add_distance(List<real_t> &r_intersectionsA, List<real_t> &r_intersectionsB, bool p_from_B, real_t p_distance) const {
530-
List<real_t> &intersections = p_from_B ? r_intersectionsB : r_intersectionsA;
529+
void CSGBrushOperation::MeshMerge::_add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance_squared, bool p_is_conormal) const {
530+
List<IntersectionDistance> &intersections = p_from_B ? r_intersectionsB : r_intersectionsA;
531531

532532
// Check if distance exists.
533-
for (const real_t E : intersections) {
534-
if (Math::is_equal_approx(E, p_distance)) {
533+
for (const IntersectionDistance E : intersections) {
534+
if (E.is_conormal == p_is_conormal && Math::is_equal_approx(E.distance_squared, p_distance_squared)) {
535535
return;
536536
}
537537
}
538-
539-
intersections.push_back(p_distance);
538+
IntersectionDistance IntersectionDistance;
539+
IntersectionDistance.is_conormal = p_is_conormal;
540+
IntersectionDistance.distance_squared = p_distance_squared;
541+
intersections.push_back(IntersectionDistance);
540542
}
541543

542544
bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const {
@@ -561,8 +563,11 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
561563
VISITED_BIT_MASK = ~NODE_IDX_MASK
562564
};
563565

564-
List<real_t> intersectionsA;
565-
List<real_t> intersectionsB;
566+
List<IntersectionDistance> intersectionsA;
567+
List<IntersectionDistance> intersectionsB;
568+
569+
Intersection closest_intersection;
570+
closest_intersection.found = false;
566571

567572
int level = 0;
568573
int pos = p_bvh_first;
@@ -587,17 +592,61 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
587592
};
588593
Vector3 current_normal = Plane(current_points[0], current_points[1], current_points[2]).normal;
589594
Vector3 intersection_point;
590-
591595
// Check if faces are co-planar.
592596
if (current_normal.is_equal_approx(face_normal) &&
593597
is_point_in_triangle(face_center, current_points)) {
594598
// Only add an intersection if not a B face.
595599
if (!face.from_b) {
596-
_add_distance(intersectionsA, intersectionsB, current_face.from_b, 0);
600+
_add_distance(intersectionsA, intersectionsB, current_face.from_b, 0, true);
597601
}
598602
} else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) {
599-
real_t distance = face_center.distance_to(intersection_point);
600-
_add_distance(intersectionsA, intersectionsB, current_face.from_b, distance);
603+
real_t distance_squared = face_center.distance_squared_to(intersection_point);
604+
real_t inner = current_normal.dot(face_normal);
605+
// If the faces are perpendicular, ignore this face.
606+
// The triangles on the side should be intersected and result in the correct behavior.
607+
if (!Math::is_zero_approx(inner)) {
608+
_add_distance(intersectionsA, intersectionsB, current_face.from_b, distance_squared, inner > 0.0f);
609+
}
610+
}
611+
612+
if (face.from_b != current_face.from_b) {
613+
if (current_normal.is_equal_approx(face_normal) &&
614+
is_point_in_triangle(face_center, current_points)) {
615+
// Only add an intersection if not a B face.
616+
if (!face.from_b) {
617+
closest_intersection.found = true;
618+
closest_intersection.conormal = 1.0f;
619+
closest_intersection.distance_squared = 0.0f;
620+
closest_intersection.origin_angle = -FLT_MAX;
621+
}
622+
} else if (ray_intersects_triangle(face_center, face_normal, current_points, CMP_EPSILON, intersection_point)) {
623+
Intersection potential_intersection;
624+
potential_intersection.found = true;
625+
potential_intersection.conormal = face_normal.dot(current_normal);
626+
potential_intersection.distance_squared = face_center.distance_squared_to(intersection_point);
627+
potential_intersection.origin_angle = Math::abs(potential_intersection.conormal);
628+
real_t intersection_dist_from_face = face_normal.dot(intersection_point - face_center);
629+
for (int i = 0; i < 3; i++) {
630+
real_t point_dist_from_face = face_normal.dot(current_points[i] - face_center);
631+
if (!Math::is_equal_approx(point_dist_from_face, intersection_dist_from_face) &&
632+
point_dist_from_face < intersection_dist_from_face) {
633+
potential_intersection.origin_angle = -potential_intersection.origin_angle;
634+
break;
635+
}
636+
}
637+
if (potential_intersection.conormal != 0.0f) {
638+
if (!closest_intersection.found) {
639+
closest_intersection = potential_intersection;
640+
} else if (!Math::is_equal_approx(potential_intersection.distance_squared, closest_intersection.distance_squared) &&
641+
potential_intersection.distance_squared < closest_intersection.distance_squared) {
642+
closest_intersection = potential_intersection;
643+
} else if (Math::is_equal_approx(potential_intersection.distance_squared, closest_intersection.distance_squared)) {
644+
if (potential_intersection.origin_angle < closest_intersection.origin_angle) {
645+
closest_intersection = potential_intersection;
646+
}
647+
}
648+
}
649+
}
601650
}
602651
}
603652

@@ -652,8 +701,11 @@ bool CSGBrushOperation::MeshMerge::_bvh_inside(FaceBVH *facebvhptr, int p_max_de
652701
}
653702
}
654703

655-
// Inside if face normal intersects other faces an odd number of times.
656-
return (intersectionsA.size() + intersectionsB.size()) & 1;
704+
if (!closest_intersection.found) {
705+
return false;
706+
} else {
707+
return closest_intersection.conormal > 0.0f;
708+
}
657709
}
658710

659711
void CSGBrushOperation::MeshMerge::mark_inside_faces() {

modules/csg/csg.h

+12-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,17 @@ struct CSGBrushOperation {
135135
return h;
136136
}
137137
};
138+
struct Intersection {
139+
bool found = false;
140+
real_t conormal = FLT_MAX;
141+
real_t distance_squared = FLT_MAX;
142+
real_t origin_angle = FLT_MAX;
143+
};
144+
145+
struct IntersectionDistance {
146+
bool is_conormal;
147+
real_t distance_squared;
148+
};
138149

139150
Vector<Vector3> points;
140151
Vector<Face> faces;
@@ -143,7 +154,7 @@ struct CSGBrushOperation {
143154
OAHashMap<VertexKey, int, VertexKeyHash> snap_cache;
144155
float vertex_snap = 0.0;
145156

146-
inline void _add_distance(List<real_t> &r_intersectionsA, List<real_t> &r_intersectionsB, bool p_from_B, real_t p_distance) const;
157+
inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const;
147158
inline bool _bvh_inside(FaceBVH *facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const;
148159
inline int _create_bvh(FaceBVH *facebvhptr, FaceBVH **facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc);
149160

modules/csg/csg_shape.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ void CSGShape3D::_bind_methods() {
653653
ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes);
654654

655655
ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation");
656-
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.0001,1,0.001,suffix:m"), "set_snap", "get_snap");
656+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap");
657657
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents");
658658

659659
ADD_GROUP("Collision", "collision_");

modules/csg/doc_classes/CSGShape3D.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent.
7474
</member>
7575
<member name="snap" type="float" setter="set_snap" getter="get_snap" default="0.001">
76-
Snap makes the mesh snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust.
76+
Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust.
7777
</member>
7878
<member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false">
7979
Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority].

0 commit comments

Comments
 (0)