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.
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCZnEWdKDqoObG9sHaQc8jNjanyD3aWxBx6RABbDqSoOo_9d4j4kuyxFksMBiskg9KKDxSvpGrEn6-aEVUXBdMmYxW5sr6g-Uiv-NoiOC51FjXcSKInNcFt-LYJQDVnn1TTVde4GM2IKdP/s400/j1.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBzYfrk206udLwTyIE6CzeRoyajhqRmzGD_BCrrgAqz9unttQwaLtcb7XpOViZt2HuEqV9KdJL89W46TiT2Mvjf9Qi1YYJBOP_3AXVMiMd9JA8Pk0b43UrVemwwdOALlHlUhUR4l5DbauR/s400/j2.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFAANQ_9LR1mJiJd4m2WqPNV8NYDdunN6dHIs8sihdOtVRlDzWNTtwzDER6egYuLpFqxlVp4tPbd12VLpQNh3NWzgRgGGClzQFF6ISRkIGbi_LYlufer-pIFxSXI9ST_7oWtvpT8SEbEyj/s200/j3.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAlP-mqwpETk8AV4GQ2kMxYOcaqT5W-QMLDDVmEcM8jbK5_iQE7PQl-igczwxL06bVQwliPLgFUxuOLwWR00OMeyeo17HYYpYzXV7brOTrbp_wKb8UyBEvgwwoSz3RIkwfZlvWptZIN34M/s200/j4.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSwylpSTgHprJ8HP5JCwP9Q7gWtQtR2nG1HFkIL05PR7zVy3MDhILefM1fa9TqMPEGR0ZYJo3-8htlc2BDEu7MrGuVNjI3hKf_tKh757BNNR3h5rY2roL1wsydBz70_SbaiDJYguktigvu/s200/j5.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEIxzWyt5DTy-FBrDyzEok0D4cNqYyfu_L6jXF-nTd9DfgHMIBFeWutFGXFMv8XJzxHMgGENKrwWQQcZyz4OgdehdaW7TWZAfj1luOvyrj7dJ9KkvKpB3we83SftaSx2AJSt5ecqxQE8Xu/s200/j6.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi056QWGf8JDi4sI-YJisHrP8peR6dpC4r61DnspGsA-1RBpx4U2t33NhMbTE8iuHjTTHo45M1fhjGF6VgT2IYregFvELJSr7OwAR3AmJA6-tfrC7qdsJPV7-wvf9guLvxWqWMhyCPdRd8_/s200/j7.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYdH7Gdp-t6oVcAmCuWrcT41XAzf4vMP8MgtybHRC9oVEOkz0qOKMTb54GEy_ZcnRiYKMjwAZs7_dCz4il3AlLbxDtu6UQhdThhyphenhyphen4_mVqmCtlC789GrubjRqCKY38Otn2x6rWwXTs4SC8V/s200/j8.png)
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjr4mD1pEZUk57UXCT55uTTgWetpsosgXdaDDpJLeLg30MG07bt3gFktgfB74i_MMcI2qhdil4LqSrnXUXIfjcnNoNQb_ZtkjKI1FRABBojUcM5w1NSLX2cIGk4eqhD4u3yCCOz4XQNVhP/s200/j9.png)
@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; } }