Pārlūkot izejas kodu

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

Adam Blake 13 gadi atpakaļ
vecāks
revīzija
c6e5ddb00b

BIN
gameplay-template/icon.png


+ 2 - 1
gameplay/src/PhysicsVehicle.cpp

@@ -204,7 +204,7 @@ float PhysicsVehicle::getSpeedKph() const
     return _vehicle->getCurrentSpeedKmHour();
 }
 
-void PhysicsVehicle::update(float steering, float braking, float driving)
+void PhysicsVehicle::update(float elapsedTime, float steering, float braking, float driving)
 {
     PhysicsVehicleWheel* wheel;
     for (int i = 0; i < _vehicle->getNumWheels(); i++)
@@ -221,6 +221,7 @@ void PhysicsVehicle::update(float steering, float braking, float driving)
             _vehicle->setBrake(braking * _brakingForce, i);
         }
 
+        wheel->update(elapsedTime);
         wheel->transform(wheel->getNode());
     }
 }

+ 2 - 1
gameplay/src/PhysicsVehicle.h

@@ -84,11 +84,12 @@ public:
      * Updates the vehicle state using the specified normalized command
      * inputs, and updates the transform on the visual node for each wheel.
      *
+     * @param elapsedTime The elapsed game time.
      * @param steering steering command (-1 to 1).
      * @param braking braking command (0 to 1).
      * @param driving net drivetrain command (0 to 1).
      */
-    void update(float steering, float braking, float driving);
+    void update(float elapsedTime, float steering, float braking, float driving);
 
     /**
      * Gets steering gain at full deflection.

+ 35 - 2
gameplay/src/PhysicsVehicleWheel.cpp

@@ -14,6 +14,8 @@ PhysicsVehicleWheel::PhysicsVehicleWheel(Node* node, const PhysicsCollisionShape
     _rigidBody = new PhysicsRigidBody(node, shape, parameters);
 
     findAncestorAndBind();
+
+    _initialOffset = node->getTranslation();
 }
 
 PhysicsVehicleWheel::PhysicsVehicleWheel(Node* node, PhysicsRigidBody* rigidBody)
@@ -22,6 +24,8 @@ PhysicsVehicleWheel::PhysicsVehicleWheel(Node* node, PhysicsRigidBody* rigidBody
     _rigidBody = rigidBody;
 
     findAncestorAndBind();
+
+    _initialOffset = node->getTranslation();
 }
 
 PhysicsVehicleWheel* PhysicsVehicleWheel::create(Node* node, Properties* properties)
@@ -178,12 +182,41 @@ void PhysicsVehicleWheel::transform(Node* node) const
 {
     GP_ASSERT(_host);
     GP_ASSERT(_host->_vehicle);
+    GP_ASSERT(_host->_node);
+
+    const btTransform& trans = _host->_vehicle->getWheelInfo(_indexInHost).m_worldTransform;
+    const btVector3& pos = trans.getOrigin();
+    node->setRotation(_orientation);
+
+    // Use only the component parallel to the defined strut line
+    Vector3 strutLine;
+    getWheelDirection(&strutLine);
+    Vector3 wheelPos = _initialOffset;
+    _host->_node->getMatrix().transformPoint(&wheelPos);
+    node->setTranslation(wheelPos + strutLine*(strutLine.dot(_positionDelta) / strutLine.lengthSquared()));
+}
+
+void PhysicsVehicleWheel::update(float elapsedTime)
+{
+    GP_ASSERT(_host);
+    GP_ASSERT(_host->_vehicle);
+    GP_ASSERT(_host->_node);
 
     const btTransform& trans = _host->_vehicle->getWheelInfo(_indexInHost).m_worldTransform;
     const btQuaternion& rot = trans.getRotation();
     const btVector3& pos = trans.getOrigin();
-    node->setRotation(rot.x(), rot.y(), rot.z(), rot.w());
-    node->setTranslation(pos.x(), pos.y(), pos.z());
+    _orientation.set(rot.x(), rot.y(), rot.z(), rot.w());
+
+    Vector3 commandedPosition(pos.x(), pos.y(), pos.z());
+    Vector3 wheelPos = _initialOffset;
+    _host->_node->getMatrix().transformPoint(&wheelPos);
+
+    // Filter out noise from Bullet
+    float dt = elapsedTime / 1000.0f;
+    Vector3 delta = commandedPosition - wheelPos - _positionDelta;
+    float threshold = getStrutRestLength() * 2.0f;
+    float tau = (delta.lengthSquared() > threshold*threshold) ? 0 : 0.06f;
+    _positionDelta += (commandedPosition - wheelPos - _positionDelta) * (dt / (dt + tau));
 }
 
 bool PhysicsVehicleWheel::isFront() const

+ 10 - 0
gameplay/src/PhysicsVehicleWheel.h

@@ -326,9 +326,19 @@ private:
      */
     void addToVehicle(btRaycastVehicle* vehicle);
 
+    /**
+     * Update state of this wheel, per frame.
+     *
+     * @param elapsedTime The elapsed game time.
+     */
+    void update(float elapsedTime);
+
     PhysicsRigidBody* _rigidBody;
     PhysicsVehicle* _host;
     unsigned int _indexInHost;
+    Vector3 _initialOffset;
+    Vector3 _positionDelta;
+    Quaternion _orientation;
 };
 
 }

+ 47 - 5
gameplay/src/PlatformMacOSX.mm

@@ -104,7 +104,7 @@ static View* __view = NULL;
     }
     CGLFlushDrawable((CGLContextObj)[[self openGLContext] CGLContextObj]);
     CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);  
-    
+
     [gameLock unlock];
 }
 
@@ -116,9 +116,21 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 }
 
 - (id) initWithFrame: (NSRect) frame
-{    
+{
+    _game = Game::getInstance();
+    
+    Properties* config = _game->getConfig()->getNamespace("window", true);
+    int samples = config ? config->getInt("samples") : 0;
+    if (samples < 0)
+        samples = 0;
+    
+    // Note: Keep multisampling attributes at the start of the attribute lists since code below
+    // assumes they are array elements 0 through 4.
     NSOpenGLPixelFormatAttribute windowedAttrs[] = 
     {
+        NSOpenGLPFAMultisample,
+        NSOpenGLPFASampleBuffers, samples ? 1 : 0,
+        NSOpenGLPFASamples, samples,
         NSOpenGLPFAAccelerated,
         NSOpenGLPFADoubleBuffer,
         NSOpenGLPFAColorSize, 32,
@@ -129,6 +141,9 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     };
     NSOpenGLPixelFormatAttribute fullscreenAttrs[] = 
     {
+        NSOpenGLPFAMultisample,
+        NSOpenGLPFASampleBuffers, samples ? 1 : 0,
+        NSOpenGLPFASamples, samples,
         NSOpenGLPFADoubleBuffer,
         NSOpenGLPFAScreenMask, (NSOpenGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(CGMainDisplayID()),
         NSOpenGLPFAFullScreen,
@@ -140,16 +155,38 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     };
     NSOpenGLPixelFormatAttribute* attrs = __fullscreen ? fullscreenAttrs : windowedAttrs;
     
+    // Try to choose a supported pixel format
     NSOpenGLPixelFormat* pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
     if (!pf)
-        NSLog(@"OpenGL pixel format not supported.");
+    {
+        bool valid = false;
+        while (!pf && samples > 0)
+        {
+            samples /= 2;
+            attrs[2] = samples ? 1 : 0;
+            attrs[4] = samples;
+            pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
+            if (pf)
+            {
+                valid = true;
+                break;
+            }
+        }
+        
+        if (!valid)
+        {
+            NSLog(@"OpenGL pixel format not supported.");
+            GP_ERROR("Failed to create a valid OpenGL pixel format.");
+            return nil;
+        }
+    }
     
-    if((self = [super initWithFrame:frame pixelFormat:[pf autorelease]])) 
+    if ((self = [super initWithFrame:frame pixelFormat:[pf autorelease]])) 
     {
         gameLock = [[NSRecursiveLock alloc] init];
-        _game = Game::getInstance();
         __timeStart = getMachTimeInMilliseconds();
     }
+    
     return self;
 }
 
@@ -745,6 +782,11 @@ int Platform::enterMessagePump()
     NSRect viewBounds = NSMakeRect(0, 0, __width, __height);
     
     __view = [[View alloc] initWithFrame:viewBounds];
+    if (__view == NULL)
+    {
+        GP_ERROR("Failed to create view: exiting.");
+        return EXIT_FAILURE;
+    }
     
     NSRect centered = NSMakeRect(NSMidX(screenBounds) - NSMidX(viewBounds),
                                  NSMidY(screenBounds) - NSMidY(viewBounds),

+ 3 - 2
gameplay/src/PlatformWin32.cpp

@@ -737,6 +737,9 @@ bool initializeGL(WindowCreationParams* params)
 
     // Choose pixel format using wglChoosePixelFormatARB, which allows us to specify
     // additional attributes such as multisampling.
+    //
+    // Note: Keep multisampling attributes at the start of the attribute lists since code below
+    // assumes they are array elements 0 through 3.
     int attribList[] = {
         WGL_SAMPLES_ARB, params->samples,
         WGL_SAMPLE_BUFFERS_ARB, params->samples > 0 ? 1 : 0,
@@ -887,8 +890,6 @@ Platform* Platform::create(Game* game, void* attachToWindow)
 
             // Read multisampling state.
             params.samples = config->getInt("samples");
-            if (params.samples == 0)
-                params.samples = 1;
         }
     }
 

+ 162 - 52
gameplay/src/PlatformiOS.mm

@@ -53,14 +53,19 @@ int getKey(unichar keyCode);
 {
     EAGLContext* context;
     CADisplayLink* displayLink;
+    BOOL updateFramebuffer;
     GLuint defaultFramebuffer;
     GLuint colorRenderbuffer;
     GLuint depthRenderbuffer;
     GLint framebufferWidth;
-    GLint framebufferHeight;    
+    GLint framebufferHeight;
+    GLuint multisampleFramebuffer;
+    GLuint multisampleRenderbuffer;
+    GLuint multisampleDepthbuffer;
     NSInteger swapInterval;
     BOOL updating;
-    Game* _game;
+    Game* game;
+    BOOL oglDiscardSupported;
     
     UITapGestureRecognizer *_tapRecognizer;
     UIPinchGestureRecognizer *_pinchRecognizer;
@@ -82,11 +87,10 @@ int getKey(unichar keyCode);
 @end
 
 @interface View (Private)
-- (void)createFramebuffer;
+- (BOOL)createFramebuffer;
 - (void)deleteFramebuffer;
 @end
 
-
 @implementation View
 
 @synthesize updating;
@@ -111,11 +115,21 @@ int getKey(unichar keyCode);
         }
         else
         {
-            print("Invalid OS Version: %s\n", (currSysVer == NULL?"NULL":[currSysVer cStringUsingEncoding:NSASCIIStringEncoding]));
+            GP_ERROR("Invalid OS Version: %s\n", (currSysVer == NULL?"NULL":[currSysVer cStringUsingEncoding:NSASCIIStringEncoding]));
             [self release];
             return nil;
         }
         
+        // Check for OS 4.0+ features
+        if ([currSysVer compare:@"4.0" options:NSNumericSearch] != NSOrderedAscending)
+        {
+            oglDiscardSupported = YES;
+        }
+        else
+        {
+            oglDiscardSupported = NO;
+        }
+        
         // Configure the CAEAGLLayer and setup out the rendering context
         CGFloat scale = [[UIScreen mainScreen] scale];
         CAEAGLLayer* layer = (CAEAGLLayer *)self.layer;
@@ -129,42 +143,36 @@ int getKey(unichar keyCode);
         context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
         if (!context || ![EAGLContext setCurrentContext:context])
         {
+            GP_ERROR("Failed to make context current.");
             [self release];
             return nil;
         }
 
-        if (!defaultFramebuffer)
-        {
-            [self createFramebuffer];
-        }
-            
-        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
-        glViewport(0, 0, framebufferWidth, framebufferHeight);
-
         // Initialize Internal Defaults
         displayLink = nil;
+        updateFramebuffer = YES;
         defaultFramebuffer = 0;
         colorRenderbuffer = 0;
         depthRenderbuffer = 0;
         framebufferWidth = 0;
         framebufferHeight = 0;
+        multisampleFramebuffer = 0;
+        multisampleRenderbuffer = 0;
+        multisampleDepthbuffer = 0;
         swapInterval = 1;        
         updating = FALSE;
-        
-        [self createFramebuffer];
+        game = Game::getInstance();
         
         // Set the resource path and initalize the game
         NSString* bundlePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/"];
         FileSystem::setResourcePath([bundlePath fileSystemRepresentation]); 
-        
     }
     return self;
 }
 
-
 - (void) dealloc
 {
-    _game->exit();
+    game->exit();
     [self deleteFramebuffer];
     
     if ([EAGLContext currentContext] == context)
@@ -183,35 +191,85 @@ int getKey(unichar keyCode);
 
 - (void) layoutSubviews
 {
-    // Called on 'resize'
-    [self deleteFramebuffer];
+    // Called on 'resize'.
+    // Mark that framebuffer needs to be updated.
+    updateFramebuffer = YES;
 }
 
-- (void)createFramebuffer
+- (BOOL)createFramebuffer
 {
     // iOS Requires all content go to a rendering buffer then it is swapped into the windows rendering surface
     assert(defaultFramebuffer == 0);
     
-    // Create the default frame buffer, and render buffer
-    glGenFramebuffers(1, &defaultFramebuffer);
-    glGenRenderbuffers(1, &colorRenderbuffer);
-    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);    
-    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
+    // Create the default frame buffer
+    GL_ASSERT( glGenFramebuffers(1, &defaultFramebuffer) );
+    GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer) );
+    
+    // Create a color buffer to attach to the frame buffer
+    GL_ASSERT( glGenRenderbuffers(1, &colorRenderbuffer) );
+    GL_ASSERT( glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer) );
     
-    // request storage, width, and height of the view that we will render in
+    // Associate our render buffer with CAEAGLLauyer so that the rendered content is display on our UI layer.
     [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
-    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
-    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth);
-    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight);
     
-    glGenRenderbuffers(1, &depthRenderbuffer);
-    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
-    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, framebufferWidth, framebufferHeight);
-    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
+    // Attach the color buffer to our frame buffer
+    GL_ASSERT( glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer) );
+    
+    // Retrieve framebuffer size
+    GL_ASSERT( glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth) );
+    GL_ASSERT( glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight) );
+    
+    // If multisampling is enabled in config, create and setup a multisample buffer
+    Properties* config = game->getConfig()->getNamespace("window");
+    int samples = config ? config->getInt("samples") : 0;
+    if (samples < 0)
+        samples = 0;
+    if (samples)
+    {
+        GL_ASSERT( glGenFramebuffers(1, &multisampleFramebuffer) );
+        GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, multisampleFramebuffer) );
+        
+        GL_ASSERT( glGenRenderbuffers(1, &multisampleRenderbuffer) );
+        GL_ASSERT( glBindRenderbuffer(1, multisampleRenderbuffer) );
+        GL_ASSERT( glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, samples, GL_RGBA8_OES, framebufferWidth, framebufferHeight) );
+        GL_ASSERT( glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, multisampleRenderbuffer) );
+        
+        GL_ASSERT( glGenRenderbuffers(1, &multisampleDepthbuffer) );
+        GL_ASSERT( glBindRenderbuffer(GL_RENDERBUFFER, multisampleDepthbuffer) );
+        GL_ASSERT( glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, samples, GL_DEPTH_COMPONENT24_OES, framebufferWidth, framebufferHeight) );
+        GL_ASSERT( glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, multisampleDepthbuffer) );
+        
+        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+        {
+            NSLog(@"Failed to make complete multisample buffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
+            [self deleteFramebuffer];
+            return NO;
+        }
+        
+        // Re-bind the default framebuffer
+        GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer) );
+    }
+    
+    // Create default depth buffer and attach to the frame buffer.
+    // Note: If we are using multisample buffers, we can skip depth buffer creation here since we only
+    // need the color buffer to resolve to.
+    if (multisampleFramebuffer == 0)
+    {
+        GL_ASSERT( glGenRenderbuffers(1, &depthRenderbuffer) );
+        GL_ASSERT( glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer) );
+        GL_ASSERT( glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, framebufferWidth, framebufferHeight) );
+        GL_ASSERT( glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer) );
+    }
     
     // Sanity check, ensure that the framebuffer is valid
     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+    {
         NSLog(@"ERROR: Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
+        [self deleteFramebuffer];
+        return NO;
+    }
+    
+    return YES;
 }
 
 - (void)deleteFramebuffer
@@ -234,6 +292,21 @@ int getKey(unichar keyCode);
             glDeleteRenderbuffers(1, &depthRenderbuffer);
             depthRenderbuffer = 0;
         }
+        if (multisampleFramebuffer)
+        {
+            glDeleteFramebuffers(1, &multisampleFramebuffer);
+            multisampleFramebuffer = 0;
+        }
+        if (multisampleRenderbuffer)
+        {
+            glDeleteRenderbuffers(1, &multisampleRenderbuffer);
+            multisampleRenderbuffer = 0;
+        }
+        if (multisampleDepthbuffer)
+        {
+            glDeleteRenderbuffers(1, &multisampleDepthbuffer);
+            multisampleDepthbuffer = 0;
+        }
     }
 }
 
@@ -257,8 +330,35 @@ int getKey(unichar keyCode);
 
 - (void)swapBuffers
 {
-    if (context != nil)
+    if (context)
     {
+        if (multisampleFramebuffer)
+        {
+            // Multisampling is enabled: resolve the multisample buffer into the default framebuffer
+            glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, defaultFramebuffer);
+            glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, multisampleFramebuffer);
+            glResolveMultisampleFramebufferAPPLE();
+            
+            if (oglDiscardSupported)
+            {
+                // Performance hint that the GL driver can discard the contents of the multisample buffers
+                // since they have now been resolved into the default framebuffer
+                const GLenum discards[]  = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT };
+                glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 2, discards);
+            }
+        }
+        else
+        {
+            if (oglDiscardSupported)
+            {
+                // Performance hint to the GL driver that the depth buffer is no longer required.
+                const GLenum discards[]  = { GL_DEPTH_ATTACHMENT };
+                glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
+                glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
+            }
+        }
+        
+        // Present the color buffer
         glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
         [context presentRenderbuffer:GL_RENDERBUFFER];
     }
@@ -266,9 +366,9 @@ int getKey(unichar keyCode);
 
 - (void)startGame 
 {
-    _game = Game::getInstance();
+    game = Game::getInstance();
     __timeStart = getMachTimeInMilliseconds();
-    _game->run();  
+    game->run();
 }
 
 - (void)startUpdating
@@ -278,7 +378,7 @@ int getKey(unichar keyCode);
         displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update:)];
         [displayLink setFrameInterval:swapInterval];
         [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
-        _game->resume();
+        game->resume();
         updating = TRUE;
     }
 }
@@ -287,7 +387,7 @@ int getKey(unichar keyCode);
 {
     if (updating)
     {
-        _game->pause();
+        game->pause();
         [displayLink invalidate];
         displayLink = nil;
         updating = FALSE;
@@ -298,18 +398,28 @@ int getKey(unichar keyCode);
 {   
     if (context != nil)
     {
+        // Ensure our context is current
         [EAGLContext setCurrentContext:context];
-        if (!defaultFramebuffer)
+        
+        // If the framebuffer needs (re)creating, do so
+        if (updateFramebuffer)
+        {
+            updateFramebuffer = NO;
+            [self deleteFramebuffer];
             [self createFramebuffer];
+        }
         
-        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
-        glViewport(0, 0, framebufferWidth, framebufferHeight);
-
-        if (_game && _game->getState() == Game::RUNNING)       
-            _game->frame();
+        // Bind our framebuffer for rendering.
+        // If multisampling is enabled, bind the multisample buffer - otherwise bind the default buffer
+        GL_ASSERT( glBindFramebuffer(GL_FRAMEBUFFER, multisampleFramebuffer ? multisampleFramebuffer : defaultFramebuffer) );
+        GL_ASSERT( glViewport(0, 0, framebufferWidth, framebufferHeight) );
         
-        glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
-        [context presentRenderbuffer:GL_RENDERBUFFER];
+        // Execute a single game frame
+        if (game && game->getState() == Game::RUNNING)
+            game->frame();
+        
+        // Present the contents of the color buffer
+        [self swapBuffers];
     }
 }
 
@@ -448,14 +558,14 @@ int getKey(unichar keyCode);
 - (void)handleTapGesture:(UITapGestureRecognizer *)sender
 {
     CGPoint location = [sender locationInView:self];
-    _game->gestureTapEvent(location.x, location.y);
+    game->gestureTapEvent(location.x, location.y);
 }
 
 - (void)handlePinchGesture:(UIPinchGestureRecognizer *)sender
 {
     CGFloat factor = [sender scale];
     CGPoint location = [sender locationInView:self];
-    _game->gesturePinchEvent(location.x, location.y, factor);
+    game->gesturePinchEvent(location.x, location.y, factor);
 }
 
 - (void)handleSwipeGesture:(UISwipeGestureRecognizer *)sender
@@ -477,7 +587,7 @@ int getKey(unichar keyCode);
             gameplayDirection = Gesture::SWIPE_DIRECTION_DOWN;
             break;
     }
-    _game->gestureSwipeEvent(location.x, location.y, gameplayDirection);
+    game->gestureSwipeEvent(location.x, location.y, gameplayDirection);
 }
 
 @end
@@ -940,7 +1050,7 @@ int Platform::enterMessagePump()
     [pool release];
     return EXIT_SUCCESS;
 }
-    
+
 void Platform::signalShutdown() 
 {
     // Cannot 'exit' an iOS Application
@@ -948,7 +1058,7 @@ void Platform::signalShutdown()
     [__view stopUpdating];
     exit(0);
 }
-    
+
 unsigned int Platform::getDisplayWidth()
 {
     CGSize size = DeviceOrientedSize([__appDelegate.viewController interfaceOrientation]);

+ 4 - 1
gameplay/src/Properties.cpp

@@ -134,13 +134,16 @@ void Properties::readProperties(FILE* file)
                 rc = strchr(line, '}');
 
                 // First token should be the property name.
-                name = strtok(line, " =\t");
+                name = strtok(line, "=");
                 if (name == NULL)
                 {
                     GP_ERROR("Error parsing properties file: attribute without name.");
                     return;
                 }
 
+                // Remove white-space from name.
+                name = trimWhiteSpace(name);
+
                 // Scan for next token, the property's value.
                 value = strtok(NULL, "=");
                 if (value == NULL)

+ 8 - 4
gameplay/src/lua/lua_PhysicsVehicle.cpp

@@ -1031,12 +1031,13 @@ int lua_PhysicsVehicle_update(lua_State* state)
     // Attempt to match the parameters to a valid binding.
     switch (paramCount)
     {
-        case 4:
+        case 5:
         {
             if ((lua_type(state, 1) == LUA_TUSERDATA) &&
                 lua_type(state, 2) == LUA_TNUMBER &&
                 lua_type(state, 3) == LUA_TNUMBER &&
-                lua_type(state, 4) == LUA_TNUMBER)
+                lua_type(state, 4) == LUA_TNUMBER &&
+                lua_type(state, 5) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
                 float param1 = (float)luaL_checknumber(state, 2);
@@ -1047,8 +1048,11 @@ int lua_PhysicsVehicle_update(lua_State* state)
                 // Get parameter 3 off the stack.
                 float param3 = (float)luaL_checknumber(state, 4);
 
+                // Get parameter 4 off the stack.
+                float param4 = (float)luaL_checknumber(state, 5);
+
                 PhysicsVehicle* instance = getInstance(state);
-                instance->update(param1, param2, param3);
+                instance->update(param1, param2, param3, param4);
                 
                 return 0;
             }
@@ -1061,7 +1065,7 @@ int lua_PhysicsVehicle_update(lua_State* state)
         }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 4).");
+            lua_pushstring(state, "Invalid number of parameters (expected 5).");
             lua_error(state);
             break;
         }