/* This is a script to create a simple crowd simulation. To run it, execute the following (it helps to add it to your toolbar): source CrowdSystem; crowdMain; Or, alternatively, drag-and-drop the script into your Maya window and type "crowdMain" in the command line. This will open the main window with all of the controls this tool allows. Please see the attached tutorial for in-depth explanation of all the available features. This script was written by Nikita Pavlov as part of the VIS Project at Carnegie Mellon University's Entertainment Technology Center. It is intended for public use as long as the creator and the university are acknowledged as listed herein. This was designed for Maya 6.0. Feel free to add modifications and features and share them with us! */ // Create crowd global proc createCrowd (int $iNumAgents, float $fCrowdRad, float $fImpulseRange, int $agentHasAnim, int $agentHasGoal) { global string $agentModel; global string $agentCharSet; global string $agentAnim[]; global string $agentSkin; global string $agentGoal; //global int $agentHasAnim; global float $fAnimOffsetRange; global float $fAnimSpeedRange; //global float $agentCollRadius; //global float $fMaxVelocity; //global float $fAvgVelocity; //global int $collideWithPriorAgents; global string $placementPattern[]; global int $iCrowdRad2D; global float $fGenCenter[]; //global float $fMassMin, $fMassMax; //global int $gforces; global float $fGoalPriority; // Set up the central crowd system object. $crowdProxy = `setupCrowdSystemProxy`; // Check whether an agent model has been specified. string $agentCmd; if ($agentModel == "") { $agentCmd = "polyCube -width 2 -height 2.5 -depth 2;"; } else { if ($agentHasAnim == 0) { $agentCmd = "duplicate -rr $agentModel;"; } else { // If we have animations, duplicate the input graph. $agentCmd = "duplicate -rr -renameChildren -un $agentModel;"; //$agentCmd = "duplicate -rr -un $agentSkin;"; } } // Figure out what number to start on. int $idxStart = 0; while (`objExists ("vehicle_"+$idxStart)`) $idxStart++; // Basic vehicle model type for ($i=$idxStart; $i<$iNumAgents+$idxStart; $i++) { // Name the agent, since we'll be using this value a LOT. $aName = ("agent_" + $i); $cName = ("agentCtrl_" + $i); // Unfortunately, polyCube produces two results, and we want only the first // (hence the array). string $agt[] = eval ($agentCmd); rename $agt[0] $aName; // Make the vehicle a rigid body with random placement float $agentPos[3]; if (size($placementPattern) == $iNumAgents) $agentPos = `xform -q -t $placementPattern[$i-$idxStart]`; else { $agentPos[0] = rand(-$fCrowdRad,$fCrowdRad) + $fGenCenter[0]; if ($iCrowdRad2D == 0) $agentPos[1] = rand(-$fCrowdRad,$fCrowdRad) + $fGenCenter[1]; else $agentPos[1] = $fGenCenter[1]; $agentPos[2] = rand(-$fCrowdRad,$fCrowdRad) + $fGenCenter[2]; } // Figure out the object's size along y, so that we can position the agent proxy // above and outside the object. float $objSize[] = `xform -q -bb $aName`; // Returns [xmin, ymin, zmin, xmax, ymax, zmax] //$proxyY = $objSize[4] + ($objSize[4]-$objSize[1])/2.0; $proxyY = $agentPos[1]; // Create a proxy representing the agent, which will be the parent // of the duplicated geometry. Move the pivot of the proxy to the center // of the agent itself. setupAgentProxy($cName, $agentPos[0], $proxyY, $agentPos[2]); move -a $agentPos[0] $agentPos[1] $agentPos[2] $aName; move -r 0 ($proxyY - $agentPos[1]) 0 ($aName + ".scalePivot") ($aName + ".rotatePivot"); parent $aName $cName; // Create the debug shapes. $r1 = `getAttr ($crowdProxy+".CollisionRadius")`; $r2 = `getAttr ($crowdProxy+".ComfortRadius")`; $r3 = `getAttr ($crowdProxy+".AwarenessRadius")`; $c1 = `circle -c $agentPos[0] $agentPos[1] $agentPos[2] -nr 0 1 0 -sw 360 -r $r1 -d 3 -ut 0 -tol 0.01 -s 8 -ch 1 -n ("CollisionRadius_"+$i)`; $c2 = `circle -c $agentPos[0] $agentPos[1] $agentPos[2] -nr 0 1 0 -sw 360 -r $r2 -d 3 -ut 0 -tol 0.01 -s 8 -ch 1 -n ("ComfortRadius_"+$i)`; $c3 = `circle -c $agentPos[0] $agentPos[1] $agentPos[2] -nr 0 1 0 -sw 360 -r $r3 -d 3 -ut 0 -tol 0.01 -s 8 -ch 1 -n ("AwarenessRadius_"+$i)`; parent ("CollisionRadius_"+$i) ("ComfortRadius_"+$i) ("AwarenessRadius_"+$i) $cName; connectAttr -f ($crowdProxy+".ShowDebugShapes") ("CollisionRadius_"+$i+".visibility"); connectAttr -f ($crowdProxy+".ShowDebugShapes") ("ComfortRadius_"+$i+".visibility"); connectAttr -f ($crowdProxy+".ShowDebugShapes") ("AwarenessRadius_"+$i+".visibility"); connectAttr -f ($crowdProxy+".CollisionRadius") ($c1[1]+".radius"); connectAttr -f ($crowdProxy+".ComfortRadius") ($c2[1]+".radius"); connectAttr -f ($crowdProxy+".AwarenessRadius") ($c3[1]+".radius"); string $attrs[] = {".tx",".ty",".tz",".rx",".ry",".rz",".sx",".sy",".sz"}; for ($attr in $attrs) { setAttr -lock true -keyable false ("ComfortRadius_"+$i+$attr); setAttr -lock true -keyable false ("CollisionRadius_"+$i+$attr); setAttr -lock true -keyable false ("AwarenessRadius_"+$i+$attr); } setAttr -keyable false ("ComfortRadius_"+$i+".v"); setAttr -keyable false ("CollisionRadius_"+$i+".v"); setAttr -keyable false ("AwarenessRadius_"+$i+".v"); // Create expression for vehicle with a random multiplier // added to expression. // connect to goal object $expString = "if (" + $crowdProxy + ".EnableProceduralMovement) {\n\n"; if ($agentHasGoal == 1) { $expString += "// Make the agent follow the goal.\n"; $expString += "$goalX = " + $agentGoal + ".tx;\n"; $expString += "$goalY = " + $agentGoal + ".ty;\n"; $expString += "$goalZ = " + $agentGoal + ".tz;\n"; $expString += "$dx = $goalX - " + $cName + ".tx;\n"; $expString += "$dy = $goalY - " + $cName + ".ty;\n"; $expString += "$dz = $goalZ - " + $cName + ".tz;\n"; $expString += "$ratio = " + $crowdProxy + ".GoalPriority;\n"; } else { $expString += "// Randomize direction.\n"; $expString += "$r1 = rand(-1,1);\n"; $expString += "$r2 = rand(-1,1);\n\n"; $expString += "$dx = sin(time*$r1);\n"; $expString += "$dy = 0.0;\n"; $expString += "$dz = noise(time*$r1);\n"; $expString += "$ratio = .5;\n"; } $expString += "$dist = sqrt($dx*$dx + $dy*$dy + $dz*$dz);\n"; $expString += "if ($dist > " + $crowdProxy + ".MaxAcceleration)\n"; $expString += "{\n"; $expString += " $scale = " + $crowdProxy + ".MaxAcceleration/$dist;\n"; $expString += " $dx = $dx * $scale;\n"; $expString += " $dy = $dy * $scale;\n"; $expString += " $dz = $dz * $scale;\n"; $expString += "}\n\n"; $expString += "// Compute forces acting on particles.\n"; $expString += "float $cf[3]; // Cohesion force\n"; $expString += "float $sf[3]; // Separation force (comfort zone)\n"; $expString += "float $af[3]; // Alignment force\n"; $expString += "float $collf[3]; // Collision force (overrides the others)\n"; $expString += "int $isColliding = 0;\n"; $expString += "for ($i = "+$idxStart+"; $i < "+($idxStart+$iNumAgents)+"; $i++)\n"; $expString += "{\n"; $expString += " if ($i != "+$i+" && objExists(\"agentCtrl_\"+$i))\n"; $expString += " {\n"; $expString += " float $iVec[3];\n"; $expString += " $iVec[0] = getAttr(\"agentCtrl_\"+$i+\".tx\") - "+$cName+".translateX;\n"; $expString += " $iVec[1] = getAttr(\"agentCtrl_\"+$i+\".ty\") - "+$cName+".translateY;\n"; $expString += " $iVec[2] = getAttr(\"agentCtrl_\"+$i+\".tz\") - "+$cName+".translateZ;\n"; $expString += " $iDist = sqrt(pow($iVec[0],2)+pow($iVec[1],2)+pow($iVec[2],2));\n\n"; $expString += " if ($iDist <= "+$crowdProxy+".AwarenessRadius)\n"; $expString += " {\n"; $expString += " // Compute cohesion force.\n"; $expString += " $cf[0] += $iVec[0]/$iDist;\n"; $expString += " $cf[1] += $iVec[1]/$iDist;\n"; $expString += " $cf[2] += $iVec[2]/$iDist;\n\n"; $expString += " // Compute alignment force.\n"; $expString += " float $iVel[3];\n"; $expString += " $iVel[0] = getAttr(\"agentCtrl_\"+$i+\".VelX\");\n"; $expString += " $iVel[1] = getAttr(\"agentCtrl_\"+$i+\".VelY\");\n"; $expString += " $iVel[2] = getAttr(\"agentCtrl_\"+$i+\".VelZ\");\n"; $expString += " $iVelMag = sqrt(pow($iVel[0],2)+pow($iVel[1],2)+pow($iVel[2],2));\n"; $expString += " if ($iVelMag > 0)\n"; $expString += " {\n"; $expString += " $af[0] += $iVel[0]/$iVelMag;\n"; $expString += " $af[1] += $iVel[1]/$iVelMag;\n"; $expString += " $af[2] += $iVel[2]/$iVelMag;\n"; $expString += " }\n"; $expString += " }\n"; $expString += " // Compute separation force.\n"; $expString += " if ($iDist <= "+$crowdProxy+".ComfortRadius)\n"; $expString += " {\n"; $expString += " $sf[0] += -1*$iVec[0]/$iDist;\n"; $expString += " $sf[1] += -1*$iVec[1]/$iDist;\n"; $expString += " $sf[2] += -1*$iVec[2]/$iDist;\n"; $expString += " }\n"; $expString += " // Compute collision force.\n"; $expString += " if ($iDist <= "+$crowdProxy+".CollisionRadius)\n"; $expString += " {\n"; $expString += " $isColliding = 1;\n"; $expString += " $collf[0] += -1*$iVec[0]/$iDist;\n"; $expString += " $collf[1] += -1*$iVec[1]/$iDist;\n"; $expString += " $collf[2] += -1*$iVec[2]/$iDist;\n"; $expString += " }\n"; $expString += " }\n"; $expString += "}\n"; $expString += "// Multiply each of the above steering vectors by its priority.\n"; $expString += "$cf[0] *= "+$crowdProxy+".CohesionPriority;\n"; $expString += "$cf[1] *= "+$crowdProxy+".CohesionPriority;\n"; $expString += "$cf[2] *= "+$crowdProxy+".CohesionPriority;\n"; $expString += "$af[0] *= "+$crowdProxy+".AlignmentPriority;\n"; $expString += "$af[1] *= "+$crowdProxy+".AlignmentPriority;\n"; $expString += "$af[2] *= "+$crowdProxy+".AlignmentPriority;\n"; $expString += "$sf[0] *= "+$crowdProxy+".SeparationPriority;\n"; $expString += "$sf[1] *= "+$crowdProxy+".SeparationPriority;\n"; $expString += "$sf[2] *= "+$crowdProxy+".SeparationPriority;\n"; $expString += "// Put them together.\n"; $expString += "if ($isColliding)\n"; $expString += "{\n"; $expString += " " + $cName+".VelX += $collf[0];\n"; $expString += " " + $cName+".VelY += $collf[1];\n"; $expString += " " + $cName+".VelZ += $collf[2];\n"; $expString += "}\n"; $expString += "else\n"; $expString += "{\n"; $expString += " " + $cName+".VelX += ($cf[0] + $af[0] + $sf[0])*(1-$ratio)+$dx*$ratio;\n"; $expString += " " + $cName+".VelY += ($cf[1] + $af[1] + $sf[1])*(1-$ratio)+$dy*$ratio;\n"; $expString += " " + $cName+".VelZ += ($cf[2] + $af[2] + $sf[2])*(1-$ratio)+$dz*$ratio;\n"; $expString += "}\n\n"; $expString += "$speed = sqrt(pow("+$cName+".VelX,2) + pow("+$cName+".VelY,2) + pow("+$cName+".VelZ,2));\n"; $expString += "if ($speed > " + $crowdProxy + ".MaxVelocity)\n"; $expString += "{\n"; $expString += " $scale = "+$crowdProxy+".MaxVelocity/$speed;\n"; $expString += " "+$cName+".VelX *= $scale;\n"; $expString += " "+$cName+".VelY *= $scale;\n"; $expString += " "+$cName+".VelZ *= $scale;\n"; $expString += "}\n\n"; $expString += $cName+".translateX += "+$cName+".VelX;\n"; $expString += "if (" + $crowdProxy + ".Enable3DMovement)\n"; $expString += "{\n"; $expString += " " + $cName+".translateY += "+$cName+".VelY;\n"; $expString += "}\n"; $expString += "else\n"; $expString += "{\n"; $expString += " " + $cName+".translateY += 0;\n"; $expString += "}\n"; $expString += $cName+".translateZ += "+$cName+".VelZ;\n\n"; $expString += "// Rotate the agent based on velocity.\n"; $expString += $cName + ".rotateX = 0;\n"; $expString += $cName + ".rotateY = "; $expString += "atan2d("+$cName+".VelX, "+$cName+".VelZ);\n"; $expString += $cName + ".rotateZ = 0;\n\n"; $expString += "}\n"; expression -s $expString -name ("wander_exp"+$i) -alwaysEvaluate true -unitConversion all; } // End agent creation // Randomly offset the animation clips. // Battle plan: first, go through all the clips in the trax editor, // and sort them according to "parent" or "source" animation clip. // Then, we generate a random offset per group rather than per clip, // ensuring that all clips belonging to a single character have the // same offset. Part of the problem is that MEL doesn't do dynamic // 2D arrays (or at least not to my knowledge), so I'm replacing the // second dimension by space-separated strings that are tokenized. if ($agentHasAnim == 1 && size($agentAnim) > 0 && $fAnimOffsetRange > 0.0) { // Select all animation clips. string $allClips[]; // Its size will be size($agentAnim)-1. int $numTokens = -1; for ($i=1; $i