Fix Transform::xform(Plane) functions

The Transform::xform and xform_inv are made safe for Planes when using non-uniform scaling.
Optimization of calling sites to prevent loss of performance from the changes to xform(Plane).
This commit is contained in:
lawnjelly 2021-08-07 11:10:50 +01:00
parent 3c85ef14c3
commit dd0f54a368
2 changed files with 77 additions and 33 deletions

View file

@ -75,16 +75,24 @@ public:
bool operator!=(const Transform3D &p_transform) const;
_FORCE_INLINE_ Vector3 xform(const Vector3 &p_vector) const;
_FORCE_INLINE_ Vector3 xform_inv(const Vector3 &p_vector) const;
_FORCE_INLINE_ AABB xform(const AABB &p_aabb) const;
_FORCE_INLINE_ Vector<Vector3> xform(const Vector<Vector3> &p_array) const;
// NOTE: These are UNSAFE with non-uniform scaling, and will produce incorrect results.
// They use the transpose.
// For safe inverse transforms, xform by the affine_inverse.
_FORCE_INLINE_ Vector3 xform_inv(const Vector3 &p_vector) const;
_FORCE_INLINE_ AABB xform_inv(const AABB &p_aabb) const;
_FORCE_INLINE_ Vector<Vector3> xform_inv(const Vector<Vector3> &p_array) const;
// Safe with non-uniform scaling (uses affine_inverse).
_FORCE_INLINE_ Plane xform(const Plane &p_plane) const;
_FORCE_INLINE_ Plane xform_inv(const Plane &p_plane) const;
_FORCE_INLINE_ AABB xform(const AABB &p_aabb) const;
_FORCE_INLINE_ AABB xform_inv(const AABB &p_aabb) const;
_FORCE_INLINE_ Vector<Vector3> xform(const Vector<Vector3> &p_array) const;
_FORCE_INLINE_ Vector<Vector3> xform_inv(const Vector<Vector3> &p_array) const;
// These fast versions use precomputed affine inverse, and should be used in bottleneck areas where
// multiple planes are to be transformed.
_FORCE_INLINE_ Plane xform_fast(const Plane &p_plane, const Basis &p_basis_inverse_transpose) const;
static _FORCE_INLINE_ Plane xform_inv_fast(const Plane &p_plane, const Transform3D &p_inverse, const Basis &p_basis_transpose);
void operator*=(const Transform3D &p_transform);
Transform3D operator*(const Transform3D &p_transform) const;
@ -130,30 +138,20 @@ _FORCE_INLINE_ Vector3 Transform3D::xform_inv(const Vector3 &p_vector) const {
(basis.elements[0][2] * v.x) + (basis.elements[1][2] * v.y) + (basis.elements[2][2] * v.z));
}
// Neither the plane regular xform or xform_inv are particularly efficient,
// as they do a basis inverse. For xforming a large number
// of planes it is better to pre-calculate the inverse transpose basis once
// and reuse it for each plane, by using the 'fast' version of the functions.
_FORCE_INLINE_ Plane Transform3D::xform(const Plane &p_plane) const {
Vector3 point = p_plane.normal * p_plane.d;
Vector3 point_dir = point + p_plane.normal;
point = xform(point);
point_dir = xform(point_dir);
Vector3 normal = point_dir - point;
normal.normalize();
real_t d = normal.dot(point);
return Plane(normal, d);
Basis b = basis.inverse();
b.transpose();
return xform_fast(p_plane, b);
}
_FORCE_INLINE_ Plane Transform3D::xform_inv(const Plane &p_plane) const {
Vector3 point = p_plane.normal * p_plane.d;
Vector3 point_dir = point + p_plane.normal;
point = xform_inv(point);
point_dir = xform_inv(point_dir);
Vector3 normal = point_dir - point;
normal.normalize();
real_t d = normal.dot(point);
return Plane(normal, d);
Transform3D inv = affine_inverse();
Basis basis_transpose = basis.transposed();
return xform_inv_fast(p_plane, inv, basis_transpose);
}
_FORCE_INLINE_ AABB Transform3D::xform(const AABB &p_aabb) const {
@ -231,4 +229,37 @@ Vector<Vector3> Transform3D::xform_inv(const Vector<Vector3> &p_array) const {
return array;
}
_FORCE_INLINE_ Plane Transform3D::xform_fast(const Plane &p_plane, const Basis &p_basis_inverse_transpose) const {
// Transform a single point on the plane.
Vector3 point = p_plane.normal * p_plane.d;
point = xform(point);
// Use inverse transpose for correct normals with non-uniform scaling.
Vector3 normal = p_basis_inverse_transpose.xform(p_plane.normal);
normal.normalize();
real_t d = normal.dot(point);
return Plane(normal, d);
}
_FORCE_INLINE_ Plane Transform3D::xform_inv_fast(const Plane &p_plane, const Transform3D &p_inverse, const Basis &p_basis_transpose) {
// Transform a single point on the plane.
Vector3 point = p_plane.normal * p_plane.d;
point = p_inverse.xform(point);
// Note that instead of precalculating the transpose, an alternative
// would be to use the transpose for the basis transform.
// However that would be less SIMD friendly (requiring a swizzle).
// So the cost is one extra precalced value in the calling code.
// This is probably worth it, as this could be used in bottleneck areas. And
// where it is not a bottleneck, the non-fast method is fine.
// Use transpose for correct normals with non-uniform scaling.
Vector3 normal = p_basis_transpose.xform(p_plane.normal);
normal.normalize();
real_t d = normal.dot(point);
return Plane(normal, d);
}
#endif // TRANSFORM_H

View file

@ -956,9 +956,12 @@ static void _collision_sphere_convex_polygon(const Shape3DSW *p_a, const Transfo
const Vector3 *vertices = mesh.vertices.ptr();
int vertex_count = mesh.vertices.size();
// Precalculating this makes the transforms faster.
Basis b_xform_normal = p_transform_b.basis.inverse().transposed();
// faces of B
for (int i = 0; i < face_count; i++) {
Vector3 axis = p_transform_b.xform(faces[i].plane).normal;
Vector3 axis = b_xform_normal.xform(faces[i].plane.normal).normalized();
if (!separator.test_axis(axis)) {
return;
@ -1379,9 +1382,12 @@ static void _collision_box_convex_polygon(const Shape3DSW *p_a, const Transform3
}
}
// Precalculating this makes the transforms faster.
Basis b_xform_normal = p_transform_b.basis.inverse().transposed();
// faces of B
for (int i = 0; i < face_count; i++) {
Vector3 axis = p_transform_b.xform(faces[i].plane).normal;
Vector3 axis = b_xform_normal.xform(faces[i].plane.normal).normalized();
if (!separator.test_axis(axis)) {
return;
@ -1733,9 +1739,12 @@ static void _collision_capsule_convex_polygon(const Shape3DSW *p_a, const Transf
int edge_count = mesh.edges.size();
const Vector3 *vertices = mesh.vertices.ptr();
// Precalculating this makes the transforms faster.
Basis b_xform_normal = p_transform_b.basis.inverse().transposed();
// faces of B
for (int i = 0; i < face_count; i++) {
Vector3 axis = p_transform_b.xform(faces[i].plane).normal;
Vector3 axis = b_xform_normal.xform(faces[i].plane.normal).normalized();
if (!separator.test_axis(axis)) {
return;
@ -2057,20 +2066,24 @@ static void _collision_convex_polygon_convex_polygon(const Shape3DSW *p_a, const
const Vector3 *vertices_B = mesh_B.vertices.ptr();
int vertex_count_B = mesh_B.vertices.size();
// Precalculating this makes the transforms faster.
Basis a_xform_normal = p_transform_b.basis.inverse().transposed();
// faces of A
for (int i = 0; i < face_count_A; i++) {
Vector3 axis = p_transform_a.xform(faces_A[i].plane).normal;
//Vector3 axis = p_transform_a.basis.xform( faces_A[i].plane.normal ).normalized();
Vector3 axis = a_xform_normal.xform(faces_A[i].plane.normal).normalized();
if (!separator.test_axis(axis)) {
return;
}
}
// Precalculating this makes the transforms faster.
Basis b_xform_normal = p_transform_b.basis.inverse().transposed();
// faces of B
for (int i = 0; i < face_count_B; i++) {
Vector3 axis = p_transform_b.xform(faces_B[i].plane).normal;
//Vector3 axis = p_transform_b.basis.xform( faces_B[i].plane.normal ).normalized();
Vector3 axis = b_xform_normal.xform(faces_B[i].plane.normal).normalized();
if (!separator.test_axis(axis)) {
return;