Last active
October 13, 2017 09:09
-
-
Save gregtemp/5de3d2161c6d10f8191d to your computer and use it in GitHub Desktop.
Boids Sim - Maya Python Mel (with comments)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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