3/21/15

iOS Scene Kit Jet

The Scene Kit 3D Jet in flight with clockwise roll, in iPad airspace.
Trailing transparent snapshots are 3D model clones of the jet at variable intervals.
Use SCNView allowsCameraControl to zoom and rotate the entire scene, including models, for different points of view.

* The 3D Jet ship mesh is part of the default Xcode Scene Kit project.





@import SceneKit;
@import GLKit;


@interface ESJet: NSObject

    @property (nonatomic) SCNNode * node;
    @property (nonatomic, assign) CGFloat velocity;
    @property (nonatomic, assign) BOOL roll;
    @property (nonatomic, assign) CGFloat rollRate;


    -(void)update:(NSTimeInterval)aTime isVisible:(BOOL)aVisible;

    -(instancetype)init;
    -(instancetype)initWithNode:(SCNNode *)aNode zPosition:(int)aZPosition;
@end


@implementation ESJet

    -(void)update:(NSTimeInterval)aTime isVisible:(BOOL)aVisible;
    {
        CGFloat nextZPosition = self.velocity;

     // NSLog(@"jet position: %@", NSStringFromGLKVector3(SCNVector3ToGLKVector3(self.node.position)));

        if (aVisible)
        {
            NSLog(@"jet visible");
        }
        else
        {
            //when jet flys off right-side of screen, reposition on left
            NSLog(@"...");
            nextZPosition = -(self.node.position.x * 1.95);
        }

        GLKVector3 nextFramePosition = GLKVector3Make(0, 0, nextZPosition);
        GLKMatrix4 transform = SCNMatrix4ToGLKMatrix4(self.node.transform);
        self.node.transform = SCNMatrix4FromGLKMatrix4(GLKMatrix4TranslateWithVector3(transform, nextFramePosition));


        if (self.roll)
        {
            //rotate jet ~ 1 degree per frame on X axis
            GLKQuaternion rot = GLKQuaternionMakeWithAngleAndAxis((M_1_PI * self.rollRate), 1, 0, 0);
            GLKQuaternion orientation = GLKQuaternionMultiply(rot, GLKQuaternionMake(self.node.orientation.x,
                                                                                     self.node.orientation.y,
                                                                                     self.node.orientation.z,
                                                                                     self.node.orientation.w));

            self.node.orientation = SCNVector4Make(orientation.x, orientation.y, orientation.z, orientation.w);
        }
    }

    -(instancetype)init
    {
        if (self = [super init])
        {
            self.velocity = 0.33f;
            self.rollRate = (self.velocity * 0.364);
        }

        return self;
    }

    -(instancetype)initWithNode:(SCNNode *)aNode zPosition:(int)aZPosition
    {
        if ([self init])
        {
            self.node = aNode;
            [self.node removeAllActions];

            //rotate jet 90 degrees on Y axis, facing right edge of screen
            GLKQuaternion rot = GLKQuaternionMakeWithAngleAndAxis(M_PI_2, 0, 1, 0);
            self.node.orientation = SCNVector4Make(rot.x, rot.y, rot.z, rot.w);
            self.node.position = SCNVector3Make(0, 0, aZPosition);
        }

        return self;
    }
@end


@interface GameViewController: UIViewController<SCNSceneRendererDelegate>
@end

@implementation GameViewController
{
    SCNScene * skScene;
    SCNView * skView;
    SCNNode * cameraNode;

    NSMutableArray * nodeSnapshots;
    NSDate * frameTimer;

    ESJet * jet;
}

    -(void)viewDidLoad
    {
        //default project code here...

        skScene = scene;
        skView = scnView;
        skView.delegate = self;
        skView.playing = YES;

        nodeSnapshots = [NSMutableArray array];

        jet = [[ESJet alloc] initWithNode:ship zPosition:-20];
        jet.roll = YES;
    }

    //SCNSceneRendererDelegate
    -(void)renderer:(id<SCNSceneRenderer>)aRenderer updateAtTime:(NSTimeInterval)time
    {
                void (^cloneNode)() = ^
                { 
                    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
                    dispatch_async(q, ^
                                   {
                                       SCNNode * clone = [jet.node clone];
                                       clone.opacity = 0.20f;
                                       [nodeSnapshots addObject:clone];
                                       [skScene.rootNode addChildNode:clone];
                                   });
                };

                void (^cloneNodeOnTimer)(float) = ^(float interval)
                {
                    if ((frameTimer == nil) || (frameTimer.timeIntervalSinceNow <= -interval))
                    {
                        frameTimer = [NSDate date];
                        cloneNode();
                    }
                };


        //SCNSceneRenderer protocol
        BOOL nodeIsVisible = [aRenderer isNodeInsideFrustum:jet.node withPointOfView:cameraNode];

        if (nodeIsVisible)
        {
            cloneNodeOnTimer(0.6f);
        }
        else
        {
            skView.playing = NO;
            jet.node.hidden = YES;

            if (nodeSnapshots.count > 0)
            {
                //remove transparency from last cloned node
                ((SCNNode *)[nodeSnapshots lastObject]).opacity = 1.0;
            }
        }


        [jet update:time isVisible:nodeIsVisible];
    }

    //tap any node to render new scene
    -(void)handleTap:(UIGestureRecognizer*)gestureRecognize
    {
        CGPoint tp = [gestureRecognize locationInView:skView];

        NSArray * hitTestResults = [skView hitTest:tp options:nil];
        if (hitTestResults.count > 0)
        {
            [nodeSnapshots enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop)
            {
                [(SCNNode *)obj removeFromParentNode];
            }];

            [nodeSnapshots removeAllObjects];

            jet.node.hidden = NO;
            skView.playing = YES;
        }
    }

No comments:

Post a Comment