//v1.0.0
//Author: Nathaniel Anozie
//ogbonnawork at gmail dot com
//
////description: common math functions
//supports rounding
//
//How to Install: Source:
// naGeneral.mel --querying types ex, getting curve type, joints on scene
// naMath.mel
//
//date created: January 10, 2012
//date last revised: March 21, 2012
//
//Acknowledgements: Duncan Brinsmead for rounding a float to decimal places
// Bryan Ewert, xyz2 dot net for rounding a float to decimal places, querying number cvs
//Modify at your own risk
/**return integer number of cvs
@param $userCurve -- curve argument
@result int list where first element is number of cvs
*/
global proc int[]
na_getNumberCurveCV(string $userCurve)
{
int $result[] = {};
//check this is a curve
int $isCurve = 0;
$isCurve = na_isCurve($userCurve);
if($isCurve == 1){
int $degree[]={};
int $span[]={};
$degree[0] = `getAttr ($userCurve+".degree")`;
$span[0] = `getAttr ($userCurve+".spans")`;
//number cvs equals number spans plus degree of curve
$numcv = $span[0] + $degree[0];
$result[size($result)] = $numcv;
}
else{ print("cannot perform operation on this node type\n"); }
return $result;
}
/**
@pre expect clean maya scene
@pre naMath.mel
@pre naSegment.mel
@pre naGeneral.mel
*/
global proc float[]
na_getLength_userTest()
{
joint -p 0 0 0 ;
joint -p 7 0 0 ;
joint -e -zso -oj xyz -sao yup "joint1";
return na_getLength("joint1","joint2");
}
/**return shortest distance between two nodes
*/
global proc float[]
na_getLength(string $startNode, string $endNode)
{
float $result[];
if( (`objExists $startNode`) && ( `objExists $endNode` ) )
{
//make things to help give us length
//here were going to use the length of curve drawn between end points
float $startPos[] = {};
float $endPos[] = {};
string $tempName = "naTempCurve_na_getLength";
string $tempNameNode = "naTempNode_na_getLength";
$startPos = getComponentWorldPosition({$startNode});
$endPos = getComponentWorldPosition({$endNode});
na_drawCurveBySegment(1,$tempName, $startPos, $endPos);
//note change to drawCurve through all points between these to figure
//out length for a curving joint hiearchy
na_makeCurveInfoNode({$tempName}, {$tempNameNode});
if( `objExists $tempNameNode` )
{
$result[size($result)] = `getAttr ($tempNameNode+".arcLength")`;
}
//clean up created helper things
if( `objExists $tempName` ){ delete $tempName; }
if( `objExists $tempNameNode` ){ delete $tempNameNode; }
}
return $result;
}
/**excepts float and number of decimal places to round, returns float rounded appropriately
@param float $arg -- number to round
@param int $numberOfPlaces -- number of places to round
@note default returns 0.0
*/
global proc float roundToNearestDecimal(float $arg, int $numberOfPlaces)
{
//idea adding 0.5 to 0.5 then flooring makes it 1, adding 0.5 to 0.4 makes it 0.9 flooring makes it 0
//multiply a decimal by 10 raised to number of places puts number in position to add or subtract 0.5
float $rounded = 0.0;
if($numberOfPlaces >= 0 ){
int $multiplyFactor = pow(10, $numberOfPlaces);
if($arg > 0){
$rounded = trunc( $arg*$multiplyFactor + 0.5 ) / $multiplyFactor ; // since positive adding 0.5 then truncating
}
else{
$rounded = trunc( $arg*$multiplyFactor - 0.5 ) /$multiplyFactor ; //since negative subtracting 0.5 then truncating
}
}
return $rounded;
}
/**return 1 if the integer value is within range specified [min,max)
//ex: indexInRange(0,5), 0 is allowed, 4 is allowed, but 5 is not
@param int $value -- number
@param int $min -- low inclusive
@param int $max -- upper not inclusive
*/
global proc int
indexInRange(int $value, int $min, int $max)
{
int $result = 0;
if( ($value >= 0) && ($value < $max ) ){
$result = 1;
}
return $result;
}
/**give me the world positions for these components
@param string list $component components
@note supports: vertices, joints, edges, faces
@note returned world positions {x y z, x y z, ... x y z} a 1-D array to get a vtx use [i*3+0]..[i*3+2]
*/
global proc float[]
getComponentWorldPosition( string $component[] )
{
float $result[];
//loop over component
for($i=0; $i<size($component); $i++){
if( `objExists $component[$i]` == 0 ){error("cannot find: "+$component[$i]);}
float $selPos[] = `xform -q -t -ws $component[$i]`;
$result[size($result)] = $selPos[0];
$result[size($result)] = $selPos[1];
$result[size($result)] = $selPos[2];
}
return $result;
}
/**get difference in position of two joints
@param string $jointStart
@param string $jointEnd
@result 3 element float array
*/
global proc float[]
na_getJointWorldPositionDiff(string $jointStart, string $jointEnd)
{
float $result[] = {};
string $start = "";
string $end = "";
int $directionFound = 0; //when we find a direction don't need to check others
//verify they are joints
int $isStartJoint = na_isType($jointStart,"joint") ;
int $isEndJoint = na_isType($jointEnd,"joint") ;
if( $isStartJoint == 0 || $isEndJoint == 0 )
{error("expecting joints in direction calculation");}
$start = $jointStart;
$end = $jointEnd;
//we have world positions of start and end
float $pos[] = getComponentWorldPosition({$start,$end});
float $start_tx = $pos[0*3+0];
float $start_ty = $pos[0*3+1];
float $start_tz = $pos[0*3+2];
float $end_tx = $pos[1*3+0];
float $end_ty = $pos[1*3+1];
float $end_tz = $pos[1*3+2];
//then we get difference
float $tx = $end_tx - $start_tx;
float $ty = $end_ty - $start_ty;
float $tz = $end_tz - $start_tz;
$result[size($result)] = $tx;
$result[size($result)] = $ty;
$result[size($result)] = $tz;
return $result;
}
/**expects empty scene
*/
global proc
na_getStraightJointDirectionOfPair_unitTest_1()
{
select -cl;
joint -p 0 0 0 ;
joint -p 7 0 0 ;
string $result1[] = na_getStraightJointDirectionOfPair("joint1","joint2");
select -cl;
joint -p 0 0 0 ;
joint -p 0 4 0 ;
string $result2[] = na_getStraightJointDirectionOfPair("joint3","joint4");
print $result1;
print "\n";
print $result2;
}
global proc
na_getStraightJointDirectionOfPair_unitTest_2()
{
joint -p 0 -2 0 ;
joint -p 0 4 0 ;
joint -e -zso -oj xyz -sao yup joint1;
string $result1[] = na_getStraightJointDirectionOfPair("joint1","joint2");
print $result1;
}
global proc
na_getStraightJointSignOfPair_unitTest_1()
{
select -cl;
joint -p 0 0 0 ;
joint -p 7 0 0 ;
int $result1[] = na_getStraightJointSignOfPair("joint1","joint2");
select -cl;
joint -p 0 0 0 ;
joint -p -7 0 0 ;
int $result2[] = na_getStraightJointSignOfPair("joint3","joint4");
print $result1;
print "\n";
print $result2;
}
/**get direction for a straight joint in world space returns empty if joints not straight
@param string $jointStart -- start joint
@param string $jointEnd -- end joint
@result direction going from one joint to the next joint ex: "X" or "Y" or "Z"
@note pair joints don't need to be in same hierarchy
@note returns empty result if input not on same plane that is if joint end has more than one nonzero translate axis
@pre there needs to be some sort of length of chain to find direction
*/
global proc string[]
na_getStraightJointDirectionOfPair(string $jointStart, string $jointEnd)
{
string $result[] = {};
int $directionFound = 0; //when we find a direction don't need to check others
float $diff[] = {};
$diff = na_getJointWorldPositionDiff($jointStart,$jointEnd);
if( size($diff) != 3 ){ error("skipping -- cannot compute direction"); }
//then we get difference
float $tx = $diff[0];
float $ty = $diff[1];
float $tz = $diff[2];
//because channel editor showed zero there was still some numeric value really small
//anything below this i assume as zero
float $constZero = (1.0 * pow(10,-14) );
//non zero translate z but all others zero says
//direction is z
if($directionFound == 0){
if( ((abs($ty) + abs($tx)) < $constZero) && (abs($tz) > 0) )
{
$result[size($result)] = "Z";
$directionFound = 1;
}
}
//non zero translate y but all others zero says
//direction is y
if($directionFound == 0){
if( ((abs($tx) + abs($tz)) < $constZero) && (abs($ty) > 0) )
{
$result[size($result)] = "Y";
$directionFound = 1;
}
}
//
if($directionFound == 0){
if( ((abs($ty) + abs($tz)) < $constZero) && (abs($tx) > 0) )
{
$result[size($result)] = "X";
$directionFound = 1;
}
}
return $result;
}
/**what sign is straight joint chain in world space
@param start joint
@param end joint
*/
global proc int[]
na_getStraightJointSignOfPair(string $jointStart, string $jointEnd)
{
int $result[] = {};
string $direction[] = {};
float $val[] = {};
$direction = na_getStraightJointDirectionOfPair($jointStart, $jointEnd);
if(size($direction) == 1 ){
//weve got the direction the chain is in world
//now we use it to find the sign of the direction
//ex: is it positive or negative.
float $diff[] = {};
$diff = na_getJointWorldPositionDiff($jointStart,$jointEnd);
if( size($diff) != 3 ){ error("skipping -- cannot compute sign direction down chain"); }
int $index[] = {};
if( `strcmp $direction[0] "X"` == 0 ){ $index[0] = 0;}
else if( `strcmp $direction[0] "Y"` == 0 ){ $index[0] = 1;}
else if( `strcmp $direction[0] "Z"` == 0 ){ $index[0] = 2;}
else{error("error computing sing -- unknown direction input");}
$val[size($val)] = $diff[$index[0]];//index 0 for X, 1 for Y, 2 for Z
if($val[0] > 0)
{ $result[size($result)] = -1; }
else if($val[0] < 0)
{ $result[size($result)] = 1; }
else{ print("please verify chain has a positive length\n"); }
}
else{ print("skipping currently needs straightness of chain to figure out direction of chain \n") ;}
return $result;
}
/**get me the nearest joints to specified world positions
@param float array world positions {x y z, x y z, ... x y z} a 1-D array to get a vertex use [i*3+0]..[i*3+2]
@param float how close do we need to be
*/
global proc string[]
getClosestJointToWorldPositionArray(float $pointArray[], float $epsilon)
{
string $result[] = {};
if( isDivisibleByThis(size($pointArray),3) == 0){
error("check for 3 worldpositions per component");
}
//this could be extended to getting nearest locators, clusters by
//moving this to be a parameter
string $argJoint[] = getListAllJoints();
$result = getClosestToComponentArray($argJoint,$pointArray,$epsilon);
return $result;
}
/**get me the vertices that are closest to the worldpositions i give you in the mesh i give you
it really is expecting this to be done on meshes where the positions we give are exactly at a vertex
@param string poly
@param float array world positions {x y z, x y z, ... x y z} a 1-D array to get a vertex use [i*3+0]..[i*3+2]
*/
//assumes nearestPointOnMesh plugin loaded
global proc string[]
getClosestVertexToWorldPosition(string $polyName, float $wolrdPos[], float $epsilon )
{
string $result[];
print(`date -time`+"\n");
string $sel[] = `ls -sl`;
if( isDivisibleByThis(size($wolrdPos),3) == 0){
error("check for 3 worldpositions per vertex");
}
int $numberVertices = (size($wolrdPos)/3);
if( `objExists $polyName` ){
string $nearestFace[]; //nearest face to each vtx
//get shape for mesh that has these verts
string $shapes[] = `listRelatives - shapes $polyName`;
string $shape = $shapes[0];
//this will be able to tell given a point what is nearest face to it
string $near = "nearNode";
addNearNodeToShape($shape,$near);
//loop over verts
for($i=0; $i<$numberVertices; $i++){
float $selPos[] = { ($wolrdPos[$i*3+0]), ($wolrdPos[$i*3+1]), ($wolrdPos[$i*3+2]) };
//get position for each slected vtx
float $vPosX = $selPos[0];
float $vPosY = $selPos[1];
float $vPosZ = $selPos[2];
//we give it the point
tellNodeWhereToLook($near, {$vPosX,$vPosY,$vPosZ});
//we get the face
$nearestFace[$i] = getClosestFace( $polyName, $near );
//we get the vertex
string $closeVtx[] = getClosestVertToPosOfFace($nearestFace[$i],{$vPosX,$vPosY,$vPosZ});
$result[$i] = $closeVtx[0];
}
//end loop
//delete created nodes
delete($near);
}
else{ print("skipping -- poly not found\n"); }
print(`date -time`+"\n");
select -r $sel;
return $result;
}
/**give near node point were interested in
@param string near node name
@param float array world position {x y z}
@note kindof lowlevel cause it needs a node to operate
*/
global proc tellNodeWhereToLook(string $nearNodeName, float $pos[])
{
if( `objExists $nearNodeName` ){
setAttr ($nearNodeName+".inPositionX") ($pos[0]);
setAttr ($nearNodeName+".inPositionY") ($pos[1]);
setAttr ($nearNodeName+".inPositionZ") ($pos[2]);
}
else{
error("cannot find node: "+$nearNodeName);
}
}
/**this will be able to tell given a point what is nearest face to it
@param string shape
@param string name for near node
*/
global proc
addNearNodeToShape(string $shape, string $nearNodeName)
{
//no checks for if shape exists
//don't make if node exists
if( `objExists $nearNodeName` == 0 ){
createNode nearestPointOnMesh -n $nearNodeName;
connectAttr -f ($shape+".outMesh") ($nearNodeName+".inMesh");
}
else{
print("skipping, node exists\n");
}
}
/**given face and a world position get the closest vertex of face to that position
@param string name of face ex: "pCube1.f[2]"
@param float 3 element array ex: {0.5,0.5,0.5}
@note I think this is more useful on onetime operations than something like attaching nulls to world position of geometry
*/
global proc string[]
getClosestVertToPosOfFace(string $_face, float $wpos[] )
{
string $sel[] = `ls -sl`;
string $result[];
string $face = $_face;
string $_vertsOfFace[] = `polyListComponentConversion -ff -tv $face`;//has [2:3] possibly
//replace with filter expand
select -cl;
select -r $_vertsOfFace;
string $vertsOfFace[] = `filterExpand -sm 31 -expand true`;
if(size($wpos)==3){
string $minVertex = $vertsOfFace[0];
float $minWorldPos[] = `xform -q -t -ws $minVertex`;
float $minValue[] = euclidDistance( $minWorldPos, $wpos );
//loop verts of face
int $i=0;
string $_minVertex="";
float $_minWorldPos[];
float $_minValue[];
for($i=0; $i<size($vertsOfFace); $i++)
{
$_minVertex = $vertsOfFace[$i];
$_minWorldPos = `xform -q -t -ws $_minVertex`;
$_minValue = euclidDistance( $_minWorldPos, $wpos );
//if found a closer point to input position save it
if( $_minValue[0] < $minValue[0] )
{
$minVertex = $_minVertex;
$minWorldPos = $_minWorldPos;
$minValue[0] = $_minValue[0];
}
//end if
clear($_minWorldPos);
clear($_minValue);
$_minVertex="";
}
//end loop
$result[0] = $minVertex;
}
select -r $sel;
return $result;
}
/**give me the nearest face
@param string poly
@param string name for near node
*/
global proc string
getClosestFace( string $polyName, string $nearNodeName )
{
string $result;
//does poly exist
if( `objExists $polyName` == 0 ){
error("cannot find poly: "+$polyName);
}
//get face using node if node is there
if( `objExists $nearNodeName` ){
string $nearestFaceId = `getAttr $nearNodeName (".nearestFaceIndex")`;
$result = $polyName+".f"+"["+$nearestFaceId+"]";
}
else{
error("cannot find node: "+$nearNodeName);
}
return $result;
}
/**get me the nearest component to specified world position
@param string array components we will search in for nearest point
@param float array 1-d array of 3d points to find closest component to
@param float how close do we need to be
*/
global proc string[]
getClosestToComponentArray(string $argComponent[], float $pointArray[], float $epsilon)
{
string $result[] = {};
if( isDivisibleByThis(size($pointArray),3) == 0){
error("check for 3 worldpositions per component");
}
int $numberMatchingComponentsWeNeed = (size($pointArray)/3);
//loop points
string $nearestComponent[]={};//temporary hold nearest component
for($j=0; $j < $numberMatchingComponentsWeNeed; $j++){
//first get a single point x y z
float $point[] ={};
$point[size($point)] = $pointArray[$j*3+0];
$point[size($point)] = $pointArray[$j*3+1];
$point[size($point)] = $pointArray[$j*3+2];
//saving nearest component if possible
clear($nearestComponent);
$nearestComponent = getClosestComponentToWorldPosition($argComponent, $point, $epsilon);
if(size($nearestComponent)>0)
{
$result[size($result)] = $nearestComponent[0];
}
else
{ error("could not find a match for point (xyz)"+$point[0]+","+$point[1]+","+$point[2]+"\n");}
}
return $result;
}
/**get me the nearest joint to specified world position
@param float array the point we should compare every candidate too
@param float how close do we need to be
@note note it returns a single element as soon as it finds a joint close enough to the threshold
*/
global proc string[]
getClosestComponentToWorldPosition(string $argComponent[], float $point[], float $epsilon)
{
string $result[] = {}
;
if( isDivisibleByThis(size($point),3) == 0){
error("check for 3 worldpositions per component");
}
//loop candidate components comparing to this point
for($i=0; $i < size($argComponent); $i++){
//which of all components is closest to this point
string $component = $argComponent[$i];
float $posComponent[] = `xform -q -t -ws $component`;
float $distanceArray[] = euclidDistance( $point, $posComponent );
if(size($distanceArray) > 0){
$distance = $distanceArray[0];
//exit when found a nearest component to point
if( $distance < $epsilon ){
//print("found nearest component: "+$component+"\n");
$result[size($result)] = $component;
break;//exit loop
}
}
}
return $result;
}
/**get 1 if argument is divisibile by second integer
@param int size of argument array
@param int what we need it to be divisible by
@note ex: isDivisibleByThis( 5,2 ) get 0, but isDivisibleByThis( 6,2 ) get 1
*/
global proc int
isDivisibleByThis( int $arg, int $divisibleBy )
{
int $result = 0;
//checking divisibility by 3 for world position
//a way of doing that check if ceil and floor of result are equal
float $numberArg = ($arg/$divisibleBy );
if( ceil($numberArg) == floor($numberArg) ){
$result = 1;
}
return $result;
}
/**given two 3-D vectors return euclidean distance
@param float $a[] -- first vector of 3 elements
@param float $b[] -- second vector of 3 elements
@note this is used to find closeness on a mesh
suppose you wanted to know of the vertices of a face
which one is closest this other point, user can
use this to find out
*/
global proc float[]
euclidDistance(float $a[], float $b[] )
{
float $result[];
if( (size($a)==3) && (size($b)==3) )
{
$result[0] = sqrt( pow(($a[0]-$b[0]),2)+
pow(($a[1]-$b[1]),2)+
pow(($a[2]-$b[2]),2)
);
}
return $result;
}