// =========================================================================== // Copyright 2018 Autodesk, Inc. All rights reserved. // // Use of this software is subject to the terms of the Autodesk license // agreement provided at the time of installation or download, or which // otherwise accompanies this software in either electronic or hard copy form. // =========================================================================== // // // Creation Date: 2003 // // Description: Create hairs on selected objects // global string $gMainProgressBar; global proc createHair(int $uCount, int $vCount, int $numCvs, int $restCurves, int $passiveFill, int $edgeBounded, int $equalize, float $length, float $randomization, int $outputMode, int $simulationType, int $hairCreateType) { string $nameFailedFmt = (uiRes("m_createHair.kNameFailedErr")); if(!`optionVar -exists createHairMaxDensity`){ optionVar -intValue createHairMaxDensity 100; } int $maxDensity = `optionVar -query createHairMaxDensity`; int $doCollideMesh = false; string $collideMesh = ""; if( `optionVar -exists createHairCollideWithMesh` ){ $doCollideMesh = `optionVar -query createHairCollideWithMesh`; } int $doOutputCurve = $outputMode > 1; int $doPfxHair = $outputMode == 1 || $outputMode == 3; string $createHairWarning = (uiRes("m_createHair.kCreateHairWarning")); int $largeNumberOfCurves = 1000; // The following trys to gage the relative effect of large number of cvs on performance int $largeNumberOfHairs = 10000.0/(1.0 + ((float)$numCvs * .05)); string $potentialTargets[]; string $targets[]; int $maxHairs; int $numTargets; string $noObjectSelected = (uiRes("m_createHair.kNoObjectSelectedWarn")); if($hairCreateType == 1) { //Grid hairs $potentialTargets = `ls -sl -dag`; $targets = `ls -sl -dag -ni -type nurbsSurface -type mesh`; $numTargets = size($targets); if( $numTargets < 1 ){ if( size ($potentialTargets) < 1) warning( $noObjectSelected ); else warning (uiRes("m_createHair.kHairCreationWarning")) ; return; } $maxHairs = $uCount * $vCount * $numTargets; } else if($hairCreateType == 2) { // Hair at selected surface points/faces string $allObjects[] = `ls -sl -fl`; for($obj in $allObjects) { string $parts[]; tokenize( $obj,".[]",$parts ); if ( `nodeType $parts[0]` == "transform" ) { string $kids[] = `listRelatives -f -children $parts[0]`; int $i; for ( $i=0; $i $largeNumberOfCurves ){ string $msg = (uiRes("m_createHair.kGenerateHairsMessage")); $msg = `format -s $maxHairs $msg`; string $createAndHide = (uiRes("m_createHair.kCreateandHideHair")); string $retString = `confirmDialog -title $createHairConfirm -message $msg -button (uiRes("m_createHair.kCreateHairCurves")) -button $createAndHide -button $cancel -defaultButton $cancel -cancelButton $cancel -dismissString $cancel`; if( $retString == $cancel ){ return; }else if( $retString == $createAndHide ){ $hideOutputCurves = true; } } else if( $maxHairs > $largeNumberOfHairs ){ string $warnStr; if( $maxHairs > $largeNumberOfHairs * 10 ){ $warnStr = (uiRes("m_createHair.kHandlingMessage")); } else { $warnStr = (uiRes("m_createHair.kLowPerformanceMessage")); } $warnStr = `format -s $maxHairs $warnStr`; string $retString = `confirmDialog -title $createHairConfirm -message $warnStr -button $createHair -button $cancel -defaultButton $cancel -cancelButton $cancel -dismissString $cancel`; if( $retString != $createHair ){ return; } } int $uhairs; int $vhairs; if($hairCreateType == 1) { //Grid if( $uCount > $maxDensity || $vCount > $maxDensity ){ int $uvMax; if( $uCount > $vCount ){ $uvMax = $uCount; } else { $uvMax = $vCount; } string $createpaintablehair = (uiRes("m_createHair.kCreatePaintableHair")); string $msg = (uiRes("m_createHair.kUVCountExceedsMessage")); $msg = `format -s $uvMax $msg`; string $retString = `confirmDialog -title $createHairConfirm -message $msg -button $createpaintablehair -button $createHair -button $cancel -defaultButton $cancel -cancelButton $cancel -dismissString $cancel`; if( $retString == $createpaintablehair ){ optionVar -intValue createHairMaxDensity $uvMax; $maxDensity = $uvMax; } else if( $retString == $createHair ){ $maxDensity = $uvMax; } else if( $retString == $cancel ){ return; } } /* int $uhairs = sqrt( $numHairs ); int $vhairs = $uhairs; */ $uhairs = $uCount; $vhairs = $vCount; if( $uhairs < 2 || $vhairs < 2 ){ if( $uhairs < 1 || $vhairs < 1 ){ return; } $equalize = false; } } // Get hair system from hair system menu string $hsys; int $placeIn; int $newHairSys = false; if(`optionMenuGrp -q -ex hsPlaceMenu`) { $placeIn = `optionMenuGrp -q -select hsPlaceMenu`; if ( $placeIn == 1 ) { $hsys = `createNode hairSystem`; connectAttr time1.outTime ($hsys + ".currentTime"); $newHairSys = true; } else { $hsys = `optionMenuGrp -q -value hsPlaceMenu`; if( $doPfxHair ){ string $con[] = `listConnections ($hsys + ".outputRenderHairs")`; if( size($con) > 0 ){ $doPfxHair = false; } } $newHairSys = false; } } else { string $hairSystems[] = `ls -sl -dag -ni -type hairSystem`; if( size( $hairSystems ) > 0 ){ $hsys = $hairSystems[0]; if( $doPfxHair ){ string $con[] = `listConnections ($hsys + ".outputRenderHairs")`; if( size($con) > 0 ){ $doPfxHair = false; } } $newHairSys = false; } else { $hsys = `createNode hairSystem`; connectAttr time1.outTime ($hsys + ".currentTime"); $newHairSys = true; } } if( $newHairSys && `optionVar -exists createHairHairsPerClump`){ int $hairsPerClump = `optionVar -query createHairHairsPerClump`; setAttr ( $hsys + ".hairsPerClump") $hairsPerClump; if( $hairsPerClump == 1 ){ setAttr ( $hsys + ".clumpWidth") 0; } } string $hsysGroup = ""; string $hsysOutHairGroup = ""; string $hsysParent[] = `listTransforms $hsys`; string $hsysNames[]; if( size($hsysParent) > 0 ){ tokenize( $hsysParent[0], ":", $hsysNames ); $hsysGroup = $hsysNames[size($hsysNames) - 1] + "Follicles"; if( !objExists( $hsysGroup ) ){ $hsysGroup = `group -em -name $hsysGroup`; } if( $doOutputCurve ){ $hsysOutHairGroup = ( $hsysNames[size($hsysNames) - 1] + "OutputCurves"); if( !objExists( $hsysOutHairGroup ) ){ $hsysOutHairGroup = `group -em -name $hsysOutHairGroup`; } if( $hideOutputCurves ){ setAttr ($hsysOutHairGroup + ".visibility") false; } } } int $isCancelled = false; int $coincidentHairs = false; int $createdHairs = false; if($hairCreateType == 1) { //Grid int $i, $ui, $ji; int $currentHair = 0; float $minU = 0.5/(float)$uhairs; float $minV = 0.5/(float)$vhairs; float $uDists[]; float $vDists[]; int $uSamples[]; int $vSamples[]; int $lastIndex[] = {0}; int $numDistTests = 4; for( $i = 0; $i < $numTargets; $i++ ){ if( $isCancelled ){ break; } string $target = $targets[$i]; int $doEqualize = $equalize; string $parent[] = `listRelatives -p $target`; tokenize( $parent[0], ":", $hsysNames ); string $hairCurveNamePrefix = $hsysNames[size($hsysNames) - 1] + "Follicle"; // compute rough worldspace distances for each row float $maxV = 0; float $minV = 1000000; float $maxU = 0; float $minU = 1000000; float $pu, $pv; int $ubased = true; if( $doEqualize ){ // create a temporary follicle for point on surface evaluation string $follicle = `createNode follicle`; connectAttr ($target + ".worldMatrix[0]") ($follicle + ".inputWorldMatrix"); string $nType = `nodeType $target`; if( "nurbsSurface" == $nType ){ connectAttr ($target + ".local") ($follicle + ".inputSurface"); } else if( "mesh" == $nType ){ connectAttr ($target + ".outMesh") ($follicle + ".inputMesh"); string $currentUVSet[] = `polyUVSet -q -currentUVSet $target`; setAttr ($follicle + ".mapSetName") -type "string" $currentUVSet[0]; } for( $ui = 0; $ui < $uhairs; $ui++ ){ if( $edgeBounded && $uhairs > 1){ $pu =(float)$ui/(float)($uhairs-1); } else { $pu = ((float)$ui+0.5)/(float)$uhairs; } // new code uses a follicle node rather than pointOnSurface // float $lastPos[] = `pointOnSurface -top 1 -u $pu -v 0 -position $target`; float $lastPos[]; int $numValidPoints = 0; float $dist = 0; setAttr ($follicle + ".parameterU") $pu; int $try = 0; int $testCount = $numDistTests; // we may need to sample more than $numDistTest points for // polysets where some uvs are not defined in regions, like the poles // of a sphere. while ( $try < 3 && $numValidPoints < 2 ){ $numValidPoints = 0; for( $vi=0;$vi < $testCount; $vi++ ){ $pv = (float)$vi/(float)$testCount; setAttr ($follicle + ".parameterV") $pv; if( getAttr( $follicle + ".validUv" )){ float $pos[] = getAttr ($follicle + ".outTranslate"); // float $pos[] = `pointOnSurface -top 1 -u $pu -v $pv -position $target`; if( $numValidPoints > 0 ){ float $x = $pos[0] - $lastPos[0]; float $y = $pos[1] - $lastPos[1]; float $z = $pos[2] - $lastPos[2]; $dist += sqrt( $x*$x + $y*$y + $z*$z ); } $lastPos = $pos; $numValidPoints++; } } $try++; $testCount *= 2; } if( $try > 1 && $numValidPoints > 1 ){ $numValidPoints = $numValidPoints/$try; if( $numValidPoints < 2 ){ $numValidPoints = 2; } } if( $dist > $maxV ){ $maxV = $dist; } if( $dist < $minV ){ $minV = $dist; } $vDists[$ui] = $dist; $vSamples[$ui] = $numValidPoints; } for( $vi = 0; $vi < $vhairs; $vi++ ){ if( $edgeBounded && $vhairs > 1){ $pv =(float)$vi/(float)($vhairs-1); } else { $pv = ((float)$vi+0.5)/(float)$vhairs; } // float $lastPos[] = `pointOnSurface -top 1 -u 0 -v $pv -position $target`; float $lastPos[]; int $numValidPoints = 0; float $dist = 0; setAttr ($follicle + ".parameterV") $pv; int $try = 0; int $testCount = $numDistTests; while ( $try < 3 && $numValidPoints < 2 ){ $numValidPoints = 0; for( $ui=0;$ui < $testCount; $ui++ ){ $pu = (float)$ui/(float)$testCount; setAttr ($follicle + ".parameterU") $pu; if( getAttr( $follicle + ".validUv" )){ float $pos[] = getAttr ($follicle + ".outTranslate"); //float $pos[] = `pointOnSurface -top 1 -u $pu -v $pv -position $target`; if( $numValidPoints > 0 ){ float $x = $pos[0] - $lastPos[0]; float $y = $pos[1] - $lastPos[1]; float $z = $pos[2] - $lastPos[2]; $dist += sqrt( $x*$x + $y*$y + $z*$z ); } $lastPos = $pos; $numValidPoints++; } } $try++; $testCount *= 2; } if( $try > 1 && $numValidPoints > 1 ){ $numValidPoints = $numValidPoints/$try; if( $numValidPoints < 2 ){ $numValidPoints = 2; } } if( $dist > $maxU ){ $maxU = $dist; } if( $dist < $minU ){ $minU = $dist; } $uDists[$vi] = $dist; $uSamples[$vi] = $numValidPoints; } // find out which direction is more variable: if( $minU == 0 ){ $ubased = false; } else if( $minV == 0 ){ $ubased = true; } else { $ubased = (float)$maxU/(float)$minU < (float)$maxV/(float)$minV; } /* if( $maxV < $maxU ){ $vHairs = ($vHairs*$maxV)/$maxU; } else { $uHairs = ($uHairs*$maxU)/$maxV; } */ // delete temporary follicle string $tforms[] = `listTransforms $follicle`; delete $tforms[0]; } int $xi, $yi, $xsize, $ysize; if( $ubased ){ $xsize = $uhairs; } else { $xsize = $vhairs; } int $totalSteps = $uhairs * $vhairs; int $doProgressBar = $totalSteps > 100; global string $gMainProgressBar; string $progressMessage; if( $doProgressBar ){ $progressMessage = (uiRes("m_createHair.kCreateHairOnMsg")); $progressMessage = ($progressMessage + " "+$target+" ..."); progressBar -edit -beginProgress -isInterruptable true -status $progressMessage -maxValue $totalSteps $gMainProgressBar; } for( $xi = 0; $xi < $xsize; $xi++ ){ if( $isCancelled ){ break; } int $passiveI = ($xi%($passiveFill+1)) && $xi != ($xsize-1); if( $ubased ){ if( $doEqualize && $vSamples[$xi] > 1 ){ $ysize = ((float)$numDistTests/(float)$vSamples[$xi]) * (float)$vhairs*$vDists[$xi]/$maxV; if( $ysize > $vhairs ){ $ysize = $vhairs; } } else { $ysize = $vhairs; } } else { $ysize = $uhairs; if( $doEqualize && $uSamples[$xi] > 1 ){ $ysize = ((float)$numDistTests/(float)$uSamples[$xi]) *(float)$uhairs*$uDists[$xi]/$maxU; if( $ysize > $uhairs ){ $ysize = $uhairs; } } else { $ysize = $uhairs; } } for( $yi = 0; $yi < $ysize; $yi++ ){ if( $doProgressBar ){ if(`progressBar -query -isCancelled $gMainProgressBar`){ $isCancelled = true; break; } progressBar -edit -step 1 -status $progressMessage $gMainProgressBar; } int $isPassive = $passiveI || ($yi%($passiveFill+1) && $yi != ($ysize-1)); // float $rval = `rand 1`; if( $edgeBounded ){ if( $ubased ){ if( $xsize > 1 ){ $pu =(float)$xi/(float)($xsize-1); } else { $pu = ((float)$xi + 0.5)/(float)$xsize; } if( $ysize > 1 ){ $pv =(float)$yi/(float)($ysize-1); } else { $pv = ((float)$yi + 0.5)/(float)$ysize; } } else { if( $xsize > 1 ){ $pv =(float)$xi/(float)($xsize-1); } else { $pv = ((float)$xi + 0.5)/(float)$xsize; } if( $ysize > 1 ){ $pu =(float)$yi/(float)($ysize-1); } else { $pu = ((float)$yi + 0.5)/(float)$ysize; } } } else { if( $ubased ){ $pu = ((float)$xi + 0.5)/(float)$xsize; $pv = ((float)$yi + 0.5)/(float)$ysize; } else { $pv = ((float)$xi + 0.5)/(float)$xsize; $pu = ((float)$yi + 0.5)/(float)$ysize; } } if( $randomization > 0 ){ float $uRand = rand(1.0); float $vRand = rand(1.0); $pu += ($uRand - 0.5) * $randomization/(float)$uhairs; $pv += ($vRand - 0.5)* $randomization/(float)$vhairs; if( $pu < 0 ){ $pu += 1.0; } else if( $pu > 1.0 ){ $pu -= 1.0; } if( $pv < 0 ){ $pv += 1.0; } else if( $pv > 1.0 ){ $pv -= 1.0; } } // The hairCurve is named based on the object and the UV index // This allows the script hairCurvePaint to function on the resulting nodes. int $namingIndex = $maxDensity * (int)($pu * (float)($maxDensity-1) + 0.5) + (int)($pv * (float)($maxDensity-1) +0.5); string $newName = $hairCurveNamePrefix + $namingIndex; int $doStart = !$isPassive; // int $doStart = true; int $createFollicle = true; if ( `objExists $newName` ) { // Note when a follicle exists we do not overwrite it. // The name signifies the position and thus we allow subsequent // create hair calls with different uv density to insert new hairs between // existing ones. Thus this case is not an error. $createFollicle = false; if( nodeType( $newName ) == "transform" ){ string $shapes[] = `ls -s -dag $newName`; // If the transform is empty it is likely left over from an incomplete hairSystem deletion // We delete it so that we can create a new one. if( 0 == size($shapes) ){ delete $newName; $createFollicle = true; warning( `format -s $newName $createHairWarning` ); } } if( !$createFollicle ){ $coincidentHairs = true; } } if ( $createFollicle ) { string $newHair = createHairCurveNode( $hsys, $target, $pu, $pv, $numCvs, $doOutputCurve, $doStart, $restCurves, $isPassive, "", $length, $lastIndex, $hsysGroup, $hsysOutHairGroup, $simulationType); if( $newHair != "" ){ // rename $newHair $newName; string $hname = `rename $newHair $newName`; if ( $hname != $newName ) { catch (error (`format -s $newName -s $hname $nameFailedFmt`)); } } $createdHairs = true; } $currentHair++; } } if( $doProgressBar ){ progressBar -edit -endProgress $gMainProgressBar; } } } else if($hairCreateType == 2) { // Hair at selected surface points/faces int $i; int $currentHair = 0; int $lastIndex[] = {0}; int $doProgressBar = $numTargets > 20; global string $gMainProgressBar; string $progressMessage; if( $doProgressBar ){ $progressMessage = (uiRes("m_createHair.kCreateHairAtPointMsg")); progressBar -edit -beginProgress -isInterruptable true -status $progressMessage -maxValue $numTargets $gMainProgressBar; } for( $i = 0; $i < $numTargets; $i++ ) { if( $doProgressBar ){ if(`progressBar -query -isCancelled $gMainProgressBar`){ $isCancelled = true; break; } progressBar -edit -step 1 -status $progressMessage $gMainProgressBar; } string $parent[]; float $pu, $pv; string $parts[]; string $surface; int $selectedSurfacePt=false; string $target = $targets[$i]; tokenize( $target,".[]",$parts ); if ( `nodeType $parts[0]` == "transform" ) { string $kids[] = `listRelatives -f -children $parts[0]`; int $j; for ( $j=0; $j 0.0 ) $pu = ($pu - $minU) /($maxU-$minU); else $pu = 0.0; if( $maxV-$minV > 0.0 ) $pv = ($pv - $minV) /($maxV-$minV); else $pv = 0.0; } } else if( `nodeType $kids[$j]` == "mesh" ) { if(!`getAttr ($kids[$j]+".io")` && $parts[1] == "f") { $surface = $kids[$j]; $parent = `listRelatives -p $surface`; // If it has multiple UVs... string $UVs[]; $UVs = `polyListComponentConversion -tuv $target`; if (size($UVs) != 0) { select -r $target; float $UVbbox[] = `polyEvaluate -bc2`; $pu = (($UVbbox[0]+$UVbbox[1])/2.0) + 1e-5; $pv = (($UVbbox[2]+$UVbbox[3])/2.0) + 1e-5; if($pu < 0.0 || $pu > 1.0 || $pv < 0.0 || $pv > 1.0) { continue; } else{ $selectedSurfacePt = true; } } else { continue; } } } } } if(!$selectedSurfacePt) continue; string $hairCurveNamePrefix = $parent[0] + "Follicle"; int $namingIndex = $maxDensity * (int)($pu * (float)($maxDensity-1) + 0.5) + (int)($pv * (float)($maxDensity-1) +0.5); string $newName = $hairCurveNamePrefix + $namingIndex; int $doStart = true; int $createFollicle = true; if ( `objExists $newName` ) { // Note when a follicle exists we do not overwrite it. // The name signifies the position and thus we allow subsequent // create hair calls with different uv density to insert new hairs between // existing ones. Thus this case is not an error. $createFollicle = false; if( nodeType( $newName ) == "transform" ){ string $shapes[] = `ls -s -dag $newName`; // If the transform is empty it is likely left over from an incomplete hairSystem deletion // We delete it so that we can create a new one. if( 0 == size($shapes) ){ delete $newName; $createFollicle = true; warning( `format -s $newName $createHairWarning` ); } } if( !$createFollicle ){ $coincidentHairs = true; } } if ( $createFollicle ) { $collideMesh = $surface; string $newHair = createHairCurveNode( $hsys, $surface, $pu, $pv, $numCvs, $doOutputCurve, $doStart, $restCurves, false, "", $length, $lastIndex, $hsysGroup, $hsysOutHairGroup, $simulationType); if( $newHair != "" ){ // rename $newHair $newName; string $hname = `rename $newHair $newName`; if ( $hname != $newName ) { catch (error (`format -s $newName -s $hname $nameFailedFmt`)); } } $createdHairs = true; } $currentHair++; } if( $doProgressBar ){ progressBar -edit -endProgress $gMainProgressBar; } } if( $doPfxHair || $hideOutputCurves){ string $pfxHair = `createNode pfxHair`; setAttr ( $pfxHair + ".displayPercent") 100; setAttr ( $pfxHair + ".drawAsMesh") false; if( !$doPfxHair ){ setAttr ( $hsys + ".hairsPerClump") 1; setAttr ( $hsys + ".clumpWidth") 0; } connectAttr ($hsys + ".outputRenderHairs") ($pfxHair + ".renderHairs"); } if($newHairSys == false && $outputMode > 1) { if(`pluginInfo -q -l Fur`) HfAddCurvesToFur($hsys); } if( $newHairSys == true ){ string $nucleus = getActiveNucleusNode( false, true ); addActiveToNSystem( $hsys, $nucleus); connectAttr ($nucleus + ".startFrame") ($hsys + ".startFrame"); int $iterationLevel = ($numCvs*$numCvs*$numCvs)/210.0; if( $iterationLevel < 2 ){ $iterationLevel = 2; } setAttr ($hsys + ".evaluationOrder") 0; setAttr ($hsys + ".stiffness") 0.1; setAttr ($hsys + ".iterations") $iterationLevel; if( $numCvs >= 30 ){ warning (uiRes("m_createHair.kHighIterationWarning")); } } select -r $hsys; displayHairCurves( "current", true ); if( $coincidentHairs ){ if( !$createdHairs ){ warning (uiRes("m_createHair.kFolliciesExistWarning")); } else { print( (uiRes("m_createHair.kHairNotCreatedWarning"))); } } else if( !$createdHairs ){ warning (uiRes("m_createHair.kNoHairCreatedWarning")); } attachNObjectToHair( $hsys, $collideMesh, $doCollideMesh ); }