2019-05-14 06:11:55 +00:00
//////////////////////////////////////////////////////////////////////
2019-05-17 21:41:45 +00:00
// LibFile: affine.scad
2019-05-14 06:11:55 +00:00
// Matrix math and affine transformation matrices.
// To use, add the following lines to the beginning of your file:
// ```
// use <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
// Section: Matrix Manipulation
// Function: ident()
// Description: Create an `n` by `n` identity matrix.
// Arguments:
// n = The size of the identity matrix square, `n` by `n`.
2019-05-27 05:34:46 +00:00
function ident ( n ) = [ for ( i = [ 0 : 1 : n - 1 ] ) [ for ( j = [ 0 : 1 : n - 1 ] ) ( i = = j ) ? 1 : 0 ] ] ;
2019-05-14 06:11:55 +00:00
2019-08-09 20:07:18 +00:00
// Function: affine2d_to_3d()
2019-05-14 06:11:55 +00:00
// Description: Takes a 3x3 affine2d matrix and returns its 4x4 affine3d equivalent.
2019-08-09 20:07:18 +00:00
function affine2d_to_3d ( m ) = concat (
2020-05-30 02:04:34 +00:00
[ for ( r = [ 0 : 2 ] )
concat (
[ for ( c = [ 0 : 2 ] ) m [ r ] [ c ] ] ,
[ 0 ]
)
] ,
[ [ 0 , 0 , 0 , 1 ] ]
2019-05-14 06:11:55 +00:00
) ;
// Section: Affine2d 3x3 Transformation Matrices
// Function: affine2d_identity()
// Description: Create a 3x3 affine2d identity matrix.
function affine2d_identity ( ) = ident ( 3 ) ;
// Function: affine2d_translate()
// Description:
// Returns the 3x3 affine2d matrix to perform a 2D translation.
// Arguments:
// v = 2D Offset to translate by. [X,Y]
function affine2d_translate ( v ) = [
2020-05-30 02:04:34 +00:00
[ 1 , 0 , v . x ] ,
[ 0 , 1 , v . y ] ,
[ 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine2d_scale()
// Description:
// Returns the 3x3 affine2d matrix to perform a 2D scaling transformation.
// Arguments:
// v = 2D vector of scaling factors. [X,Y]
function affine2d_scale ( v ) = [
2020-05-30 02:04:34 +00:00
[ v . x , 0 , 0 ] ,
[ 0 , v . y , 0 ] ,
[ 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine2d_zrot()
// Description:
// Returns the 3x3 affine2d matrix to perform a rotation of a 2D vector around the Z axis.
// Arguments:
// ang = Number of degrees to rotate.
function affine2d_zrot ( ang ) = [
2020-05-30 02:04:34 +00:00
[ cos ( ang ) , - sin ( ang ) , 0 ] ,
[ sin ( ang ) , cos ( ang ) , 0 ] ,
[ 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
2019-12-04 03:04:56 +00:00
// Function: affine2d_mirror()
// Usage:
// mat = affine2d_mirror(v);
// Description:
// Returns the 3x3 affine2d matrix to perform a reflection of a 2D vector across the line given by its normal vector.
// Arguments:
// v = The normal vector of the line to reflect across.
function affine2d_mirror ( v ) =
2020-05-30 02:04:34 +00:00
let ( v = unit ( point2d ( v ) ) , a = v . x , b = v . y )
[
[ 1 - 2 * a * a , 0 - 2 * a * b , 0 ] ,
[ 0 - 2 * a * b , 1 - 2 * b * b , 0 ] ,
[ 0 , 0 , 1 ]
] ;
2019-12-04 03:04:56 +00:00
2019-05-14 06:11:55 +00:00
// Function: affine2d_skew()
// Usage:
// affine2d_skew(xa, ya)
// Description:
// Returns the 3x3 affine2d matrix to skew a 2D vector along the XY plane.
// Arguments:
// xa = Skew angle, in degrees, in the direction of the X axis.
// ya = Skew angle, in degrees, in the direction of the Y axis.
function affine2d_skew ( xa , ya ) = [
2020-05-30 02:04:34 +00:00
[ 1 , tan ( xa ) , 0 ] ,
[ tan ( ya ) , 1 , 0 ] ,
[ 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
2019-05-14 21:38:54 +00:00
// Function: affine2d_chain()
2019-05-14 06:11:55 +00:00
// Usage:
2019-05-14 21:38:54 +00:00
// affine2d_chain(affines)
2019-05-14 06:11:55 +00:00
// Description:
// Returns a 3x3 affine2d transformation matrix which results from applying each matrix in `affines` in order.
// Arguments:
// affines = A list of 3x3 affine2d matrices.
2019-05-14 21:38:54 +00:00
function affine2d_chain ( affines , _m = undef , _i = 0 ) =
2020-05-30 02:04:34 +00:00
( _i >= len ( affines ) ) ? ( is_undef ( _m ) ? ident ( 3 ) : _m ) :
affine2d_chain ( affines , _m = ( is_undef ( _m ) ? affines [ _i ] : affines [ _i ] * _m ) , _i = _i + 1 ) ;
2019-05-14 06:11:55 +00:00
// Section: Affine3d 4x4 Transformation Matrices
// Function: affine3d_identity()
// Description: Create a 4x4 affine3d identity matrix.
function affine3d_identity ( ) = ident ( 4 ) ;
// Function: affine3d_translate()
// Description:
// Returns the 4x4 affine3d matrix to perform a 3D translation.
// Arguments:
// v = 3D offset to translate by. [X,Y,Z]
function affine3d_translate ( v ) = [
2020-05-30 02:04:34 +00:00
[ 1 , 0 , 0 , v . x ] ,
[ 0 , 1 , 0 , v . y ] ,
[ 0 , 0 , 1 , v . z ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_scale()
// Description:
// Returns the 4x4 affine3d matrix to perform a 3D scaling transformation.
// Arguments:
// v = 3D vector of scaling factors. [X,Y,Z]
function affine3d_scale ( v ) = [
2020-05-30 02:04:34 +00:00
[ v . x , 0 , 0 , 0 ] ,
[ 0 , v . y , 0 , 0 ] ,
[ 0 , 0 , v . z , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_xrot()
// Description:
// Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around the X axis.
// Arguments:
// ang = number of degrees to rotate.
function affine3d_xrot ( ang ) = [
2020-05-30 02:04:34 +00:00
[ 1 , 0 , 0 , 0 ] ,
[ 0 , cos ( ang ) , - sin ( ang ) , 0 ] ,
[ 0 , sin ( ang ) , cos ( ang ) , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_yrot()
// Description:
// Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around the Y axis.
// Arguments:
// ang = Number of degrees to rotate.
function affine3d_yrot ( ang ) = [
2020-05-30 02:04:34 +00:00
[ cos ( ang ) , 0 , sin ( ang ) , 0 ] ,
[ 0 , 1 , 0 , 0 ] ,
[ - sin ( ang ) , 0 , cos ( ang ) , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_zrot()
// Usage:
// affine3d_zrot(ang)
// Description:
// Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around the Z axis.
// Arguments:
// ang = number of degrees to rotate.
function affine3d_zrot ( ang ) = [
2020-05-30 02:04:34 +00:00
[ cos ( ang ) , - sin ( ang ) , 0 , 0 ] ,
[ sin ( ang ) , cos ( ang ) , 0 , 0 ] ,
[ 0 , 0 , 1 , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_rot_by_axis()
// Usage:
// affine3d_rot_by_axis(u, ang);
// Description:
// Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around an axis.
// Arguments:
// u = 3D axis vector to rotate around.
// ang = number of degrees to rotate.
2020-03-22 08:11:06 +00:00
function affine3d_rot_by_axis ( u , ang ) =
2020-05-30 02:04:34 +00:00
approx ( ang , 0 ) ? affine3d_identity ( ) :
let (
u = unit ( u ) ,
c = cos ( ang ) ,
c2 = 1 - c ,
s = sin ( ang )
) [
[ u . x * u . x * c2 + c , u . x * u . y * c2 - u . z * s , u . x * u . z * c2 + u . y * s , 0 ] ,
[ u . y * u . x * c2 + u . z * s , u . y * u . y * c2 + c , u . y * u . z * c2 - u . x * s , 0 ] ,
[ u . z * u . x * c2 - u . y * s , u . z * u . y * c2 + u . x * s , u . z * u . z * c2 + c , 0 ] ,
[ 0 , 0 , 0 , 1 ]
] ;
2019-05-14 06:11:55 +00:00
2019-05-26 06:31:05 +00:00
// Function: affine3d_rot_from_to()
// Usage:
// affine3d_rot_from_to(from, to);
// Description:
// Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector from one vector direction to another.
// Arguments:
// from = 3D axis vector to rotate from.
// to = 3D axis vector to rotate to.
2020-03-22 08:11:06 +00:00
function affine3d_rot_from_to ( from , to ) =
2020-05-30 02:04:34 +00:00
let (
from = unit ( point3d ( from ) ) ,
to = unit ( point3d ( to ) )
) approx ( from , to ) ? affine3d_identity ( ) :
let (
u = vector_axis ( from , to ) ,
ang = vector_angle ( from , to ) ,
c = cos ( ang ) ,
c2 = 1 - c ,
s = sin ( ang )
) [
[ u . x * u . x * c2 + c , u . x * u . y * c2 - u . z * s , u . x * u . z * c2 + u . y * s , 0 ] ,
[ u . y * u . x * c2 + u . z * s , u . y * u . y * c2 + c , u . y * u . z * c2 - u . x * s , 0 ] ,
[ u . z * u . x * c2 - u . y * s , u . z * u . y * c2 + u . x * s , u . z * u . z * c2 + c , 0 ] ,
[ 0 , 0 , 0 , 1 ]
] ;
2019-05-26 06:31:05 +00:00
2020-03-01 03:54:33 +00:00
// Function: affine_frame_map()
2020-06-20 06:00:40 +00:00
// Usage:
// map = affine_frame_map(x=v1,y=v2);
// map = affine_frame_map(x=v1,z=v2);
// map = affine_frame_map(y=v1,y=v2);
// map = affine_frame_map(v1,v2,v3);
2020-03-01 03:54:33 +00:00
// Description:
// Returns a transformation that maps one coordinate frame to another. You must specify two or three of `x`, `y`, and `z`. The specified
2020-06-20 06:00:40 +00:00
// axes are mapped to the vectors you supplied. If you give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand coordinate system.
2020-03-17 11:11:25 +00:00
// If the vectors you give are orthogonal the result will be a rotation and the `reverse` parameter will supply the inverse map, which enables you
2020-03-22 08:11:06 +00:00
// to map two arbitrary coordinate systems to each other by using the canonical coordinate system as an intermediary. You cannot use the `reverse` option
2020-03-17 11:11:25 +00:00
// with non-orthogonal inputs.
2020-03-01 03:54:33 +00:00
// Arguments:
// x = Destination vector for x axis
// y = Destination vector for y axis
// z = Destination vector for z axis
2020-03-17 11:11:25 +00:00
// reverse = reverse direction of the map for orthogonal inputs. Default: false
2020-03-01 03:54:33 +00:00
// Examples:
2020-06-20 06:00:40 +00:00
// T = affine_frame_map(x=[1,1,0], y=[-1,1,0]); // This map is just a rotation around the z axis
// T = affine_frame_map(x=[1,0,0], y=[1,1,0]); // This map is not a rotation because x and y aren't orthogonal
2020-03-01 03:54:33 +00:00
// // The next map sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]
// T = affine_frame_map(x=[0,1,1], y=[0,-1,1]) * affine_frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
function affine_frame_map ( x , y , z , reverse = false ) =
2020-05-30 02:04:34 +00:00
assert ( num_defined ( [ x , y , z ] ) >= 2 , "Must define at least two inputs" )
let (
xvalid = is_undef ( x ) || ( is_vector ( x ) && len ( x ) = = 3 ) ,
yvalid = is_undef ( y ) || ( is_vector ( y ) && len ( y ) = = 3 ) ,
zvalid = is_undef ( z ) || ( is_vector ( z ) && len ( z ) = = 3 )
)
assert ( xvalid , "Input x must be a length 3 vector" )
assert ( yvalid , "Input y must be a length 3 vector" )
assert ( zvalid , "Input z must be a length 3 vector" )
let (
2020-07-20 06:05:54 +00:00
x = is_undef ( x ) ? undef : unit ( x , RIGHT ) ,
y = is_undef ( y ) ? undef : unit ( y , BACK ) ,
z = is_undef ( z ) ? undef : unit ( z , UP ) ,
2020-05-30 02:04:34 +00:00
map = is_undef ( x ) ? [ cross ( y , z ) , y , z ] :
is_undef ( y ) ? [ x , cross ( z , x ) , z ] :
is_undef ( z ) ? [ x , y , cross ( x , y ) ] :
[ x , y , z ]
)
reverse ? (
let (
ocheck = (
approx ( map [ 0 ] * map [ 1 ] , 0 ) &&
approx ( map [ 0 ] * map [ 2 ] , 0 ) &&
approx ( map [ 1 ] * map [ 2 ] , 0 )
)
)
assert ( ocheck , "Inputs must be orthogonal when reverse==true" )
affine2d_to_3d ( map )
) : affine2d_to_3d ( transpose ( map ) ) ;
2020-03-01 03:54:33 +00:00
2019-12-04 03:04:56 +00:00
// Function: affine3d_mirror()
// Usage:
// mat = affine3d_mirror(v);
// Description:
// Returns the 4x4 affine3d matrix to perform a reflection of a 3D vector across the plane given by its normal vector.
// Arguments:
// v = The normal vector of the plane to reflect across.
function affine3d_mirror ( v ) =
2020-05-30 02:04:34 +00:00
let (
v = unit ( point3d ( v ) ) ,
a = v . x , b = v . y , c = v . z
) [
[ 1 - 2 * a * a , - 2 * a * b , - 2 * a * c , 0 ] ,
[ - 2 * b * a , 1 - 2 * b * b , - 2 * b * c , 0 ] ,
[ - 2 * c * a , - 2 * c * b , 1 - 2 * c * c , 0 ] ,
[ 0 , 0 , 0 , 1 ]
] ;
2019-12-04 03:04:56 +00:00
2019-12-20 07:26:54 +00:00
// Function: affine3d_skew()
// Usage:
2020-06-20 06:00:40 +00:00
// mat = affine3d_skew([sxy], [sxz], [syx], [syz], [szx], [szy]);
2019-12-20 07:26:54 +00:00
// Description:
// Returns the 4x4 affine3d matrix to perform a skew transformation.
// Arguments:
// sxy = Skew factor multiplier for skewing along the X axis as you get farther from the Y axis. Default: 0
// sxz = Skew factor multiplier for skewing along the X axis as you get farther from the Z axis. Default: 0
// syx = Skew factor multiplier for skewing along the Y axis as you get farther from the X axis. Default: 0
// syz = Skew factor multiplier for skewing along the Y axis as you get farther from the Z axis. Default: 0
// szx = Skew factor multiplier for skewing along the Z axis as you get farther from the X axis. Default: 0
// szy = Skew factor multiplier for skewing along the Z axis as you get farther from the Y axis. Default: 0
2020-01-07 00:55:22 +00:00
function affine3d_skew ( sxy = 0 , sxz = 0 , syx = 0 , syz = 0 , szx = 0 , szy = 0 ) = [
2020-05-30 02:04:34 +00:00
[ 1 , sxy , sxz , 0 ] ,
[ syx , 1 , syz , 0 ] ,
[ szx , szy , 1 , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-12-20 07:26:54 +00:00
] ;
2019-05-14 06:11:55 +00:00
// Function: affine3d_skew_xy()
// Usage:
// affine3d_skew_xy(xa, ya)
// Description:
2019-12-20 07:26:54 +00:00
// Returns the 4x4 affine3d matrix to perform a skew transformation along the XY plane.
2019-05-14 06:11:55 +00:00
// Arguments:
// xa = Skew angle, in degrees, in the direction of the X axis.
// ya = Skew angle, in degrees, in the direction of the Y axis.
function affine3d_skew_xy ( xa , ya ) = [
2020-05-30 02:04:34 +00:00
[ 1 , 0 , tan ( xa ) , 0 ] ,
[ 0 , 1 , tan ( ya ) , 0 ] ,
[ 0 , 0 , 1 , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_skew_xz()
// Usage:
// affine3d_skew_xz(xa, za)
// Description:
// Returns the 4x4 affine3d matrix to perform a skew transformation along the XZ plane.
// Arguments:
// xa = Skew angle, in degrees, in the direction of the X axis.
// za = Skew angle, in degrees, in the direction of the Z axis.
function affine3d_skew_xz ( xa , za ) = [
2020-05-30 02:04:34 +00:00
[ 1 , tan ( xa ) , 0 , 0 ] ,
[ 0 , 1 , 0 , 0 ] ,
[ 0 , tan ( za ) , 1 , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
// Function: affine3d_skew_yz()
// Usage:
// affine3d_skew_yz(ya, za)
// Description:
// Returns the 4x4 affine3d matrix to perform a skew transformation along the YZ plane.
// Arguments:
// ya = Skew angle, in degrees, in the direction of the Y axis.
// za = Skew angle, in degrees, in the direction of the Z axis.
function affine3d_skew_yz ( ya , za ) = [
2020-05-30 02:04:34 +00:00
[ 1 , 0 , 0 , 0 ] ,
[ tan ( ya ) , 1 , 0 , 0 ] ,
[ tan ( za ) , 0 , 1 , 0 ] ,
[ 0 , 0 , 0 , 1 ]
2019-05-14 06:11:55 +00:00
] ;
2019-05-14 21:38:54 +00:00
// Function: affine3d_chain()
2019-05-14 06:11:55 +00:00
// Usage:
2019-05-14 21:38:54 +00:00
// affine3d_chain(affines)
2019-05-14 06:11:55 +00:00
// Description:
// Returns a 4x4 affine3d transformation matrix which results from applying each matrix in `affines` in order.
// Arguments:
// affines = A list of 4x4 affine3d matrices.
2019-05-14 21:38:54 +00:00
function affine3d_chain ( affines , _m = undef , _i = 0 ) =
2020-05-30 02:04:34 +00:00
( _i >= len ( affines ) ) ? ( is_undef ( _m ) ? ident ( 4 ) : _m ) :
affine3d_chain ( affines , _m = ( is_undef ( _m ) ? affines [ _i ] : affines [ _i ] * _m ) , _i = _i + 1 ) ;
2019-05-14 06:11:55 +00:00
2020-02-27 22:32:03 +00:00
// Function: apply()
// Usage: apply(transform, points)
// Description:
// Applies the specified transformation matrix to a point list (or single point). Both inputs can be 2d or 3d, and it is also allowed
// to supply 3d transformations with 2d data as long as the the only action on the z coordinate is a simple scaling.
// Examples:
// transformed = apply(xrot(45), path3d(circle(r=3))); // Rotates 3d circle data around x axis
// transformed = apply(rot(45), circle(r=3)); // Rotates 2d circle data by 45 deg
// transformed = apply(rot(45)*right(4)*scale(3), circle(r=3)); // Scales, translates and rotates 2d circle data
function apply ( transform , points ) =
2020-06-01 01:49:27 +00:00
points = = [ ] ? [ ] :
2020-02-27 22:32:03 +00:00
is_vector ( points ) ? apply ( transform , [ points ] ) [ 0 ] :
let (
tdim = len ( transform [ 0 ] ) - 1 ,
datadim = len ( points [ 0 ] )
)
tdim = = 3 && datadim = = 3 ? [ for ( p = points ) point3d ( transform * concat ( p , [ 1 ] ) ) ] :
tdim = = 2 && datadim = = 2 ? [ for ( p = points ) point2d ( transform * concat ( p , [ 1 ] ) ) ] :
tdim = = 3 && datadim = = 2 ?
assert ( is_2d_transform ( transform ) , str ( "Transforms is 3d but points are 2d" ) )
[ for ( p = points ) point2d ( transform * concat ( p , [ 0 , 1 ] ) ) ] :
assert ( false , str ( "Unsupported combination: transform with dimension " , tdim , ", data of dimension " , datadim ) ) ;
// Function: apply_list()
// Usage: apply_list(points, transform_list)
// Description:
// Transforms the specified point list (or single point) using a list of transformation matrices. Transformations on
// the list are applied in the order they appear in the list (as in right multiplication of matrices). Both inputs can be
// 2d or 3d, and it is also allowed to supply 3d transformations with 2d data as long as the the only action on the z coordinate
// is a simple scaling. All transformations on `transform_list` must have the same dimension: you cannot mix 2d and 3d transformations
// even when acting on 2d data.
// Examples:
// transformed = apply_list(path3d(circle(r=3)),[xrot(45)]); // Rotates 3d circle data around x axis
// transformed = apply_list(circle(r=3), [scale(3), right(4), rot(45)]); // Scales, then translates, and then rotates 2d circle data
function apply_list ( points , transform_list ) =
2020-06-20 06:00:40 +00:00
transform_list = = [ ] ? points :
2020-02-27 22:32:03 +00:00
is_vector ( points ) ? apply_list ( [ points ] , transform_list ) [ 0 ] :
let (
tdims = array_dim ( transform_list ) ,
datadim = len ( points [ 0 ] )
)
assert ( len ( tdims ) = = 3 || tdims [ 1 ] ! = tdims [ 2 ] , "Invalid transformation list" )
let ( tdim = tdims [ 1 ] - 1 )
tdim = = 2 && datadim = = 2 ? apply ( affine2d_chain ( transform_list ) , points ) :
tdim = = 3 && datadim = = 3 ? apply ( affine3d_chain ( transform_list ) , points ) :
tdim = = 3 && datadim = = 2 ?
let (
badlist = [ for ( i = idx ( transform_list ) ) if ( ! is_2d_transform ( transform_list [ i ] ) ) i ]
)
assert ( badlist = = [ ] , str ( "Transforms with indices " , badlist , " are 3d but points are 2d" ) )
apply ( affine3d_chain ( transform_list ) , points ) :
assert ( false , str ( "Unsupported combination: transform with dimension " , tdim , ", data of dimension " , datadim ) ) ;
// Function: is_2d_transform()
2020-03-22 08:11:06 +00:00
// Usage:
// is_2d_transform(t)
// Description:
// Checks if the input is a 3d transform that does not act on the z coordinate, except
2020-02-27 22:32:03 +00:00
// possibly for a simple scaling of z. Note that an input which is only a zscale returns false.
function is_2d_transform ( t ) = // z-parameters are zero, except we allow t[2][2]!=1 so scale() works
2020-03-22 08:11:06 +00:00
t [ 2 ] [ 0 ] = = 0 && t [ 2 ] [ 1 ] = = 0 && t [ 2 ] [ 3 ] = = 0 && t [ 0 ] [ 2 ] = = 0 && t [ 1 ] [ 2 ] = = 0 &&
2020-02-27 22:32:03 +00:00
( t [ 2 ] [ 2 ] = = 1 || ! ( t [ 0 ] [ 0 ] = = 1 && t [ 0 ] [ 1 ] = = 0 && t [ 1 ] [ 0 ] = = 0 && t [ 1 ] [ 1 ] = = 1 ) ) ; // But rule out zscale()
2020-03-01 03:54:33 +00:00
2020-05-30 02:04:34 +00:00
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap