diff options
Diffstat (limited to 'src/core/MathUtils.cpp')
-rw-r--r-- | src/core/MathUtils.cpp | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/src/core/MathUtils.cpp b/src/core/MathUtils.cpp new file mode 100644 index 0000000..19691d7 --- /dev/null +++ b/src/core/MathUtils.cpp @@ -0,0 +1,603 @@ +/* +Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of Sony Pictures Imageworks nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <OpenColorIO/OpenColorIO.h> + +#include <cstring> + +#include "MathUtils.h" + +OCIO_NAMESPACE_ENTER +{ + namespace + { + const float FLTMIN = std::numeric_limits<float>::min(); + } + + bool IsScalarEqualToZero(float v) + { + return equalWithAbsError(v, 0.0f, FLTMIN); + } + + bool IsScalarEqualToOne(float v) + { + return equalWithAbsError(v, 1.0f, FLTMIN); + } + + float GetSafeScalarInverse(float v, float defaultValue) + { + if(IsScalarEqualToZero(v)) return defaultValue; + return 1.0f / v; + } + + bool IsVecEqualToZero(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(!IsScalarEqualToZero(v[i])) return false; + } + return true; + } + + bool IsVecEqualToOne(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(!IsScalarEqualToOne(v[i])) return false; + } + return true; + } + + bool VecContainsZero(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(IsScalarEqualToZero(v[i])) return true; + } + return false; + } + + bool VecContainsOne(const float* v, int size) + { + for(int i=0; i<size; ++i) + { + if(IsScalarEqualToOne(v[i])) return true; + } + return false; + } + + bool VecsEqualWithRelError(const float* v1, int size1, + const float* v2, int size2, + float e) + { + if(size1 != size2) return false; + for(int i=0; i<size1; ++i) + { + if(!equalWithRelError(v1[i], v2[i], e)) return false; + } + + return true; + } + + double ClampToNormHalf(double val) + { + if(val < -GetHalfMax()) + { + return -GetHalfMax(); + } + + if(val > -GetHalfNormMin() && val<GetHalfNormMin()) + { + return 0.0; + } + + if(val > GetHalfMax()) + { + return GetHalfMax(); + } + + return val; + } + + bool IsM44Identity(const float* m44) + { + int index=0; + + for(unsigned int j=0; j<4; ++j) + { + for(unsigned int i=0; i<4; ++i) + { + index = 4*j+i; + + if(i==j) + { + if(!IsScalarEqualToOne(m44[index])) return false; + } + else + { + if(!IsScalarEqualToZero(m44[index])) return false; + } + } + } + + return true; + } + + bool IsM44Diagonal(const float* m44) + { + for(int i=0; i<16; ++i) + { + if((i%5)==0) continue; // If we're on the diagonal, skip it + if(!IsScalarEqualToZero(m44[i])) return false; + } + + return true; + } + + void GetM44Diagonal(float* out4, const float* m44) + { + for(int i=0; i<4; ++i) + { + out4[i] = m44[i*5]; + } + } + + // We use an intermediate double representation to make sure + // there is minimal float precision error on the determinant's computation + // (We have seen IsScalarEqualToZero sensitivities here on 32-bit + // virtual machines) + + bool GetM44Inverse(float* inverse_out, const float* m_) + { + double m[16]; + for(unsigned int i=0; i<16; ++i) m[i] = (double)m_[i]; + + double d10_21 = m[4]*m[9] - m[5]*m[8]; + double d10_22 = m[4]*m[10] - m[6]*m[8]; + double d10_23 = m[4]*m[11] - m[7]*m[8]; + double d11_22 = m[5]*m[10] - m[6]*m[9]; + double d11_23 = m[5]*m[11] - m[7]*m[9]; + double d12_23 = m[6]*m[11] - m[7]*m[10]; + + double a00 = m[13]*d12_23 - m[14]*d11_23 + m[15]*d11_22; + double a10 = m[14]*d10_23 - m[15]*d10_22 - m[12]*d12_23; + double a20 = m[12]*d11_23 - m[13]*d10_23 + m[15]*d10_21; + double a30 = m[13]*d10_22 - m[14]*d10_21 - m[12]*d11_22; + + double det = a00*m[0] + a10*m[1] + a20*m[2] + a30*m[3]; + + if(IsScalarEqualToZero((float)det)) return false; + + det = 1.0/det; + + double d00_31 = m[0]*m[13] - m[1]*m[12]; + double d00_32 = m[0]*m[14] - m[2]*m[12]; + double d00_33 = m[0]*m[15] - m[3]*m[12]; + double d01_32 = m[1]*m[14] - m[2]*m[13]; + double d01_33 = m[1]*m[15] - m[3]*m[13]; + double d02_33 = m[2]*m[15] - m[3]*m[14]; + + double a01 = m[9]*d02_33 - m[10]*d01_33 + m[11]*d01_32; + double a11 = m[10]*d00_33 - m[11]*d00_32 - m[8]*d02_33; + double a21 = m[8]*d01_33 - m[9]*d00_33 + m[11]*d00_31; + double a31 = m[9]*d00_32 - m[10]*d00_31 - m[8]*d01_32; + + double a02 = m[6]*d01_33 - m[7]*d01_32 - m[5]*d02_33; + double a12 = m[4]*d02_33 - m[6]*d00_33 + m[7]*d00_32; + double a22 = m[5]*d00_33 - m[7]*d00_31 - m[4]*d01_33; + double a32 = m[4]*d01_32 - m[5]*d00_32 + m[6]*d00_31; + + double a03 = m[2]*d11_23 - m[3]*d11_22 - m[1]*d12_23; + double a13 = m[0]*d12_23 - m[2]*d10_23 + m[3]*d10_22; + double a23 = m[1]*d10_23 - m[3]*d10_21 - m[0]*d11_23; + double a33 = m[0]*d11_22 - m[1]*d10_22 + m[2]*d10_21; + + inverse_out[0] = (float) (a00*det); + inverse_out[1] = (float) (a01*det); + inverse_out[2] = (float) (a02*det); + inverse_out[3] = (float) (a03*det); + inverse_out[4] = (float) (a10*det); + inverse_out[5] = (float) (a11*det); + inverse_out[6] = (float) (a12*det); + inverse_out[7] = (float) (a13*det); + inverse_out[8] = (float) (a20*det); + inverse_out[9] = (float) (a21*det); + inverse_out[10] = (float) (a22*det); + inverse_out[11] = (float) (a23*det); + inverse_out[12] = (float) (a30*det); + inverse_out[13] = (float) (a31*det); + inverse_out[14] = (float) (a32*det); + inverse_out[15] = (float) (a33*det); + + return true; + } + + void GetM44M44Product(float* mout, const float* m1_, const float* m2_) + { + float m1[16]; + float m2[16]; + memcpy(m1, m1_, 16*sizeof(float)); + memcpy(m2, m2_, 16*sizeof(float)); + + mout[ 0] = m1[ 0]*m2[0] + m1[ 1]*m2[4] + m1[ 2]*m2[ 8] + m1[ 3]*m2[12]; + mout[ 1] = m1[ 0]*m2[1] + m1[ 1]*m2[5] + m1[ 2]*m2[ 9] + m1[ 3]*m2[13]; + mout[ 2] = m1[ 0]*m2[2] + m1[ 1]*m2[6] + m1[ 2]*m2[10] + m1[ 3]*m2[14]; + mout[ 3] = m1[ 0]*m2[3] + m1[ 1]*m2[7] + m1[ 2]*m2[11] + m1[ 3]*m2[15]; + mout[ 4] = m1[ 4]*m2[0] + m1[ 5]*m2[4] + m1[ 6]*m2[ 8] + m1[ 7]*m2[12]; + mout[ 5] = m1[ 4]*m2[1] + m1[ 5]*m2[5] + m1[ 6]*m2[ 9] + m1[ 7]*m2[13]; + mout[ 6] = m1[ 4]*m2[2] + m1[ 5]*m2[6] + m1[ 6]*m2[10] + m1[ 7]*m2[14]; + mout[ 7] = m1[ 4]*m2[3] + m1[ 5]*m2[7] + m1[ 6]*m2[11] + m1[ 7]*m2[15]; + mout[ 8] = m1[ 8]*m2[0] + m1[ 9]*m2[4] + m1[10]*m2[ 8] + m1[11]*m2[12]; + mout[ 9] = m1[ 8]*m2[1] + m1[ 9]*m2[5] + m1[10]*m2[ 9] + m1[11]*m2[13]; + mout[10] = m1[ 8]*m2[2] + m1[ 9]*m2[6] + m1[10]*m2[10] + m1[11]*m2[14]; + mout[11] = m1[ 8]*m2[3] + m1[ 9]*m2[7] + m1[10]*m2[11] + m1[11]*m2[15]; + mout[12] = m1[12]*m2[0] + m1[13]*m2[4] + m1[14]*m2[ 8] + m1[15]*m2[12]; + mout[13] = m1[12]*m2[1] + m1[13]*m2[5] + m1[14]*m2[ 9] + m1[15]*m2[13]; + mout[14] = m1[12]*m2[2] + m1[13]*m2[6] + m1[14]*m2[10] + m1[15]*m2[14]; + mout[15] = m1[12]*m2[3] + m1[13]*m2[7] + m1[14]*m2[11] + m1[15]*m2[15]; + } + + namespace + { + + void GetM44V4Product(float* vout, const float* m, const float* v_) + { + float v[4]; + memcpy(v, v_, 4*sizeof(float)); + + vout[0] = m[ 0]*v[0] + m[ 1]*v[1] + m[ 2]*v[2] + m[ 3]*v[3]; + vout[1] = m[ 4]*v[0] + m[ 5]*v[1] + m[ 6]*v[2] + m[ 7]*v[3]; + vout[2] = m[ 8]*v[0] + m[ 9]*v[1] + m[10]*v[2] + m[11]*v[3]; + vout[3] = m[12]*v[0] + m[13]*v[1] + m[14]*v[2] + m[15]*v[3]; + } + + void GetV4Sum(float* vout, const float* v1, const float* v2) + { + for(int i=0; i<4; ++i) + { + vout[i] = v1[i] + v2[i]; + } + } + + } // anon namespace + + // All m(s) are 4x4. All v(s) are size 4 vectors. + // Return mout, vout, where mout*x+vout == m2*(m1*x+v1)+v2 + // mout = m2*m1 + // vout = m2*v1 + v2 + void GetMxbCombine(float* mout, float* vout, + const float* m1_, const float* v1_, + const float* m2_, const float* v2_) + { + float m1[16]; + float v1[4]; + float m2[16]; + float v2[4]; + memcpy(m1, m1_, 16*sizeof(float)); + memcpy(v1, v1_, 4*sizeof(float)); + memcpy(m2, m2_, 16*sizeof(float)); + memcpy(v2, v2_, 4*sizeof(float)); + + GetM44M44Product(mout, m2, m1); + GetM44V4Product(vout, m2, v1); + GetV4Sum(vout, vout, v2); + } + + namespace + { + + void GetMxbResult(float* vout, float* m, float* x, float* v) + { + GetM44V4Product(vout, m, x); + GetV4Sum(vout, vout, v); + } + + } // anon namespace + + bool GetMxbInverse(float* mout, float* vout, + const float* m_, const float* v_) + { + float m[16]; + float v[4]; + memcpy(m, m_, 16*sizeof(float)); + memcpy(v, v_, 4*sizeof(float)); + + if(!GetM44Inverse(mout, m)) return false; + + for(int i=0; i<4; ++i) + { + v[i] = -v[i]; + } + GetM44V4Product(vout, mout, v); + + return true; + } + +} + +OCIO_NAMESPACE_EXIT + + + + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef OCIO_UNIT_TEST + +OCIO_NAMESPACE_USING + +#include "UnitTest.h" + +OIIO_ADD_TEST(MathUtils, M44_is_diagonal) +{ + { + float m44[] = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + bool isdiag = IsM44Diagonal(m44); + OIIO_CHECK_EQUAL(isdiag, true); + + m44[1] += 1e-8f; + isdiag = IsM44Diagonal(m44); + OIIO_CHECK_EQUAL(isdiag, false); + } +} + + +OIIO_ADD_TEST(MathUtils, IsScalarEqualToZero) +{ + OIIO_CHECK_EQUAL(IsScalarEqualToZero(0.0f), true); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-0.0f), true); + + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-1.072883670794056e-09f), false); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(1.072883670794056e-09f), false); + + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-1.072883670794056e-03f), false); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(1.072883670794056e-03f), false); + + OIIO_CHECK_EQUAL(IsScalarEqualToZero(-1.072883670794056e-01f), false); + OIIO_CHECK_EQUAL(IsScalarEqualToZero(1.072883670794056e-01f), false); +} + +OIIO_ADD_TEST(MathUtils, GetM44Inverse) +{ + // This is a degenerate matrix, and shouldnt be invertible. + float m[] = { 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + float mout[16]; + bool invertsuccess = GetM44Inverse(mout, m); + OIIO_CHECK_EQUAL(invertsuccess, false); +} + + +OIIO_ADD_TEST(MathUtils, M44_M44_product) +{ + { + float mout[16]; + float m1[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float m2[] = { 1.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 2.0f, 0.0f, 0.0f, 1.0f }; + GetM44M44Product(mout, m1, m2); + + float mcorrect[] = { 1.0f, 3.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, + 2.0f, 1.0f, 3.0f, 1.0f }; + + for(int i=0; i<16; ++i) + { + OIIO_CHECK_EQUAL(mout[i], mcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, M44_V4_product) +{ + { + float vout[4]; + float m[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float v[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + GetM44V4Product(vout, m, v); + + float vcorrect[] = { 5.0f, 5.0f, 4.0f, 15.0f }; + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_EQUAL(vout[i], vcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, V4_add) +{ + { + float vout[4]; + float v1[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + float v2[] = { 3.0f, 1.0f, 4.0f, 1.0f }; + GetV4Sum(vout, v1, v2); + + float vcorrect[] = { 4.0f, 3.0f, 7.0f, 5.0f }; + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_EQUAL(vout[i], vcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, mxb_eval) +{ + { + float vout[4]; + float m[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float x[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float v[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + GetMxbResult(vout, m, x, v); + + float vcorrect[] = { 4.0f, 4.0f, 5.0f, 9.0f }; + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_EQUAL(vout[i], vcorrect[i]); + } + } +} + +OIIO_ADD_TEST(MathUtils, Combine_two_mxb) +{ + float m1[] = { 1.0f, 0.0f, 2.0f, 0.0f, + 2.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 2.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 1.0f }; + float v1[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + float m2[] = { 2.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 3.0f, 0.0f, + 1.0f,1.0f, 1.0f, 1.0f }; + float v2[] = { 0.0f, 2.0f, 1.0f, 0.0f }; + float tolerance = 1e-9f; + + { + float x[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float vout[4]; + + // Combine two mx+b operations, and apply to test point + float mout[16]; + float vcombined[4]; + GetMxbCombine(mout, vout, m1, v1, m2, v2); + GetMxbResult(vcombined, mout, x, vout); + + // Sequentially apply the two mx+b operations. + GetMxbResult(vout, m1, x, v1); + GetMxbResult(vout, m2, vout, v2); + + // Compare outputs + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vcombined[i], vout[i], tolerance); + } + } + + { + float x[] = { 6.0f, 0.5f, -2.0f, -0.1f }; + float vout[4]; + + float mout[16]; + float vcombined[4]; + GetMxbCombine(mout, vout, m1, v1, m2, v2); + GetMxbResult(vcombined, mout, x, vout); + + GetMxbResult(vout, m1, x, v1); + GetMxbResult(vout, m2, vout, v2); + + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vcombined[i], vout[i], tolerance); + } + } + + { + float x[] = { 26.0f, -0.5f, 0.005f, 12.1f }; + float vout[4]; + + float mout[16]; + float vcombined[4]; + GetMxbCombine(mout, vout, m1, v1, m2, v2); + GetMxbResult(vcombined, mout, x, vout); + + GetMxbResult(vout, m1, x, v1); + GetMxbResult(vout, m2, vout, v2); + + // We pick a not so small tolerance, as we're dealing with + // large numbers, and the error for CHECK_CLOSE is absolute. + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vcombined[i], vout[i], 1e-3); + } + } +} + +OIIO_ADD_TEST(MathUtils, mxb_invert) +{ + { + float m[] = { 1.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 3.0f, 1.0f }; + float x[] = { 1.0f, 0.5f, -1.0f, 60.0f }; + float v[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + + float vresult[4]; + float mout[16]; + float vout[4]; + + GetMxbResult(vresult, m, x, v); + bool invertsuccess = GetMxbInverse(mout, vout, m, v); + OIIO_CHECK_EQUAL(invertsuccess, true); + + GetMxbResult(vresult, mout, vresult, vout); + + float tolerance = 1e-9f; + for(int i=0; i<4; ++i) + { + OIIO_CHECK_CLOSE(vresult[i], x[i], tolerance); + } + } + + { + float m[] = { 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.3f, 0.3f, 0.3f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + float v[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + float mout[16]; + float vout[4]; + + bool invertsuccess = GetMxbInverse(mout, vout, m, v); + OIIO_CHECK_EQUAL(invertsuccess, false); + } +} + +#endif + |