Hi!
So if you want to do this with code there are two ways of achieving this. One is to use an Expression, but for that you have to use MEL, you also have to use the approach that you outlined where you have to use multiple arrays to keep track of which vertex is currently in its deformed state and which isn't.
I wouldn't do this way, because arrays in MEL are absolute hell to work with, there are only like 3 builtin funtions that can manipulate an array, if you want to delete out elements from an array you have to recreate it without the element in question. Unless you convert all your inner arrays into strings, because string arrays have some special functions you can work with. But all the converting to and from strings, tokenizing etc is just not something I would recommend. Also since this funtion would have to run through every vertex on every sixth frame, with an expression this effect would would massively slow down playback, increasing with every vertex added.
The other way is to do it with keyframes in python. This will bake the effect on your vertices using point-level-animation.
If i'd do it like you suggested in your pseudocode, it would look something like this:
import maya.cmds as mc
import random as rand
sel = mc.ls(sl = True) or []
startTime = mc.playbackOptions(minTime = True, q = True)
endTime = mc.playbackOptions(maxTime = True, q = True)
vertexDict = {}
startPostDict = {}
for s in sel:
vertexCount = mc.polyEvaluate(v= True)
shapes = mc.listRelatives(s, s= True) or []
for sh in shapes:
startTimeNew = startTime +6
while startTimeNew < endTime:
for i in range(0, vertexCount):
startPos = mc.getAttr("{0}.vtx[{1}]".format(sh, i))[0]
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTime), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTime), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTime), v = startPos[2])
if "{0}.vtx[{1}]".format(sh, i) in vertexDict:
changedPosition = vertexDict.get("{0}.vtx[{1}]".format(sh, i))
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew -1), v = changedPosition[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew -1), v = changedPosition[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew -1), v = changedPosition[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew), v = startPos[2])
del vertexDict["{0}.vtx[{1}]".format(sh, i)]
elif rand.randint(1,5) == 2:
changeVector = (rand.uniform(0,1),rand.uniform(0,1), rand.uniform(0,1))
position = [startPos[0]+changeVector[0],startPos[1]+changeVector[1],startPos[2]+changeVector[2]]
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew -1), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew -1), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew -1), v = startPos[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew), v = position[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew), v = position[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew), v = position[2])
vertexDict["{0}.vtx[{1}]".format(sh, i)] = position
startTimeNew = startTimeNew + 6
But since I work with keyframes already and I know that the effect will be reversed after 6 frames anyways, I can just skip the whole adding it to a dictionary and removing from the dictionary by simply adding the keyframes needed after 6 frames and skipping that frames evaluation. Which makes the code faster and less cluttered
import maya.cmds as mc
import random as rand
sel = mc.ls(sl = True) or []
startTime = mc.playbackOptions(minTime = True, q = True)
endTime = mc.playbackOptions(maxTime = True, q = True)
for s in sel:
vertexCount = mc.polyEvaluate(v= True)
shapes = mc.listRelatives(s, s= True) or []
for sh in shapes:
for i in range(0, vertexCount):
startTimeNew = startTime +6
while startTimeNew < endTime:
startPos = mc.getAttr("{0}.vtx[{1}]".format(sh, i))[0]
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTime), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTime), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTime), v = startPos[2])
if rand.randint(1,5) == 2:
changeVector = (rand.uniform(0,1),rand.uniform(0,1), rand.uniform(0,1))
position = [startPos[0]+changeVector[0],startPos[1]+changeVector[1],startPos[2]+changeVector[2]]
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew -1), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew -1), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew -1), v = startPos[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew), v = position[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew), v = position[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew), v = position[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew+5), v = position[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew+5), v = position[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew)+5, v = position[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew +6), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew +6), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew +6), v = startPos[2])
startTimeNew = startTimeNew +6
startTimeNew = startTimeNew + 6
I'm not a huge fan of effects that are this static, where you have it after 6 frames, and the distance is 0-1. So I would probably add some parameters that easily allow me to change the maximum offset and the time between changes when I call the function.
import maya.cmds as mc
import random as rand
def glitchDeform(maxDistance= 1, step = 6):
sel = mc.ls(sl = True) or []
startTime = mc.playbackOptions(minTime = True, q = True)
endTime = mc.playbackOptions(maxTime = True, q = True)
for s in sel:
vertexCount = mc.polyEvaluate(v= True)
shapes = mc.listRelatives(s, s= True) or []
for sh in shapes:
for i in range(0, vertexCount):
startTimeNew = startTime +step
while startTimeNew < endTime:
startPos = mc.getAttr("{0}.vtx[{1}]".format(sh, i))[0]
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTime), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTime), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTime), v = startPos[2])
if rand.randint(1,5) == 2:
changeVector = (rand.uniform(0,maxDistance),rand.uniform(0,maxDistance), rand.uniform(0,maxDistance))
position = [startPos[0]+changeVector[0],startPos[1]+changeVector[1],startPos[2]+changeVector[2]]
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew -1), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew -1), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew -1), v = startPos[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew), v = position[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew), v = position[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew), v = position[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew+(step-1)), v = position[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew+(step-1)), v = position[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew)+(step-1), v = position[2])
mc.setKeyframe("{0}.vtx[{1}].px".format(sh, i), t = (startTimeNew +step), v = startPos[0])
mc.setKeyframe("{0}.vtx[{1}].py".format(sh, i), t = (startTimeNew +step), v = startPos[1])
mc.setKeyframe("{0}.vtx[{1}].pz".format(sh, i), t = (startTimeNew +step), v = startPos[2])
startTimeNew = startTimeNew +step
startTimeNew = startTimeNew + step
glitchDeform(maxDistance= 1, step = 6)
Short warning: this code still is faaaaaaar from perfect. It also runs pretty slow, so adding a processbar would probably be a good idea.
But it does what you wanted.
If you want to have this effect work in runtime, I would suggest that you take the rigging approach that @jmreinhart outlined in his response.
I hope it helps!