diff options
Diffstat (limited to 'thirdparty/thekla_atlas/nvmath/Basis.cpp')
-rw-r--r-- | thirdparty/thekla_atlas/nvmath/Basis.cpp | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/thirdparty/thekla_atlas/nvmath/Basis.cpp b/thirdparty/thekla_atlas/nvmath/Basis.cpp new file mode 100644 index 0000000000..0824179633 --- /dev/null +++ b/thirdparty/thekla_atlas/nvmath/Basis.cpp @@ -0,0 +1,270 @@ +// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com> + +#include "Basis.h" + +using namespace nv; + + +/// Normalize basis vectors. +void Basis::normalize(float epsilon /*= NV_EPSILON*/) +{ + normal = ::normalizeSafe(normal, Vector3(0.0f), epsilon); + tangent = ::normalizeSafe(tangent, Vector3(0.0f), epsilon); + bitangent = ::normalizeSafe(bitangent, Vector3(0.0f), epsilon); +} + + +/// Gram-Schmidt orthogonalization. +/// @note Works only if the vectors are close to orthogonal. +void Basis::orthonormalize(float epsilon /*= NV_EPSILON*/) +{ + // N' = |N| + // T' = |T - (N' dot T) N'| + // B' = |B - (N' dot B) N' - (T' dot B) T'| + + normal = ::normalize(normal, epsilon); + + tangent -= normal * dot(normal, tangent); + tangent = ::normalize(tangent, epsilon); + + bitangent -= normal * dot(normal, bitangent); + bitangent -= tangent * dot(tangent, bitangent); + bitangent = ::normalize(bitangent, epsilon); +} + + + + +/// Robust orthonormalization. +/// Returns an orthonormal basis even when the original is degenerate. +void Basis::robustOrthonormalize(float epsilon /*= NV_EPSILON*/) +{ + // Normalize all vectors. + normalize(epsilon); + + if (lengthSquared(normal) < epsilon*epsilon) + { + // Build normal from tangent and bitangent. + normal = cross(tangent, bitangent); + + if (lengthSquared(normal) < epsilon*epsilon) + { + // Arbitrary basis. + tangent = Vector3(1, 0, 0); + bitangent = Vector3(0, 1, 0); + normal = Vector3(0, 0, 1); + return; + } + + normal = nv::normalize(normal, epsilon); + } + + // Project tangents to normal plane. + tangent -= normal * dot(normal, tangent); + bitangent -= normal * dot(normal, bitangent); + + if (lengthSquared(tangent) < epsilon*epsilon) + { + if (lengthSquared(bitangent) < epsilon*epsilon) + { + // Arbitrary basis. + buildFrameForDirection(normal); + } + else + { + // Build tangent from bitangent. + bitangent = nv::normalize(bitangent, epsilon); + + tangent = cross(bitangent, normal); + nvDebugCheck(isNormalized(tangent, epsilon)); + } + } + else + { + tangent = nv::normalize(tangent, epsilon); +#if 0 + bitangent -= tangent * dot(tangent, bitangent); + + if (lengthSquared(bitangent) < epsilon*epsilon) + { + bitangent = cross(tangent, normal); + nvDebugCheck(isNormalized(bitangent, epsilon)); + } + else + { + bitangent = nv::normalize(bitangent, epsilon); + } +#else + if (lengthSquared(bitangent) < epsilon*epsilon) + { + // Build bitangent from tangent. + bitangent = cross(tangent, normal); + nvDebugCheck(isNormalized(bitangent, epsilon)); + } + else + { + bitangent = nv::normalize(bitangent, epsilon); + + // At this point tangent and bitangent are orthogonal to normal, but we don't know whether their orientation. + + Vector3 bisector; + if (lengthSquared(tangent + bitangent) < epsilon*epsilon) + { + bisector = tangent; + } + else + { + bisector = nv::normalize(tangent + bitangent); + } + Vector3 axis = nv::normalize(cross(bisector, normal)); + + //nvDebugCheck(isNormalized(axis, epsilon)); + nvDebugCheck(equal(dot(axis, tangent), -dot(axis, bitangent), epsilon)); + + if (dot(axis, tangent) > 0) + { + tangent = bisector + axis; + bitangent = bisector - axis; + } + else + { + tangent = bisector - axis; + bitangent = bisector + axis; + } + + // Make sure the resulting tangents are still perpendicular to the normal. + tangent -= normal * dot(normal, tangent); + bitangent -= normal * dot(normal, bitangent); + + // Double check. + nvDebugCheck(equal(dot(normal, tangent), 0.0f, epsilon)); + nvDebugCheck(equal(dot(normal, bitangent), 0.0f, epsilon)); + + // Normalize. + tangent = nv::normalize(tangent); + bitangent = nv::normalize(bitangent); + + // If tangent and bitangent are not orthogonal, then derive bitangent from tangent, just in case... + if (!equal(dot(tangent, bitangent), 0.0f, epsilon)) { + bitangent = cross(tangent, normal); + bitangent = nv::normalize(bitangent); + } + } +#endif + } + + /*// Check vector lengths. + if (!isNormalized(normal, epsilon)) + { + nvDebug("%f %f %f\n", normal.x, normal.y, normal.z); + nvDebug("%f %f %f\n", tangent.x, tangent.y, tangent.z); + nvDebug("%f %f %f\n", bitangent.x, bitangent.y, bitangent.z); + }*/ + + nvDebugCheck(isNormalized(normal, epsilon)); + nvDebugCheck(isNormalized(tangent, epsilon)); + nvDebugCheck(isNormalized(bitangent, epsilon)); + + // Check vector angles. + nvDebugCheck(equal(dot(normal, tangent), 0.0f, epsilon)); + nvDebugCheck(equal(dot(normal, bitangent), 0.0f, epsilon)); + nvDebugCheck(equal(dot(tangent, bitangent), 0.0f, epsilon)); + + // Check vector orientation. + const float det = dot(cross(normal, tangent), bitangent); + nvDebugCheck(equal(det, 1.0f, epsilon) || equal(det, -1.0f, epsilon)); +} + + +/// Build an arbitrary frame for the given direction. +void Basis::buildFrameForDirection(Vector3::Arg d, float angle/*= 0*/) +{ + nvCheck(isNormalized(d)); + normal = d; + + // Choose minimum axis. + if (fabsf(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z)) + { + tangent = Vector3(1, 0, 0); + } + else if (fabsf(normal.y) < fabsf(normal.z)) + { + tangent = Vector3(0, 1, 0); + } + else + { + tangent = Vector3(0, 0, 1); + } + + // Ortogonalize + tangent -= normal * dot(normal, tangent); + tangent = ::normalize(tangent); + + bitangent = cross(normal, tangent); + + // Rotate frame around normal according to angle. + if (angle != 0.0f) { + float c = cosf(angle); + float s = sinf(angle); + Vector3 tmp = c * tangent - s * bitangent; + bitangent = s * tangent + c * bitangent; + tangent = tmp; + } +} + +bool Basis::isValid() const +{ + if (equal(normal, Vector3(0.0f))) return false; + if (equal(tangent, Vector3(0.0f))) return false; + if (equal(bitangent, Vector3(0.0f))) return false; + + if (equal(determinant(), 0.0f)) return false; + + return true; +} + + +/// Transform by this basis. (From this basis to object space). +Vector3 Basis::transform(Vector3::Arg v) const +{ + Vector3 o = tangent * v.x; + o += bitangent * v.y; + o += normal * v.z; + return o; +} + +/// Transform by the transpose. (From object space to this basis). +Vector3 Basis::transformT(Vector3::Arg v) +{ + return Vector3(dot(tangent, v), dot(bitangent, v), dot(normal, v)); +} + +/// Transform by the inverse. (From object space to this basis). +/// @note Uses Cramer's rule so the inverse is not accurate if the basis is ill-conditioned. +Vector3 Basis::transformI(Vector3::Arg v) const +{ + const float det = determinant(); + nvDebugCheck(!equal(det, 0.0f, 0.0f)); + + const float idet = 1.0f / det; + + // Rows of the inverse matrix. + Vector3 r0( + (bitangent.y * normal.z - bitangent.z * normal.y), + -(bitangent.x * normal.z - bitangent.z * normal.x), + (bitangent.x * normal.y - bitangent.y * normal.x)); + + Vector3 r1( + -(tangent.y * normal.z - tangent.z * normal.y), + (tangent.x * normal.z - tangent.z * normal.x), + -(tangent.x * normal.y - tangent.y * normal.x)); + + Vector3 r2( + (tangent.y * bitangent.z - tangent.z * bitangent.y), + -(tangent.x * bitangent.z - tangent.z * bitangent.x), + (tangent.x * bitangent.y - tangent.y * bitangent.x)); + + return Vector3(dot(v, r0), dot(v, r1), dot(v, r2)) * idet; +} + + |