Skip to content

Instantly share code, notes, and snippets.

@gregtemp
Last active October 13, 2017 09:09
Show Gist options
  • Save gregtemp/5de3d2161c6d10f8191d to your computer and use it in GitHub Desktop.
Save gregtemp/5de3d2161c6d10f8191d to your computer and use it in GitHub Desktop.
Boids Sim - Maya Python Mel (with comments)
# Boids Simulation
# Basic outline of the functionality of the boid simulation
# This example does not include the user interface and is
# stripped back to it's core functionality for documentation
# purposes.
import maya.cmds as cmds
import random
from maya.OpenMaya import MVector
# Set a seed amount to maintain uniform random numbers
random.seed(1234)
# Set some preliminary values
amountOfParticles = 100
r = 10
vr = 10
# Min and max distance and angle for one Boid to perceive another
perceptionDist = 5
perceptionDistMin = 0
# This is ca;culated using the dot product so numbers between 0 and 1 are in front,
# and 0 to -1 are behind (0 is perpendicular).
perceptionAngle = 0.5
# Set some weight values to help control our Boids
alignWeight = 0.0
cohesionWeight = 0.7
separationWeight = 0.3
# Optional acceleration limit
accelLimit = 100
# Initialize lists to store position, velocity and acceleration
positionList = list()
velocityList = list()
accelList = list()
# Here we set up our functions that we will call later
def deleteAllCreateAgain ():
# For conveniece we list all the particles on screen and delete them
particleList = list()
tempList = list()
particleList = cmds.ls( 'particleObject*' )
# If there are any particles, delete them all.
if len(particleList) >= 1:
cmds.delete(particleList)
print ('--- print --- deleted old particleObject')
else:
print ('--- print --- no old particleObjects')
# Initialize new particles
for i in range( 0, amountOfParticles):
x = 0
y = 0
z = 0
newVector = (x,y,z)
tempList.append(newVector)
positionList.append(MVector(*newVector))
newVector = (x,y,z)
velocityList.append(MVector(*newVector))
newVector = (x,y,z)
accelList.append(MVector(*newVector))
# Add new particles to the scene
cmds.particle(p=tempList,n='particleObject#')
# Update master lists from particle expressions
def sendToMaster (pID, myPx, myPy, myPz, myVx, myVy, myVz, myAx, myAy, myAz):
positionList[pID] = MVector(myPx, myPy, myPz)
velocityList[pID] = MVector(myVx, myVy, myVz)
accelList[pID] = MVector(myAx, myAy, myAz)
# Reset particle locations, velocity and acceleration (called from particle creation expression)
def resetFromMaster (pID):
x = random.uniform(-10, 10)
y = random.uniform(-10, 10)
z = random.uniform(-10, 10)
positionList[pID] = MVector(x,y,z)
x = 0
y = 0
z = 0
velocityList[pID] = MVector(x,y,z)
x = 0
y = 0
z = 0
accelList[pID] = MVector(x,y,z)
# Store the pos, vel and accel of the particle with current ID (from master lists) as temporary vector
tempP = positionList[pID]
tempV = velocityList[pID]
tempA = accelList[pID]
# Assemble into a list (separated as floats) to pass back to particle with current ID
listForMel = [tempP.x, tempP.y, tempP.z, tempV.x, tempV.y, tempV.z, tempA.x, tempA.y, tempA.z]
return listForMel
# Read the current particle's pos, vel and accel from master list
def readFromMaster (pID):
tempP = positionList[pID]
tempV = velocityList[pID]
tempA = accelList[pID]
# Assemble list (separated as floats) to pass back to particle with current ID
listForMel = [tempP.x, tempP.y, tempP.z, tempV.x, tempV.y, tempV.z, tempA.x, tempA.y, tempA.z]
return listForMel
# Find list of neighbors for the particle with current ID
def calculateNeighbor(pID):
# Initialize vectors
pAlignment = MVector()
pCohesion = MVector()
pSeparation = MVector()
# Iterate through list of particles
for i in range(0, amountOfParticles):
if i == id: # If particle ID equals self then skip this one
continue
dist = MVector(positionList[pID]-positionList[i]) # Calculate distance between self and current particle
if dist.length() >= perceptionDist: # If it's bigger than our perceptionDist then skip this one
continue
dotProduct = (velocityList[pID].normal() * velocityList[i].normal()) # Calculate dot product (to find angle between vectors)
if dotProduct < perceptionAngle: # If it's smaller than our perceptionAngle (ie: behind us), skip this one
continue
# All the ID's that get to this point are neighbors.
# Calculate the difference between us and neighbor's velocity
velDiff = MVector(velocityList[pID]-velocityList[i])
# Add velocity difference to alignment value
pAlignment += velDiff
# Add distance between us and neighbor to cohesion value
pCohesion += dist
# And if distance is smaller than perception distance, add distance to separation value
if dist.length() < perceptionDistMin:
pSeparation += dist
# After iterating through the whole list, the alignment, cohesion and separation values will be the sum of the entire
# neighbor list.
# Normalize the align, cohes and sep values if smaller than 0.
if pAlignment.length() > 0:
pAlignment.normal()
if pCohesion.length() > 0:
pCohesion.normal()
if pSeparation.length() > 0:
pSeparation.normal()
# Multiply values by their weight (to control how much they'll affect our simulation)
pAlignment *= alignWeight
pCohesion *= cohesionWeight
pSeparation *= separationWeight
# Our new acceleration value is calculated using our new alignment, cohesion and separation values
accelList[pID] = pAlignment
accelList[pID] += pCohesion
accelList[pID] -= pSeparation
# Make our list in order to pass acceleration values back to particle
tempA = accelList[pID]
listForMel = [tempA.x, tempA.y, tempA.z]
return listForMel
# Set up creation expression for particles
def cExpression ():
# We have to build our expression with strings. I've separated them so it's easier to read.
expString0 = 'string $strForPy1 = "resetFromMaster(" + particleObject2Shape.particleId + ")";'
expString1 = 'float $resultList[] = python($strForPy1);'
expString2 = 'particleObject2Shape.position = <<$resultList[0], $resultList[1], $resultList[2]>>; particleObject2Shape.velocity = <<$resultList[3], $resultList[4], $resultList[5]>>; particleObject2Shape.acceleration = <<$resultList[6], $resultList[7], $resultList[8]>>;'
bigString = expString0 + expString1 + expString2
cmds.dynExpression( 'particleObject*Shape', s=bigString, c=1)
# Set up our "Runtime Before Dynamics" expression for particles
# Runtime before dynamics, runs on every particle, on every frame. So we pass our particle ID and
# we don't need any for loops.
def rBDExpression ():
expString0 = 'vector $myP = particleObject2Shape.position; vector $myV = particleObject2Shape.velocity; vector $myA = particleObject2Shape.acceleration; '
expString1 = 'string $sendToMasterPy = "sendToMaster(" + particleObject2Shape.particleId + ", " + $myP.x + ", " + $myP.y + ", " + $myP.z + ", " + $myV.x + ", " + $myV.y + ", " + $myV.z + ", " + $myA.x + ", " + $myA.y + ", " + $myA.z + ")"; python ($sendToMasterPy); '
expString2 = 'string $strForPy = "calculateNeighbor(" + particleObject2Shape.particleId + ")"; float $resultList[] = python($strForPy); vector $accV = <<$resultList[0], $resultList[1], $resultList[2]>>; particleObject2Shape.acceleration = $accV;'
bigString = expString0 + expString1 + expString2
cmds.dynExpression( 'particleObject*Shape', s= bigString, rbd=1)
# Now that all our functions are set up we can call them in the appropriate order.
deleteAllCreateAgain()
cExpression()
rBDExpression()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment