simpleEmitter.py

#-
# ==========================================================================
# Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
# rights reserved.
#
# The coded instructions, statements, computer programs, and/or related 
# material (collectively the "Data") in these files contain unpublished 
# information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
# licensors, which is protected by U.S. and Canadian federal copyright 
# law and by international treaties.
#
# The Data is provided for use exclusively by You. You have the right 
# to use, modify, and incorporate this Data into other products for 
# purposes authorized by the Autodesk software license agreement, 
# without fee.
#
# The copyright notices in the Software and this entire statement, 
# including the above license grant, this restriction and the 
# following disclaimer, must be included in all copies of the 
# Software, in whole or in part, and all derivative works of 
# the Software, unless such copies or derivative works are solely 
# in the form of machine-executable object code generated by a 
# source language processor.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
# AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
# WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
# NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
# PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
# TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
# BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
# DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
# AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
# OR PROBABILITY OF SUCH DAMAGES.
#
# ==========================================================================
#+


import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMayaRender as OpenMayaRender
import math
import random
import sys

kSimpleEmitterNodeName = "spSimpleEmitter"
kSimpleEmitterNodeID = OpenMaya.MTypeId(0x8700F)

SEGMENTS = 20
EDGES = 30
TORUS_PI = 3.14159265
TORUS_2PI = 2.0 * TORUS_PI

glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer()
glFT = glRenderer.glFunctionTable()

class simpleEmitter(OpenMayaMPx.MPxEmitterNode):
    def __init__(self):
        OpenMayaMPx.MPxEmitterNode.__init__(self)

    def isFullValue( self, plugIndex, block ):
        value = 1
        mIsFull = OpenMayaMPx.cvar.MPxEmitterNode_mIsFull

        try:
            mhValue = block.inputArrayValue( mIsFull )
            mhValue.jumpToElement( plugIndex )
            hValue = mhValue.inputValue( )
            value = hValue.asBool()
        except:
            sys.stderr.write("Error getting the input array value\n")
            raise
        return value
                    
    
    def getWorldPosition( self, point ):
        try:
            thisNode = simpleEmitter.thisMObject( self )
            fnThisNode = OpenMaya.MFnDependencyNode(thisNode)

            worldMatrixAttr = fnThisNode.attribute( "worldMatrix" )

            matrixPlug = OpenMaya.MPlug( thisNode, worldMatrixAttr )
            matrixPlug = matrixPlug.elementByLogicalIndex( 0 )

            matrixObject = OpenMaya.MObject()
            matrixObject = matrixPlug.asMObject(  )

            worldMatrixData = OpenMaya.MFnMatrixData( matrixObject )
            worldMatrix = worldMatrixData.matrix( )

            point.x = worldMatrix( 3, 0 )
            point.y = worldMatrix( 3, 1 )
            point.z = worldMatrix( 3, 2 )
        except:
            sys.stderr.write( "Error in getWorldPosition\n" )
            raise

    def currentTimeValue( self, block ):
        try:
            mCurrentTime = OpenMayaMPx.cvar.MPxEmitterNode_mCurrentTime
            hValue = block.inputValue( mCurrentTime )
            value = hValue.asTime()
        except:
            sys.stderr.write("Error getting current time value, returning 0")
            value = OpenMaya.MTime(0.0)
        return value

    def startTimeValue( self, plugIndex, block ):
        try:
            mStartTime = OpenMayaMPx.cvar.MPxEmitterNode_mStartTime
            mhValue = block.inputArrayValue( mStartTime )
            mhValue.jumpToElement( plugIndex )
            hValue = mhValue.inputValue( )
            value = hValue.asTime( )        
        except:
            sys.stderr.write("Error getting start time value, setting to 0")
            value = OpenMaya.MTime(0.0)
        return value

    def deltaTimeValue( self, plugIndex, block ):
        try:
            mDeltaTime = OpenMayaMPx.cvar.MPxEmitterNode_mDeltaTime

            mhValue = block.inputArrayValue( mDeltaTime )
            mhValue.jumpToElement( plugIndex )

            hValue = mhValue.inputValue()
            value = hValue.asTime()
        except:
            sys.stderr.write("Error getting delta time value, setting to 0\n")
            value = OpenMaya.MTime(0.0)
        return value

    def rateValue( self, block ):
        try:
            mRate = OpenMayaMPx.cvar.MPxEmitterNode_mRate
            hValue = block.inputValue( mRate )
            value = hValue.asDouble()       
        except:
            sys.stderr.write("Error getting rate value, setting to 0\n")
            value = 0.0
        return value

    def directionValue( self, block ):
        try:
            mDirection = OpenMayaMPx.cvar.MPxEmitterNode_mDirection
            hValue = block.inputValue( mDirection )
            value = hValue.asDouble3()
            dirV = OpenMaya.MVector(value[0], value[1], value[2])
        except:
            sys.stderr.write("Error getting direction value, setting to 0,0,0\n")
            dirV = OpenMaya.MVector(0.0, 0.0, 0.0)
        return dirV

    def speedValue( self, block ):
        try:
            mSpeed = OpenMayaMPx.cvar.MPxEmitterNode_mSpeed
            hValue = block.inputValue( mSpeed )
            value = hValue.asDouble()       
        except:
            sys.stderr.write("Error getting speed value, setting to 0\n")
            value = 0.0
        return value

    def inheritFactorValue( self, plugIndex, block ):
        try:
            mInheritFactor = OpenMayaMPx.cvar.MPxEmitterNode_mInheritFactor
            mhValue = block.inputArrayValue( mInheritFactor )
            mhValue.jumpToElement( plugIndex )
            hValue = mhValue.inputValue( )
            value = hValue.asDouble()       
        except:
            sys.stderr.write("Error getting inherit factor value, setting to 0\n")
            value = 0.0
        return value

    def useRotation( self, direction ):
        try:
            thisNode = simpleEmitter.thisMObject(self)
            fnThisNode = OpenMaya.MFnDependencyNode( thisNode )

            worldMatrixAttr = fnThisNode.attribute( "worldMatrix" )

            matrixPlug = OpenMaya.MPlug( thisNode, worldMatrixAttr )
            matrixPlug = matrixPlug.elementByLogicalIndex( 0 )

            matrixObject = OpenMaya.MObject()
            matrixObject = matrixPlug.asMObject( )

            worldMatrixData = OpenMaya.MFnMatrixData( matrixObject )
            worldMatrix = worldMatrixData.matrix( )

            rotatedVector = OpenMaya.MVector()
            rotatedVector = direction * worldMatrix
        except:
            sys.stderr.write("Error getting rotation value, setting to 0,0,0\n")
            rotatedVector = OpenMaya.MVector(0.0, 0.0, 0.0)
        return rotatedVector


    def compute(self, plug, block):
        mOutput = OpenMayaMPx.cvar.MPxEmitterNode_mOutput

        # Determine if we are requesting the output plug for this emitter node.
        #
        if plug == mOutput:
            # Get the logical index of the element this plug refers to,
            # because the node can be emitting particles into more than
            # one particle shape.
            #
            try:
                multiIndex = plug.logicalIndex( )

                # Get output data arrays (position, velocity, or parentId)
                # that the particle shape is holding from the previous frame.
                #
                hOutArray = block.outputArrayValue ( mOutput )

                # Create a builder to aid in the array construction efficiently.
                #
                bOutArray = hOutArray.builder( )

                # Get the appropriate data array that is being currently evaluated.
                #
                hOut = bOutArray.addElement( multiIndex )

                # Create the data and apply the function set,
                # particle array initialized to length zero, 
                # fnOutput.clear()
                #
                fnOutput = OpenMaya.MFnArrayAttrsData()
                dOutput = fnOutput.create( )

                # Check if the particle object has reached it's maximum,
                # hence is full. If it is full then just return with zero particles.
                #
                beenFull = simpleEmitter.isFullValue( self, multiIndex, block )
                if beenFull == 1:
                    return

                # Get input position and velocity arrays where new particles are from,
                # also known as the owner. An owner is determined if connections exist
                # to the emitter node from a shape such as nurbs, polymesh, curve, 
                # or a lattice shape.
                #
                
                # Get a single position from world transform
                #
                inPosAry = OpenMaya.MVectorArray()
                inPosAry.clear()
            
                worldPos = OpenMaya.MPoint(0.0, 0.0, 0.0)
                simpleEmitter.getWorldPosition( self, worldPos )

                worldV = OpenMaya.MVector(worldPos[0], worldPos[1], worldPos[2])
                inPosAry.append( worldV )

                # Create a single velocity          
                inVelAry = OpenMaya.MVectorArray()
                inVelAry.clear()
                velocity = OpenMaya.MVector(0, 0, 0)
                inVelAry.append( velocity )

                # Get deltaTime, currentTime and startTime.
                # If deltaTime <= 0.0, or currentTime <= startTime,
                # do not emit new pariticles and return.
                #
                cT = simpleEmitter.currentTimeValue( self, block )
                sT = simpleEmitter.startTimeValue( self, multiIndex, block )
                dT = simpleEmitter.deltaTimeValue( self, multiIndex, block )

                dTValue = dT.value()
            
                if cT <= sT or dTValue <= 0.0:
                    # We do not emit particles before the start time, 
                    # and do not emit particles when moving backwards in time.
                    # 

                    # This code is necessary primarily the first time to 
                    # establish the new data arrays allocated, and since we have 
                    # already set the data array to length zero it does 
                    # not generate any new particles.
                    # 
                    hOut.setMObject( dOutput )
                    block.setClean( plug )
                    return

                # Compute and store an emission rate
                #
                emitCountPP = OpenMaya.MIntArray()
                emitCountPP.clear()

                plugIndex = plug.logicalIndex( )

                # Get rate and delta time.
                #
                rate = simpleEmitter.rateValue( self, block )
                dtRate = simpleEmitter.deltaTimeValue( self, plugIndex, block )
                dtRateDbl = dtRate.asUnits( OpenMaya.MTime.kSeconds )
                dblCount = rate * dtRateDbl
                intCount = int(dblCount)
                emitCountPP.append( intCount )

                # Get speed, direction vector, and inheritFactor attributes.
                #           
                speed = simpleEmitter.speedValue( self, block )
                dirV = simpleEmitter.directionValue( self, block )
                inheritFactor = simpleEmitter.inheritFactorValue( self, multiIndex, block )

                # Get the position, velocity, and normalized time arrays to append new particle data.
                #
                fnOutPos = fnOutput.vectorArray( "position" )
                fnOutVel = fnOutput.vectorArray( "velocity" )
                fnOutTime = fnOutput.doubleArray( "timeInStep" )

                # Convert deltaTime into seconds.
                #           
                dt = dT.asUnits( OpenMaya.MTime.kSeconds )

                # Rotate the direction attribute by world transform
                rotatedV = simpleEmitter.useRotation( self, dirV )

                # Start emitting particles.
                #
                simpleEmitter.emit( self, inPosAry, inVelAry, emitCountPP, dt, speed, inheritFactor,\
                        rotatedV, fnOutPos, fnOutVel, fnOutTime)

                # Update the data block with new dOutput and set plug clean.
                #   
                # sys.__stdout__.write( " handle: " + str(hOut) + "\n" )        
                hOut.setMObject( dOutput )
                block.setClean( plug )
            except:
                sys.stderr.write("simpleEmitter compute error\n")
                raise
        else:
            return OpenMaya.kUnknownParameter

    def emit( self, inPosAry, inVelAry, emitCountPP, dt, speed, inheritFactor, dirV, outPosAry, outVelAry, outTimeAry):
        try:
            posLength = inPosAry.length()
            velLength = inVelAry.length()
            countLength = emitCountPP.length()

            if not posLength == velLength or not posLength == countLength:
                return

            totalCount = 0
            for index in range(countLength):
                totalCount += emitCountPP[index]
            if totalCount <= 0:
                return

            dirV.normalize()
            
            for index in range(posLength):
                emitCount = emitCountPP[index]
                if emitCount <= 0:
                    continue
            
                sPos = inPosAry[index]
                sVel = inVelAry[index]
                prePos = sPos - sVel * dt

                for i in range(emitCount):
                    alpha = ( float(i) + random.random() ) / float(emitCount)
                    newPos = prePos * (1.0 - alpha) + sPos * alpha
                    newVel = dirV * speed

                    newPos += newVel * ( dt * (1.0 - alpha) )
                    newVel += sVel * inheritFactor

                    # Add new data into output arrays.
                    #
                    outPosAry.append( newPos )
                    outVelAry.append( newVel )
                    outTimeAry.append( alpha )
        except Exception, e:
            sys.stderr.write( "Error in simpleEmitter.emit\n" )
            raise

    def draw( self, view, path, style, status):
        view.beginGL()

        for j in range(0, SEGMENTS):
            glFT.glPushMatrix()
            glFT.glRotatef(float(360.0 * j/SEGMENTS), 0.0, 1.0, 0.0)
            glFT.glTranslatef( 1.5, 0.0, 0.0)
            
            for i in range(0, EDGES):
                glFT.glBegin(OpenMayaRender.MGL_LINE_STRIP)

                p0 = float(TORUS_2PI * i/EDGES)
                p1 = float(TORUS_2PI * (i+1)/EDGES)
                glFT.glVertex2f(math.cos(p0), math.sin(p0))
                glFT.glVertex2f(math.cos(p1), math.sin(p1))
                
                glFT.glEnd()

            glFT.glPopMatrix()

        view.endGL()


def nodeCreator():
    return OpenMayaMPx.asMPxPtr( simpleEmitter() )

def nodeInitializer():
    return

# initialize the script plug-in
def initializePlugin(mobject):
    mplugin = OpenMayaMPx.MFnPlugin(mobject)

    try:
        mplugin.registerNode( kSimpleEmitterNodeName, kSimpleEmitterNodeID, \
                                nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kEmitterNode )
    except:
        sys.stderr.write( "Failed to register node: %s\n" % kSimpleEmitterNodeName )
        raise

# uninitialize the script plug-in
def uninitializePlugin(mobject):
    mplugin = OpenMayaMPx.MFnPlugin(mobject)

    try:
        mplugin.deregisterNode( kSimpleEmitterNodeID )
    except:
        sys.stderr.write( "Failed to unregister node: %s\n" % kSimpleEmitterNodeName )
        raise