2019-06-08 17:10:47 -04:00
//
// NopSCADlib Copyright Chris Palmer 2018
// nop.head@gmail.com
// hydraraptor.blogspot.com
//
// This file is part of NopSCADlib.
//
// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with NopSCADlib.
// If not, see <https://www.gnu.org/licenses/>.
//
//
2019-09-06 06:28:49 -04:00
//! Maths utilities for manipulating vectors and matrices.
2019-06-08 17:10:47 -04:00
//
2020-07-06 07:43:24 -04:00
function sqr ( x ) = x * x ; //! Square x
function radians ( degrees ) = degrees * PI / 180 ; //! Convert radians to degrees
function degrees ( radians ) = radians * 180 / PI ; //! Convert degrees to radians
2020-09-15 15:58:39 -04:00
function sinh ( x ) = ( exp ( x ) - exp ( - x ) ) / 2 ; //! hyperbolic sine
function cosh ( x ) = ( exp ( x ) + exp ( - x ) ) / 2 ; //! hyperbolic cosine
function tanh ( x ) = sinh ( x ) / cosh ( x ) ; //! hyperbolic tangent
function coth ( x ) = cosh ( x ) / sinh ( x ) ; //! hyperbolic cotangent
function argsinh ( x ) = ln ( x + sqrt ( sqr ( x ) + 1 ) ) ; //! inverse hyperbolic sine
function argcosh ( x ) = ln ( x + sqrt ( sqr ( x ) - 1 ) ) ; //! inverse hyperbolic cosine
function argtanh ( x ) = ln ( ( 1 + x ) / ( 1 - x ) ) / 2 ; //! inverse hyperbolic tangent
function argcoth ( x ) = ln ( ( x + 1 ) / ( x - 1 ) ) / 2 ; //! inverse hyperbolic cotangent
2019-06-08 17:10:47 -04:00
2020-12-24 11:04:59 -05:00
function translate ( v ) = let ( u = is_list ( v ) ? len ( v ) = = 2 ? [ v . x , v . y , 0 ] //! Generate a 4x4 translation matrix, `v` can be `[x, y]`, `[x, y, z]` or `z`
2019-06-08 17:10:47 -04:00
: v
: [ 0 , 0 , v ] )
[ [ 1 , 0 , 0 , u . x ] ,
[ 0 , 1 , 0 , u . y ] ,
[ 0 , 0 , 1 , u . z ] ,
[ 0 , 0 , 0 , 1 ] ] ;
2020-12-24 11:04:59 -05:00
function rotate ( a , v ) = //! Generate a 4x4 rotation matrix, `a` can be a vector of three angles or a single angle around `z`, or around axis `v`
2019-06-08 17:10:47 -04:00
is_undef ( v ) ? let ( av = is_list ( a ) ? a : [ 0 , 0 , a ] ,
cx = cos ( av [ 0 ] ) ,
cy = cos ( av [ 1 ] ) ,
cz = cos ( av [ 2 ] ) ,
sx = sin ( av [ 0 ] ) ,
sy = sin ( av [ 1 ] ) ,
sz = sin ( av [ 2 ] ) )
[
2020-02-22 14:44:01 -05:00
[ cy * cz , sx * sy * cz - cx * sz , cx * sy * cz + sx * sz , 0 ] ,
[ cy * sz , sx * sy * sz + cx * cz , cx * sy * sz - sx * cz , 0 ] ,
[ - sy , sx * cy , cx * cy , 0 ] ,
2019-06-08 17:10:47 -04:00
[ 0 , 0 , 0 , 1 ]
]
: let ( s = sin ( a ) ,
c = cos ( a ) ,
C = 1 - c ,
m = sqr ( v . x ) + sqr ( v . y ) + sqr ( v . z ) , // m used instead of norm to avoid irrational roots as much as possible
u = v / sqrt ( m ) )
[
[ C * v . x * v . x / m + c , C * v . x * v . y / m - u . z * s , C * v . x * v . z / m + u . y * s , 0 ] ,
[ C * v . y * v . x / m + u . z * s , C * v . y * v . y / m + c , C * v . y * v . z / m - u . x * s , 0 ] ,
[ C * v . z * v . x / m - u . y * s , C * v . z * v . y / m + u . x * s , C * v . z * v . z / m + c , 0 ] ,
[ 0 , 0 , 0 , 1 ]
] ;
2020-03-24 13:22:32 -04:00
function rot3_z ( a ) = //! Generate a 3x3 matrix to rotate around z
let ( c = cos ( a ) ,
s = sin ( a ) )
[ [ c , - s , 0 ] ,
[ s , c , 0 ] ,
[ 0 , 0 , 1 ] ] ;
2020-07-06 07:43:24 -04:00
function rot2_z ( a ) = //! Generate a 2x2 matrix to rotate around z
let ( c = cos ( a ) ,
s = sin ( a ) )
[ [ c , - s ] ,
[ s , c ] ] ;
2020-12-24 11:04:59 -05:00
function scale ( v ) = let ( s = is_list ( v ) ? v : [ v , v , v ] ) //! Generate a 4x4 matrix that scales by `v`, which can be a vector of xyz factors or a scalar to scale all axes equally
2019-06-08 17:10:47 -04:00
[
[ s . x , 0 , 0 , 0 ] ,
[ 0 , s . y , 0 , 0 ] ,
[ 0 , 0 , s . z , 0 ] ,
[ 0 , 0 , 0 , 1 ]
] ;
2020-12-24 11:04:59 -05:00
function vec3 ( v ) = [ v . x , v . y , v . z ] ; //! Return a 3 vector with the first three elements of `v`
function vec4 ( v ) = [ v . x , v . y , v . z , 1 ] ; //! Return a 4 vector with the first three elements of `v`
2019-06-08 17:10:47 -04:00
function transform ( v , m ) = vec3 ( m * [ v . x , v . y , v . z , 1 ] ) ; //! Apply 4x4 transform to a 3 vector by extending it and cropping it again
function transform_points ( path , m ) = [ for ( p = path ) transform ( p , m ) ] ; //! Apply transform to a path
2020-12-24 11:04:59 -05:00
function unit ( v ) = let ( n = norm ( v ) ) n ? v / n : v ; //! Convert `v` to a unit vector
2019-06-08 17:10:47 -04:00
function transpose ( m ) = [ for ( j = [ 0 : len ( m [ 0 ] ) - 1 ] ) [ for ( i = [ 0 : len ( m ) - 1 ] ) m [ i ] [ j ] ] ] ; //! Transpose an arbitrary size matrix
function identity ( n , x = 1 ) = [ for ( i = [ 0 : n - 1 ] ) [ for ( j = [ 0 : n - 1 ] ) i = = j ? x : 0 ] ] ; //! Construct an arbitrary size identity matrix
function reverse ( v ) = let ( n = len ( v ) - 1 ) n < 0 ? [ ] : [ for ( i = [ 0 : n ] ) v [ n - i ] ] ; //! Reverse a vector
2020-02-22 14:44:01 -05:00
function angle_between ( v1 , v2 ) = acos ( v1 * v2 / ( norm ( v1 ) * norm ( v2 ) ) ) ; //! Return the angle between two vectors
// https://www.gregslabaugh.net/publications/euler.pdf
function euler ( R ) = let ( ay = asin ( - R [ 2 ] [ 0 ] ) , cy = cos ( ay ) ) //! Convert a rotation matrix to a Euler rotation vector.
cy ? [ atan2 ( R [ 2 ] [ 1 ] / cy , R [ 2 ] [ 2 ] / cy ) , ay , atan2 ( R [ 1 ] [ 0 ] / cy , R [ 0 ] [ 0 ] / cy ) ]
: R [ 2 ] [ 0 ] < 0 ? [ atan2 ( R [ 0 ] [ 1 ] , R [ 0 ] [ 2 ] ) , 180 , 0 ]
: [ atan2 ( - R [ 0 ] [ 1 ] , - R [ 0 ] [ 2 ] ) , - 180 , 0 ] ;
2020-06-20 10:04:03 -04:00
module position_children ( list , t ) //! Position children if they are on the Z = 0 plane when transformed by t
for ( p = list )
let ( q = t * p )
if ( abs ( transform ( [ 0 , 0 , 0 ] , q ) . z ) < 0.01 )
multmatrix ( q )
children ( ) ;
2020-06-20 10:01:01 -04:00
// Matrix inversion: https://www.mathsisfun.com/algebra/matrix-inverse-row-operations-gauss-jordan.html
function augment ( m ) = let ( l = len ( m ) , n = identity ( l ) ) [ //! Augment a matrix by adding an identity matrix to the right
for ( i = [ 0 : l - 1 ] )
concat ( m [ i ] , n [ i ] )
] ;
function rowswap ( m , i , j ) = [ //! Swap two rows of a matrix
for ( k = [ 0 : len ( m ) - 1 ] )
k = = i ? m [ j ] : k = = j ? m [ i ] : m [ k ]
] ;
function solve_row ( m , i ) = let ( diag = m [ i ] [ i ] ) [ //! Make diagonal one by dividing the row by it and subtract from other rows to make column zero
for ( j = [ 0 : len ( m ) - 1 ] )
i = = j ? m [ j ] / diag : m [ j ] - m [ i ] * m [ j ] [ i ] / diag
] ;
function nearly_zero ( x ) = abs ( x ) < 1e-5 ; //! True if x is close to zero
function solve ( m , i = 0 , j = 0 ) = //! Solve each row ensuring diagonal is not zero
i < len ( m ) ?
assert ( i + j < len ( m ) , "matrix is singular" )
solve ( ! nearly_zero ( m [ i + j ] [ i ] ) ? solve_row ( j ? rowswap ( m , i , i + j ) : m , i ) : solve ( m , i , j + 1 ) , i + 1 )
: m ;
function invert ( m ) = let ( n = len ( m ) , m = solve ( augment ( m ) ) ) [ //! Invert a matrix
for ( i = [ 0 : n - 1 ] ) [
for ( j = [ n : 2 * n - 1 ] )
each m [ i ] [ j ]
]
] ;
2020-10-04 17:01:08 -04:00
function circle_intersect ( c1 , r1 , c2 , r2 ) = //! Calculate one point where two circles in the X-Z plane intersect, clockwise around c1
let (
v = c1 - c2 , // Line between centres
d = norm ( v ) , // Distance between centres
a = atan2 ( v . z , v . x ) - acos ( ( sqr ( d ) + sqr ( r2 ) - sqr ( r1 ) ) / ( 2 * d * r2 ) ) // Cosine rule to find angle from c2
) c2 + r2 * [ cos ( a ) , 0 , sin ( a ) ] ; // Point on second circle