// // Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif static void SwapRow( double matrix[4][4], int i0, int i1 ) { double* p0; double* p1; double t; p0 = &matrix[i0][0]; p1 = &matrix[i1][0]; t = *p0; *p0++ = *p1; *p1++ = t; t = *p0; *p0++ = *p1; *p1++ = t; t = *p0; *p0++ = *p1; *p1++ = t; t = *p0; *p0 = *p1; *p1 = t; } static void SwapCol( double matrix[4][4], int j0, int j1 ) { double* p0; double* p1; double t; p0 = &matrix[0][j0]; p1 = &matrix[0][j1]; t = *p0; *p0 = *p1; *p1 = t; p0 += 4; p1 += 4; t = *p0; *p0 = *p1; *p1 = t; p0 += 4; p1 += 4; t = *p0; *p0 = *p1; *p1 = t; p0 += 4; p1 += 4; t = *p0; *p0 = *p1; *p1 = t; } //static void ScaleRow( double matrix[4][4], double c, int i ) //{ // double* p = &matrix[i][0]; // *p++ *= c; // *p++ *= c; // *p++ *= c; // *p *= c; //} // //static void InvScaleRow( double matrix[4][4], double c, int i ) //{ // double* p = &matrix[i][0]; // *p++ /= c; // *p++ /= c; // *p++ /= c; // *p /= c; //} static void AddCxRow( double matrix[4][4], double c, int i0, int i1 ) { const double* p0; double* p1; p0 = &matrix[i0][0]; p1 = &matrix[i1][0]; *p1++ += c* *p0++; *p1++ += c* *p0++; *p1++ += c* *p0++; *p1 += c* *p0; } /* static void AddCxCol( double matrix[4][4], double c, int j0, int j1 ) { const double* p0; double* p1; p0 = &matrix[0][j0]; p1 = &matrix[0][j1]; *p1 += c* *p0; p0 += 4; p1 += 4; *p1 += c* *p0; p0 += 4; p1 += 4; *p1 += c* *p0; p0 += 4; p1 += 4; *p1 += c* *p0; } */ static int Inv( const double* src, double dst[4][4], double* determinant, double* pivot ) { // returns rank (0, 1, 2, 3, or 4), inverse, and smallest pivot double M[4][4], I[4][4], x, c, d; int i, j, ix, jx; int col[4] = {0,1,2,3}; int swapcount = 0; int rank = 0; *pivot = 0.0; *determinant = 0.0; memset( I, 0, sizeof(I) ); I[0][0] = I[1][1] = I[2][2] = I[3][3] = 1.0; memcpy( M, src, sizeof(M) ); // some loops unrolled for speed ix = jx = 0; x = fabs(M[0][0]); for ( i = 0; i < 4; i++ ) for ( j = 0; j < 4; j++ ) { if ( fabs(M[i][j]) > x ) { ix = i; jx = j; x = fabs(M[i][j]); } } *pivot = x; if ( ix != 0 ) { SwapRow( M, 0, ix ); SwapRow( I, 0, ix ); swapcount++; } if ( jx != 0 ) { SwapCol( M, 0, jx ); col[0] = jx; swapcount++; } if ( x > 0.0 ) { rank++; // 17 August 2011 Dale Lear // The result is slightly more accurate when using division // instead of multiplying by the inverse of M[0][0]. If there // is any speed penalty at this point in history, the accuracy // is more important than the additional clocks. //c = d = 1.0/M[0][0]; //M[0][1] *= c; M[0][2] *= c; M[0][3] *= c; //ScaleRow( I, c, 0 ); c = M[0][0]; M[0][1] /= c; M[0][2] /= c; M[0][3] /= c; I[0][0] /= c; I[0][1] /= c; I[0][2] /= c; I[0][3] /= c; d = 1.0/c; x *= ON_EPSILON; if (fabs(M[1][0]) > x) { c = -M[1][0]; M[1][1] += c*M[0][1]; M[1][2] += c*M[0][2]; M[1][3] += c*M[0][3]; AddCxRow( I, c, 0, 1 ); } if (fabs(M[2][0]) > x) { c = -M[2][0]; M[2][1] += c*M[0][1]; M[2][2] += c*M[0][2]; M[2][3] += c*M[0][3]; AddCxRow( I, c, 0, 2 ); } if (fabs(M[3][0]) > x) { c = -M[3][0]; M[3][1] += c*M[0][1]; M[3][2] += c*M[0][2]; M[3][3] += c*M[0][3]; AddCxRow( I, c, 0, 3 ); } ix = jx = 1; x = fabs(M[1][1]); for ( i = 1; i < 4; i++ ) for ( j = 1; j < 4; j++ ) { if ( fabs(M[i][j]) > x ) { ix = i; jx = j; x = fabs(M[i][j]); } } if ( x < *pivot ) *pivot = x; if ( ix != 1 ) { SwapRow( M, 1, ix ); SwapRow( I, 1, ix ); swapcount++; } if ( jx != 1 ) { SwapCol( M, 1, jx ); col[1] = jx; swapcount++; } if ( x > 0.0 ) { rank++; // 17 August 2011 Dale Lear // The result is slightly more accurate when using division // instead of multiplying by the inverse of M[1][1]. If there // is any speed penalty at this point in history, the accuracy // is more important than the additional clocks. //c = 1.0/M[1][1]; //d *= c; //M[1][2] *= c; M[1][3] *= c; //ScaleRow( I, c, 1 ); c = M[1][1]; M[1][2] /= c; M[1][3] /= c; I[1][0] /= c; I[1][1] /= c; I[1][2] /= c; I[1][3] /= c; d /= c; x *= ON_EPSILON; if (fabs(M[0][1]) > x) { c = -M[0][1]; M[0][2] += c*M[1][2]; M[0][3] += c*M[1][3]; AddCxRow( I, c, 1, 0 ); } if (fabs(M[2][1]) > x) { c = -M[2][1]; M[2][2] += c*M[1][2]; M[2][3] += c*M[1][3]; AddCxRow( I, c, 1, 2 ); } if (fabs(M[3][1]) > x) { c = -M[3][1]; M[3][2] += c*M[1][2]; M[3][3] += c*M[1][3]; AddCxRow( I, c, 1, 3 ); } ix = jx = 2; x = fabs(M[2][2]); for ( i = 2; i < 4; i++ ) for ( j = 2; j < 4; j++ ) { if ( fabs(M[i][j]) > x ) { ix = i; jx = j; x = fabs(M[i][j]); } } if ( x < *pivot ) *pivot = x; if ( ix != 2 ) { SwapRow( M, 2, ix ); SwapRow( I, 2, ix ); swapcount++; } if ( jx != 2 ) { SwapCol( M, 2, jx ); col[2] = jx; swapcount++; } if ( x > 0.0 ) { rank++; // 17 August 2011 Dale Lear // The result is slightly more accurate when using division // instead of multiplying by the inverse of M[2][2]. If there // is any speed penalty at this point in history, the accuracy // is more important than the additional clocks. //c = 1.0/M[2][2]; //d *= c; //M[2][3] *= c; //ScaleRow( I, c, 2 ); c = M[2][2]; M[2][3] /= c; I[2][0] /= c; I[2][1] /= c; I[2][2] /= c; I[2][3] /= c; d /= c; x *= ON_EPSILON; if (fabs(M[0][2]) > x) { c = -M[0][2]; M[0][3] += c*M[2][3]; AddCxRow( I, c, 2, 0 ); } if (fabs(M[1][2]) > x) { c = -M[1][2]; M[1][3] += c*M[2][3]; AddCxRow( I, c, 2, 1 ); } if (fabs(M[3][2]) > x) { c = -M[3][2]; M[3][3] += c*M[2][3]; AddCxRow( I, c, 2, 3 ); } x = fabs(M[3][3]); if ( x < *pivot ) *pivot = x; if ( x > 0.0 ) { rank++; // 17 August 2011 Dale Lear // The result is slightly more accurate when using division // instead of multiplying by the inverse of M[3][3]. If there // is any speed penalty at this point in history, the accuracy // is more important than the additional clocks. //c = 1.0/M[3][3]; //d *= c; //ScaleRow( I, c, 3 ); c = M[3][3]; I[3][0] /= c; I[3][1] /= c; I[3][2] /= c; I[3][3] /= c; d /= c; x *= ON_EPSILON; if (fabs(M[0][3]) > x) { AddCxRow( I, -M[0][3], 3, 0 ); } if (fabs(M[1][3]) > x) { AddCxRow( I, -M[1][3], 3, 1 ); } if (fabs(M[2][3]) > x) { AddCxRow( I, -M[2][3], 3, 2 ); } *determinant = (swapcount%2) ? -d : d; } } } } if ( col[3] != 3 ) SwapRow( I, 3, col[3] ); if ( col[2] != 2 ) SwapRow( I, 2, col[2] ); if ( col[1] != 1 ) SwapRow( I, 1, col[1] ); if ( col[0] != 0 ) SwapRow( I, 0, col[0] ); memcpy( dst, I, sizeof(I) ); return rank; } /////////////////////////////////////////////////////////////// // // ON_Xform constructors // ON_Xform::ON_Xform() { memset( m_xform, 0, sizeof(m_xform) ); m_xform[3][3] = 1.0; } ON_Xform::ON_Xform( double x ) { memset( m_xform, 0, sizeof(m_xform) ); m_xform[0][0] = x; m_xform[1][1] = x; m_xform[2][2] = x; m_xform[3][3] = 1.0; } const ON_Xform ON_Xform::DiagonalTransformation( double d ) { return ON_Xform::DiagonalTransformation(d, d, d); } const ON_Xform ON_Xform::DiagonalTransformation( const ON_3dVector& diagnoal ) { return ON_Xform::DiagonalTransformation(diagnoal.x, diagnoal.y, diagnoal.z); } const ON_Xform ON_Xform::DiagonalTransformation( double d0, double d1, double d2 ) { ON_Xform xform(ON_Xform::IdentityTransformation); xform.m_xform[0][0] = d0; xform.m_xform[1][1] = d1; xform.m_xform[2][2] = d2; return xform; } #if defined(ON_COMPILER_MSC) ON_Xform::ON_Xform( double m[4][4] ) { memcpy( m_xform, m, sizeof(m_xform) ); } #endif ON_Xform::ON_Xform( const double m[4][4] ) { memcpy( m_xform, m, sizeof(m_xform) ); } #if defined(ON_COMPILER_MSC) ON_Xform::ON_Xform( float m[4][4] ) { m_xform[0][0] = (double)m[0][0]; m_xform[0][1] = (double)m[0][1]; m_xform[0][2] = (double)m[0][2]; m_xform[0][3] = (double)m[0][3]; m_xform[1][0] = (double)m[1][0]; m_xform[1][1] = (double)m[1][1]; m_xform[1][2] = (double)m[1][2]; m_xform[1][3] = (double)m[1][3]; m_xform[2][0] = (double)m[2][0]; m_xform[2][1] = (double)m[2][1]; m_xform[2][2] = (double)m[2][2]; m_xform[2][3] = (double)m[2][3]; m_xform[3][0] = (double)m[3][0]; m_xform[3][1] = (double)m[3][1]; m_xform[3][2] = (double)m[3][2]; m_xform[3][3] = (double)m[3][3]; } #endif ON_Xform::ON_Xform( const float m[4][4] ) { m_xform[0][0] = (double)m[0][0]; m_xform[0][1] = (double)m[0][1]; m_xform[0][2] = (double)m[0][2]; m_xform[0][3] = (double)m[0][3]; m_xform[1][0] = (double)m[1][0]; m_xform[1][1] = (double)m[1][1]; m_xform[1][2] = (double)m[1][2]; m_xform[1][3] = (double)m[1][3]; m_xform[2][0] = (double)m[2][0]; m_xform[2][1] = (double)m[2][1]; m_xform[2][2] = (double)m[2][2]; m_xform[2][3] = (double)m[2][3]; m_xform[3][0] = (double)m[3][0]; m_xform[3][1] = (double)m[3][1]; m_xform[3][2] = (double)m[3][2]; m_xform[3][3] = (double)m[3][3]; } ON_Xform::ON_Xform( const double* m ) { memcpy( m_xform, m, sizeof(m_xform) ); } ON_Xform::ON_Xform( const float* m ) { m_xform[0][0] = (double)m[0]; m_xform[0][1] = (double)m[1]; m_xform[0][2] = (double)m[2]; m_xform[0][3] = (double)m[3]; m_xform[1][0] = (double)m[4]; m_xform[1][1] = (double)m[5]; m_xform[1][2] = (double)m[6]; m_xform[1][3] = (double)m[7]; m_xform[2][0] = (double)m[8]; m_xform[2][1] = (double)m[9]; m_xform[2][2] = (double)m[10]; m_xform[2][3] = (double)m[11]; m_xform[3][0] = (double)m[12]; m_xform[3][1] = (double)m[13]; m_xform[3][2] = (double)m[14]; m_xform[3][3] = (double)m[15]; } ON_Xform::ON_Xform( const ON_3dPoint& P, const ON_3dVector& X, const ON_3dVector& Y, const ON_3dVector& Z) { m_xform[0][0] = X[0]; m_xform[1][0] = X[1]; m_xform[2][0] = X[2]; m_xform[3][0] = 0; m_xform[0][1] = Y[0]; m_xform[1][1] = Y[1]; m_xform[2][1] = Y[2]; m_xform[3][1] = 0; m_xform[0][2] = Z[0]; m_xform[1][2] = Z[1]; m_xform[2][2] = Z[2]; m_xform[3][2] = 0; m_xform[0][3] = P[0]; m_xform[1][3] = P[1]; m_xform[2][3] = P[2]; m_xform[3][3] = 1; } ON_Xform::ON_Xform( const ON_Matrix& m ) { *this = m; } /////////////////////////////////////////////////////////////// // // ON_Xform operator[] // double* ON_Xform::operator[](int i) { return ( i >= 0 && i < 4 ) ? &m_xform[i][0] : nullptr; } const double* ON_Xform::operator[](int i) const { return ( i >= 0 && i < 4 ) ? &m_xform[i][0] : nullptr; } /////////////////////////////////////////////////////////////// // // ON_Xform operator* operator- operator+ // // All non-commutative operations have "this" as left hand side and // argument as right hand side. ON_Xform ON_Xform::operator*( const ON_Xform& rhs ) const { double m[4][4]; const double* p = &rhs.m_xform[0][0]; m[0][0] = m_xform[0][0]*p[0] + m_xform[0][1]*p[4] + m_xform[0][2]*p[ 8] + m_xform[0][3]*p[12]; m[0][1] = m_xform[0][0]*p[1] + m_xform[0][1]*p[5] + m_xform[0][2]*p[ 9] + m_xform[0][3]*p[13]; m[0][2] = m_xform[0][0]*p[2] + m_xform[0][1]*p[6] + m_xform[0][2]*p[10] + m_xform[0][3]*p[14]; m[0][3] = m_xform[0][0]*p[3] + m_xform[0][1]*p[7] + m_xform[0][2]*p[11] + m_xform[0][3]*p[15]; m[1][0] = m_xform[1][0]*p[0] + m_xform[1][1]*p[4] + m_xform[1][2]*p[ 8] + m_xform[1][3]*p[12]; m[1][1] = m_xform[1][0]*p[1] + m_xform[1][1]*p[5] + m_xform[1][2]*p[ 9] + m_xform[1][3]*p[13]; m[1][2] = m_xform[1][0]*p[2] + m_xform[1][1]*p[6] + m_xform[1][2]*p[10] + m_xform[1][3]*p[14]; m[1][3] = m_xform[1][0]*p[3] + m_xform[1][1]*p[7] + m_xform[1][2]*p[11] + m_xform[1][3]*p[15]; m[2][0] = m_xform[2][0]*p[0] + m_xform[2][1]*p[4] + m_xform[2][2]*p[ 8] + m_xform[2][3]*p[12]; m[2][1] = m_xform[2][0]*p[1] + m_xform[2][1]*p[5] + m_xform[2][2]*p[ 9] + m_xform[2][3]*p[13]; m[2][2] = m_xform[2][0]*p[2] + m_xform[2][1]*p[6] + m_xform[2][2]*p[10] + m_xform[2][3]*p[14]; m[2][3] = m_xform[2][0]*p[3] + m_xform[2][1]*p[7] + m_xform[2][2]*p[11] + m_xform[2][3]*p[15]; m[3][0] = m_xform[3][0]*p[0] + m_xform[3][1]*p[4] + m_xform[3][2]*p[ 8] + m_xform[3][3]*p[12]; m[3][1] = m_xform[3][0]*p[1] + m_xform[3][1]*p[5] + m_xform[3][2]*p[ 9] + m_xform[3][3]*p[13]; m[3][2] = m_xform[3][0]*p[2] + m_xform[3][1]*p[6] + m_xform[3][2]*p[10] + m_xform[3][3]*p[14]; m[3][3] = m_xform[3][0]*p[3] + m_xform[3][1]*p[7] + m_xform[3][2]*p[11] + m_xform[3][3]*p[15]; return ON_Xform(m); } ON_Xform ON_Xform::operator+( const ON_Xform& rhs ) const { double m[4][4]; const double* p = &rhs.m_xform[0][0]; m[0][0] = m_xform[0][0] + p[0]; m[0][1] = m_xform[0][1] + p[1]; m[0][2] = m_xform[0][2] + p[2]; m[0][3] = m_xform[0][3] + p[3]; m[1][0] = m_xform[1][0] + p[4]; m[1][1] = m_xform[1][1] + p[5]; m[1][2] = m_xform[1][2] + p[6]; m[1][3] = m_xform[1][3] + p[7]; m[2][0] = m_xform[2][0] + p[ 8]; m[2][1] = m_xform[2][1] + p[ 9]; m[2][2] = m_xform[2][2] + p[10]; m[2][3] = m_xform[2][3] + p[11]; m[3][0] = m_xform[3][0] + p[12]; m[3][1] = m_xform[3][1] + p[13]; m[3][2] = m_xform[3][2] + p[14]; m[3][3] = m_xform[3][3] + p[15]; return ON_Xform(m); } ON_Xform ON_Xform::operator-( const ON_Xform& rhs ) const { double m[4][4]; const double* p = &rhs.m_xform[0][0]; m[0][0] = m_xform[0][0] - p[0]; m[0][1] = m_xform[0][1] - p[1]; m[0][2] = m_xform[0][2] - p[2]; m[0][3] = m_xform[0][3] - p[3]; m[1][0] = m_xform[1][0] - p[4]; m[1][1] = m_xform[1][1] - p[5]; m[1][2] = m_xform[1][2] - p[6]; m[1][3] = m_xform[1][3] - p[7]; m[2][0] = m_xform[2][0] - p[ 8]; m[2][1] = m_xform[2][1] - p[ 9]; m[2][2] = m_xform[2][2] - p[10]; m[2][3] = m_xform[2][3] - p[11]; m[3][0] = m_xform[3][0] - p[12]; m[3][1] = m_xform[3][1] - p[13]; m[3][2] = m_xform[3][2] - p[14]; m[3][3] = m_xform[3][3] - p[15]; return ON_Xform(m); } /////////////////////////////////////////////////////////////// // // ON_Xform // void ON_Xform::Identity() { memset( m_xform, 0, sizeof(m_xform) ); m_xform[0][0] = m_xform[1][1] = m_xform[2][2] = m_xform[3][3] = 1.0; } void ON_Xform::Diagonal( double d ) { memset( m_xform, 0, sizeof(m_xform) ); m_xform[0][0] = m_xform[1][1] = m_xform[2][2] = d; m_xform[3][3] = 1.0; } void ON_Xform::Scale( double x, double y, double z ) { memset( m_xform, 0, sizeof(m_xform) ); m_xform[0][0] = x; m_xform[1][1] = y; m_xform[2][2] = z; m_xform[3][3] = 1.0; } void ON_Xform::Scale( const ON_3dVector& v ) { memset( m_xform, 0, sizeof(m_xform) ); m_xform[0][0] = v.x; m_xform[1][1] = v.y; m_xform[2][2] = v.z; m_xform[3][3] = 1.0; } void ON_Xform::Scale ( ON_3dPoint fixed_point, double scale_factor ) { *this = ON_Xform::ScaleTransformation(fixed_point, scale_factor); } const ON_Xform ON_Xform::ScaleTransformation( const ON_3dPoint& fixed_point, double scale_factor ) { return ON_Xform::ScaleTransformation(fixed_point, scale_factor, scale_factor, scale_factor); } const ON_Xform ON_Xform::ScaleTransformation( const ON_3dPoint& fixed_point, double x_scale_factor, double y_scale_factor, double z_scale_factor ) { const ON_Xform s(ON_Xform::DiagonalTransformation(x_scale_factor, y_scale_factor, z_scale_factor)); if ( fixed_point.x == 0.0 && fixed_point.y == 0.0 && fixed_point.z == 0.0 ) { return s; } const ON_3dVector delta = fixed_point - ON_3dPoint::Origin; ON_Xform t0(ON_Xform::TranslationTransformation(-delta)); ON_Xform t1(ON_Xform::TranslationTransformation(delta)); return (t1*s*t0); } void ON_Xform::Scale ( const ON_Plane& plane, double x_scale_factor, double y_scale_factor, double z_scale_factor ) { *this = ON_Xform::ScaleTransformation(plane, x_scale_factor, z_scale_factor, y_scale_factor); } const ON_Xform ON_Xform::ScaleTransformation ( const ON_Plane& plane, double x_scale_factor, double y_scale_factor, double z_scale_factor ) { return (x_scale_factor == y_scale_factor && x_scale_factor == z_scale_factor) ? ON_Xform::ScaleTransformation(plane.origin,x_scale_factor) : ON_Xform::ShearTransformation( plane, x_scale_factor*plane.xaxis, y_scale_factor*plane.yaxis, z_scale_factor*plane.zaxis ); } void ON_Xform::Shear ( const ON_Plane& plane, const ON_3dVector& x1, const ON_3dVector& y1, const ON_3dVector& z1 ) { *this = ON_Xform::ShearTransformation(plane, x1, y1, z1); } const ON_Xform ON_Xform::ShearTransformation( const ON_Plane& plane, const ON_3dVector& x1, const ON_3dVector& y1, const ON_3dVector& z1 ) { const ON_3dVector delta = plane.origin - ON_3dPoint::Origin; const ON_Xform t0(ON_Xform::TranslationTransformation(-delta)); const ON_Xform t1(ON_Xform::TranslationTransformation(delta)); ON_Xform s0(ON_Xform::IdentityTransformation); ON_Xform s1(ON_Xform::IdentityTransformation); s0.m_xform[0][0] = plane.xaxis.x; s0.m_xform[0][1] = plane.xaxis.y; s0.m_xform[0][2] = plane.xaxis.z; s0.m_xform[1][0] = plane.yaxis.x; s0.m_xform[1][1] = plane.yaxis.y; s0.m_xform[1][2] = plane.yaxis.z; s0.m_xform[2][0] = plane.zaxis.x; s0.m_xform[2][1] = plane.zaxis.y; s0.m_xform[2][2] = plane.zaxis.z; s1.m_xform[0][0] = x1.x; s1.m_xform[1][0] = x1.y; s1.m_xform[2][0] = x1.z; s1.m_xform[0][1] = y1.x; s1.m_xform[1][1] = y1.y; s1.m_xform[2][1] = y1.z; s1.m_xform[0][2] = z1.x; s1.m_xform[1][2] = z1.y; s1.m_xform[2][2] = z1.z; return (t1*s1*s0*t0); } void ON_Xform::Translation( double dx, double dy, double dz ) { *this = ON_Xform::TranslationTransformation(dx,dy,dz); } void ON_Xform::Translation( const ON_3dVector& delta ) { *this = ON_Xform::TranslationTransformation(delta); } const ON_Xform ON_Xform::TranslationTransformation( const ON_2dVector& delta ) { return ON_Xform::TranslationTransformation(delta.x, delta.y, 0.0); } const ON_Xform ON_Xform::TranslationTransformation( const ON_3dVector& delta ) { return ON_Xform::TranslationTransformation(delta.x, delta.y, delta.z); } const ON_Xform ON_Xform::TranslationTransformation( double dx, double dy, double dz ) { ON_Xform xform(ON_Xform::IdentityTransformation); xform.m_xform[0][3] = dx; xform.m_xform[1][3] = dy; xform.m_xform[2][3] = dz; return xform; } void ON_Xform::PlanarProjection( const ON_Plane& plane ) { int i, j; double x[3] = {plane.xaxis.x,plane.xaxis.y,plane.xaxis.z}; double y[3] = {plane.yaxis.x,plane.yaxis.y,plane.yaxis.z}; double p[3] = {plane.origin.x,plane.origin.y,plane.origin.z}; double q[3]; for ( i = 0; i < 3; i++ ) { for ( j = 0; j < 3; j++ ) { m_xform[i][j] = x[i]*x[j] + y[i]*y[j]; } q[i] = m_xform[i][0]*p[0] + m_xform[i][1]*p[1] + m_xform[i][2]*p[2]; } for ( i = 0; i < 3; i++ ) { m_xform[3][i] = 0.0; m_xform[i][3] = p[i]-q[i]; } m_xform[3][3] = 1.0; } /////////////////////////////////////////////////////////////// // // ON_Xform // void ON_Xform::ActOnLeft(double x,double y,double z,double w,double v[4]) const { if ( v ) { v[0] = m_xform[0][0]*x + m_xform[0][1]*y + m_xform[0][2]*z + m_xform[0][3]*w; v[1] = m_xform[1][0]*x + m_xform[1][1]*y + m_xform[1][2]*z + m_xform[1][3]*w; v[2] = m_xform[2][0]*x + m_xform[2][1]*y + m_xform[2][2]*z + m_xform[2][3]*w; v[3] = m_xform[3][0]*x + m_xform[3][1]*y + m_xform[3][2]*z + m_xform[3][3]*w; } } void ON_Xform::ActOnRight(double x,double y,double z,double w,double v[4]) const { if ( v ) { v[0] = m_xform[0][0]*x + m_xform[1][0]*y + m_xform[2][0]*z + m_xform[3][0]*w; v[1] = m_xform[0][1]*x + m_xform[1][1]*y + m_xform[2][1]*z + m_xform[3][1]*w; v[2] = m_xform[0][2]*x + m_xform[1][2]*y + m_xform[2][2]*z + m_xform[3][2]*w; v[3] = m_xform[0][3]*x + m_xform[1][3]*y + m_xform[2][3]*z + m_xform[3][3]*w; } } const ON_Xform operator*(double c, const ON_Xform& xform) { ON_Xform cx(xform); double* p = &cx.m_xform[0][0]; double* p1 = p + 16; while (p < p1) { const double x = *p; *p++ = c*x; } return cx; } const ON_Xform operator*(const ON_Xform& xform, double c) { ON_Xform xc(xform); double* p = &xc.m_xform[0][0]; double* p1 = p + 16; while (p < p1) { const double x = *p; *p++ = x*c; } return xc; } ON_2dPoint ON_Xform::operator*( const ON_2dPoint& p ) const { // Note well: The right hand column and bottom row have an important effect // when transforming a Euclidean point and have no effect when transforming a vector. // Be sure you understand the differences between vectors and points when applying a 4x4 transformation. const double x = p.x; // optimizer should put x,y in registers const double y = p.y; double xh[2], w; const double* m = &m_xform[0][0]; xh[0] = m[ 0]*x + m[ 1]*y + m[ 3]; xh[1] = m[ 4]*x + m[ 5]*y + m[ 7]; w = m[12]*x + m[13]*y + m[15]; w = (w != 0.0) ? 1.0/w : 1.0; return ON_2dPoint( w*xh[0], w*xh[1] ); } ON_3dPoint ON_Xform::operator*( const ON_3dPoint& p ) const { // Note well: The right hand column and bottom row have an important effect // when transforming a Euclidean point and have no effect when transforming a vector. // Be sure you understand the differences between vectors and points when applying a 4x4 transformation. const double x = p.x; // optimizer should put x,y,z in registers const double y = p.y; const double z = p.z; double xh[3], w; const double* m = &m_xform[0][0]; xh[0] = m[ 0]*x + m[ 1]*y + m[ 2]*z + m[ 3]; xh[1] = m[ 4]*x + m[ 5]*y + m[ 6]*z + m[ 7]; xh[2] = m[ 8]*x + m[ 9]*y + m[10]*z + m[11]; w = m[12]*x + m[13]*y + m[14]*z + m[15]; w = (w != 0.0) ? 1.0/w : 1.0; return ON_3dPoint( w*xh[0], w*xh[1], w*xh[2] ); } ON_4dPoint ON_Xform::operator*( const ON_4dPoint& h ) const { const double x = h.x; // optimizer should put x,y,z,w in registers const double y = h.y; const double z = h.z; const double w = h.w; double xh[4]; const double* m = &m_xform[0][0]; xh[0] = m[ 0]*x + m[ 1]*y + m[ 2]*z + m[ 3]*w; xh[1] = m[ 4]*x + m[ 5]*y + m[ 6]*z + m[ 7]*w; xh[2] = m[ 8]*x + m[ 9]*y + m[10]*z + m[11]*w; xh[3] = m[12]*x + m[13]*y + m[14]*z + m[15]*w; return ON_4dPoint( xh[0],xh[1],xh[2],xh[3] ); } ON_2dVector ON_Xform::operator*( const ON_2dVector& v ) const { // Note well: The right hand column and bottom row have an important effect // when transforming a Euclidean point and have no effect when transforming a vector. // Be sure you understand the differences between vectors and points when applying a 4x4 transformation. const double x = v.x; // optimizer should put x,y in registers const double y = v.y; double xh[2]; const double* m = &m_xform[0][0]; xh[0] = m[0]*x + m[1]*y; xh[1] = m[4]*x + m[5]*y; return ON_2dVector( xh[0],xh[1] ); } ON_3dVector ON_Xform::operator*( const ON_3dVector& v ) const { // Note well: The right hand column and bottom row have an important effect // when transforming a Euclidean point and have no effect when transforming a vector. // Be sure you understand the differences between vectors and points when applying a 4x4 transformation. const double x = v.x; // optimizer should put x,y,z in registers const double y = v.y; const double z = v.z; double xh[3]; const double* m = &m_xform[0][0]; xh[0] = m[0]*x + m[1]*y + m[ 2]*z; xh[1] = m[4]*x + m[5]*y + m[ 6]*z; xh[2] = m[8]*x + m[9]*y + m[10]*z; return ON_3dVector( xh[0],xh[1],xh[2] ); } const ON_SHA1_Hash ON_Xform::Hash() const { ON_SHA1 sha1; sha1.AccumulateDoubleArray(16, &this->m_xform[0][0]); return sha1.Hash(); } ON__UINT32 ON_Xform::CRC32(ON__UINT32 current_remainder) const { const ON_SHA1_Hash hash = this->Hash(); return ON_CRC32(current_remainder, sizeof(hash), &hash); } bool ON_Xform::IsValid() const { const double* x = &m_xform[0][0]; const double* x16 = x + 16; while ( x < x16 ) { const double t = *x++; if (ON_IS_VALID(t)) continue; return false; // t is not valid } return true; } bool ON_Xform::IsNan() const { const double* x = &m_xform[0][0]; const double* x16 = x + 16; while ( x < x16 ) { const double t = *x++; if (!(t == t)) return true; // t is a nan } return false; } bool ON_Xform::operator==(const ON_Xform& rhs) const { // Intentionally returns false if any coefficient is a nan. const double* x = &m_xform[0][0]; const double* x16 = x + 16; const double* y = &rhs.m_xform[0][0]; while (x < x16) { if (*x++ == *y++) continue; return false; // not equal or a nan } return true; } bool ON_Xform::operator!=(const ON_Xform& rhs) const { // Intentionally returns false if any coefficient is a nan. const double* x = &m_xform[0][0]; const double* x16 = x + 16; const double* y = &rhs.m_xform[0][0]; while (x < x16) { double a = *x++; double b = *y++; if (a == b) continue; if (a != b) { while (x < x16) { a = *x++; b = *y++; if (a == a && b == b) continue; return false; // a or b is a nan. } return true; // no nans and at least one not equal equalcoefficient. } } return false; // nans or equal } bool ON_Xform::IsIdentity( double zero_tolerance ) const { // The code below will return false if m_xform[][] contains // a nan value. if (!(zero_tolerance >= 0.0 && zero_tolerance < ON_UNSET_POSITIVE_VALUE)) return false; const double* v = &m_xform[0][0]; for ( int i = 0; i < 3; i++ ) { if ( !(fabs(1.0 - *v++) <= zero_tolerance) ) return false; if ( !(fabs(*v++) <= zero_tolerance) ) return false; if ( !(fabs(*v++) <= zero_tolerance) ) return false; if ( !(fabs(*v++) <= zero_tolerance) ) return false; if ( !(fabs(*v++) <= zero_tolerance) ) return false; } if ( !(fabs( 1.0 - *v ) <= zero_tolerance) ) return false; return true; } bool ON_Xform::IsNotIdentity( double zero_tolerance ) const { // It is intentional that this functions returns false if any coefficient is a nan or unset value. return (zero_tolerance >= 0.0 && zero_tolerance < ON_UNSET_POSITIVE_VALUE && false == ON_Xform::IsIdentity(zero_tolerance) && IsValid()); } bool ON_Xform::IsValidAndNotZeroAndNotIdentity( double zero_tolerance ) const { if (false == IsValid()) return false; if (!(zero_tolerance >= 0.0 && zero_tolerance < ON_UNSET_POSITIVE_VALUE)) return false; int one_count = 0; int zero_count = 0; const double* v = &m_xform[0][0]; for ( int i = 0; i < 3; i++ ) { if (fabs(1.0 - *v) <= zero_tolerance) { // this diagonal coefficient = 1 one_count++; if (zero_count > 0) return true; } else if (fabs(*v) <= zero_tolerance) { // this diagonal coefficient = 0 zero_count++; if (one_count > 0) return true; } else { // this diagonal coefficient != 1 and != 0 return true; } // If any off diagonal coefficient is not zero, return true v++; if ( !(fabs(*v++) <= zero_tolerance) ) return true; if ( !(fabs(*v++) <= zero_tolerance) ) return true; if ( !(fabs(*v++) <= zero_tolerance) ) return true; if ( !(fabs(*v++) <= zero_tolerance) ) return true; } if (!(fabs(1.0 - *v) <= zero_tolerance)) { if (3 == zero_count && fabs(1.0 - *v) <= zero_tolerance) return false; // every matrix coefficient = 0 // otherwise, xform[3][3] != 1 so return true. return true; } if (3 == one_count || 3 == zero_count) return false; return true; } bool ON_Xform::IsTranslation( double zero_tolerance ) const { if (!(zero_tolerance >= 0.0 && zero_tolerance < ON_UNSET_POSITIVE_VALUE)) return false; const double* v = &m_xform[0][0]; if ( fabs(1.0 - *v++) > zero_tolerance ) return false; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs(*v++) > zero_tolerance ) return false; v++; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs(1.0 - *v++) > zero_tolerance ) return false; if ( fabs(*v++) > zero_tolerance ) return false; v++; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs(1.0 - *v++) > zero_tolerance ) return false; v++; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs(*v++) > zero_tolerance ) return false; if ( fabs( 1.0 - *v ) > zero_tolerance ) return false; return IsValid(); } int ON_Xform::Compare( const ON_Xform& rhs ) const { const double* a = &m_xform[0][0]; const double* b = &rhs.m_xform[0][0]; const double* a16 = a + 16; while ( a < a16 ) { const double x = *a++; const double y = *b++; if ( x < y ) return -1; if ( x > y ) return 1; if (x == y) continue; if (!(x == x)) { // x is a nan if (!(y == y)) continue; // x and y are nans return 1; // x is a nan and y is not. } // y is a nan and x is not a nan. return -1; } return 0; } static ON_Interval BoundEVals( const ON_Xform& M ) { // assume M is Linear(). // Bound the eigenvalues. Spectrum ( M ) \in [emin, emax] // that is if lambda is a eigenvalue of M then emin<=Re(lambda)<=emax // Uses Gershgorin circle theorem. ON_Interval SpectrumHull; for (int i = 0; i < 3; i++) { double R = 0.0; for (int j = 0; j < 3; j++) if (j != i) R += fabs( M[i][j] ); ON_Interval GCircle ( M[i][i] - R , M[i][i] + R ); if (i == 0) SpectrumHull = GCircle; else SpectrumHull.Union(GCircle); } return SpectrumHull; } // Given a Linear transformation L. return an interval containing Spectrum(L^T *L) static ON_Interval ApproxSpectrumLTL(const ON_Xform& L) { // L.IsLinear() is a precondition // LTL = L^T * L ON_Xform LTL = L; LTL.Transpose(); LTL = LTL * L; return BoundEVals(LTL); } // Given a linear transformation bound distance to group of orthogonal transformations // dist ( L, O(3) ) < ApproxDist2Ortho(L) // L = R * P is the polar decomposition of L. R is the closest rotation to L and // P = (L^T * L)^(-1/2) // So || L-R || = || R*( P - I ) || = || P - I || = || (L^T L)^(1/2) - I || static double ApproxDist2Ortho(const ON_Xform& L) { // L.IsLinear() is a precondition ON_Interval Spec = ApproxSpectrumLTL(L); if (Spec[0] < 0) Spec[0] = 0.0; Spec[0] = sqrt(Spec[0]) - 1.0; Spec[1] = sqrt(Spec[1]) - 1.0; // contains Spectrum of (L^T L)^(-1/2) - I double dist = fabs(Spec[0]); if (dist < fabs(Spec[1])) dist = fabs(Spec[1]); return dist; } int ON_Xform::IsSimilarity() const { return IsSimilarity(ON_ZERO_TOLERANCE); } int ON_Xform::IsSimilarity(double tol) const { // This function does not construct a similarity transformation, // ( see ON_Xform::DecomposeSimilarity() for this ). It merely // indicates that this transformation is sufficiently close to a similarity. // However using with a tight tolerance like toldist ) { double det = L.Determinant(); rval = (det > 0) ? 1 : -1; } } return rval; } int ON_Xform::DecomposeSimilarity(ON_3dVector& T, double& dilation, ON_Xform& R, double tolerance) const { int rval = 0; if (IsAffine()) { ON_Xform L; DecomposeAffine(T, L); /* Three cases: I. L is within OrthogonalTol of being orthogonal then just return R = Linear (this is an optimization to avoid doing an eigen solve) II. Linear<10*tolerance or tol>1.0 then find the closest orthogonal matrix R. test the final solution to see if |*this-R| 0) ? 1 : -1; } else if (dist < 10 * tolerance || tolerance>1.0) { // Case II. ON_Xform Q; // ortho change of coordinate matrix ON_3dVector lambda; ON_3dVector Ttrash; if (L.DecomposeAffine(Ttrash, R, Q, lambda)) { // Find the min and max eigen-values int mini=0, maxi=0; double l0 = ON_DBL_MAX; double l1 = ON_DBL_MIN; for (int i = 0; i < 3; i++) { if (lambda[i] < l0) { mini = i; l0 = lambda[i]; } if (l1< lambda[i]) { maxi = i; l1 = lambda[i]; } } double err = (lambda[maxi] - lambda[mini]) / 2.0; if (err > tolerance) rval = 0; else { dilation = (lambda[mini] + lambda[maxi]) / 2.0; rval = (dilation > 0) ? 1 : -1; //dilation; } } } } return rval; } bool ON_Xform::DecomposeSymmetric(ON_Xform& Q, ON_3dVector& diagonal) const { bool rc = false; if (IsLinear()) { bool symmetric = ( m_xform[0][1] == m_xform[1][0] && m_xform[0][2] == m_xform[2][0] && m_xform[1][2] == m_xform[2][1] ); if (symmetric) { ON_3dVector evec[3]; rc = ON_Sym3x3EigenSolver(m_xform[0][0], m_xform[1][1], m_xform[2][2], m_xform[0][1], m_xform[1][2], m_xform[0][2], &diagonal.x, evec[0], &diagonal.y, evec[1], &diagonal.z, evec[2]); if (rc) { Q = ON_Xform(ON_3dPoint::Origin, evec[0], evec[1], evec[2]); } } } return rc; } int ON_Xform::DecomposeRigid(ON_3dVector& T, ON_Xform& R, double tolerance) const { int rval = 0; if (IsAffine()) { ON_Xform Linear; DecomposeAffine(T, Linear); /* Three cases: I. Linear is within OrthogonalTol of being orthogonal then just return R = Linear (this is an optimization to avoid doing an eigen solve) II. Linear~~<10*tolerance or tol>1.0 then find the closest orthogonal matrix R. test the final solution to see if |*this-R| 0) ? 1: -1; } else if (dist < 10*tolerance || tolerance>1.0) { // Case II. // Closest orthogonal matrix is given by polar decomposition // BHP Horn, http://people.csail.mit.edu/bkph/articles/Nearest_Orthonormal_Matrix.pdf ON_Xform Q; ON_3dVector lambda; if (DecomposeAffine(T, R, Q, lambda)) { // Is ||R - Linear|| = || R ( I - Q lam QT ) || = || I - Q lam QT ||= || Q QT - Q lam QT ||= || I - lam || double err = 0.0; for (int i = 0; i < 3; i++) { double x = fabs(1.0 - lambda[i]); if (x > err) err = x; } if (err < tolerance) { double det = lambda[0] * lambda[1] * lambda[2]; rval = (det > 0) ? 1 : -1; } } } } return rval; } int ON_Xform::IsRigid(double tolerance) const { // This function does not construct a rigid transformation, // ( see ON_Xform::DecomposeRigid() for this ). It merely // indicates that this transformation is sufficiently close to a rigid one. // However using with a tight tolerance like tol0; } return rc; } bool ON_Xform::GetQuaternion(ON_Quaternion& Q) const { bool rc = IsRotation(); if (rc) { double theta = 0; ON_3dVector Axis(m_xform[2][1] - m_xform[1][2], m_xform[0][2] - m_xform[2][0], m_xform[1][0] - m_xform[0][1]); double Alen = Axis.Length(); double trace = m_xform[0][0] + m_xform[1][1] + m_xform[2][2]; theta = atan2(Alen, trace - 1); // 0<= theta <= Pi/2 if(Alen>0.0 && trace> -.999) Axis = 1.0 / Alen * Axis; else { if (theta == 0.0) Axis = ON_3dVector::ZAxis; // case where axis is unspecified else { // Form off diagonal elements of the symmetric matrix (R+R^t)/2 double S12 = (m_xform[1][2] + m_xform[2][1]) / 2; double S13 = (m_xform[1][2] + m_xform[3][1]) / 2; // double S23 = (m_xform[2][3] + m_xform[3][2]) / 2; double c = (1 - trace) / 2.0; // cos(theta) should be ~-1.0 // magnitude of axis coefficients are computed as such: for (int i = 0; i < 3; i++) Axis[i] = sqrt((m_xform[i][i] - c) / (1 - c)); // need to set the signs if (S12 < 0) Axis[1] *= -1; if (S13 < 0) Axis[2] *= -1; } } Q.SetRotation(theta, Axis); } return rc; } bool ON_Xform::Orthogonalize(double tol) { bool rc = false; if (IsAffine()) { ON_3dVector T; ON_Xform L; DecomposeAffine(T, L); ON_Xform LTL = L; LTL.Transpose(); LTL = LTL * L; if (!LTL.IsIdentity(tol)) { // Gram - Schmidt ON_3dVector V[3]; V[0] = ON_3dVector(m_xform[0]); V[1] = ON_3dVector(m_xform[1]); V[2] = ON_3dVector(m_xform[2]); rc = true; for (int i = 0; rc && i < 3; i++) { for (int j = 0; j < i; j++) V[i] -= V[i] * V[j] * V[j]; rc = V[i].Unitize(); } if (rc) { *(reinterpret_cast(m_xform[0])) = V[0]; *(reinterpret_cast(m_xform[1])) = V[1]; *(reinterpret_cast(m_xform[2])) = V[2]; } } else rc = true; } return rc; } bool ON_Xform::DecomposeAffine(ON_3dVector& T, ON_Xform& R, ON_Xform& Q, ON_3dVector& lambda) const { bool rc = false; if (IsAffine()) { ON_Xform L; DecomposeAffine(T, L); ON_Xform LT = L; LT.Transpose(); ON_Xform LTL = LT * L; rc = LTL.DecomposeSymmetric(Q, lambda); if (rc) { rc = (lambda[0] > 0 && lambda[1] > 0 && lambda[2] > 0); if(rc) { lambda[0] = sqrt(lambda[0]); lambda[1] = sqrt(lambda[1]); lambda[2] = sqrt(lambda[2]); ON_Xform QT = Q; QT.Transpose(); ON_Xform Diag = ON_Xform::DiagonalTransformation(1.0 / lambda[0], 1.0 / lambda[1], 1.0 / lambda[2]); R = Q * Diag * QT; R = L * R; if (R.Determinant() < 0) { R = ON_Xform(-1) * R; lambda = -1 * lambda; } R.Orthogonalize(ON_ZERO_TOLERANCE); // tune it up - tol should be <= that in // Is_Rotation() } } } return rc; } bool ON_Xform::DecomposeAffine(ON_3dVector& T, ON_Xform& L) const { bool rc = IsAffine(); if (rc) { T = ON_3dVector(m_xform[0][3], m_xform[1][3], m_xform[2][3]); L = (*this); L.m_xform[0][3] = L.m_xform[1][3] = L.m_xform[2][3] = 0.0; } return rc; } // Suppose the transformation is given by f(x) = Lx + B. If L is invertible then // f(x) = L ( x + L^(-1) B) so T = L^(-1) B. bool ON_Xform::DecomposeAffine(ON_Xform& L, ON_3dVector& T) const { bool rc = IsAffine(); if (rc) { ON_Xform Linv = *this; rc = Linv.Invert(); if (rc) { T = - ON_3dVector( Linv[0][3], Linv[1][3], Linv[2][3] ); L = (*this); L[0][3] = L[1][3] = L[2][3] = 0.0; } /* TODO: A more thorough solution would be to take a tolerance and compute the best approximate T using psuodoinvese and comparing it using the tolerance. */ } return rc; } void ON_Xform::DecomposeTextureMapping(ON_3dVector& offset, ON_3dVector& repeat, ON_3dVector& rotation) const { // All angles in radians. ON_Xform xform = *this; repeat.x = sqrt(xform[0][0] * xform[0][0] + xform[0][1] * xform[0][1] + xform[0][2] * xform[0][2]); repeat.y = sqrt(xform[1][0] * xform[1][0] + xform[1][1] * xform[1][1] + xform[1][2] * xform[1][2]); repeat.z = sqrt(xform[2][0] * xform[2][0] + xform[2][1] * xform[2][1] + xform[2][2] * xform[2][2]); const ON_Xform S = TextureMapping(ON_3dVector::ZeroVector, repeat, ON_3dVector::ZeroVector).Inverse(); xform = S * xform; const double dSinBeta = -xform[2][0]; double dCosBeta = sqrt(1.0 - dSinBeta * dSinBeta); if (dCosBeta < 0.0) { dCosBeta = -dCosBeta; } double dSinAlpha, dCosAlpha, dSinGamma, dCosGamma; if (dCosBeta < 1e-6) { dSinAlpha = -xform[1][2]; dCosAlpha = xform[1][1]; dSinGamma = 0.0; dCosGamma = 1.0; } else { dSinAlpha = xform[2][1] / dCosBeta; dCosAlpha = xform[2][2] / dCosBeta; dSinGamma = xform[1][0] / dCosBeta; dCosGamma = xform[0][0] / dCosBeta; } rotation.x = atan2(dSinAlpha, dCosAlpha); rotation.y = atan2(dSinBeta, dCosBeta); rotation.z = atan2(dSinGamma, dCosGamma); const ON_Xform R = TextureMapping(ON_3dVector::ZeroVector, repeat, rotation).Inverse(); const ON_Xform T = R * *this; offset.x = -T[0][3]; offset.y = -T[1][3]; offset.z = -T[2][3]; } //static const ON_Xform ON_Xform::TextureMapping(const ON_3dVector& offset, const ON_3dVector& repeat, const ON_3dVector& rotation) { // All angles in radians. ON_Xform S = ON_Xform::DiagonalTransformation(repeat.x, repeat.y, repeat.z); ON_Xform R; R.Rotation(rotation.x, ON_3dVector::XAxis, ON_3dPoint::Origin); ON_3dVector vRotate = ON_3dVector::YAxis; vRotate.Transform(R.Inverse()); ON_Xform Ry; Ry.Rotation(rotation.y, vRotate, ON_3dPoint::Origin); R = R * Ry; vRotate = ON_3dVector::ZAxis; vRotate.Transform(R.Inverse()); ON_Xform Rz; Rz.Rotation(rotation.z, vRotate, ON_3dPoint::Origin); R = R * Rz; const ON_Xform T = ON_Xform::TranslationTransformation(-offset.x, -offset.y, -offset.z); return S * R * T; } bool ON_Xform::IsZero() const { const double* v = &m_xform[0][0]; for ( int i = 0; i < 15; i++ ) { if ( !(*v++ == 0.0 ) ) return false; // nonzero or nan } return (m_xform[3][3] == m_xform[3][3]); } bool ON_Xform::IsZero4x4() const { return (0.0 == m_xform[3][3] && IsZero()); } bool ON_Xform::IsZero4x4(double tol) const { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (false == fabs(m_xform[i][j]) <= tol) return false; return true; } bool ON_Xform::IsZeroTransformation() const { return IsZeroTransformation(0.0); } bool ON_Xform::IsZeroTransformation(double tol) const { bool rc = true; for(int i=0; rc && i<4; i++) for (int j = 0; rc && j < 4; j++) { if (i == 3 && j == 3) continue; rc = fabs(m_xform[i][j]) <= tol; } return (rc && 1.0 == m_xform[3][3] ); } void ON_Xform::Transpose() { double t; t = m_xform[0][1]; m_xform[0][1] = m_xform[1][0]; m_xform[1][0] = t; t = m_xform[0][2]; m_xform[0][2] = m_xform[2][0]; m_xform[2][0] = t; t = m_xform[0][3]; m_xform[0][3] = m_xform[3][0]; m_xform[3][0] = t; t = m_xform[1][2]; m_xform[1][2] = m_xform[2][1]; m_xform[2][1] = t; t = m_xform[1][3]; m_xform[1][3] = m_xform[3][1]; m_xform[3][1] = t; t = m_xform[2][3]; m_xform[2][3] = m_xform[3][2]; m_xform[3][2] = t; } int ON_Xform::Rank( double* pivot ) const { double I[4][4], d = 0.0, p = 0.0; int r = Inv( &m_xform[0][0], I, &d, &p ); if ( pivot ) *pivot = p; return r; } double ON_Xform::Determinant( double* pivot ) const { double I[4][4], d = 0.0, p = 0.0; //int rank = Inv( &m_xform[0][0], I, &d, &p ); if ( pivot ) *pivot = p; if (d != 0.0 ) d = 1.0/d; return d; } int ON_Xform::SignOfDeterminant(bool bFastTest) const { if (bFastTest) { if ( (0.0 == m_xform[3][0] && 0.0 == m_xform[3][1] && 0.0 == m_xform[3][2]) || (0.0 == m_xform[0][3] && 0.0 == m_xform[1][3] && 0.0 == m_xform[2][3]) ) { // vast majority of the 4x4 cases in practice. if (0.0 == m_xform[3][3]) { return 0; // 100% accurate result. } // Use the simple 3x3 formula with the fewest flops. const double x = m_xform[0][0] * (m_xform[1][1] * m_xform[2][2] - m_xform[1][2] * m_xform[2][1]) + m_xform[0][1] * (m_xform[1][2] * m_xform[2][0] - m_xform[1][0] * m_xform[2][2]) + m_xform[0][2] * (m_xform[1][0] * m_xform[2][1] - m_xform[1][1] * m_xform[2][0]); if (0.0 == x) { // It is very likely we had a simple case and, mathematically, det = 0. // (not a 100% safe assumption, but good enough for bFastTest = true case). return 0; } if (fabs(x) > 1e-8) { // x is big enough that it's very likely the sign is mathematically correct. // (not a 100% safe assumption, but good enough for bFastTest = true case). const int s = ((x < 0.0) ? -1 : 1) * ((m_xform[3][3] < 0.0) ? -1 : 1); return s; } } } // do it a very slow and careful way double min_pivot = 0.0; const double det = this->Determinant(&min_pivot); if (fabs(min_pivot) > ON_ZERO_TOLERANCE && fabs(det) > ON_ZERO_TOLERANCE) { return (det < 0.0) ? -1 : 1; } return 0; } bool ON_Xform::Invert( double* pivot ) { double mrofx[4][4], d = 0.0, p = 0.0; int rank = Inv( &m_xform[0][0], mrofx, &d, &p ); memcpy( m_xform, mrofx, sizeof(m_xform) ); if ( pivot ) *pivot = p; return (rank == 4) ? true : false; } ON_Xform ON_Xform::Inverse( double* pivot ) const { ON_Xform inv; double d = 0.0, p = 0.0; //int rank = Inv( &m_xform[0][0], inv.m_xform, &d, &p ); if ( pivot ) *pivot = p; return inv; } double ON_Xform::GetSurfaceNormalXform( ON_Xform& N_xform ) const { // since were are transforming vectors, we don't need // the translation column or bottom row. memcpy(&N_xform.m_xform[0][0],&m_xform[0][0], 3*sizeof(N_xform.m_xform[0][0]) ); N_xform.m_xform[0][3] = 0.0; memcpy(&N_xform.m_xform[1][0],&m_xform[1][0], 3*sizeof(N_xform.m_xform[0][0]) ); N_xform.m_xform[1][3] = 0.0; memcpy(&N_xform.m_xform[2][0],&m_xform[2][0], 3*sizeof(N_xform.m_xform[0][0]) ); N_xform.m_xform[2][3] = 0.0; N_xform.m_xform[3][0] = 0.0; N_xform.m_xform[3][1] = 0.0; N_xform.m_xform[3][2] = 0.0; N_xform.m_xform[3][3] = 1.0; double mrofx[4][4], d = 0.0, p = 0.0; double dtol = ON_SQRT_EPSILON*ON_SQRT_EPSILON*ON_SQRT_EPSILON; if ( 4 == Inv( &N_xform.m_xform[0][0], mrofx, &d, &p ) && fabs(d) > dtol && fabs(d)*dtol < 1.0 && fabs(p) > ON_EPSILON*fabs(d) ) { // Set N_xform = transpose of mrofx (only upper 3x3 matters) N_xform.m_xform[0][0] = mrofx[0][0]; N_xform.m_xform[0][1] = mrofx[1][0]; N_xform.m_xform[0][2] = mrofx[2][0]; N_xform.m_xform[1][0] = mrofx[0][1]; N_xform.m_xform[1][1] = mrofx[1][1]; N_xform.m_xform[1][2] = mrofx[2][1]; N_xform.m_xform[2][0] = mrofx[0][2]; N_xform.m_xform[2][1] = mrofx[1][2]; N_xform.m_xform[2][2] = mrofx[2][2]; } else { d = 0.0; } return d; } double ON_Xform::GetSurfaceNormalXformKeepLengthAndOrientation(ON_Xform& N_xform) const { N_xform = *this; N_xform.Linearize(); double pivot = 0; double det = N_xform.Determinant(&pivot); double tol = ON_SQRT_EPSILON * ON_SQRT_EPSILON * ON_SQRT_EPSILON; if (fabs(det) <= tol || fabs(det) * tol >= 1.0 || fabs(pivot) <= ON_EPSILON * fabs(det)) return 0.; ON_3dVector translation{ ON_3dVector::NanVector }; double scale{ ON_DBL_QNAN }; ON_Xform rotation{ ON_Xform::Nan }; const int is_similarity = N_xform.DecomposeSimilarity(translation, scale, rotation, ON_ZERO_TOLERANCE); // If it's a uniform scale, handle it, otherwise need to get the inverse. if (is_similarity != 0) { N_xform = rotation; if (det < 0) { N_xform = ON_Xform(-1.) * N_xform; } return det; } if (!N_xform.Invert()) return 0.; N_xform.Linearize(); N_xform.Transpose(); if (abs(abs(det) - 1) > ON_SQRT_EPSILON) { N_xform = ON_Xform(pow(det, -1. / 3.)) * N_xform; } return det; } double ON_Xform::GetMappingXforms( ON_Xform& P_xform, ON_Xform& N_xform ) const { double d = 0.0, p = 0.0; double dtol = ON_SQRT_EPSILON*ON_SQRT_EPSILON*ON_SQRT_EPSILON; if ( 4 == Inv( &m_xform[0][0], P_xform.m_xform, &d, &p ) && fabs(d) > dtol && fabs(d)*dtol < 1.0 && fabs(p) > ON_EPSILON*fabs(d) ) { // Set N_xform = transpose of this (only upper 3x3 matters) N_xform.m_xform[0][0] = m_xform[0][0]; N_xform.m_xform[0][1] = m_xform[1][0]; N_xform.m_xform[0][2] = m_xform[2][0]; N_xform.m_xform[0][3] = 0.0; N_xform.m_xform[1][0] = m_xform[0][1]; N_xform.m_xform[1][1] = m_xform[1][1]; N_xform.m_xform[1][2] = m_xform[2][1]; N_xform.m_xform[1][3] = 0.0; N_xform.m_xform[2][0] = m_xform[0][2]; N_xform.m_xform[2][1] = m_xform[1][2]; N_xform.m_xform[2][2] = m_xform[2][2]; N_xform.m_xform[2][3] = 0.0; N_xform.m_xform[3][0] = 0.0; N_xform.m_xform[3][1] = 0.0; N_xform.m_xform[3][2] = 0.0; N_xform.m_xform[3][3] = 1.0; } else { P_xform = ON_Xform::IdentityTransformation; N_xform = ON_Xform::IdentityTransformation; d = 0.0; } return d; } void ON_Xform::Rotation( double angle, ON_3dVector axis, // 3d nonzero axis of rotation ON_3dPoint center // 3d center of rotation ) { Rotation( sin(angle), cos(angle), axis, center ); } void ON_Xform::Rotation( ON_3dVector start_dir, ON_3dVector end_dir, ON_3dPoint rotation_center ) { if ( fabs(start_dir.Length()-1.0) > ON_SQRT_EPSILON ) start_dir.Unitize(); if ( fabs(end_dir.Length()-1.0) > ON_SQRT_EPSILON ) end_dir.Unitize(); double cos_angle = start_dir*end_dir; ON_3dVector axis = ON_CrossProduct(start_dir,end_dir); double sin_angle = axis.Length(); if ( 0.0 == sin_angle || !axis.Unitize() ) { axis.PerpendicularTo(start_dir); axis.Unitize(); sin_angle = 0.0; cos_angle = (cos_angle < 0.0) ? -1.0 : 1.0; } Rotation(sin_angle,cos_angle,axis,rotation_center); } void ON_Xform::Rotation( double sin_angle, double cos_angle, ON_3dVector axis, ON_3dPoint center ) { *this = ON_Xform::IdentityTransformation; for(;;) { // 29 June 2005 Dale Lear // Kill noise in input if ( fabs(sin_angle) >= 1.0-ON_SQRT_EPSILON && fabs(cos_angle) <= ON_SQRT_EPSILON ) { cos_angle = 0.0; sin_angle = (sin_angle < 0.0) ? -1.0 : 1.0; break; } if ( fabs(cos_angle) >= 1.0-ON_SQRT_EPSILON && fabs(sin_angle) <= ON_SQRT_EPSILON ) { cos_angle = (cos_angle < 0.0) ? -1.0 : 1.0; sin_angle = 0.0; break; } if ( fabs(cos_angle*cos_angle + sin_angle*sin_angle - 1.0) > ON_SQRT_EPSILON ) { ON_2dVector cs(cos_angle,sin_angle); if ( cs.Unitize() ) { cos_angle = cs.x; sin_angle = cs.y; // no break here } else { ON_ERROR("sin_angle and cos_angle are both zero."); cos_angle = 1.0; sin_angle = 0.0; break; } } if ( fabs(cos_angle) > 1.0-ON_EPSILON || fabs(sin_angle) < ON_EPSILON ) { cos_angle = (cos_angle < 0.0) ? -1.0 : 1.0; sin_angle = 0.0; break; } if ( fabs(sin_angle) > 1.0-ON_EPSILON || fabs(cos_angle) < ON_EPSILON ) { cos_angle = 0.0; sin_angle = (sin_angle < 0.0) ? -1.0 : 1.0; break; } break; } if (sin_angle != 0.0 || cos_angle != 1.0) { const double one_minus_cos_angle = 1.0 - cos_angle; ON_3dVector a = axis; if ( fabs(a.LengthSquared() - 1.0) > ON_EPSILON ) a.Unitize(); m_xform[0][0] = a.x*a.x*one_minus_cos_angle + cos_angle; m_xform[0][1] = a.x*a.y*one_minus_cos_angle - a.z*sin_angle; m_xform[0][2] = a.x*a.z*one_minus_cos_angle + a.y*sin_angle; m_xform[1][0] = a.y*a.x*one_minus_cos_angle + a.z*sin_angle; m_xform[1][1] = a.y*a.y*one_minus_cos_angle + cos_angle; m_xform[1][2] = a.y*a.z*one_minus_cos_angle - a.x*sin_angle; m_xform[2][0] = a.z*a.x*one_minus_cos_angle - a.y*sin_angle; m_xform[2][1] = a.z*a.y*one_minus_cos_angle + a.x*sin_angle; m_xform[2][2] = a.z*a.z*one_minus_cos_angle + cos_angle; if ( center.x != 0.0 || center.y != 0.0 || center.z != 0.0 ) { m_xform[0][3] = -((m_xform[0][0]-1.0)*center.x + m_xform[0][1]*center.y + m_xform[0][2]*center.z); m_xform[1][3] = -(m_xform[1][0]*center.x + (m_xform[1][1]-1.0)*center.y + m_xform[1][2]*center.z); m_xform[2][3] = -(m_xform[2][0]*center.x + m_xform[2][1]*center.y + (m_xform[2][2]-1.0)*center.z); } m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } } void ON_Xform::Rotation( const ON_3dVector& X0, // initial frame X (X,Y,Z = right handed orthonormal frame) const ON_3dVector& Y0, // initial frame Y const ON_3dVector& Z0, // initial frame Z const ON_3dVector& X1, // final frame X (X,Y,Z = another right handed orthonormal frame) const ON_3dVector& Y1, // final frame Y const ON_3dVector& Z1 // final frame Z ) { // transformation maps X0 to X1, Y0 to Y1, Z0 to Z1 // F0 changes x0,y0,z0 to world X,Y,Z ON_Xform F0; F0[0][0] = X0.x; F0[0][1] = X0.y; F0[0][2] = X0.z; F0[1][0] = Y0.x; F0[1][1] = Y0.y; F0[1][2] = Y0.z; F0[2][0] = Z0.x; F0[2][1] = Z0.y; F0[2][2] = Z0.z; F0[3][3] = 1.0; // F1 changes world X,Y,Z to x1,y1,z1 ON_Xform F1; F1[0][0] = X1.x; F1[0][1] = Y1.x; F1[0][2] = Z1.x; F1[1][0] = X1.y; F1[1][1] = Y1.y; F1[1][2] = Z1.y; F1[2][0] = X1.z; F1[2][1] = Y1.z; F1[2][2] = Z1.z; F1[3][3] = 1.0; *this = F1*F0; } void ON_Xform::Rotation( const ON_Plane& plane0, const ON_Plane& plane1 ) { Rotation( plane0.origin, plane0.xaxis, plane0.yaxis, plane0.zaxis, plane1.origin, plane1.xaxis, plane1.yaxis, plane1.zaxis ); } void ON_Xform::Rotation( // (not strictly a rotation) // transformation maps P0 to P1, P0+X0 to P1+X1, ... const ON_3dPoint& P0, // initial frame center const ON_3dVector& X0, // initial frame X const ON_3dVector& Y0, // initial frame Y const ON_3dVector& Z0, // initial frame Z const ON_3dPoint& P1, // final frame center const ON_3dVector& X1, // final frame X const ON_3dVector& Y1, // final frame Y const ON_3dVector& Z1 // final frame Z ) { // transformation maps P0 to P1, P0+X0 to P1+X1, ... // T0 translates point P0 to (0,0,0) const ON_Xform T0(ON_Xform::TranslationTransformation(ON_3dPoint::Origin - P0)); ON_Xform R; R.Rotation(X0,Y0,Z0,X1,Y1,Z1); // T1 translates (0,0,0) to point P1 ON_Xform T1(ON_Xform::TranslationTransformation(P1 - ON_3dPoint::Origin)); *this = T1*R*T0; } void ON_Xform::RotationZYX(double yaw, double pitch, double roll) { ON_Xform Rx; Rx.Rotation(roll, ON_3dVector::XAxis, ON_3dPoint::Origin); ON_Xform Ry; Ry.Rotation( pitch, ON_3dVector::YAxis, ON_3dPoint::Origin); ON_Xform Rz; Rz.Rotation(yaw, ON_3dVector::ZAxis, ON_3dPoint::Origin); (*this) = Rz * Ry * Rx; } void ON_Xform::RotationZYZ(double alpha, double beta, double gamma) { ON_Xform Rz; Rz.Rotation(gamma, ON_3dVector::ZAxis, ON_3dPoint::Origin); ON_Xform Ry; Ry.Rotation(beta, ON_3dVector::YAxis, ON_3dPoint::Origin); ON_Xform Rzz; Rzz.Rotation(alpha, ON_3dVector::ZAxis, ON_3dPoint::Origin); (*this) = Rzz * Ry * Rz; } bool ON_Xform::GetYawPitchRoll(double& yaw, double& pitch, double& roll)const { bool rc = IsRotation(); if (rc) { if( (m_xform[1][0] == 0.0 && m_xform[0][0] == 0.0) || (m_xform[2][1] == 0.0 && m_xform[2][2] == 0.0) || (fabs(m_xform[2][0])>=1.0) ) { pitch = (m_xform[2][0] > 0) ? -ON_PI / 2.0 : ON_PI / 2.0; yaw = atan2(-m_xform[0][1], m_xform[1][1] ); roll = 0.0; } else { yaw = atan2(m_xform[1][0], m_xform[0][0]); roll = atan2(m_xform[2][1], m_xform[2][2]); pitch = asin(-m_xform[2][0]); } } return rc; } bool ON_Xform::GetEulerZYZ(double& alpha, double& beta, double& gamma)const { bool rc = IsRotation(); if(rc) { if ((fabs(m_xform[2][2]) >= 1.0) || (m_xform[1][2] == 0.0 && m_xform[0][2] == 0.0) || (m_xform[2][1] == 0.0 && m_xform[2][0] == 0.0)) { beta = (m_xform[2][2] > 0) ? 0.0 : ON_PI; alpha = atan2(-m_xform[0][1], m_xform[1][1]); gamma = 0.0; } else { beta = acos(m_xform[2][2]); alpha = atan2(m_xform[1][2], m_xform[0][2]); gamma = atan2(m_xform[2][1], -m_xform[2][0]); } } return rc; } bool ON_Xform::GetKMLOrientationAnglesRadians(double& heading_radians, double& tilt_radians, double& roll_radians ) const { // NOTE: In KML, positive rotations are CLOCKWISE about the specified axis. // This is opposite the conventional "right hand rule." // https://developers.google.com/kml/documentation/kmlreference#orientation heading_radians = ON_DBL_QNAN; tilt_radians = ON_DBL_QNAN; roll_radians = ON_DBL_QNAN; bool rc = false; for (;;) { if (false == IsRotation()) break; // sin(1 degree)^3 = 5e-6. const double zero_tol = ON_ZERO_TOLERANCE; ON_Xform clean(*this); for (int i = 0; i < 4; ++i) for (int j = 0; j < 4; ++j) { double x = (i < 3 && j < 3) ? m_xform[i][j] : ((3==i&&3==j) ? 1.0 : 0.0); if (fabs(x) <= zero_tol) x = 0.0; else if (fabs(x-1.0) <= zero_tol) x = 1.0; else if (fabs(x+1.0) <= zero_tol) x = -1.0; else continue; clean.m_xform[i][j] = x; } if (false == clean.IsRotation()) clean = *this; // Set: h = -heading angle, t = -tilt angle, r = -roll angle. // (negatives because KML angles are opposite the right hand rule direction, and trig works the other way.) // // The KML specification says 0 <= heading <= 2pi, 0 <= tilt <= pi, 0 <= roll <= pi. // So, if this transformation is really a KML rotation, then we are looking for h,t,r // in the ranges -2pi <= h <= 0, -pi <= t <= 0, and -pi <= r <= 0. // // If you calculate the 3x3 KML orientation matrix M by hand, // then you get // M[1][0] = -sin(h)*cos(t) // M[1][1] = +cos(h)*cos(t) // M[2][0] = -cos(t)*sin(r) // M[2][1] = +sin(t) // M[2][2] = +cos(t)*cos(r) // So, a bit of trigonometry and you have h, t, and r. // NOTE WELL: When cos(t) is very near zero, but not equal to zero, // this calculation is unstable. In practice t is typically // a integer number of degrees between 0 and 180, and this // instability rarely matters. // tol = one half an arc second. // Should be way more precise than KML requires. const double zero_angle_tol = (0.5 / (60.0 * 60.0)) * ON_DEGREES_TO_RADIANS; double h = ON_DBL_QNAN; double r = ON_DBL_QNAN; double t = ON_DBL_QNAN; if ( (0.0 == clean.m_xform[0][1] && 0.0 == clean.m_xform[1][1]) || (0.0 == clean.m_xform[2][0] && 0.0 == clean.m_xform[2][2]) || (1.0 == fabs(clean.m_xform[2][1])) ) { // In this case, cos(tilt angle) = 0, clean.m_xform[2][1] = sin(tilt angle) = +1 or -1. // In this case it is impossible to distinguish between the initial rotation around // the y axis and the final rotation around the z axis // (tilt is the middle rotation around the x axis). // I'm choosing to set roll = 0 in this case. h = atan2(clean.m_xform[1][0], clean.m_xform[0][0]); // = atan2(clean.m_xform[0][1], -clean.m_xform[1][2]) if (fabs(h) <= zero_angle_tol) h = 0.0; r = 0.0; t = clean.m_xform[2][1] < 0.0 ? -ON_HALFPI : ON_HALFPI; // t = asin(clean.m_xform[2][1]); } else { // KML wants -pi <= r <= 0, so sin(r) <= 0 // clean.m_xform[2][0] = -cos(t)*sin(r) const double sign_cos_t = (clean.m_xform[2][0] < 0.0) ? -1.0 : 1.0; h = atan2(-sign_cos_t * clean.m_xform[0][1], sign_cos_t * clean.m_xform[1][1]); if (fabs(h) <= zero_angle_tol) h = 0.0; r = atan2(-sign_cos_t * clean.m_xform[2][0], sign_cos_t * clean.m_xform[2][2]); const double cos_h = cos(h); const double sin_h = sin(h); double cos_t = (fabs(sin_h) >= fabs(cos_h)) ? (-clean.m_xform[0][1] / sin_h) : (clean.m_xform[1][1] / cos_h) ; t = asin(clean.m_xform[2][1]); if (cos_t < 0.0) { // adjust the branch of t accordingly // cos_t could have a fair bit of noise in it // but the sign should generally be correct. if (0.0 == t) { // KML specification has -pi <= t <= 0 if (cos_t < -0.99) t = -ON_PI; } else if (t > -ON_HALFPI && t < 0.0) t = -ON_PI - t; } } if (h == h && r == r && t == t) { // NOTE: In KML, positive rotations are CLOCKWISE about the specified axis. // This is opposite the conventional "right hand rule." // https://developers.google.com/kml/documentation/kmlreference#orientation heading_radians = -h; if (heading_radians < 0.0) heading_radians += ON_2PI; // KML wants headings >= 0. tilt_radians = -t; roll_radians = -r; // Specifying a 3D rotation as a sequence of rotations about fixed axes // requires restricting rotations to intervals in order to get a one-to-one // correspondence between the 3 angles and the rotation. KML specifies // 0 <= heading < 360 // 0 <= tilt <= 180 // 0 <= roll <= 180 // TODO - If the angles we have are not in the specified intervals, // adjust them to produce the same rotation and be in the specified intervals. rc = true; } break; } return rc; } static double Internal_RadiansToPrettyKMLDegrees(double r, double min_degrees) { double d = r * ON_RADIANS_TO_DEGREES; double f = floor(d); if ( d-f > 0.5) f += 1.0; const double one_half_second_in_decimal_degrees = 0.5 / (60.0 * 60.0); if ( fabs(d - f) < one_half_second_in_decimal_degrees) // fabs(d-f) d = f; if (d < min_degrees) d += 360.0; if (fabs(d) < one_half_second_in_decimal_degrees) d = 0.0; // clean up -0.0 return d; } bool ON_Xform::GetKMLOrientationAnglesDegrees(double& heading_degrees, double& tilt_degrees, double& roll_degrees) const { double heading_radians = ON_DBL_QNAN; double tilt_radians = ON_DBL_QNAN; double roll_radians = ON_DBL_QNAN; const bool rc = ON_Xform::GetKMLOrientationAnglesRadians(heading_radians, tilt_radians, roll_radians); heading_degrees = Internal_RadiansToPrettyKMLDegrees(heading_radians, 0.0); tilt_degrees = Internal_RadiansToPrettyKMLDegrees(tilt_radians, -180.0); roll_degrees = Internal_RadiansToPrettyKMLDegrees(roll_radians, -180.0); return rc; } const ON_Xform ON_Xform::RotationTransformationFromKMLAnglesRadians( double heading_radians, double tilt_radians, double roll_radians ) { // NOTE: In KML, positive rotations are CLOCKWISE looking down the specified axis towards the origin. // This is opposite the conventional "right hand rule." // https://developers.google.com/kml/documentation/kmlreference#orientation ON_Xform H, R, T; // Standard trigonometry functions (cosine, sine, ...) follow the right hand rule // convention, so the input angles must be negated. H.Rotation(-heading_radians, ON_3dVector::ZAxis, ON_3dPoint::Origin); // KML Earth z-axis = up T.Rotation(-tilt_radians, ON_3dVector::XAxis, ON_3dPoint::Origin); // KML Earth x-axis = east R.Rotation(-roll_radians, ON_3dVector::YAxis, ON_3dPoint::Origin); // KML Earth y-axis = north // KML specifies the rotation order as first R, second T, third H. // Since opennurbs ON_Xform acts on the left of points and vectors, // H*T*R is the correct order. // Example transformed_point = H*T*R*point (R is first, T is second, H is third). ON_Xform kml_orientation = H * T * R; // clean up -0, .99999999999999999, and other similar results that // commonly occur and commonly disturb novices. for (int i = 0; i < 4; ++i) for (int j = 0; j < 4; ++j) { double x = kml_orientation.m_xform[i][j]; if (fabs(x) <= ON_ZERO_TOLERANCE) x = 0.0; else if (fabs(x - 1.0) <= ON_ZERO_TOLERANCE) x = 1.0; else if (fabs(x + 1.0) <= ON_ZERO_TOLERANCE) x = -1.0; else continue; kml_orientation.m_xform[i][j] = x; } return kml_orientation; } const ON_Xform ON_Xform::RotationTransformationFromKMLAnglesDegrees( double heading_degrees, double tilt_degrees, double roll_degrees ) { return ON_Xform::RotationTransformationFromKMLAnglesRadians( heading_degrees * ON_DEGREES_TO_RADIANS, tilt_degrees * ON_DEGREES_TO_RADIANS, roll_degrees * ON_DEGREES_TO_RADIANS ); } void ON_Xform::Mirror( ON_3dPoint point_on_mirror_plane, ON_3dVector normal_to_mirror_plane ) { ON_3dPoint P = point_on_mirror_plane; ON_3dVector N = normal_to_mirror_plane; N.Unitize(); ON_3dVector V = (2.0*(N.x*P.x + N.y*P.y + N.z*P.z))*N; m_xform[0][0] = 1 - 2.0*N.x*N.x; m_xform[0][1] = -2.0*N.x*N.y; m_xform[0][2] = -2.0*N.x*N.z; m_xform[0][3] = V.x; m_xform[1][0] = -2.0*N.y*N.x; m_xform[1][1] = 1.0 -2.0*N.y*N.y; m_xform[1][2] = -2.0*N.y*N.z; m_xform[1][3] = V.y; m_xform[2][0] = -2.0*N.z*N.x; m_xform[2][1] = -2.0*N.z*N.y; m_xform[2][2] = 1.0 -2.0*N.z*N.z; m_xform[2][3] = V.z; m_xform[3][0] = 0.0; m_xform[3][1] = 0.0; m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } const ON_Xform ON_Xform::MirrorTransformation( ON_PlaneEquation mirror_plane ) { const ON_PlaneEquation e = mirror_plane.UnitizedPlaneEquation(); const ON_3dVector N(e.x, e.y, e.z); ON_3dVector V = (-2.0*e.d)*N; ON_Xform mirror; mirror.m_xform[0][0] = 1 - 2.0*N.x*N.x; mirror.m_xform[0][1] = -2.0*N.x*N.y; mirror.m_xform[0][2] = -2.0*N.x*N.z; mirror.m_xform[0][3] = V.x; mirror.m_xform[1][0] = -2.0*N.y*N.x; mirror.m_xform[1][1] = 1.0 - 2.0*N.y*N.y; mirror.m_xform[1][2] = -2.0*N.y*N.z; mirror.m_xform[1][3] = V.y; mirror.m_xform[2][0] = -2.0*N.z*N.x; mirror.m_xform[2][1] = -2.0*N.z*N.y; mirror.m_xform[2][2] = 1.0 - 2.0*N.z*N.z; mirror.m_xform[2][3] = V.z; mirror.m_xform[3][0] = 0.0; mirror.m_xform[3][1] = 0.0; mirror.m_xform[3][2] = 0.0; mirror.m_xform[3][3] = 1.0; return mirror; } bool ON_Xform::ChangeBasis( // General: If you have points defined with respect to planes, this // computes the transformation to change coordinates from // one plane to another. The predefined world plane // ON_world_plane can be used as an argument. // Details: If P = plane0.Evaluate( a0,b0,c0 ) and // {a1,b1,c1} = ChangeBasis(plane0,plane1)*ON_3dPoint(a0,b0,c0), // then P = plane1.Evaluate( a1, b1, c1 ) // const ON_Plane& plane0, // initial plane const ON_Plane& plane1 // final plane ) { return ChangeBasis( plane0.origin, plane0.xaxis, plane0.yaxis, plane0.zaxis, plane1.origin, plane1.xaxis, plane1.yaxis, plane1.zaxis ); } bool ON_Xform::ChangeBasis( const ON_3dVector& X0, // initial frame X (X,Y,Z = arbitrary basis) const ON_3dVector& Y0, // initial frame Y const ON_3dVector& Z0, // initial frame Z const ON_3dVector& X1, // final frame X (X,Y,Z = arbitrary basis) const ON_3dVector& Y1, // final frame Y const ON_3dVector& Z1 // final frame Z ) { // Q = a0*X0 + b0*Y0 + c0*Z0 = a1*X1 + b1*Y1 + c1*Z1 // then this transform will map the point (a0,b0,c0) to (a1,b1,c1) *this = ON_Xform::ZeroTransformation; double a,b,c,d; a = X1*Y1; b = X1*Z1; c = Y1*Z1; double R[3][6] = {{X1*X1, a, b, X1*X0, X1*Y0, X1*Z0}, { a, Y1*Y1, c, Y1*X0, Y1*Y0, Y1*Z0}, { b, c, Z1*Z1, Z1*X0, Z1*Y0, Z1*Z0}}; //double R[3][6] = {{X1*X1, a, b, X0*X1, X0*Y1, X0*Z1}, // { a, Y1*Y1, c, Y0*X1, Y0*Y1, Y0*Z1}, // { b, c, Z1*Z1, Z0*X1, Z0*Y1, Z0*Z1}}; // row reduce R int i0 = (R[0][0] >= R[1][1]) ? 0 : 1; if ( R[2][2] > R[i0][i0] ) i0 = 2; int i1 = (i0+1)%3; int i2 = (i1+1)%3; if ( R[i0][i0] == 0.0 ) return false; d = 1.0/R[i0][i0]; R[i0][0] *= d; R[i0][1] *= d; R[i0][2] *= d; R[i0][3] *= d; R[i0][4] *= d; R[i0][5] *= d; R[i0][i0] = 1.0; if ( R[i1][i0] != 0.0 ) { d = -R[i1][i0]; R[i1][0] += d*R[i0][0]; R[i1][1] += d*R[i0][1]; R[i1][2] += d*R[i0][2]; R[i1][3] += d*R[i0][3]; R[i1][4] += d*R[i0][4]; R[i1][5] += d*R[i0][5]; R[i1][i0] = 0.0; } if ( R[i2][i0] != 0.0 ) { d = -R[i2][i0]; R[i2][0] += d*R[i0][0]; R[i2][1] += d*R[i0][1]; R[i2][2] += d*R[i0][2]; R[i2][3] += d*R[i0][3]; R[i2][4] += d*R[i0][4]; R[i2][5] += d*R[i0][5]; R[i2][i0] = 0.0; } if ( fabs(R[i1][i1]) < fabs(R[i2][i2]) ) { int i = i1; i1 = i2; i2 = i; } if ( R[i1][i1] == 0.0 ) return false; d = 1.0/R[i1][i1]; R[i1][0] *= d; R[i1][1] *= d; R[i1][2] *= d; R[i1][3] *= d; R[i1][4] *= d; R[i1][5] *= d; R[i1][i1] = 1.0; if ( R[i0][i1] != 0.0 ) { d = -R[i0][i1]; R[i0][0] += d*R[i1][0]; R[i0][1] += d*R[i1][1]; R[i0][2] += d*R[i1][2]; R[i0][3] += d*R[i1][3]; R[i0][4] += d*R[i1][4]; R[i0][5] += d*R[i1][5]; R[i0][i1] = 0.0; } if ( R[i2][i1] != 0.0 ) { d = -R[i2][i1]; R[i2][0] += d*R[i1][0]; R[i2][1] += d*R[i1][1]; R[i2][2] += d*R[i1][2]; R[i2][3] += d*R[i1][3]; R[i2][4] += d*R[i1][4]; R[i2][5] += d*R[i1][5]; R[i2][i1] = 0.0; } if ( R[i2][i2] == 0.0 ) return false; d = 1.0/R[i2][i2]; R[i2][0] *= d; R[i2][1] *= d; R[i2][2] *= d; R[i2][3] *= d; R[i2][4] *= d; R[i2][5] *= d; R[i2][i2] = 1.0; if ( R[i0][i2] != 0.0 ) { d = -R[i0][i2]; R[i0][0] += d*R[i2][0]; R[i0][1] += d*R[i2][1]; R[i0][2] += d*R[i2][2]; R[i0][3] += d*R[i2][3]; R[i0][4] += d*R[i2][4]; R[i0][5] += d*R[i2][5]; R[i0][i2] = 0.0; } if ( R[i1][i2] != 0.0 ) { d = -R[i1][i2]; R[i1][0] += d*R[i2][0]; R[i1][1] += d*R[i2][1]; R[i1][2] += d*R[i2][2]; R[i1][3] += d*R[i2][3]; R[i1][4] += d*R[i2][4]; R[i1][5] += d*R[i2][5]; R[i1][i2] = 0.0; } m_xform[0][0] = R[0][3]; m_xform[0][1] = R[0][4]; m_xform[0][2] = R[0][5]; m_xform[1][0] = R[1][3]; m_xform[1][1] = R[1][4]; m_xform[1][2] = R[1][5]; m_xform[2][0] = R[2][3]; m_xform[2][1] = R[2][4]; m_xform[2][2] = R[2][5]; return true; } bool ON_Xform::ChangeBasis( const ON_3dPoint& P0, // initial frame center const ON_3dVector& X0, // initial frame X (X,Y,Z = arbitrary basis) const ON_3dVector& Y0, // initial frame Y const ON_3dVector& Z0, // initial frame Z const ON_3dPoint& P1, // final frame center const ON_3dVector& X1, // final frame X (X,Y,Z = arbitrary basis) const ON_3dVector& Y1, // final frame Y const ON_3dVector& Z1 // final frame Z ) { bool rc = false; // Q = P0 + a0*X0 + b0*Y0 + c0*Z0 = P1 + a1*X1 + b1*Y1 + c1*Z1 // then this transform will map the point (a0,b0,c0) to (a1,b1,c1) ON_Xform F0(P0,X0,Y0,Z0); // Frame 0 // T1 translates by -P1 ON_Xform T1(ON_Xform::TranslationTransformation(ON_3dPoint::Origin - P1)); ON_Xform CB; rc = CB.ChangeBasis(ON_3dVector::XAxis, ON_3dVector::YAxis, ON_3dVector::ZAxis,X1,Y1,Z1); *this = CB*T1*F0; return rc; } void ON_Xform::WorldToCamera( const ON_3dPoint& cameraLocation, const ON_3dVector& cameraX, const ON_3dVector& cameraY, const ON_3dVector& cameraZ ) { // see comments in tl2_xform.h for details. /* compute world to camera coordinate xform */ m_xform[0][0] = cameraX.x; m_xform[0][1] = cameraX.y; m_xform[0][2] = cameraX.z; m_xform[0][3] = -(cameraX.x*cameraLocation.x + cameraX.y*cameraLocation.y + cameraX.z*cameraLocation.z); m_xform[1][0] = cameraY.x; m_xform[1][1] = cameraY.y; m_xform[1][2] = cameraY.z; m_xform[1][3] = -(cameraY.x*cameraLocation.x + cameraY.y*cameraLocation.y + cameraY.z*cameraLocation.z); m_xform[2][0] = cameraZ.x; m_xform[2][1] = cameraZ.y; m_xform[2][2] = cameraZ.z; m_xform[2][3] = -(cameraZ.x*cameraLocation.x + cameraZ.y*cameraLocation.y + cameraZ.z*cameraLocation.z); m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } void ON_Xform::CameraToWorld( const ON_3dPoint& cameraLocation, const ON_3dVector& cameraX, const ON_3dVector& cameraY, const ON_3dVector& cameraZ ) { // see comments in tl2_xform.h for details. /* compute camera to world coordinate m_xform */ m_xform[0][0] = cameraX.x; m_xform[0][1] = cameraY.x; m_xform[0][2] = cameraZ.x; m_xform[0][3] = cameraLocation.x; m_xform[1][0] = cameraX.y; m_xform[1][1] = cameraY.y; m_xform[1][2] = cameraZ.y; m_xform[1][3] = cameraLocation.y; m_xform[2][0] = cameraX.z; m_xform[2][1] = cameraY.z; m_xform[2][2] = cameraZ.z; m_xform[2][3] = cameraLocation.z; m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } bool ON_Xform::CameraToClip( bool bPerspective, double left, double right, double bottom, double top, double near_dist, double far_dist ) { double dd; if ( left == right || bottom == top || near_dist == far_dist ) return false; if ( !bPerspective ) { // parallel projection //d = 1.0/(left-right); //m_xform[0][0] = -2.0*d; m_xform[0][3] = (left+right)*d; m_xform[0][1] = m_xform[0][2] = 0.0; //d = 1.0/(bottom-top); //m_xform[1][1] = -2.0*d; m_xform[1][3] = (bottom+top)*d; m_xform[1][0] = m_xform[1][2] = 0.0; //d = 1.0/(far_dist-near_dist); //m_xform[2][2] = 2.0*d; m_xform[2][3] = (far_dist+near_dist)*d; m_xform[2][0] = m_xform[2][1] = 0.0; //m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; dd = (left-right); m_xform[0][0] = -2.0/dd; m_xform[0][3] = (left+right)/dd; m_xform[0][1] = m_xform[0][2] = 0.0; dd = (bottom-top); m_xform[1][1] = -2.0/dd; m_xform[1][3] = (bottom+top)/dd; m_xform[1][0] = m_xform[1][2] = 0.0; dd = (far_dist-near_dist); m_xform[2][2] = 2.0/dd; m_xform[2][3] = (far_dist+near_dist)/dd; m_xform[2][0] = m_xform[2][1] = 0.0; m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } else { // OpenNURBS uses a "right handed" camera coordinate system. // The camera X axis points horizontally left to right. // The camera Y axis points vertically bottom to top. // The camera Y axis points vertically bottom to top. // The camera Z axis points from back to front. // // If n = frustum near distance, f = frustum far distance // and 0 < n < f, then the perspective projection matrix is: // // 2n/(r-l) 0 (r+l)/(r-l) 0 // 0 2n/(t-b) (t+b)/(t-b) 0 // 0 0 (f+n)/(f-n) 2fn/(f-n) // 0 0 -1 0 // // Note that the "near frustum plane" is camera Z = -n and // the far frustum plane is camera Z = -f. Put another way // the camera Z coordinate is negative "depth". // // If (X,Y,Z,W) denotes camera coordinates, then as the value of // camera Z/W coordinate approaches -infinity from above, // (depth approaches +infinity from below), the value of // clipping z/w approaches -(f+n)/(f-n) from above. // // As camera coordinate Z/W approaches zero from below, // (depth approaches zero from above), the value of // clipping z/w approaches +infinity from below. // // The perspective projection transformation will map "points behind // the camera" (camera Z coordinate > 0) to a clipping coordinate // in the interval ( -infinity, -(f+n)/(f-n) ). // // To get a linear map from camera z to [-1,1], apply the linear // fractional transformation that maps [-1,1] -> [-1,1] // // L(s): s -> (a*s + b)/(a + bs), // // where a = (n+f) and b = (f-n), to the z coordinate // of the perspective projection transformation. // // Specifically, if M is the perspective transformation matrix above, // and transpose(x,y,z,w) = M*transpose(X,Y,(1-s)*n + s*f,1), then // (a*z + b*w)/(a*w + b*z) = 1 - 2s. // // Note that L(s) has a pole at s = -(f+n)/(f-n). // // The inverse of the linear fractional transformation L is G // // G(t): t -> (a*t - b)/(a - b*t) // dd = (right-left); m_xform[0][0] = 2.0*near_dist/dd; m_xform[0][2] = (right+left)/dd; m_xform[0][1] = m_xform[0][3] = 0.0; dd = (top-bottom); m_xform[1][1] = 2.0*near_dist/dd; m_xform[1][2] = (top+bottom)/dd; m_xform[1][0] = m_xform[1][3] = 0.0; dd = (far_dist-near_dist); m_xform[2][2] = (far_dist+near_dist)/dd; m_xform[2][3] = 2.0*near_dist*far_dist/dd; m_xform[2][0] = m_xform[2][1] = 0.0; m_xform[3][0] = m_xform[3][1] = m_xform[3][3] = 0.0; m_xform[3][2] = -1.0; } return true; } bool ON_Xform::ClipToCamera( bool bPerspective, double left, double right, double bottom, double top, double near_dist, double far_dist ) { // see comments in tl2_xform.h for details. double dd; if ( left == right || bottom == top || near_dist == far_dist ) return false; if ( !bPerspective ) { // parallel projection m_xform[0][0] = 0.5*(right-left); m_xform[0][3] = 0.5*(right+left); m_xform[0][1] = m_xform[0][2] = 0.0; m_xform[1][1] = 0.5*(top-bottom); m_xform[1][3] = 0.5*(top+bottom); m_xform[1][0] = m_xform[1][2] = 0.0; m_xform[2][2] = 0.5*(far_dist-near_dist); m_xform[2][3] = -0.5*(far_dist+near_dist); m_xform[2][0] = m_xform[2][1] = 0.0; m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } else { // perspective projection // (r-l)/(2n) 0 0 (r+l)/(2n) // 0 (t-b)/(2n) 0 (t+b)/(2n) // 0 0 0 -1 // 0 0 (f-n)/(2fn) (f+n)/(2fn) //d = 0.5/near_dist; //m_xform[0][0] = d*(right-left); //m_xform[0][3] = d*(right+left); //m_xform[0][1] = m_xform[0][2] = 0.0; //m_xform[1][1] = d*(top-bottom); //m_xform[1][3] = d*(top+bottom); //m_xform[1][0] = m_xform[1][2] = 0.0; //m_xform[2][0] = m_xform[2][1] = m_xform[2][2] = 0.0; m_xform[2][3] = -1.0; //d /= far_dist; //m_xform[3][2] = d*(far_dist-near_dist); //m_xform[3][3] = d*(far_dist+near_dist); //m_xform[3][0] = m_xform[3][1] = 0.0; dd = 2.0*near_dist; m_xform[0][0] = (right-left)/dd; m_xform[0][3] = (right+left)/dd; m_xform[0][1] = m_xform[0][2] = 0.0; m_xform[1][1] = (top-bottom)/dd; m_xform[1][3] = (top+bottom)/dd; m_xform[1][0] = m_xform[1][2] = 0.0; m_xform[2][0] = m_xform[2][1] = m_xform[2][2] = 0.0; m_xform[2][3] = -1.0; dd *= far_dist; m_xform[3][2] = (far_dist-near_dist)/dd; m_xform[3][3] = (far_dist+near_dist)/dd; m_xform[3][0] = m_xform[3][1] = 0.0; } return true; } bool ON_Xform::ClipToScreen( double left, double right, double bottom, double top, double near_z, double far_z ) { // see comments in tl2_xform.h for details. if ( left == right || bottom == top ) return false; m_xform[0][0] = 0.5*(right-left); m_xform[0][3] = 0.5*(right+left); m_xform[0][1] = m_xform[0][2] = 0.0; m_xform[1][1] = 0.5*(top-bottom); m_xform[1][3] = 0.5*(top+bottom); m_xform[1][0] = m_xform[1][2] = 0.0; if (far_z != near_z) { m_xform[2][2] = 0.5*(near_z-far_z); m_xform[2][3] = 0.5*(near_z+far_z); } else { m_xform[2][2] = 1.0; m_xform[2][3] = 0.0; } m_xform[2][0] = m_xform[2][1] = 0.0; m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; return true; } bool ON_Xform::ScreenToClip( double left, double right, double bottom, double top, double near_z, double far_z ) { // see comments in tl2_xform.h for details. ON_Xform c2s; bool rc = c2s.ClipToScreen( left, right, bottom, top, near_z, far_z ); if (rc) { m_xform[0][0] = 1.0/c2s[0][0]; m_xform[0][3] = -c2s[0][3]/c2s[0][0]; m_xform[0][1] = m_xform[0][2] = 0.0; m_xform[1][1] = 1.0/c2s[1][1]; m_xform[1][3] = -c2s[1][3]/c2s[1][1]; m_xform[1][0] = m_xform[1][2] = 0.0; m_xform[2][2] = 1.0/c2s[2][2]; m_xform[2][3] = -c2s[2][3]/c2s[2][2]; m_xform[2][0] = m_xform[2][1] = 0.0; m_xform[3][0] = m_xform[3][1] = m_xform[3][2] = 0.0; m_xform[3][3] = 1.0; } return rc; } int ON_Xform::ClipFlag4d( const double* point ) const { if ( !point ) return 1|2|4|8|16|32; int clip = 0; double x = m_xform[0][0]*point[0] + m_xform[0][1]*point[1] + m_xform[0][2]*point[2] + m_xform[0][3]*point[3]; double y = m_xform[1][0]*point[0] + m_xform[1][1]*point[1] + m_xform[1][2]*point[2] + m_xform[1][3]*point[3]; double z = m_xform[2][0]*point[0] + m_xform[2][1]*point[1] + m_xform[2][2]*point[2] + m_xform[2][3]*point[3]; double w = m_xform[3][0]*point[0] + m_xform[3][1]*point[1] + m_xform[3][2]*point[2] + m_xform[3][3]*point[3]; if ( point[3] < 0.0 ) { x = -x; y = -y; z = -z; w = -w; } if ( x <= -w ) clip |= 1; else if ( x >= w ) clip |= 2; if ( y <= -w ) clip |= 4; else if ( y >= w ) clip |= 8; if ( z <= -w ) clip |= 16; else if ( z >= w ) clip |= 32; return clip; } int ON_Xform::ClipFlag3d( const double* point ) const { if ( !point ) return 1|2|4|8|16|32; int clip = 0; const double x = m_xform[0][0]*point[0] + m_xform[0][1]*point[1] + m_xform[0][2]*point[2] + m_xform[0][3]; const double y = m_xform[1][0]*point[0] + m_xform[1][1]*point[1] + m_xform[1][2]*point[2] + m_xform[1][3]; const double z = m_xform[2][0]*point[0] + m_xform[2][1]*point[1] + m_xform[2][2]*point[2] + m_xform[2][3]; const double w = m_xform[3][0]*point[0] + m_xform[3][1]*point[1] + m_xform[3][2]*point[2] + m_xform[3][3]; if ( x <= -w ) clip |= 1; else if ( x >= w ) clip |= 2; if ( y <= -w ) clip |= 4; else if ( y >= w ) clip |= 8; if ( z <= -w ) clip |= 16; else if ( z >= w ) clip |= 32; return clip; } int ON_Xform::ClipFlag4d( int count, int stride, const double* point, bool bTestZ ) const { int clip = 1|2|4|8; if ( bTestZ) clip |= (16|32); if ( point && ((count > 0 && stride >= 4) || count == 1) ) { for ( /*empty*/; clip && count--; point += stride ) { clip &= ClipFlag4d( point ); } } return clip; } int ON_Xform::ClipFlag3d( int count, int stride, const double* point, bool bTestZ ) const { int clip = 1|2|4|8; if ( bTestZ) clip |= (16|32); if ( point && ((count > 0 && stride >= 3) || count == 1) ) { for ( /*empty*/; clip && count--; point += stride ) { clip &= ClipFlag3d( point ); } } return clip; } int ON_Xform::ClipFlag3dBox( const double* boxmin, const double* boxmax ) const { int clip = 1|2|4|8|16|32; double point[3]; int i,j,k; if ( boxmin && boxmax ) { for (i=0;i<2;i++) { point[0] = (i)?boxmax[0]:boxmin[0]; for (j=0;j<2;j++) { point[1] = (j)?boxmax[1]:boxmin[1]; for (k=0;k<2;k++) { point[2] = (k)?boxmax[2]:boxmin[2]; clip &= ClipFlag3d(point); if ( !clip ) return 0; } } } } return clip; } ON_Xform& ON_Xform::operator=(const ON_Matrix& src) { int i,j; i = src.RowCount(); const int maxi = (i>4)?4:i; j = src.ColCount(); const int maxj = (j>4)?4:j; *this = ON_Xform::IdentityTransformation; for ( i = 0; i < maxi; i++ ) for ( j = 0; j < maxj; j++ ) { m_xform[i][j] = src.m[i][j]; } return *this; } bool ON_Xform::IntervalChange( int dir, ON_Interval old_interval, ON_Interval new_interval ) { bool rc = false; *this = ON_Xform::IdentityTransformation; if ( dir >= 0 && dir <= 3 && old_interval[0] != ON_UNSET_VALUE && old_interval[1] != ON_UNSET_VALUE && new_interval[0] != ON_UNSET_VALUE && new_interval[1] != ON_UNSET_VALUE && old_interval.Length() != 0.0 ) { rc = true; if ( new_interval != old_interval ) { double s = new_interval.Length()/old_interval.Length();; double d = (new_interval[0]*old_interval[1] - new_interval[1]*old_interval[0])/old_interval.Length(); m_xform[dir][dir] = s; m_xform[dir][3] = d; } } return rc; }