The Tech Artist

Archive for May, 2012

Alien / Prometheus Trailer Mashup

by on May.25, 2012, under Video

I’ve taken a short break from coding to follow a recent creative streak. I’m a huge fan of Ridley Scott’s film Alien so it comes as no surprise that I’m eagerly awaiting the release of Prometheus. The marketing for this film has been unreal and the first full length trailer was quite impressive. I’ve never done a trailer mashup before and it’s been a while since I’ve dabbled in any video editing, so I thought I would create an edit of the trailer using clips from Alien.

My goal was to match the visuals, tone and pace of the original as closely as possible. Selecting suitable replacements ended up being a bit of a challenge as there’s some stuff going on in Prometheus that doesn’t have an Alien counterpart. The middle of the trailer was the trickiest; rather than going for an outright visual match I settled for selecting audio and visuals which propel the story forward in much the same way as the Prometheus trailer does.

Originally I intended to use After Effects but found it a bit cumbersome to use when dealing with so many shots. I was also having issues with importing AVIs – something to do with keyframes, I gather. I settled for iMovie which was really quick to bash out the initial shot layout.

Video Initial Pass

To start I brought the original trailer into iMovie and went about replacing each shot. iMovie is great at making this sort of thing really easy – you just select your source and drag in. Audio samples had to be cut up and retimed a bit to match the beats of the original trailer.

Audio Pass

This ended up being more complex than I anticipated as there was a lot of dialog to remove. I hadn’t used Audacity for anything other than converting sound files, so this gave me a chance to dive in and really test out its features. For free software I’m quite impressed – it was easy to pick up and I was able to replace the dialog with clean samples I took from the teaser trailer using envelopes.

Video Final Pass

Once the audio and video was finalised I exported the entire thing to After Effects to add titles and fades. The black flashes at the end of the trailer and the fade ins were done by animating a black solid’s opacity. The final title is taken from the opening credits; I’ve just boosted the contrast so it looks like white text on a black background.

There’s a few issues with the timing of Kane’s dialog, and some of the other dialog sounds a bit clipped but overall I’m happy with the result.

1 Comment more...

Attaching Scripts to Objects in Panda3D – Part II

by on May.06, 2012, under Musings, Panda3D, Panda3D Scene Editor

In my last post I showed how to attach a Python object to a node path in order to create a ‘hook’ in the Panda3D scene graph. In this post I’ll be showing how to dynamically add additional code to that object at runtime. Attaching code to objects in this way will be at the core of offering drag and drop scripting functionality in the same manner as Unity.

Once we have the PandaObject the next thing we could do is to subclass it in order to add additional code. Since I’m building an editor I want to offer the user an easy way to add, remove and combine different scripts for a node path, so in this case we’ll use the PandaObject as a hook only and then ‘hang’ other scripts from it.

In the context of our editor, the user will presented with a file browser displaying all the scripts in their project. This hierarchy will be representative of the directory structure on disk, and the user should be able to drag and drop any script onto any node in the scene. This presents an interesting problem as essentially we need to be able to instantiate a class from a file path the user selects at runtime. Thankfully python’s imp module offers some very handy tools for solving this kind of problem.

So now our PandaObject code looks like this:

import os
import sys
import imp

from direct.showbase.DirectObject import DirectObject

class PandaObject( object ):

    def __init__( self, np ):

        # Store the node path with a reference to this class attached to it
        self.np = np
        self.np.setPythonTag( 'PandaObject', self )

        self.instances = {}

    @staticmethod
    def Get( np ):

        # Return the panda object for the supplied node path
        return np.getPythonTag( 'PandaObject' )

    @staticmethod
    def Break( np ):

        # Detach each script from the object
        pObj = PandaObject.Get( np )
        if pObj is not None:
            for clsName in pObj.instances.keys():
                pObj.DetachScript( clsName )

        # Clear the panda object tag to allow for proper garbage collection
        np.clearPythonTag( 'PandaObject' )

    def AttachScript( self, filePath ):

        # If the script path is not absolute then we'll need to search for it.
        # imp.find_module won't take file paths so create a list of search
        # paths by joining the head of the script path to each path in sys.path.
        head, tail = os.path.split( filePath )
        if not os.path.isabs( filePath ):
            srchPaths = []
            for sysPath in sys.path:
                srchPaths.append( os.path.join( sysPath, head ) )
        else:
            srchPaths = [head]

        # Import the module. Pass imp.find_module the name of the module
        # and a list of paths to search for it on.
        name = os.path.splitext( tail )[0]
        modDetails = imp.find_module( name, srchPaths )
        mod = imp.load_module( name, *modDetails )

        # Get the class matching the name of the file, attach it to
        # the object
        clsName = name[0].upper() + name[1:]
        cls = getattr( mod, clsName )

        # Save the instance by the class name
        self.instances[clsName] = cls( self.np )

    def DetachScript( self, clsName ):

        # Remove an instance from the instance dictionary by its class name.
        # Make sure to call ignoreAll() on all instances attached to this
        # object which inherit from DirectObject or else they won't be
        # deleted properly.
        if clsName in self.instances:
            instance = self.instances[clsName]
            if isinstance( instance, DirectObject ):
                instance.ignoreAll()
            del self.instances[clsName]

Note the two new methods, AttachScript() and DetachScript(). We will use these to add and remove additional code to our PandaObject. Code to be added will be its own class which I will refer to from here as a ‘behaviour’.

Behaviours are attached using the AttachScript method which takes a path to a python script as an argument. By using Python’s imp module we can import a module using a string, and it doesn’t have to be found on sys.path either. There is one caveat though – imp.find_module will only take a file name as its first argument, absolute or relative paths will not work. As I want this to handle relative paths I have to build a list of search paths to pass to find_module by joining the directory of the script to each of the sys.paths.

There is another restriction in that the name of the behaviour’s class must be named an uppercase leading match of the file that it lives in. While this goes against my ideas of dictating how the developer approaches their project, I don’t think it’s too restrictive and also matches the Python / Unity paradigm of naming classes in accordance to the files they live in.

Once we have found the class name we create an instance of it with the PandaObject’s node path as an argument. Any behaviour we add is probably going to modify the node path in some way, so we’ll need to pass this through. All instances are then stored in the PandaObject’s instances dictionary – something I’ll probably end up changing as currently it is not possible to attach more than one of the same type (named) of behaviour to a PandaObject.

We can remove instances that are attached to our PandaObject by passing the class name to DetachScript(). I’ve added a call to ignoreAll() for any behaviours that are inherited from DirectObject as they won’t be garbage collected without removing their reference from the messenger first.

Now to create some kind of basic behaviour. The following code will move the node path slowly up and down when started:

import math

from direct.showbase.DirectObject import DirectObject

class NewBehaviour( DirectObject ):

    def __init__( self, np ):

        # Set the node path this behaviour will control then bind Start and
        # Stop events.
        self.np = np
        self.accept( 'StartNewBehaviour', self.Start )
        self.accept( 'StopNewBehaviour', self.Stop )

    def Update( self, task ):

        # This will move the node path up and down like a sine wave.
        self.np.setZ( self.initZ + math.cos( task.time ) )

        # Keep this task running so long as the node path is valid.
        if not self.np.isEmpty():
           return task.cont

    def Start( self ):

        # Get our initial z position then add an update task to be processed
        # every frame.
        self.initZ = self.np.getZ()
        self._task = taskMgr.add( self.Update, 'UpdateTask' )

    def Stop( self ):

        # Remove the update task from the task manager. Remember to set the
        # _task member to None or else the behaviour won't be garbage
        # collected!
        taskMgr.remove( self._task )
        self._task = None

Nothing too special going on here. We store the input node path and bind some events when the class is instantiated, and there’s some basic wrapping of the task manager going on there too.

Now to bring the whole thing together. We start by loading the default box model, parenting it under render and attaching a PandaObject. We then pull that object back out of the scene graph in a different scope and attach the behaviour to it by passing the file path to the script:

import direct.directbase.DirectStart

from pandaObject import PandaObject

def Foo():

    # Load a model, put it in the scene graph and attach a PandaObject hook to
    # it
    box = loader.loadModel( 'box' )
    box.reparentTo( render )
    PandaObject( box )

def Bar():

    # Pull the node path out of the scene graph and attach the new behaviour
    # to it. myNp could be obtained other ways; returned from a collision
    # picking event for example.
    myNp = render.find( '*box*' )
    pObj = PandaObject.Get( myNp )
    pObj.AttachScript( 'newBehaviour.py' )

Foo()
Bar()

# Move the camera so we can see the box, then send the message to start the
# new behaviour.
base.cam.setY( -10 )
messenger.send( 'StartNewBehaviour' )

run()

Running the above code should show a box that floats up and down. To remove the node cleanly we have to make sure we stop the task running first, then we can remove it like normal:

messenger.send( 'StopNewBehaviour' )
myNp = render.find( '*box*' )
PandaObject.Break( myNp )
myNp.removeNode()

So while this seems overly complex in order to get a box to move, you can imagine how neat this works when attached to a script file browser. By binding some drag and drop events we can easily attach the user’s script to any node path in the scene.

2 Comments more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!