"""
import maya.cmds as mc
mc.file(new=True, force=True, save=False)
mc.unloadPlugin("noiseDeformer.py")
mc.loadPlugin("noiseDeformer.py")
mc.polySphere()
mc.deformer( type='noiseDeformer' )

MGlobal::executeCommandOnIdle()
"""

import math, sys
from simplexnoise import octave_noise_4d
import maya.mel as mel
import maya.cmds as mc
import pymel.core as pm
import maya.OpenMaya as om
import maya.OpenMayaAnim as oma
import maya.OpenMayaMPx as ompx

VERSION = '0.3'
AUTHOR = 'Joss'

kPluginNodeTypeName = "noiseDeformer"
noiseDeformerId = om.MTypeId(0x19f20)

# Node definition
class noiseDeformer(ompx.MPxGeometryFilter):
	# constructor
	def __init__(self):
		# effect scale
		self.aScale = om.MObject()
		# direction vector
		self.aVecX = 0.0
		self.aVecY = 0.0
		self.aVecZ = 0.0
		self.aVecW = 0.0
		# noise params
		self.aNoctaves = om.MObject()
		self.aNpersistence = om.MObject()
		self.aNscale = om.MObject()
		# noise offsets
		self.aOfsX = 0.0
		self.aOfsY = 0.0
		self.aOfsZ = 0.0
		self.aTime = 0.0
		self.aTimeOfs = 0.0
		self.aTimeScl = 0.04
		#om.MGlobal.executeCommandOnIdle
		self.envelopeValue = 1.0
		self.inGeo = None
		self.normals = om.MFloatVectorArray()
		self.meshFn = None
		ompx.MPxGeometryFilter.__init__(self)

	# deform
	def deform(self, dataBlock, git, matrix, multiIndex):
		#print(ompx.MPxGeometryFilter.name(self))
		self.getParams(dataBlock, multiIndex)
		self.aVecW = self.aTimeOfs + (self.aTime*self.aTimeScl)

		# iterate over the object and change the angle
		while git.isDone() == False:
			pt = git.position()
			idx = git.index()
			normal = om.MVector(self.normals[idx])
			vec = om.MVector(self.aVecX, self.aVecY, self.aVecZ)
			# using vertex normal if direction vector is [0,0,0]
			if (self.aVecX==0 and self.aVecY==0 and self.aVecZ==0):
				vec = normal
			noise = octave_noise_4d(self.aNoctaves, self.aNpersistence, self.aNscale, pt.x+self.aOfsX, pt.y+self.aOfsY, pt.z+self.aOfsZ, self.aVecW)
			#noise = octave_noise_3d(n_octaves, n_persistence, n_scale, point.x, point.y, point.z)
			vec = vec * (self.aScale * self.envelopeValue * noise)
			pt = pt+vec
			git.setPosition(pt)
			git.next()

	# from autodesk's bboxDeformer:
	def getInGeo(self, dataBlock, multiIndex):
		inputAttribute = ompx.cvar.MPxGeometryFilter_input
		inputGeometryAttribute = ompx.cvar.MPxGeometryFilter_inputGeom
		inputHandle = dataBlock.outputArrayValue(inputAttribute)
		inputHandle.jumpToElement(multiIndex)
		inputGeometryObject = inputHandle.outputValue().child(inputGeometryAttribute).asMesh()
		return inputGeometryObject

	def getParams(self, dataBlock, multiIndex):
		# retrieving attrs from the datablock
		self.aScale = dataBlock.inputValue(self.scale).asDouble()
		self.aVecX = dataBlock.inputValue(self.vecx).asDouble()
		self.aVecY = dataBlock.inputValue(self.vecy).asDouble()
		self.aVecZ = dataBlock.inputValue(self.vecz).asDouble()
		self.aVecW = dataBlock.inputValue(self.vecw).asDouble()
		self.aNoctaves = dataBlock.inputValue(self.noctaves).asInt()
		if self.aNoctaves < 1:
			self.aNoctaves = 1
		self.aNpersistence = dataBlock.inputValue(self.npersistence).asDouble()
		self.aNscale = dataBlock.inputValue(self.nscale).asDouble()
		self.aOfsX = dataBlock.inputValue(self.ofsx).asDouble()
		self.aOfsY = dataBlock.inputValue(self.ofsy).asDouble()
		self.aOfsZ = dataBlock.inputValue(self.ofsz).asDouble()
		self.aTime = dataBlock.inputValue(self.time).asDouble()
		self.aTimeOfs = dataBlock.inputValue(self.timeOfs).asDouble()
		self.aTimeScl = dataBlock.inputValue(self.timeScl).asDouble()
		# get the envelope
		self.envelopeValue = dataBlock.inputValue(ompx.cvar.MPxGeometryFilter_envelope).asFloat()
		# getting inMesh and vertex normals
		self.inGeo = self.getInGeo(dataBlock, multiIndex)
		self.meshFn = om.MFnMesh(self.inGeo)
		self.meshFn.getVertexNormals(True, self.normals, om.MSpace.kTransform)

	# def autoConnect(self):
	# stat = MGlobal::selectByName( "time1", MGlobal::kReplaceList );
	# MSelectionList sl;
	# stat = MGlobal::getActiveSelectionList(sl);
	# MObject oTime;
	# stat = sl.getDependNode(0,oTime);
	# MFnDependencyNode fnTime( oTime );
	# MPlug outTime = fnTime.findPlug("outTime",&stat);
	# MPlug inTime = fn.findPlug("time");
	# MDagModifier m_Mod;
	# m_Mod.connect( outTime, inTime );
	# stat = m_Mod.doIt();
	# stat = MGlobal::setActiveSelectionList(previous_list);

# creator
def nodeCreator():
	return ompx.asMPxPtr(noiseDeformer())

# initializer
def nodeInitializer():
	nAttr = om.MFnNumericAttribute()
	# output
	outputGeom = ompx.cvar.MPxGeometryFilter_outputGeom
	# inputs
	noiseDeformer.scale = nAttr.create("scale", "scl", om.MFnNumericData.kDouble, 1.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.scale)
	noiseDeformer.attributeAffects(noiseDeformer.scale, outputGeom)
	# vecx/y/z
	noiseDeformer.vecx = nAttr.create("vec_x", "vx", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.vecx)
	noiseDeformer.attributeAffects(noiseDeformer.vecx, outputGeom)
	noiseDeformer.vecy = nAttr.create("vec_y", "vy", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.vecy)
	noiseDeformer.attributeAffects(noiseDeformer.vecy, outputGeom)
	noiseDeformer.vecz = nAttr.create("vec_z", "vz", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.vecz)
	noiseDeformer.attributeAffects(noiseDeformer.vecz, outputGeom)
	#noiseDeformer.vecw = nAttr.create("vec_w", "vw", om.MFnNumericData.kDouble, 0.0)
	#makeInput(nAttr)
	#noiseDeformer.addAttribute(noiseDeformer.vecw)
	#noiseDeformer.attributeAffects(noiseDeformer.vecw, outputGeom)
	# noise params
	noiseDeformer.noctaves = nAttr.create("Noise_octaves", "no", om.MFnNumericData.kInt, 3)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.noctaves)
	noiseDeformer.attributeAffects(noiseDeformer.noctaves, outputGeom)
	noiseDeformer.npersistence = nAttr.create("Noise_persistence", "np", om.MFnNumericData.kDouble, 0.5)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.npersistence)
	noiseDeformer.attributeAffects(noiseDeformer.npersistence, outputGeom)
	noiseDeformer.nscale = nAttr.create("Noise_scale", "ns", om.MFnNumericData.kDouble, 0.25)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.nscale)
	noiseDeformer.attributeAffects(noiseDeformer.nscale, outputGeom)
	# offset
	noiseDeformer.ofsx = nAttr.create("offsetX", "ox", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.ofsx)
	noiseDeformer.attributeAffects(noiseDeformer.ofsx, outputGeom)
	noiseDeformer.ofsy = nAttr.create("offsetY", "oy", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.ofsy)
	noiseDeformer.attributeAffects(noiseDeformer.ofsy, outputGeom)
	noiseDeformer.ofsz = nAttr.create("offsetZ", "oz", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.ofsz)
	noiseDeformer.attributeAffects(noiseDeformer.ofsz, outputGeom)
	# unitAttribute don't have kTime for some reason, produces stupid values like 3.2e-320
	noiseDeformer.time = nAttr.create("inTime", "t", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.time)
	noiseDeformer.attributeAffects(noiseDeformer.time, outputGeom)
	noiseDeformer.timeOfs = nAttr.create("timeOffset", "to", om.MFnNumericData.kDouble, 0.0)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.timeOfs)
	noiseDeformer.attributeAffects(noiseDeformer.timeOfs, outputGeom)
	noiseDeformer.timeScl = nAttr.create("timeScale", "ts", om.MFnNumericData.kDouble, 0.04)
	makeInput(nAttr)
	noiseDeformer.addAttribute(noiseDeformer.timeScl)
	noiseDeformer.attributeAffects(noiseDeformer.timeScl, outputGeom)

# initialize the script plug-in
def initializePlugin(mobject):
	mplugin = ompx.MFnPlugin(mobject, AUTHOR, VERSION, 'Any')
	try:
		mplugin.registerNode(kPluginNodeTypeName, noiseDeformerId, nodeCreator, nodeInitializer, ompx.MPxNode.kDeformerNode)
	except:
		sys.stderr.write("Failed to register node: %s\n" % kPluginNodeTypeName)

# uninitialize the script plug-in
def uninitializePlugin(mobject):
	mplugin = ompx.MFnPlugin(mobject)
	try:
		mplugin.deregisterNode(noiseDeformerId)
	except:
		sys.stderr.write("Failed to unregister node: %s\n" % kPluginNodeTypeName)

def makeInput(attr):
	attr.setKeyable(True)
	attr.setStorable(True)
	attr.setReadable(True)
	attr.setWritable(True)

def makeInputSrf(attr):
	attr.setKeyable(False)
	attr.setStorable(False)
	attr.setReadable(False)
	attr.setWritable(True)

def makeOutput(attr):
	attr.setKeyable(False)
	attr.setStorable(False)
	attr.setReadable(True)
	attr.setWritable(False)