SceneKitVehicle/AAPLGameViewController.m

/*
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 */
 
#import <GameController/GameController.h>
#import <simd/simd.h>
#import <sys/utsname.h>
 
#import "AAPLGameViewController.h"
#import "AAPLGameView.h"
#import "AAPLOverlayScene.h"
 
#define MAX_SPEED 250
 
@implementation AAPLGameViewController {
    //some node references for manipulation
    SCNNode *_spotLightNode;
    SCNNode *_cameraNode;          //the node that owns the camera
    SCNNode *_vehicleNode;
    SCNPhysicsVehicle *_vehicle;
    SCNParticleSystem *_reactor;
    
    //accelerometer
    CMMotionManager *_motionManager;
    UIAccelerationValue _accelerometer[3];
    CGFloat _orientation;
    
    //reactor's particle birth rate
    CGFloat _reactorDefaultBirthRate;
    
    // steering factor
    CGFloat _vehicleSteering;
}
 
- (NSString *)deviceName
{
    static NSString *deviceName = nil;
    
    if (deviceName == nil) {
        struct utsname systemInfo;
        uname(&systemInfo);
        
        deviceName = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
    }
    return deviceName;
}
 
- (BOOL)isHighEndDevice
{
    //return YES for iPhone 5s and iPad air, NO otherwise
    if ([[self deviceName] hasPrefix:@"iPad4"]
       || [[self deviceName] hasPrefix:@"iPhone6"]) {
        return YES;
    }
    
    return NO;
}
 
- (void)setupEnvironment:(SCNScene *)scene
{
    // add an ambient light
    SCNNode *ambientLight = [SCNNode node];
    ambientLight.light = [SCNLight light];
    ambientLight.light.type = SCNLightTypeAmbient;
    ambientLight.light.color = [UIColor colorWithWhite:0.3 alpha:1.0];
    [[scene rootNode] addChildNode:ambientLight];
    
    //add a key light to the scene
    SCNNode *lightNode = [SCNNode node];
    lightNode.light = [SCNLight light];
    lightNode.light.type = SCNLightTypeSpot;
    if ([self isHighEndDevice])
        lightNode.light.castsShadow = YES;
    lightNode.light.color = [UIColor colorWithWhite:0.8 alpha:1.0];
    lightNode.position = SCNVector3Make(0, 80, 30);
    lightNode.rotation = SCNVector4Make(1,0,0,-M_PI/2.8);
    lightNode.light.spotInnerAngle = 0;
    lightNode.light.spotOuterAngle = 50;
    lightNode.light.shadowColor = [SKColor blackColor];
    lightNode.light.zFar = 500;
    lightNode.light.zNear = 50;
    [[scene rootNode] addChildNode:lightNode];
    
    //keep an ivar for later manipulation
    _spotLightNode = lightNode;
    
    //floor
    SCNNode*floor = [SCNNode node];
    floor.geometry = [SCNFloor floor];
    floor.geometry.firstMaterial.diffuse.contents = @"wood.png";
    floor.geometry.firstMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(2, 2, 1); //scale the wood texture
    floor.geometry.firstMaterial.locksAmbientWithDiffuse = YES;
    if ([self isHighEndDevice])
        ((SCNFloor*)floor.geometry).reflectionFalloffEnd = 10;
    
    SCNPhysicsBody *staticBody = [SCNPhysicsBody staticBody];
    floor.physicsBody = staticBody;
    [[scene rootNode] addChildNode:floor];
}
 
- (void)addTrainToScene:(SCNScene *)scene atPosition:(SCNVector3)pos
{
    SCNScene *trainScene = [SCNScene sceneNamed:@"train_flat"];
    
    //physicalize the train with simple boxes
    [trainScene.rootNode.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        SCNNode *node = (SCNNode *)obj;
        if (node.geometry != nil) {
            node.position = SCNVector3Make(node.position.x + pos.x, node.position.y + pos.y, node.position.z + pos.z);
            
            SCNVector3 min, max;
            [node getBoundingBoxMin:&min max:&max];
            
            SCNPhysicsBody *body = [SCNPhysicsBody dynamicBody];
            SCNBox *boxShape = [SCNBox boxWithWidth:max.x - min.x height:max.y - min.y length:max.z - min.z chamferRadius:0.0];
            body.physicsShape = [SCNPhysicsShape shapeWithGeometry:boxShape options:nil];
            
            node.pivot = SCNMatrix4MakeTranslation(0, -min.y, 0);
            node.physicsBody = body;
            [[scene rootNode] addChildNode:node];
        }
    }];
    
    //add smoke
    SCNNode *smokeHandle = [scene.rootNode childNodeWithName:@"Smoke" recursively:YES];
    [smokeHandle addParticleSystem:[SCNParticleSystem particleSystemNamed:@"smoke" inDirectory:nil]];
    
    //add physics constraints between engine and wagons
    SCNNode *engineCar = [scene.rootNode childNodeWithName:@"EngineCar" recursively:NO];
    SCNNode *wagon1 = [scene.rootNode childNodeWithName:@"Wagon1" recursively:NO];
    SCNNode *wagon2 = [scene.rootNode childNodeWithName:@"Wagon2" recursively:NO];
    
    SCNVector3 min, max;
    [engineCar getBoundingBoxMin:&min max:&max];
    
    SCNVector3 wmin, wmax;
    [wagon1 getBoundingBoxMin:&wmin max:&wmax];
    
    // Tie EngineCar & Wagon1
    SCNPhysicsBallSocketJoint *joint = [SCNPhysicsBallSocketJoint jointWithBodyA:engineCar.physicsBody anchorA:SCNVector3Make(max.x, min.y, 0)
                                                                           bodyB:wagon1.physicsBody anchorB:SCNVector3Make(wmin.x, wmin.y, 0)];
    [scene.physicsWorld addBehavior:joint];
    
    // Wagon1 & Wagon2
    joint = [SCNPhysicsBallSocketJoint jointWithBodyA:wagon1.physicsBody anchorA:SCNVector3Make(wmax.x + 0.1, wmin.y, 0)
                                                bodyB:wagon2.physicsBody anchorB:SCNVector3Make(wmin.x - 0.1, wmin.y, 0)];
    [scene.physicsWorld addBehavior:joint];
}
 
 
- (void)addWoodenBlockToScene:(SCNScene *)scene withImageNamed:(NSString *)imageName atPosition:(SCNVector3)position
{
    //create a new node
    SCNNode *block = [SCNNode node];
    
    //place it
    block.position = position;
    
    //attach a box of 5x5x5
    block.geometry = [SCNBox boxWithWidth:5 height:5 length:5 chamferRadius:0];
 
    //use the specified images named as the texture
    block.geometry.firstMaterial.diffuse.contents = imageName;
    
    //turn on mipmapping
    block.geometry.firstMaterial.diffuse.mipFilter = SCNFilterModeLinear;
    
    //make it physically based
    block.physicsBody = [SCNPhysicsBody dynamicBody];
    
    //add to the scene
    [[scene rootNode] addChildNode:block];
}
 
- (void)setupSceneElements:(SCNScene *)scene
{
    // add a train
    [self addTrainToScene:scene atPosition:SCNVector3Make(-5, 20, -40)];
    
    // add wooden blocks
    [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeA.jpg" atPosition:SCNVector3Make(-10, 15, 10)];
    [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeB.jpg" atPosition:SCNVector3Make( -9, 10, 10)];
    [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeC.jpg" atPosition:SCNVector3Make(20, 15, -11)];
    [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeA.jpg" atPosition:SCNVector3Make(25, 5, -20)];
    
    // add walls
    SCNNode *wall = [SCNNode nodeWithGeometry:[SCNBox boxWithWidth:400 height:100 length:4 chamferRadius:0]];
    wall.geometry.firstMaterial.diffuse.contents = @"wall.jpg";
    wall.geometry.firstMaterial.diffuse.contentsTransform = SCNMatrix4Mult(SCNMatrix4MakeScale(24, 2, 1), SCNMatrix4MakeTranslation(0, 1, 0));
    wall.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeRepeat;
    wall.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeMirror;
    wall.geometry.firstMaterial.doubleSided = NO;
    wall.castsShadow = NO;
    wall.geometry.firstMaterial.locksAmbientWithDiffuse = YES;
        
    wall.position = SCNVector3Make(0, 50, -92);
    wall.physicsBody = [SCNPhysicsBody staticBody];
    [scene.rootNode addChildNode:wall];
    
    wall = [wall clone];
    wall.position = SCNVector3Make(-202, 50, 0);
    wall.rotation = SCNVector4Make(0, 1, 0, M_PI_2);
    [scene.rootNode addChildNode:wall];
        
    wall = [wall clone];
    wall.position = SCNVector3Make(202, 50, 0);
    wall.rotation = SCNVector4Make(0, 1, 0, -M_PI_2);
    [scene.rootNode addChildNode:wall];
    
    SCNNode *backWall = [SCNNode nodeWithGeometry:[SCNPlane planeWithWidth:400 height:100]];
    backWall.geometry.firstMaterial = wall.geometry.firstMaterial;
    backWall.position = SCNVector3Make(0, 50, 200);
    backWall.rotation = SCNVector4Make(0, 1, 0, M_PI);
    backWall.castsShadow = NO;
    backWall.physicsBody = [SCNPhysicsBody staticBody];
    [scene.rootNode addChildNode:backWall];
    
    // add ceil
    SCNNode *ceilNode = [SCNNode nodeWithGeometry:[SCNPlane planeWithWidth:400 height:400]];
    ceilNode.position = SCNVector3Make(0, 100, 0);
    ceilNode.rotation = SCNVector4Make(1, 0, 0, M_PI_2);
    ceilNode.geometry.firstMaterial.doubleSided = NO;
    ceilNode.castsShadow = NO;
    ceilNode.geometry.firstMaterial.locksAmbientWithDiffuse = YES;
    [scene.rootNode addChildNode:ceilNode];
    
    //add more block
    for(int i=0;i<4; i++) {
        [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeA.jpg" atPosition:SCNVector3Make(rand()%60 - 30, 20, rand()%40 - 20)];
        [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeB.jpg" atPosition:SCNVector3Make(rand()%60 - 30, 20, rand()%40 - 20)];
        [self addWoodenBlockToScene:scene withImageNamed:@"WoodCubeC.jpg" atPosition:SCNVector3Make(rand()%60 - 30, 20, rand()%40 - 20)];
    }
    
    // add cartoon book
    SCNNode *block = [SCNNode node];
    block.position = SCNVector3Make(20, 10, -16);
    block.rotation = SCNVector4Make(0, 1, 0, -M_PI_4);
    block.geometry = [SCNBox boxWithWidth:22 height:0.2 length:34 chamferRadius:0];
    SCNMaterial *frontMat = [SCNMaterial material];
    frontMat.locksAmbientWithDiffuse = YES;
    frontMat.diffuse.contents = @"book_front.jpg";
    frontMat.diffuse.mipFilter = SCNFilterModeLinear;
    SCNMaterial *backMat = [SCNMaterial material];
    backMat.locksAmbientWithDiffuse = YES;
    backMat.diffuse.contents = @"book_back.jpg";
    backMat.diffuse.mipFilter = SCNFilterModeLinear;
    block.geometry.materials = @[frontMat, backMat];
    block.physicsBody = [SCNPhysicsBody dynamicBody];
    [[scene rootNode] addChildNode:block];
    
    // add carpet
    SCNNode *rug = [SCNNode node];
    rug.position = SCNVector3Make(0, 0.01, 0);
    rug.rotation = SCNVector4Make(1, 0, 0, M_PI_2);
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(-50, -30, 100, 50) cornerRadius:2.5];
    path.flatness = 0.1;
    rug.geometry = [SCNShape shapeWithPath:path extrusionDepth:0.05];
    rug.geometry.firstMaterial.locksAmbientWithDiffuse = YES;
    rug.geometry.firstMaterial.diffuse.contents = @"carpet.jpg";
    [[scene rootNode] addChildNode:rug];
    
    // add ball
    SCNNode *ball = [SCNNode node];
    ball.position = SCNVector3Make(-5, 5, -18);
    ball.geometry = [SCNSphere sphereWithRadius:5];
    ball.geometry.firstMaterial.locksAmbientWithDiffuse = YES;
    ball.geometry.firstMaterial.diffuse.contents = @"ball.jpg";
    ball.geometry.firstMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(2, 1, 1);
    ball.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeMirror;
    ball.physicsBody = [SCNPhysicsBody dynamicBody];
    ball.physicsBody.restitution = 0.9;
    [[scene rootNode] addChildNode:ball];
}
 
 
- (SCNNode *)setupVehicle:(SCNScene *)scene
{
    SCNScene *carScene = [SCNScene sceneNamed:@"rc_car"];
    SCNNode *chassisNode = [carScene.rootNode childNodeWithName:@"rccarBody" recursively:NO];
    
    // setup the chassis
    chassisNode.position = SCNVector3Make(0, 10, 30);
    chassisNode.rotation = SCNVector4Make(0, 1, 0, M_PI);
    
    SCNPhysicsBody *body = [SCNPhysicsBody dynamicBody];
    body.allowsResting = NO;
    body.mass = 80;
    body.restitution = 0.1;
    body.friction = 0.5;
    body.rollingFriction = 0;
    
    chassisNode.physicsBody = body;
    [scene.rootNode addChildNode:chassisNode];
    
    SCNNode *pipeNode = [chassisNode childNodeWithName:@"pipe" recursively:YES];
    _reactor = [SCNParticleSystem particleSystemNamed:@"reactor" inDirectory:nil];
    _reactorDefaultBirthRate = _reactor.birthRate;
    _reactor.birthRate = 0;
    [pipeNode addParticleSystem:_reactor];
    
    //add wheels
    SCNNode *wheel0Node = [chassisNode childNodeWithName:@"wheelLocator_FL" recursively:YES];
    SCNNode *wheel1Node = [chassisNode childNodeWithName:@"wheelLocator_FR" recursively:YES];
    SCNNode *wheel2Node = [chassisNode childNodeWithName:@"wheelLocator_RL" recursively:YES];
    SCNNode *wheel3Node = [chassisNode childNodeWithName:@"wheelLocator_RR" recursively:YES];
    
    SCNPhysicsVehicleWheel *wheel0 = [SCNPhysicsVehicleWheel wheelWithNode:wheel0Node];
    SCNPhysicsVehicleWheel *wheel1 = [SCNPhysicsVehicleWheel wheelWithNode:wheel1Node];
    SCNPhysicsVehicleWheel *wheel2 = [SCNPhysicsVehicleWheel wheelWithNode:wheel2Node];
    SCNPhysicsVehicleWheel *wheel3 = [SCNPhysicsVehicleWheel wheelWithNode:wheel3Node];
 
    SCNVector3 min, max;
    [wheel0Node getBoundingBoxMin:&min max:&max];
    CGFloat wheelHalfWidth = 0.5 * (max.x - min.x);
    
    wheel0.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel0Node convertPosition:SCNVector3Zero toNode:chassisNode]) + (vector_float3){wheelHalfWidth, 0.0, 0.0});
    wheel1.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel1Node convertPosition:SCNVector3Zero toNode:chassisNode]) - (vector_float3){wheelHalfWidth, 0.0, 0.0});
    wheel2.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel2Node convertPosition:SCNVector3Zero toNode:chassisNode]) + (vector_float3){wheelHalfWidth, 0.0, 0.0});
    wheel3.connectionPosition = SCNVector3FromFloat3(SCNVector3ToFloat3([wheel3Node convertPosition:SCNVector3Zero toNode:chassisNode]) - (vector_float3){wheelHalfWidth, 0.0, 0.0});
    
    // create the physics vehicle
    SCNPhysicsVehicle *vehicle = [SCNPhysicsVehicle vehicleWithChassisBody:chassisNode.physicsBody wheels:@[wheel0, wheel1, wheel2, wheel3]];
    [scene.physicsWorld addBehavior:vehicle];
    
    _vehicle = vehicle;
    
    return chassisNode;
}
 
- (SCNScene *)setupScene
{
    // create a new scene
    SCNScene *scene = [SCNScene scene];
    
    //global environment
    [self setupEnvironment:scene];
    
    //add elements
    [self setupSceneElements:scene];
    
    //setup vehicle
    _vehicleNode = [self setupVehicle:scene];
    
    //create a main camera
    _cameraNode = [[SCNNode alloc] init];
    _cameraNode.camera = [SCNCamera camera];
    _cameraNode.camera.zFar = 500;
    _cameraNode.position = SCNVector3Make(0, 60, 50);
    _cameraNode.rotation  = SCNVector4Make(1, 0, 0, -M_PI_4*0.75);
    [scene.rootNode addChildNode:_cameraNode];
    
    //add a secondary camera to the car
    SCNNode *frontCameraNode = [SCNNode node];
    frontCameraNode.position = SCNVector3Make(0, 3.5, 2.5);
    frontCameraNode.rotation = SCNVector4Make(0, 1, 0, M_PI);
    frontCameraNode.camera = [SCNCamera camera];
    frontCameraNode.camera.xFov = 75;
    frontCameraNode.camera.zFar = 500;
    
    [_vehicleNode addChildNode:frontCameraNode];
    
    return scene;
}
 
- (void)setupAccelerometer
{
    //event
    _motionManager = [[CMMotionManager alloc] init];
    AAPLGameViewController * __weak weakSelf = self;
    
    if ([[GCController controllers] count] == 0 && [_motionManager isAccelerometerAvailable] == YES) {
        [_motionManager setAccelerometerUpdateInterval:1/60.0];
        [_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
            [weakSelf accelerometerDidChange:accelerometerData.acceleration];
        }];
    }
}
 
- (BOOL)prefersStatusBarHidden {
    return YES;
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    [[UIApplication sharedApplication] setStatusBarHidden:YES];
    
    SCNView *scnView = (SCNView *) self.view;
    
    //set the background to back
    scnView.backgroundColor = [SKColor blackColor];
    
    //setup the scene
    SCNScene *scene = [self setupScene];
    
    //present it
    scnView.scene = scene;
    
    //tweak physics
    scnView.scene.physicsWorld.speed = 4.0;
 
    //setup overlays
    scnView.overlaySKScene = [[AAPLOverlayScene alloc] initWithSize:scnView.bounds.size];
    
    //setup accelerometer
    [self setupAccelerometer];
    
    //initial point of view
    scnView.pointOfView = _cameraNode;
    
    //plug game logic
    scnView.delegate = self;
    
    
    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
    doubleTap.numberOfTapsRequired = 2;
    doubleTap.numberOfTouchesRequired = 2;
    scnView.gestureRecognizers = @[doubleTap];
    
    [super viewDidLoad];
}
 
- (void) handleDoubleTap:(UITapGestureRecognizer *) gesture
{
    SCNScene *scene = [self setupScene];
    
    SCNView *scnView = (SCNView *) self.view;
    //present it
    scnView.scene = scene;
    
    //tweak physics
    scnView.scene.physicsWorld.speed = 4.0;
    
    //initial point of view
    scnView.pointOfView = _cameraNode;
    
    ((AAPLGameView*)scnView).touchCount = 0;
}
 
// game logic
- (void)renderer:(id<SCNSceneRenderer>)aRenderer didSimulatePhysicsAtTime:(NSTimeInterval)time
{
    const float defaultEngineForce = 300.0;
    const float defaultBrakingForce = 3.0;
    const float steeringClamp = 0.6;
    const float cameraDamping = 0.3;
    
    AAPLGameView *scnView = (AAPLGameView*)self.view;
    
    CGFloat engineForce = 0;
    CGFloat brakingForce = 0;
    
    NSArray* controllers = [GCController controllers];
    
    float orientation = _orientation;
    
    //drive: 1 touch = accelerate, 2 touches = backward, 3 touches = brake
    if (scnView.touchCount == 1) {
        engineForce = defaultEngineForce;
        _reactor.birthRate = _reactorDefaultBirthRate;
    }
    else if (scnView.touchCount == 2) {
        engineForce = -defaultEngineForce;
        _reactor.birthRate = 0;
    }
    else if (scnView.touchCount == 3) {
        brakingForce = 100;
        _reactor.birthRate = 0;
    }
    else {
        brakingForce = defaultBrakingForce;
        _reactor.birthRate = 0;
    }
    
    //controller support
    if (controllers && [controllers count] > 0) {
        GCController *controller = controllers[0];
        GCGamepad *pad = [controller gamepad];
        GCControllerDirectionPad *dpad = [pad dpad];
        
        static float orientationCum = 0;
        
#define INCR_ORIENTATION 0.03
#define DECR_ORIENTATION 0.8
        
        if (dpad.right.pressed) {
            if (orientationCum < 0) orientationCum *= DECR_ORIENTATION;
            orientationCum += INCR_ORIENTATION;
            if (orientationCum > 1) orientationCum = 1;
        }
        else if (dpad.left.pressed) {
            if (orientationCum > 0) orientationCum *= DECR_ORIENTATION;
            orientationCum -= INCR_ORIENTATION;
            if (orientationCum < -1) orientationCum = -1;
        }
        else {
            orientationCum *= DECR_ORIENTATION;
        }
        
        orientation = orientationCum;
        
        if (pad.buttonX.pressed) {
            engineForce = defaultEngineForce;
            _reactor.birthRate = _reactorDefaultBirthRate;
        }
        else if (pad.buttonA.pressed) {
            engineForce = -defaultEngineForce;
            _reactor.birthRate = 0;
        }
        else if (pad.buttonB.pressed) {
            brakingForce = 100;
            _reactor.birthRate = 0;
        }
        else {
            brakingForce = defaultBrakingForce;
            _reactor.birthRate = 0;
        }
    }
    
    _vehicleSteering = -orientation;
    if (orientation==0)
        _vehicleSteering *= 0.9;
    if (_vehicleSteering < -steeringClamp)
        _vehicleSteering = -steeringClamp;
    if (_vehicleSteering > steeringClamp)
        _vehicleSteering = steeringClamp;
    
    //update the vehicle steering and acceleration
    [_vehicle setSteeringAngle:_vehicleSteering forWheelAtIndex:0];
    [_vehicle setSteeringAngle:_vehicleSteering forWheelAtIndex:1];
    
    [_vehicle applyEngineForce:engineForce forWheelAtIndex:2];
    [_vehicle applyEngineForce:engineForce forWheelAtIndex:3];
    
    [_vehicle applyBrakingForce:brakingForce forWheelAtIndex:2];
    [_vehicle applyBrakingForce:brakingForce forWheelAtIndex:3];
        
    //check if the car is upside down
    [self reorientCarIfNeeded];
 
    // make camera follow the car node
    SCNNode *car = [_vehicleNode presentationNode];
    SCNVector3 carPos = car.position;
    vector_float3 targetPos = {carPos.x, 30., carPos.z + 25.};
    vector_float3 cameraPos = SCNVector3ToFloat3(_cameraNode.position);
    cameraPos = vector_mix(cameraPos, targetPos, (vector_float3)(cameraDamping));
    _cameraNode.position = SCNVector3FromFloat3(cameraPos);
    
    if (scnView.inCarView) {
        //move spot light in front of the camera
        SCNVector3 frontPosition = [scnView.pointOfView.presentationNode convertPosition:SCNVector3Make(0, 0, -30) toNode:nil];
        _spotLightNode.position = SCNVector3Make(frontPosition.x, 80., frontPosition.z);
        _spotLightNode.rotation = SCNVector4Make(1,0,0,-M_PI/2);
    }
    else {
        //move spot light on top of the car
        _spotLightNode.position = SCNVector3Make(carPos.x, 80., carPos.z + 30.);
        _spotLightNode.rotation = SCNVector4Make(1,0,0,-M_PI/2.8);
    }
    
    //speed gauge
    AAPLOverlayScene *overlayScene = (AAPLOverlayScene*)scnView.overlaySKScene;
    overlayScene.speedNeedle.zRotation = -(_vehicle.speedInKilometersPerHour * M_PI / MAX_SPEED);
}
 
- (void)reorientCarIfNeeded
{
    SCNNode *car = [_vehicleNode presentationNode];
    SCNVector3 carPos = car.position;
 
    // make sure the car isn't upside down, and fix it if it is
    static int ticks = 0;
    static int check = 0;
    ticks++;
    if (ticks == 30) {
        SCNMatrix4 t = car.worldTransform;
        if (t.m22 <= 0.1) {
            check++;
            if (check == 3) {
                static int try = 0;
                try++;
                if (try == 3) {
                    try = 0;
                    
                    //hard reset
                    _vehicleNode.rotation = SCNVector4Make(0, 0, 0, 0);
                    _vehicleNode.position = SCNVector3Make(carPos.x, carPos.y + 10, carPos.z);
                    [_vehicleNode.physicsBody resetTransform];
                }
                else {
                    //try to upturn with an random impulse
                    SCNVector3 pos = SCNVector3Make(-10*((rand()/(float)RAND_MAX)-0.5),0,-10*((rand()/(float)RAND_MAX)-0.5));
                    [_vehicleNode.physicsBody applyForce:SCNVector3Make(0, 300, 0) atPosition:pos impulse:YES];
                }
                
                check = 0;
            }
        }
        else {
            check = 0;
        }
        
        ticks=0;
    }
}
 
- (void)accelerometerDidChange:(CMAcceleration)acceleration
{
#define kFilteringFactor            0.5
    
    //Use a basic low-pass filter to only keep the gravity in the accelerometer values
    _accelerometer[0] = acceleration.x * kFilteringFactor + _accelerometer[0] * (1.0 - kFilteringFactor);
    _accelerometer[1] = acceleration.y * kFilteringFactor + _accelerometer[1] * (1.0 - kFilteringFactor);
    _accelerometer[2] = acceleration.z * kFilteringFactor + _accelerometer[2] * (1.0 - kFilteringFactor);
    
    if (_accelerometer[0] > 0) {
        _orientation = _accelerometer[1]*1.3;
    }
    else {
        _orientation = -_accelerometer[1]*1.3;
    }
}
 
- (void)viewWillDisappear:(BOOL)animated
{
    [_motionManager stopAccelerometerUpdates];
    _motionManager = nil;
}
 
- (BOOL)shouldAutorotate
{
    return YES;
}
 
- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskLandscape;
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}
 
@end