Просмотр исходного кода

Merge branch 'next' of https://github.com/blackberry/GamePlay into next

Karim Ahmed 13 лет назад
Родитель
Сommit
12f784e396

+ 3 - 0
CHANGES.md

@@ -1,3 +1,6 @@
+## v1.7.0
+- Adds a lua function "convert(object, className)" that will convert a gameplay userdata object to another class type by changing the metatable. (For example: This lets you convert Control to Button in lua)
+
 ## v1.6.0
 
 - Adds file Stream interface for reading/writing files instead of using fread/fwrite. 

+ 10 - 2
gameplay/src/Gamepad.cpp

@@ -36,9 +36,17 @@ Gamepad::Gamepad(const char* formPath)
 Gamepad::Gamepad(GamepadHandle handle, unsigned int buttonCount, unsigned int joystickCount, unsigned int triggerCount,
                  unsigned int vendorId, unsigned int productId, const char* vendorString, const char* productString)
     : _handle(handle), _buttonCount(buttonCount), _joystickCount(joystickCount), _triggerCount(triggerCount),
-      _vendorId(vendorId), _productId(productId), _vendorString(vendorString), _productString(productString),
-      _form(NULL), _buttons(0)
+      _vendorId(vendorId), _productId(productId), _form(NULL), _buttons(0)
 {
+    if (vendorString)
+    {
+        _vendorString = vendorString;
+    }
+    
+    if (productString)
+    {
+        _productString = productString;
+    }
 }
 
 Gamepad::~Gamepad()

+ 89 - 3
gameplay/src/Joint.cpp

@@ -6,7 +6,7 @@ namespace gameplay
 {
 
 Joint::Joint(const char* id)
-    : Node(id), _jointMatrixDirty(true), _skinCount(0)
+    : Node(id), _jointMatrixDirty(true)
 {
 }
 
@@ -25,7 +25,6 @@ Node* Joint::cloneSingleNode(NodeCloneContext &context) const
     GP_ASSERT(copy);
     context.registerClonedNode(this, copy);
     copy->_bindPose = _bindPose;
-    copy->_skinCount = _skinCount;
     Node::cloneInto(copy, context);
     return copy;
 }
@@ -35,6 +34,27 @@ Node::Type Joint::getType() const
     return Node::JOINT;
 }
 
+Scene* Joint::getScene() const
+{
+    // Overrides Node::getScene() to search the node our skins.
+    for (const SkinReference* itr = &_skin; itr && itr->skin; itr = itr->next)
+    {
+        Model* model = itr->skin ? itr->skin->getModel() : NULL;
+        if (model)
+        {
+            Node* node = model->getNode();
+            if (node)
+            {
+                Scene* scene = node->getScene();
+                if (scene)
+                    return scene;
+            }
+        }
+    }
+
+    return Node::getScene();
+}
+
 void Joint::transformChanged()
 {
     Node::transformChanged();
@@ -47,7 +67,7 @@ void Joint::updateJointMatrix(const Matrix& bindShape, Vector4* matrixPalette)
     // the _jointMatrixDirty optimization since updateJointMatrix() may be
     // called multiple times a frame with different bindShape matrices (and
     // different matrixPallete pointers).
-    if (_skinCount > 1 || _jointMatrixDirty)
+    if (_skin.next || _jointMatrixDirty)
     {
         _jointMatrixDirty = false;
 
@@ -73,4 +93,70 @@ void Joint::setInverseBindPose(const Matrix& m)
     _jointMatrixDirty = true;
 }
 
+void Joint::addSkin(MeshSkin* skin)
+{
+    if (!_skin.skin)
+    {
+        // Store skin in root reference
+        _skin.skin = skin;
+    }
+    else
+    {
+        // Add a new SkinReference to the end of our list
+        SkinReference* ref = &_skin;
+        while (ref->next)
+        {
+            ref = ref->next;
+        }
+        ref->next = new SkinReference();
+        ref->next->skin = skin;
+    }
+}
+
+void Joint::removeSkin(MeshSkin* skin)
+{
+    if (_skin.skin == skin)
+    {
+        // Skin is our root referenced skin
+        _skin.skin = NULL;
+
+        // Shift the next skin reference down to the root
+        if (_skin.next)
+        {
+            SkinReference* tmp = _skin.next;
+            _skin.skin = tmp->skin;
+            _skin.next = tmp->next;
+            tmp->next = NULL; // prevent deletion
+            SAFE_DELETE(tmp);
+        }
+    }
+    else
+    {
+        // Search for the entry referencing this skin
+        SkinReference* ref = &_skin;
+        while (SkinReference* tmp = ref->next)
+        {
+            if (tmp->skin == skin)
+            {
+                // Link this refernce out
+                ref->next = tmp->next;
+                tmp->next = NULL; // prevent deletion
+                SAFE_DELETE(tmp);
+                break;
+            }
+            ref = tmp;
+        }
+    }
+}
+
+Joint::SkinReference::SkinReference()
+    : skin(NULL), next(NULL)
+{
+}
+
+Joint::SkinReference::~SkinReference()
+{
+    SAFE_DELETE(next);
+}
+
 }

+ 26 - 7
gameplay/src/Joint.h

@@ -25,6 +25,11 @@ public:
      */
     Node::Type getType() const;
 
+    /**
+     * @see Node::getScene()
+     */
+    Scene* getScene() const;
+
     /**
      * Returns the inverse bind pose matrix for this joint.
      * 
@@ -85,6 +90,18 @@ protected:
 
 private:
 
+    /**
+     * Internal structure to track mesh skins referencing a joint.
+     */
+    struct SkinReference
+    {
+        MeshSkin* skin;
+        SkinReference* next;
+
+        SkinReference();
+        ~SkinReference();
+    };
+
     /**
      * Constructor.
      */
@@ -95,22 +112,24 @@ private:
      */
     Joint& operator=(const Joint&);
 
-protected:
+    void addSkin(MeshSkin* skin);
+
+    void removeSkin(MeshSkin* skin);
 
     /** 
      * The Matrix representation of the Joint's bind pose.
      */
     Matrix _bindPose;
-    
-    /** 
+
+    /**
      * Flag used to mark if the Joint's matrix is dirty.
      */
     bool _jointMatrixDirty;
-    
-    /** 
-     * The number of MeshSkin's influencing the Joint.
+
+    /**
+     * Linked list of mesh skins that are referenced by this joint.
      */
-    unsigned int _skinCount;
+    SkinReference _skin;
 };
 
 }

+ 1 - 1
gameplay/src/Matrix.h

@@ -163,7 +163,7 @@ public:
      * and a z-coordinate ranging from 0 to 1. To obtain the viewable area (in world space) of a scene,
      * create a BoundingFrustum and pass the combined view and projection matrix to the constructor.
      *
-     * @param fieldOfView The field of view in the y direction (in radians).
+     * @param fieldOfView The field of view in the y direction (in degrees).
      * @param aspectRatio The aspect ratio, defined as view space width divided by height.
      * @param zNearPlane The distance to the near view plane.
      * @param zFarPlane The distance to the far view plane.

+ 2 - 2
gameplay/src/MeshSkin.cpp

@@ -141,7 +141,7 @@ void MeshSkin::setJoint(Joint* joint, unsigned int index)
 
     if (_joints[index])
     {
-        _joints[index]->_skinCount--;
+        _joints[index]->removeSkin(this);
         SAFE_RELEASE(_joints[index]);
     }
 
@@ -150,7 +150,7 @@ void MeshSkin::setJoint(Joint* joint, unsigned int index)
     if (joint)
     {
         joint->addRef();
-        joint->_skinCount++;
+        joint->addSkin(this);
     }
 }
 

+ 1 - 0
gameplay/src/MeshSkin.h

@@ -21,6 +21,7 @@ class MeshSkin : public Transform::Listener
     friend class Model;
     friend class Joint;
     friend class Node;
+    friend class Scene;
 
 public:
 

+ 1 - 0
gameplay/src/Model.h

@@ -20,6 +20,7 @@ class NodeCloneContext;
 class Model : public Ref
 {
     friend class Node;
+    friend class Scene;
     friend class Mesh;
     friend class Bundle;
 

+ 9 - 7
gameplay/src/Node.cpp

@@ -376,13 +376,15 @@ unsigned int Node::findNodes(const char* id, std::vector<Node*>& nodes, bool rec
 
 Scene* Node::getScene() const
 {
-    // Search for a scene in our parents.
-    for (Node* n = const_cast<Node*>(this); n != NULL; n = n->getParent())
+    if (_scene)
+        return _scene;
+
+    // Search our parent for the scene
+    if (_parent)
     {
-        if (n->_scene)
-        {
-            return n->_scene;
-        }
+        Scene* scene = _parent->getScene();
+        if (scene)
+            return scene;
     }
 
     return NULL;
@@ -933,7 +935,6 @@ const BoundingSphere& Node::getBoundingSphere() const
     return _bounds;
 }
 
-
 Node* Node::clone() const
 {
     NodeCloneContext context;
@@ -1002,6 +1003,7 @@ void Node::cloneInto(Node* node, NodeCloneContext &context) const
     }
     node->_world = _world;
     node->_bounds = _bounds;
+    node->_userData = _userData;
     if (_tags)
     {
         node->_tags = new std::map<std::string, std::string>(_tags->begin(), _tags->end());

+ 1 - 1
gameplay/src/Node.h

@@ -223,7 +223,7 @@ public:
      *
      * @return The scene.
      */
-    Scene* getScene() const;
+    virtual Scene* getScene() const;
 
     /**
      * Gets the top level node in this node's parent hierarchy.

+ 263 - 55
gameplay/src/PlatformMacOSX.mm

@@ -15,9 +15,14 @@
 #import <Foundation/Foundation.h>
 
 // These should probably be moved to a platform common file
-#define SONY_USB_VENDOR_ID          0x54c
-#define SONY_USB_PS3_PRODUCT_ID     0x268
-
+#define SONY_USB_VENDOR_ID              0x054c
+#define SONY_USB_PS3_PRODUCT_ID         0x0268
+#define MICROSOFT_VENDOR_ID             0x045e
+#define MICROSOFT_XBOX360_PRODUCT_ID    0x028e
+#define STEELSERIES_VENDOR_ID           0x1038
+#define STEELSERIES_FREE_PRODUCT_ID     0x1412
+#define FRUCTEL_VENDOR_ID               0x25B6
+#define FRUCTEL_GAMETEL_PRODUCT_ID      0x0001
 
 using namespace std;
 using namespace gameplay;
@@ -74,8 +79,6 @@ static void hidDeviceDiscoveredCallback(void *inContext, IOReturn inResult, void
 static void hidDeviceRemovalCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef);
 static void hidDeviceValueAvailableCallback(void *inContext, IOReturn inResult,  void *inSender);
 
-
-
 double getMachTimeInMilliseconds()
 {
     static const double kOneMillion = 1000 * 1000;
@@ -89,7 +92,6 @@ double getMachTimeInMilliseconds()
     return ((double)mach_absolute_time() * (double)s_timebase_info.numer) / (kOneMillion * (double)s_timebase_info.denom);
 }
 
-
 @interface HIDGamepadAxis : NSObject
 {
     IOHIDElementRef e;
@@ -97,6 +99,7 @@ double getMachTimeInMilliseconds()
     CFIndex logMin;
     CFIndex logMax;
 }
+
 + gamepadAxisWithAxisElement:(IOHIDElementRef)element;
 - initWithAxisElement:(IOHIDElementRef)element;
 - (IOHIDElementRef)element;
@@ -110,11 +113,13 @@ double getMachTimeInMilliseconds()
 - (CFIndex)value;
 - (void)setValue:(CFIndex)value;
 @end
+
 @implementation HIDGamepadAxis
 + gamepadAxisWithAxisElement:(IOHIDElementRef)element
 {
     return [[[[self class] alloc] initWithAxisElement:element] autorelease];
 }
+
 - initWithAxisElement:(IOHIDElementRef)element
 {
     if((self = [super init]))
@@ -123,48 +128,59 @@ double getMachTimeInMilliseconds()
     }
     return self;
 }
+
 - (void)dealloc
 {
     CFRelease(e);
     [super dealloc];
 }
+
 - (IOHIDElementRef)element
 {
     return e;
 }
+
 - (IOHIDElementCookie)cookie
 {
     return IOHIDElementGetCookie(e);
 }
+
 - (bool)isHatSwitch {
     return (IOHIDElementGetUsage(e) == kHIDUsage_GD_Hatswitch);
 }
+
 - (uint32_t)usage
 {
     return IOHIDElementGetUsage(e);
 }
+
 - (uint32_t)usagePage
 {
     return IOHIDElementGetUsagePage(e);
 }
+
 - (CFIndex)logicalMinimum
 {
     return IOHIDElementGetLogicalMin(e);    
 }
+
 - (CFIndex)logicalMaximum
 {
     return IOHIDElementGetLogicalMax(e);
 }
+
 - (float)calibratedValue
 {
     float cmax = 2.0f;
     float cmin = 0.0f;
     return ((((v - [self logicalMinimum]) * (cmax - cmin)) / ([self logicalMaximum] - [self logicalMinimum])) + cmin - 1.0f);    
 }
+
 - (CFIndex)value
 {
     return v;
 }
+
 - (void)setValue:(CFIndex)value
 {
     v = value;
@@ -178,6 +194,7 @@ double getMachTimeInMilliseconds()
     bool state;
     int triggerValue;
 }
+
 + gamepadButtonWithButtonElement:(IOHIDElementRef)element;
 - initWithButtonElement:(IOHIDElementRef)element;
 - (void)setTriggerElement:(IOHIDElementRef)element;
@@ -195,11 +212,13 @@ double getMachTimeInMilliseconds()
 - (bool)state;
 - (void)setState:(bool)state;
 @end
+
 @implementation HIDGamepadButton
 + gamepadButtonWithButtonElement:(IOHIDElementRef)element
 {
     return [[[[self class] alloc] initWithButtonElement:element] autorelease];
 }
+
 - initWithButtonElement:(IOHIDElementRef)element
 {
     if((self = [super init]))
@@ -210,12 +229,14 @@ double getMachTimeInMilliseconds()
     }
     return self;
 }
+
 - (void)dealloc
 {
     CFRelease(e);
     if(te != NULL) CFRelease(te);
     [super dealloc];
 }
+
 - (void)setTriggerElement:(IOHIDElementRef)element {
     if(te)
     {
@@ -227,49 +248,61 @@ double getMachTimeInMilliseconds()
         te = (IOHIDElementRef)CFRetain(element);
     }
 }
+
 - (IOHIDElementRef)element
 {
     return e;
 }
+
 - (IOHIDElementCookie)cookie
 {
     return IOHIDElementGetCookie(e);
 }
+
 - (IOHIDElementRef)triggerElement
 {
     return te;
 }
+
 - (IOHIDElementCookie)triggerCookie
 {
     return IOHIDElementGetCookie(te);
 }
+
 - (bool)isTriggerButton
 {
     return (te != NULL);
 }
+
 - (uint32_t)usage
 {
     return IOHIDElementGetUsage(e);
 }
+
 - (uint32_t)usagePage
 {
     return IOHIDElementGetUsagePage(e);
 }
+
 - (void)setStateValue:(int)value {
     triggerValue = value;
 }
+
 - (int)stateValue
 {
     return triggerValue;
 }
+
 - (float)calibratedStateValue
 {
     return (float)triggerValue / 255.0f;
 }
+
 - (bool)state
 {
     return state;
 }
+
 - (void)setState:(bool)s
 {
     state = s;
@@ -280,15 +313,17 @@ double getMachTimeInMilliseconds()
 {
     IOHIDDeviceRef hidDeviceRef;
     IOHIDQueueRef queueRef;
-    NSMutableArray *buttons;
-    NSMutableArray *triggerButtons;
-    NSMutableArray *axes;
+    NSMutableArray* buttons;
+    NSMutableArray* triggerButtons;
+    NSMutableArray* axes;
+    HIDGamepadAxis* hatSwitch;
 }
 @property (assign) IOHIDDeviceRef hidDeviceRef;
 @property (assign) IOHIDQueueRef queueRef;
-@property (retain) NSMutableArray *buttons;
-@property (retain) NSMutableArray *triggerButtons;
-@property (retain) NSMutableArray *axes;
+@property (retain) NSMutableArray* buttons;
+@property (retain) NSMutableArray* triggerButtons;
+@property (retain) NSMutableArray* axes;
+@property (retain) HIDGamepadAxis* hatSwitch;
 
 - initWithDevice:(IOHIDDeviceRef)rawDevice;
 - (IOHIDDeviceRef)rawDevice;
@@ -300,10 +335,11 @@ double getMachTimeInMilliseconds()
 - (bool)startListening;
 - (void)stopListening;
 
-- (NSString *)identifierName;
-- (NSString *)productName;
-- (NSString *)manufacturerName;
-- (NSString *)serialNumber;
+- (NSString*)identifierName;
+- (NSString*)productName;
+- (NSString*)manufacturerName;
+- (NSString*)serialNumber;
+- (int)versionNumber;
 - (int)vendorID;
 - (int)productID;
 
@@ -314,6 +350,7 @@ double getMachTimeInMilliseconds()
 - (HIDGamepadAxis*)axisAtIndex:(NSUInteger)index;
 - (HIDGamepadButton*)buttonAtIndex:(NSUInteger)index;
 - (HIDGamepadButton*)triggerButtonAtIndex:(NSUInteger)index;
+- (HIDGamepadAxis*)getHatSwitch;
 @end
 
 @implementation HIDGamepad
@@ -323,6 +360,7 @@ double getMachTimeInMilliseconds()
 @synthesize buttons;
 @synthesize triggerButtons;
 @synthesize axes;
+@synthesize hatSwitch;
 
 - initWithDevice:(IOHIDDeviceRef)rawDevice
 {
@@ -331,6 +369,7 @@ double getMachTimeInMilliseconds()
         [self setButtons:[NSMutableArray array]];
         [self setTriggerButtons:[NSMutableArray array]];
         [self setAxes:[NSMutableArray array]];
+        hatSwitch = NULL;
 
         CFRetain(rawDevice);
         IOHIDQueueRef queue = IOHIDQueueCreate(CFAllocatorGetDefault(), rawDevice, 10, kIOHIDOptionsTypeNone);
@@ -342,6 +381,7 @@ double getMachTimeInMilliseconds()
     }
     return self;
 }
+
 - (void)dealloc
 {
     [self stopListening];
@@ -354,12 +394,19 @@ double getMachTimeInMilliseconds()
     [self setButtons:NULL];
     [self setTriggerButtons:NULL];
     [self setAxes:NULL];
+    if (hatSwitch != NULL)
+    {
+        [hatSwitch dealloc];
+    }
+    
     [super dealloc];
 }
+
 - (IOHIDDeviceRef)rawDevice
 {
     return [self hidDeviceRef];
 }
+
 - (NSNumber*)locationID
 {
     return (NSNumber*)IOHIDDeviceGetProperty([self rawDevice], CFSTR(kIOHIDLocationIDKey));
@@ -367,6 +414,9 @@ double getMachTimeInMilliseconds()
 
 - (void)initializeGamepadElements
 {
+    uint32_t vendorID = [self vendorID];
+    uint32_t productID = [self productID];
+    
     CFArrayRef elements = IOHIDDeviceCopyMatchingElements([self rawDevice], NULL, kIOHIDOptionsTypeNone);
     for(int i = 0; i < CFArrayGetCount(elements); i++)
     {
@@ -382,15 +432,32 @@ double getMachTimeInMilliseconds()
             {
                 case kHIDUsage_GD_X:
                 case kHIDUsage_GD_Y:
-                case kHIDUsage_GD_Z:
                 case kHIDUsage_GD_Rx:
                 case kHIDUsage_GD_Ry:
+                case kHIDUsage_GD_Z:
                 case kHIDUsage_GD_Rz:
                 {
-                    HIDGamepadAxis *axis = [HIDGamepadAxis gamepadAxisWithAxisElement:hidElement];
-                    [[self axes] addObject:axis];
-                }
+                    if (vendorID == MICROSOFT_VENDOR_ID &&
+                        productID == MICROSOFT_XBOX360_PRODUCT_ID &&
+                        (pageUsage == kHIDUsage_GD_Z || pageUsage == kHIDUsage_GD_Rz))
+                    {
+                        HIDGamepadButton* triggerButton = [HIDGamepadButton gamepadButtonWithButtonElement:hidElement];
+                        [triggerButton setTriggerElement:hidElement];
+                        [[self triggerButtons] addObject:triggerButton];
+                    }
+                    else
+                    {
+                        HIDGamepadAxis* axis = [HIDGamepadAxis gamepadAxisWithAxisElement:hidElement];
+                        [[self axes] addObject:axis];
+                    }
                     break;
+                }
+                case kHIDUsage_GD_Hatswitch:
+                {
+                    HIDGamepadAxis* hat = [[HIDGamepadAxis alloc] initWithAxisElement:hidElement];
+                    [hat setValue: -1];
+                    hatSwitch = hat;
+                }
                 default:
                     // Ignore the pointers
                     // Note: Some of the pointers are for the 6-axis accelerometer in a PS3 controller
@@ -407,8 +474,6 @@ double getMachTimeInMilliseconds()
     }
     // Go back and get proprietary information (e.g. triggers) and associate with appropriate values
     // Example for other trigger buttons
-    uint32_t vendorID = [self vendorID];
-    uint32_t productID = [self productID];
     for(int i = 0; i < CFArrayGetCount(elements); i++)
     {
         IOHIDElementRef hidElement = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
@@ -416,30 +481,24 @@ double getMachTimeInMilliseconds()
         IOHIDElementCookie cookie = IOHIDElementGetCookie(hidElement);
         
         // Gamepad specific code
-        // Not sure if there is a better way to associate buttons and misc hid elements :/
         if(vendorID == SONY_USB_VENDOR_ID && productID == SONY_USB_PS3_PRODUCT_ID)
         {
             if((unsigned long)cookie == 39)
             {
-                //[self buttonAtIndex:8]; 
                 HIDGamepadButton *leftTriggerButton = [self buttonWithCookie:(IOHIDElementCookie)9];
                 if(leftTriggerButton)
                 {
                     [leftTriggerButton setTriggerElement:hidElement];
                     [[self triggerButtons] addObject:leftTriggerButton];
-                    // I would have thought this would work but it seems to mess things up, even after re-mapping the buttons.
-                    //[[self buttons] removeObject:leftTriggerButton];
                 }
             }
             if((unsigned long)cookie == 40)
             {
-                //[self buttonAtIndex:9];
                 HIDGamepadButton *rightTriggerButton = [self buttonWithCookie:(IOHIDElementCookie)10];
                 if(rightTriggerButton)
                 {
                     [rightTriggerButton setTriggerElement:hidElement];
                     [[self triggerButtons] addObject:rightTriggerButton];
-                    //[[self buttons] removeObject:rightTriggerButton];
                 }
             }
         }
@@ -476,6 +535,7 @@ double getMachTimeInMilliseconds()
     
     return true;
 }
+
 - (void)stopListening
 {
     IOHIDQueueUnscheduleFromRunLoop([self queueRef], CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
@@ -485,9 +545,9 @@ double getMachTimeInMilliseconds()
     IOHIDDeviceClose([self hidDeviceRef], kIOHIDOptionsTypeNone);
 }
 
-- (NSString *)identifierName
+- (NSString*)identifierName
 {
-    NSString *idName = NULL;
+    NSString* idName = NULL;
     if(idName == NULL) idName = [self productName];
     if(idName == NULL) idName = [self manufacturerName];
     if(idName == NULL) idName = [self serialNumber];
@@ -495,51 +555,66 @@ double getMachTimeInMilliseconds()
     return idName;
 }
 
-- (NSString *)productName {
+- (NSString*)productName
+{
     CFStringRef productName = (CFStringRef)IOHIDDeviceGetProperty([self rawDevice], CFSTR(kIOHIDProductKey));
-    if(productName == NULL || CFGetTypeID(productName) != CFStringGetTypeID()) {
+    if(productName == NULL || CFGetTypeID(productName) != CFStringGetTypeID())
+    {
         return NULL;
     }
     return (NSString*)productName;
 }
-- (NSString *)manufacturerName {
+
+- (NSString*)manufacturerName
+{
     CFStringRef manufacturerName = (CFStringRef)IOHIDDeviceGetProperty([self rawDevice], CFSTR(kIOHIDManufacturerKey));
-    if(manufacturerName == NULL || CFGetTypeID(manufacturerName) != CFStringGetTypeID()) {
+    if(manufacturerName == NULL || CFGetTypeID(manufacturerName) != CFStringGetTypeID())
+    {
         return NULL;
     }
     return (NSString*)manufacturerName;
 }
-- (NSString *)serialNumber {
+
+- (NSString*)serialNumber
+{
     CFStringRef serialNumber = (CFStringRef)IOHIDDeviceGetProperty([self rawDevice], CFSTR(kIOHIDSerialNumberKey));
-    if(serialNumber == NULL || CFGetTypeID(serialNumber) != CFStringGetTypeID()) {
+    if(serialNumber == NULL || CFGetTypeID(serialNumber) != CFStringGetTypeID())
+    {
         return NULL;
     }
     return (NSString*)serialNumber;
 }
 
+- (int)versionNumber
+{
+    return IOHIDDeviceGetIntProperty([self rawDevice], CFSTR(kIOHIDVersionNumberKey));
+}
 
 - (int)vendorID
 {
     return IOHIDDeviceGetIntProperty([self rawDevice], CFSTR(kIOHIDVendorIDKey));
 }
+
 - (int)productID
 {
     return IOHIDDeviceGetIntProperty([self rawDevice], CFSTR(kIOHIDProductIDKey));
 }
 
-
 - (NSUInteger)numberOfAxes
 {
     return [[self axes] count];
 }
+
 - (NSUInteger)numberOfSticks
 {
     return ([[self axes] count] / 2);
 }
+
 - (NSUInteger)numberOfButtons
 {
     return [[self buttons] count];
 }
+
 - (NSUInteger)numberOfTriggerButtons
 {
     return [[self triggerButtons] count];
@@ -564,6 +639,7 @@ double getMachTimeInMilliseconds()
     }
     return a;
 }
+
 - (HIDGamepadButton*)buttonAtIndex:(NSUInteger)index
 {
     HIDGamepadButton *b = NULL;
@@ -573,6 +649,16 @@ double getMachTimeInMilliseconds()
     }
     return b;
 }
+
+- (HIDGamepadAxis*)getHatSwitch
+{
+    if (hatSwitch != NULL)
+    {
+        return hatSwitch;
+    }
+    return NULL;
+}
+
 - (NSArray*)watchedElements
 {
     NSMutableArray *r = [NSMutableArray array];
@@ -588,6 +674,10 @@ double getMachTimeInMilliseconds()
     {
         [r addObject:(id)[t triggerElement]];
     }
+    if (hatSwitch)
+    {
+        [r addObject:(id)[hatSwitch element]];
+    }
     return [NSArray arrayWithArray:r];
 }
 
@@ -625,14 +715,14 @@ double getMachTimeInMilliseconds()
         }
     }
 
+    if (hatSwitch && [hatSwitch cookie] == cookie)
+    {
+        [hatSwitch setValue:integerValue];
+    }
 }
-
-
 @end
 
 
-
-
 @interface View : NSOpenGLView <NSWindowDelegate>
 {
 @public
@@ -1477,9 +1567,21 @@ Platform::Platform(Game* game)
     IOHIDManagerRegisterDeviceMatchingCallback(__hidManagerRef, hidDeviceDiscoveredCallback, NULL);
     IOHIDManagerRegisterDeviceRemovalCallback(__hidManagerRef, hidDeviceRemovalCallback, NULL);
     
-    CFDictionaryRef matchingCFDictRef = IOHIDCreateDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
-    if (matchingCFDictRef) IOHIDManagerSetDeviceMatching(__hidManagerRef, matchingCFDictRef);
-    CFRelease(matchingCFDictRef);
+    CFMutableArrayRef matching = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+    if (matching)
+    {
+        CFDictionaryRef matchingJoystick = IOHIDCreateDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
+        CFDictionaryRef matchingGamepad = IOHIDCreateDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
+        
+        if (matchingJoystick && matchingGamepad)
+        {
+            CFArrayAppendValue(matching, matchingJoystick);
+            CFRelease(matchingJoystick);
+            CFArrayAppendValue(matching, matchingGamepad);
+            CFRelease(matchingGamepad);
+            IOHIDManagerSetDeviceMatchingMultiple(__hidManagerRef, matching);
+        }
+    }
     
     IOHIDManagerScheduleWithRunLoop(__hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
     IOReturn kr = IOHIDManagerOpen(__hidManagerRef, kIOHIDOptionsTypeNone);
@@ -1842,7 +1944,6 @@ bool Platform::isGestureRegistered(Gesture::GestureEvent evt)
 void Platform::pollGamepadState(Gamepad* gamepad)
 {
     HIDGamepad* gp = gamepadForGameHandle(gamepad->_handle);
-    
     if (gp)
     {
         // Haven't figured out how to have the triggers not also show up in the buttons array.
@@ -1867,15 +1968,87 @@ void Platform::pollGamepadState(Gamepad* gamepad)
             Gamepad::BUTTON_MENU3   // 0x10000
         };
         
+        static const int XBox360Mapping[20] = {
+            -1, -1, -1, -1, -1,
+            Gamepad::BUTTON_UP,
+            Gamepad::BUTTON_DOWN,
+            Gamepad::BUTTON_LEFT,
+            Gamepad::BUTTON_RIGHT,
+            Gamepad::BUTTON_MENU2,
+            Gamepad::BUTTON_MENU1,
+            Gamepad::BUTTON_L3,
+            Gamepad::BUTTON_R3,
+            Gamepad::BUTTON_L1,
+            Gamepad::BUTTON_R1,
+            Gamepad::BUTTON_MENU3,
+            Gamepad::BUTTON_A,
+            Gamepad::BUTTON_B,
+            Gamepad::BUTTON_X,
+            Gamepad::BUTTON_Y
+        };
+        
+        static const int SteelSeriesFreeMapping[13] = {
+            Gamepad::BUTTON_A,
+            Gamepad::BUTTON_B,
+            -1,
+            Gamepad::BUTTON_X,
+            Gamepad::BUTTON_Y,
+            -1,
+            Gamepad::BUTTON_L1,
+            Gamepad::BUTTON_R1,
+            -1, -1, -1,
+            Gamepad::BUTTON_MENU2,
+            Gamepad::BUTTON_MENU1
+        };
+        
+        static const int GametelMapping103[12] = {
+            Gamepad::BUTTON_B,
+            Gamepad::BUTTON_X,
+            Gamepad::BUTTON_Y,
+            Gamepad::BUTTON_A,
+            Gamepad::BUTTON_L1,
+            Gamepad::BUTTON_R1,
+            Gamepad::BUTTON_MENU1,
+            Gamepad::BUTTON_MENU2,
+            Gamepad::BUTTON_RIGHT,
+            Gamepad::BUTTON_LEFT,
+            Gamepad::BUTTON_DOWN,
+            Gamepad::BUTTON_UP
+        };
+        
         const int* mapping = NULL;
+        float axisDeadZone = 0.0f;
         if (gamepad->_vendorId == SONY_USB_VENDOR_ID &&
             gamepad->_productId == SONY_USB_PS3_PRODUCT_ID)
         {
             mapping = PS3Mapping;
+            axisDeadZone = 0.07f;
+        }
+        else if (gamepad->_vendorId == MICROSOFT_VENDOR_ID &&
+                 gamepad->_productId == MICROSOFT_XBOX360_PRODUCT_ID)
+        {
+            mapping = XBox360Mapping;
+            axisDeadZone = 0.2f;
+        }
+        else if (gamepad->_vendorId == STEELSERIES_VENDOR_ID &&
+                 gamepad->_productId == STEELSERIES_FREE_PRODUCT_ID)
+        {
+            mapping = SteelSeriesFreeMapping;
+            axisDeadZone = 0.005f;
+        }
+        else if (gamepad->_vendorId == FRUCTEL_VENDOR_ID &&
+                 gamepad->_productId == FRUCTEL_GAMETEL_PRODUCT_ID)
+        {
+            int ver = [gp versionNumber];
+            int major = ver >> 8;
+            int minor = ver & 0x00ff;
+            if (major >= 1 && minor > 1)
+            {
+                mapping = GametelMapping103;
+            }
         }
         
         gamepad->_buttons = 0;
-        
         for (int i = 0; i < [gp numberOfButtons]; ++i)
         {
             HIDGamepadButton* b = [gp buttonAtIndex: i];
@@ -1893,15 +2066,51 @@ void Platform::pollGamepadState(Gamepad* gamepad)
                 }
             }
         }
-
+        
+        HIDGamepadAxis* hatSwitch = [gp getHatSwitch];
+        if (hatSwitch != NULL)
+        {
+            CFIndex v = [hatSwitch value];
+            switch (v)
+            {
+                case -1:
+                    break;
+                case 0:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_UP);
+                    break;
+                case 1:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_UP) | (1 << Gamepad::BUTTON_RIGHT);
+                    break;
+                case 2:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_RIGHT);
+                    break;
+                case 3:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_RIGHT) | (1 << Gamepad::BUTTON_DOWN);
+                    break;
+                case 4:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_DOWN);
+                    break;
+                case 5:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_DOWN) | (1 << Gamepad::BUTTON_LEFT);
+                    break;
+                case 6:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_LEFT);
+                    break;
+                case 7:
+                    gamepad->_buttons |= (1 << Gamepad::BUTTON_LEFT) | (1 << Gamepad::BUTTON_UP);
+                    break;
+            }
+        }
+        
         for (unsigned int i = 0; i < [gp numberOfSticks]; ++i)
         {
             float rawX = [[gp axisAtIndex: i*2] calibratedValue];
             float rawY = -[[gp axisAtIndex: i*2 + 1] calibratedValue];
-            if (std::fabs(rawX) <= 0.07f)
+            if (std::fabs(rawX) <= axisDeadZone)
                 rawX = 0;
-            if (std::fabs(rawY) <= 0.07f)
+            if (std::fabs(rawY) <= axisDeadZone)
                 rawY = 0;
+            
             gamepad->_joysticks[i].x = rawX;
             gamepad->_joysticks[i].y = rawY;
         }
@@ -2014,21 +2223,20 @@ int IOHIDDeviceGetIntProperty(IOHIDDeviceRef deviceRef, CFStringRef key)
     return value;
 }
 
-static void hidDeviceDiscoveredCallback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) 
+static void hidDeviceDiscoveredCallback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device)
 {
-    CFNumberRef locID = (CFNumberRef)IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey));
+    CFNumberRef locID = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDLocationIDKey));
     if(locID)
     {
-        HIDGamepad* gamepad = [[HIDGamepad alloc] initWithDevice:inIOHIDDeviceRef];
+        HIDGamepad* gamepad = [[HIDGamepad alloc] initWithDevice: device];
         [__gamepads addObject:gamepad];
     }
-    
 }
 
-static void hidDeviceRemovalCallback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) 
+static void hidDeviceRemovalCallback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device)
 {
     int removeIndex = -1;
-    NSNumber *locID = (NSNumber*)IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey));
+    NSNumber *locID = (NSNumber*)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDLocationIDKey));
     if(locID)
     {
         for(int i = 0; i < [__gamepads count]; i++)

+ 49 - 20
gameplay/src/RenderState.cpp

@@ -208,6 +208,8 @@ void RenderState::setNodeBinding(Node* node)
 
 void RenderState::applyAutoBinding(const char* uniformName, const char* autoBinding)
 {
+    GP_ASSERT(_nodeBinding);
+
     MaterialParameter* param = getParameter(uniformName);
     GP_ASSERT(param);
 
@@ -264,31 +266,19 @@ void RenderState::applyAutoBinding(const char* uniformName, const char* autoBind
     }
     else if (strcmp(autoBinding, "MATRIX_PALETTE") == 0)
     {
-        Model* model = _nodeBinding->getModel();
-        MeshSkin* skin = model ? model->getSkin() : NULL;
-        if (skin)
-        {
-            GP_ASSERT(param);
-            param->bindValue(skin, &MeshSkin::getMatrixPalette, &MeshSkin::getMatrixPaletteSize);
-        }
+        param->bindValue(this, &RenderState::autoBindingGetMatrixPalette, &RenderState::autoBindingGetMatrixPaletteSize);
     }
     else if (strcmp(autoBinding, "SCENE_AMBIENT_COLOR") == 0)
     {
-        Scene* scene = _nodeBinding->getScene();
-        if (scene)
-            param->bindValue(scene, &Scene::getAmbientColor);
+        param->bindValue(this, &RenderState::autoBindingGetAmbientColor);
     }
     else if (strcmp(autoBinding, "SCENE_LIGHT_COLOR") == 0)
     {
-        Scene* scene = _nodeBinding->getScene();
-        if (scene)
-            param->bindValue(scene, &Scene::getLightColor);
+        param->bindValue(this, &RenderState::autoBindingGetLightColor);
     }
     else if (strcmp(autoBinding, "SCENE_LIGHT_DIRECTION") == 0)
     {
-        Scene* scene = _nodeBinding->getScene();
-        if (scene)
-            param->bindValue(scene, &Scene::getLightDirection);
+        param->bindValue(this, &RenderState::autoBindingGetLightDirection);
     }
     else
     {
@@ -296,6 +286,39 @@ void RenderState::applyAutoBinding(const char* uniformName, const char* autoBind
     }
 }
 
+const Vector4* RenderState::autoBindingGetMatrixPalette() const
+{
+    Model* model = _nodeBinding ? _nodeBinding->getModel() : NULL;
+    MeshSkin* skin = model ? model->getSkin() : NULL;
+    return skin ? skin->getMatrixPalette() : NULL;
+}
+
+const Vector3& RenderState::autoBindingGetAmbientColor() const
+{
+    Scene* scene = _nodeBinding ? _nodeBinding->getScene() : NULL;
+    return scene ? scene->getAmbientColor() : Vector3::zero();
+}
+
+const Vector3& RenderState::autoBindingGetLightColor() const
+{
+    Scene* scene = _nodeBinding ? _nodeBinding->getScene() : NULL;
+    return scene ? scene->getLightColor() : Vector3::one();
+}
+
+const Vector3& RenderState::autoBindingGetLightDirection() const
+{
+    static Vector3 down(0, -1, 0);
+    Scene* scene = _nodeBinding ? _nodeBinding->getScene() : NULL;
+    return scene ? scene->getLightDirection() : down;
+}
+
+unsigned int RenderState::autoBindingGetMatrixPaletteSize() const
+{
+    Model* model = _nodeBinding ? _nodeBinding->getModel() : NULL;
+    MeshSkin* skin = model ? model->getSkin() : NULL;
+    return skin ? skin->getMatrixPaletteSize() : 0;
+}
+
 void RenderState::bind(Pass* pass)
 {
     GP_ASSERT(pass);
@@ -359,6 +382,7 @@ void RenderState::cloneInto(RenderState* renderState, NodeCloneContext& context)
 {
     GP_ASSERT(renderState);
 
+    // Clone parameters
     for (std::map<std::string, std::string>::const_iterator it = _autoBindings.begin(); it != _autoBindings.end(); ++it)
     {
         renderState->setParameterAutoBinding(it->first.c_str(), it->second.c_str());
@@ -373,13 +397,18 @@ void RenderState::cloneInto(RenderState* renderState, NodeCloneContext& context)
 
         renderState->_parameters.push_back(paramCopy);
     }
-    renderState->_parent = _parent;
+
+    // Clone our state block
     if (_state)
     {
-        renderState->setStateBlock(_state);
+        // StateBlock contains only simple primitive data, so use the default assignment
+        // operator to do a memberwise copy.
+        *renderState->getStateBlock() = *_state;
     }
-
-    // Note that _nodeBinding is not set here, it should be set by the caller.
+    
+    // Notes:
+    // 1. _nodeBinding should not be set here, it should be set by the caller.
+    // 2. _parent should not be set here, since it's set in the constructor of Technique and Pass.
 }
 
 RenderState::StateBlock::StateBlock()

+ 27 - 0
gameplay/src/RenderState.h

@@ -2,6 +2,8 @@
 #define RENDERSTATE_H_
 
 #include "Ref.h"
+#include "Vector3.h"
+#include "Vector4.h"
 
 namespace gameplay
 {
@@ -461,6 +463,31 @@ private:
      */
     RenderState& operator=(const RenderState&);
 
+    /**
+     * Internal auto binding handler.
+     */
+    const Vector3& autoBindingGetAmbientColor() const;
+
+    /**
+     * Internal auto binding handler.
+     */
+    const Vector3& autoBindingGetLightColor() const;
+
+    /**
+     * Internal auto binding handler.
+     */
+    const Vector3& autoBindingGetLightDirection() const;
+
+    /**
+     * Internal auto binding handler.
+     */
+    const Vector4* autoBindingGetMatrixPalette() const;
+
+    /**
+     * Internal auto binding handler.
+     */
+    unsigned int autoBindingGetMatrixPaletteSize() const;
+
 protected:
 
     /**

+ 24 - 0
gameplay/src/Scene.cpp

@@ -136,6 +136,30 @@ unsigned int Scene::findNodes(const char* id, std::vector<Node*>& nodes, bool re
     return count;
 }
 
+void Scene::visitNode(Node* node, const char* visitMethod)
+{
+    ScriptController* sc = Game::getInstance()->getScriptController();
+
+    // Invoke the visit method for this node.
+    if (!sc->executeFunction<bool>(visitMethod, "<Node>", node))
+        return;
+
+    // If this node has a model with a mesh skin, visit the joint hierarchy within it
+    // since we don't add joint hierarcies directly to the scene. If joints are never
+    // visited, it's possible that nodes embedded within the joint hierarchy that contain
+    // models will never get visited (and therefore never get drawn).
+    if (node->_model && node->_model->_skin && node->_model->_skin->_rootNode)
+    {
+        visitNode(node->_model->_skin->_rootNode, visitMethod);
+    }
+
+    // Recurse for all children.
+    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
+    {
+        visitNode(child, visitMethod);
+    }
+}
+
 Node* Scene::addNode(const char* id)
 {
     Node* node = Node::create(id);

+ 17 - 15
gameplay/src/Scene.h

@@ -333,7 +333,7 @@ private:
     /**
      * Visits the given node and all of its children recursively.
      */
-    inline void visitNode(Node* node, const char* visitMethod);
+    void visitNode(Node* node, const char* visitMethod);
 
     std::string _id;
     Camera* _activeCamera;
@@ -356,7 +356,6 @@ void Scene::visit(T* instance, bool (T::*visitMethod)(Node*))
     }
 }
 
-
 template <class T, class C>
 void Scene::visit(T* instance, bool (T::*visitMethod)(Node*,C), C cookie)
 {
@@ -381,6 +380,15 @@ void Scene::visitNode(Node* node, T* instance, bool (T::*visitMethod)(Node*))
     if (!(instance->*visitMethod)(node))
         return;
 
+    // If this node has a model with a mesh skin, visit the joint hierarchy within it
+    // since we don't add joint hierarcies directly to the scene. If joints are never
+    // visited, it's possible that nodes embedded within the joint hierarchy that contain
+    // models will never get visited (and therefore never get drawn).
+    if (node->_model && node->_model->_skin && node->_model->_skin->_rootNode)
+    {
+        visitNode(node->_model->_skin->_rootNode, instance, visitMethod);
+    }
+
     // Recurse for all children.
     for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
     {
@@ -395,25 +403,19 @@ void Scene::visitNode(Node* node, T* instance, bool (T::*visitMethod)(Node*,C),
     if (!(instance->*visitMethod)(node, cookie))
         return;
 
-    // Recurse for all children.
-    for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
+    // If this node has a model with a mesh skin, visit the joint hierarchy within it
+    // since we don't add joint hierarcies directly to the scene. If joints are never
+    // visited, it's possible that nodes embedded within the joint hierarchy that contain
+    // models will never get visited (and therefore never get drawn).
+    if (node->_model && node->_model->_skin && node->_model->_skin->_rootNode)
     {
-        visitNode(child, instance, visitMethod, cookie);
+        visitNode(node->_model->_skin->_rootNode, instance, visitMethod, cookie);
     }
-}
-
-inline void Scene::visitNode(Node* node, const char* visitMethod)
-{
-    ScriptController* sc = Game::getInstance()->getScriptController();
-
-    // Invoke the visit method for this node.
-    if (!sc->executeFunction<bool>(visitMethod, "<Node>", node))
-        return;
 
     // Recurse for all children.
     for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
     {
-        visitNode(child, visitMethod);
+        visitNode(child, instance, visitMethod, cookie);
     }
 }
 

+ 162 - 26
gameplay/src/ScriptController.cpp

@@ -41,6 +41,92 @@
     \
     return arr
 
+#define PUSH_NESTED_VARIABLE(name, defaultValue) \
+    int top = lua_gettop(_lua); \
+    if (!getNestedVariable(_lua, (name))) \
+    { \
+        lua_settop(_lua, top); \
+        return (defaultValue); \
+    }
+
+#define POP_NESTED_VARIABLE() \
+    lua_settop(_lua, top)
+
+/**
+ * Pushes onto the stack, the value of the global 'name' or the nested table value if 'name' is a '.' separated 
+ * list of tables of the form "A.B.C.D", where A, B and C are tables and D is a variable name in the table C.
+ * 
+ * If 'name' does not contain any '.' then it is assumed to be the name of a global variable.
+ * 
+ * This function will not restore the stack if there is an error.
+ * 
+ * @param lua  The Lua state.
+ * @param name The name of a global variable or a '.' separated list of nested tables ending with a variable name.
+ *             The name value may be in the format "A.B.C.D" where A is a table and B, C are child tables.
+ *             D is any type, which will be accessed by the calling function.
+ * 
+ * @return True if the tables were pushed on the stack or the global variable was pushed. Returns false on error.
+ */
+static bool getNestedVariable(lua_State* lua, const char* name)
+{
+    if (strchr(name, '.') == NULL)
+    {
+        lua_getglobal(lua, name);
+        return true;
+    }
+    static std::string str;
+    // Copy the input string to a std::string so we can modify it because 
+    // some of the Lua functions require NULL terminated c-strings.
+    str.assign(name);
+
+    // Find the first table, which will be a global variable.
+    char* start = const_cast<char*>(str.c_str());
+    char* end = strchr(start, '.');
+    if (end == NULL)
+    {
+        return false;
+    }
+    ++end;
+    *(end - 1) = '\0';
+    lua_getglobal(lua, start);
+    *(end - 1) = '.';
+    if (!lua_istable(lua, -1))
+    {
+        return false;
+    }
+    // Push the nested tables
+    for (;;)
+    {
+        start = end;
+        end = strchr(start, '.');
+        if (end == '\0' || end == NULL)
+        {
+            // push the last variable
+            lua_pushstring(lua, start);
+            lua_gettable(lua, -2);
+            return true;
+        }
+        else
+        {
+            // Push the next table
+            *end = '\0';
+            lua_pushstring(lua, start);
+            *end = '.';
+            lua_gettable(lua, -2);
+            if (!lua_istable(lua, -1))
+            {
+                return false;
+            }
+            ++end;
+            if (*end == '.')
+            {
+                return false;
+            }
+        }
+    }
+    return false;
+}
+
 namespace gameplay
 {
 
@@ -377,97 +463,97 @@ std::string ScriptController::loadUrl(const char* url)
 
 bool ScriptController::getBool(const char* name, bool defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     bool b = lua_isboolean(_lua, -1) ? ScriptUtil::luaCheckBool(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return b;
 }
 
 char ScriptController::getChar(const char* name, char defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     char c = lua_isnumber(_lua, -1) ?  (char)luaL_checkint(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return c;
 }
 
 short ScriptController::getShort(const char* name, short defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     short n = lua_isnumber(_lua, -1) ? (short)luaL_checkint(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 int ScriptController::getInt(const char* name, int defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     int n = lua_isnumber(_lua, -1) ? luaL_checkint(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 long ScriptController::getLong(const char* name, long defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     long n = lua_isnumber(_lua, -1) ? luaL_checklong(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 unsigned char ScriptController::getUnsignedChar(const char* name, unsigned char defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     unsigned char c = lua_isnumber(_lua, -1) ? (unsigned char)luaL_checkunsigned(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return c;
 }
 
 unsigned short ScriptController::getUnsignedShort(const char* name, unsigned short defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     unsigned short n = lua_isnumber(_lua, -1) ? (unsigned short)luaL_checkunsigned(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 unsigned int ScriptController::getUnsignedInt(const char* name, unsigned int defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     unsigned int n = lua_isnumber(_lua, -1) ? (unsigned int)luaL_checkunsigned(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 unsigned long ScriptController::getUnsignedLong(const char* name, unsigned long defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     unsigned long n = lua_isnumber(_lua, -1) ? (unsigned long)luaL_checkunsigned(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 float ScriptController::getFloat(const char* name, float defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     float f = lua_isnumber(_lua, -1) ? (float)luaL_checknumber(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return f;
 }
 
 double ScriptController::getDouble(const char* name, double defaultValue)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, defaultValue);
     double n = lua_isnumber(_lua, -1) ? (double)luaL_checknumber(_lua, -1) : defaultValue;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return n;
 }
 
 const char* ScriptController::getString(const char* name)
 {
-    lua_getglobal(_lua, name);
+    PUSH_NESTED_VARIABLE(name, NULL);
     const char* s = lua_isstring(_lua, -1) ? luaL_checkstring(_lua, -1) : NULL;
-    lua_pop(_lua, 1);
+    POP_NESTED_VARIABLE();
     return s;
 }
 
@@ -606,6 +692,7 @@ void ScriptController::initialize()
 
 #ifndef NO_LUA_BINDINGS
     lua_RegisterAllBindings();
+    ScriptUtil::registerFunction("convert", ScriptController::convert);
 #endif
 
     // Create our own print() function that uses gameplay::print.
@@ -723,10 +810,14 @@ void ScriptController::executeFunctionHelper(int resultCount, const char* func,
         return;
     }
 
-    const char* sig = args;
+    if (!getNestedVariable(_lua, func))
+    {
+        GP_WARN("Failed to call function '%s'", func);
+        return;
+    }
 
+    const char* sig = args;
     int argumentCount = 0;
-    lua_getglobal(_lua, func);
 
     // Push the arguments to the Lua stack if there are any.
     if (sig)
@@ -864,31 +955,74 @@ ScriptController::ScriptCallback ScriptController::toCallback(const char* name)
         return ScriptController::INVALID_CALLBACK;
 }
 
+int ScriptController::convert(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 2:
+        {
+            if (lua_type(state, 1) == LUA_TUSERDATA && lua_type(state, 2) == LUA_TSTRING )
+            {
+                // Get parameter 2
+                const char* param2 = ScriptUtil::getString(2, false);
+                if (param2 != NULL)
+                {
+                    luaL_getmetatable(state, param2);
+                    lua_setmetatable(state, -3);
+                }
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_convert - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 // Helper macros.
 #define SCRIPT_EXECUTE_FUNCTION_NO_PARAM(type, checkfunc) \
+    int top = lua_gettop(_lua); \
     executeFunctionHelper(1, func, NULL, NULL); \
     type value = (type)checkfunc(_lua, -1); \
     lua_pop(_lua, -1); \
+    lua_settop(_lua, top); \
     return value;
 
 #define SCRIPT_EXECUTE_FUNCTION_PARAM(type, checkfunc) \
+    int top = lua_gettop(_lua); \
     va_list list; \
     va_start(list, args); \
     executeFunctionHelper(1, func, args, &list); \
     type value = (type)checkfunc(_lua, -1); \
     lua_pop(_lua, -1); \
     va_end(list); \
+    lua_settop(_lua, top); \
     return value;
 
 #define SCRIPT_EXECUTE_FUNCTION_PARAM_LIST(type, checkfunc) \
+    int top = lua_gettop(_lua); \
     executeFunctionHelper(1, func, args, list); \
     type value = (type)checkfunc(_lua, -1); \
     lua_pop(_lua, -1); \
+    lua_settop(_lua, top); \
     return value;
 
 template<> void ScriptController::executeFunction<void>(const char* func)
 {
+    int top = lua_gettop(_lua);
     executeFunctionHelper(0, func, NULL, NULL);
+    lua_settop(_lua, top);
 }
 
 template<> bool ScriptController::executeFunction<bool>(const char* func)
@@ -954,10 +1088,12 @@ template<> std::string ScriptController::executeFunction<std::string>(const char
 /** Template specialization. */
 template<> void ScriptController::executeFunction<void>(const char* func, const char* args, ...)
 {
+    int top = lua_gettop(_lua);
     va_list list;
     va_start(list, args);
     executeFunctionHelper(0, func, args, &list);
     va_end(list);
+    lua_settop(_lua, top);
 }
 
 /** Template specialization. */

+ 26 - 0
gameplay/src/ScriptController.h

@@ -884,6 +884,32 @@ private:
      */
     static ScriptController::ScriptCallback toCallback(const char* name);
 
+    /**
+     * Converts a Gameplay userdata value to the type with the given class name.
+     * This function will change the metatable of the userdata value to the metatable that matches the given string.
+     * 
+     * Example:
+     * <code>
+     * local launchButton = form:getControl("launch")
+     * convert(launchButton, "Button")
+     * print("Button text: " .. launchButton:getText())
+     * </code>
+     * 
+     * <code>
+     * -- The signature of the lua function:
+     * -- param: object    A userdata object that represents a Gameplay object.
+     * -- param: className The name of the class to convert the object to. (Examples: "Button", "PhysicsRigidBody")
+     * function convert(object, className)
+     * </code>
+     * 
+     * @param state The Lua state.
+     * 
+     * @return The number of values being returned by this function.
+     * 
+     * @script{ignore}
+     */
+    static int convert(lua_State* state);
+
     // Friend functions (used by Lua script bindings).
     friend void ScriptUtil::registerLibrary(const char* name, const luaL_Reg* functions);
     friend void ScriptUtil::registerConstantBool(const std::string& name, bool value, const std::vector<std::string>& scopePath);