How to zoom paused ARFaceTrackingSession - swift

I think the title is self-explanatory. I am starting a session like this:
let configuration = ARFaceTrackingConfiguration()
sceneView.session.run(configuration)
Then I add some nodes to the scene.
Then I pause the scene:
self.sceneView.session.pause()
Then, I want to programmatically zoom into the face so that the screen is filled with the face.
But I cannot figure out how. I have tried
let cameraNode = sceneView.pointOfView!
faceNode.simdScale = cameraNode.simdWorldFront * 1
But that does not seem to take any effect.
I can do it with CGAffinTransform on the SceneView itself but that approach seems wrong.

Related

Spritekit Animation not working

I'm trying to make a game where the main user is animated and i converted the gif to png and took each frame and put them all into a folder. However, right now I'm simply trying to get the first frame to appear, but nothing appears. Sorry if i worded this weird my English isn't very good.
Right now I think just the first frame, "trumpyhappy1.png" should show when I try to run it, but when I run it nothing shows up.
For anyone that stumbles upon this and is still wondering why their animations are blank. I found that programmatically adding the actions would cause this before viewDidAppear got called. However, setting the animation in the scene file directly I was able to get around the blank animations
So basically in spritekit to animate a spritenode you should be doing this
override func didMove() {
let mainGuy = SKSpriteNode()
self.addChild(mainGuy)
// Give your size and position and everything
// Then to animate that spriteNode with frames
let textureAtlas = SKTextureAtlas(named: "trump")
let frames: [SKTexture] = []
for i in 0...textureAtlas.textureNames.count {
var name = "trumphappy\(i).png"
frames.append(SKTexture(named: name))
}
// Then to create the animation add this code
let animation = SKAction.animate(with: frames, timePerFrame: ) // Whatever time you want your animation to spend on each frame
}
That should work, hope it works!

How do I programmatically move an ARAnchor?

I'm trying out the new ARKit to replace another similar solution I have. It's pretty great! But I can't seem to figure out how to move an ARAnchor programmatically. I want to slowly move the anchor to the left of the user.
Creating the anchor to be 2 meters in front of the user:
var translation = matrix_identity_float4x4
translation.columns.3.z = -2.0
let transform = simd_mul(currentFrame.camera.transform, translation)
let anchor = ARAnchor(transform: transform)
sceneView.session.add(anchor: anchor)
later, moving the object to the left/right of the user (x-axis)...
anchor.transform.columns.3.x = anchor.transform.columns.3.x + 0.1
repeated every 50 milliseconds (or whatever).
The above does not work because transform is a get-only property.
I need a way to change the position of an AR object in space relative to the user in a way that keeps the AR experience intact - meaning, if you move your device, the AR object will be moving but also won't be "stuck" to the camera like it's simply painted on, but moves like you would see a person move while you were walking by - they are moving and you are moving and it looks natural.
Please note the scope of this question relates only to how to move an object in space in relation to the user (ARAnchor), not in relation to a plane (ARPlaneAnchor) or to another detected surface (ARHitTestResult).
Thanks!
You don't need to move anchors. (hand wave) That's not the API you're looking for...
Adding ARAnchor objects to a session is effectively about "labeling" a point in real-world space so that you can refer to it later. The point (1,1,1) (for example) is always the point (1,1,1) — you can't move it someplace else because then it's not the point (1,1,1) anymore.
To make a 2D analogy: anchors are reference points sort of like the bounds of a view. The system (or another piece of your code) tells the view where it's boundaries are, and the view draws its content relative to those boundaries. Anchors in AR give you reference points you can use for drawing content in 3D.
What you're asking is really about moving (and animating the movement of) virtual content between two points. And ARKit itself really isn't about displaying or animating virtual content — there are plenty of great graphics engines out there, so ARKit doesn't need to reinvent that wheel. What ARKit does is provide a real-world frame of reference for you to display or animate content using an existing graphics technology like SceneKit or SpriteKit (or Unity or Unreal, or a custom engine built with Metal or GL).
Since you mentioned trying to do this with SpriteKit... beware, it gets messy. SpriteKit is a 2D engine, and while ARSKView provides some ways to shoehorn a third dimension in there, those ways have their limits.
ARSKView automatically updates the xScale, yScale, and zRotation of each sprite associated with an ARAnchor, providing the illusion of 3D perspective. But that applies only to nodes attached to anchors, and as noted above, anchors are static.
You can, however, add other nodes to your scene, and use those same properties to make those nodes match the ARSKView-managed nodes. Here's some code you can add/replace in the ARKit/SpriteKit Xcode template project to do that. We'll start with some basic logic to run a bouncing animation on the third tap (after using the first two taps to place anchors).
var anchors: [ARAnchor] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Start bouncing on touch after placing 2 anchors (don't allow more)
if anchors.count > 1 {
startBouncing(time: 1)
return
}
// Create anchor using the camera's current position
guard let sceneView = self.view as? ARSKView else { return }
if let currentFrame = sceneView.session.currentFrame {
// Create a transform with a translation of 30 cm in front of the camera
var translation = matrix_identity_float4x4
translation.columns.3.z = -0.3
let transform = simd_mul(currentFrame.camera.transform, translation)
// Add a new anchor to the session
let anchor = ARAnchor(transform: transform)
sceneView.session.add(anchor: anchor)
anchors.append(anchor)
}
}
Then, some SpriteKit fun for making that animation happen:
var ballNode: SKLabelNode = {
let labelNode = SKLabelNode(text: "🏀")
labelNode.horizontalAlignmentMode = .center
labelNode.verticalAlignmentMode = .center
return labelNode
}()
func startBouncing(time: TimeInterval) {
guard
let sceneView = self.view as? ARSKView,
let first = anchors.first, let start = sceneView.node(for: first),
let last = anchors.last, let end = sceneView.node(for: last)
else { return }
if ballNode.parent == nil {
addChild(ballNode)
}
ballNode.setScale(start.xScale)
ballNode.zRotation = start.zRotation
ballNode.position = start.position
let scale = SKAction.scale(to: end.xScale, duration: time)
let rotate = SKAction.rotate(toAngle: end.zRotation, duration: time)
let move = SKAction.move(to: end.position, duration: time)
let scaleBack = SKAction.scale(to: start.xScale, duration: time)
let rotateBack = SKAction.rotate(toAngle: start.zRotation, duration: time)
let moveBack = SKAction.move(to: start.position, duration: time)
let action = SKAction.repeatForever(.sequence([
.group([scale, rotate, move]),
.group([scaleBack, rotateBack, moveBack])
]))
ballNode.removeAllActions()
ballNode.run(action)
}
Here's a video so you can see this code in action. You'll notice that the illusion only works as long as you don't move the camera — not so great for AR. When using SKAction, we can't adjust the start/end states of the animation while animating, so the ball keeps bouncing back and forth between its original (screen-space) positions/rotations/scales.
You could do better by animating the ball directly, but it's a lot of work. You'd need to, on every frame (or every view(_:didUpdate:for:) delegate callback):
Save off the updated position, rotation, and scale values for the anchor-based nodes at each end of the animation. You'll need to do this twice per didUpdate callback, because you'll get one callback for each node.
Work out position, rotation, and scale values for the node being animated, by interpolating between the two endpoint values based on the current time.
Set the new attributes on the node. (Or maybe animate it to those attributes over a very short duration, so it doesn't jump too much in one frame?)
That's kind of a lot of work to shoehorn a fake 3D illusion into a 2D graphics toolkit — hence my comments about SpriteKit not being a great first step into ARKit.
If you want 3D positioning and animation for your AR overlays, it's a lot easier to use a 3D graphics toolkit. Here's a repeat of the previous example, but using SceneKit instead. Start with the ARKit/SceneKit Xcode template, take the spaceship out, and paste the same touchesBegan function from above into the ViewController. (Change the as ARSKView casts to as ARSCNView, too.)
Then, some quick code for placing 2D billboarded sprites, matching via SceneKit the behavior of the ARKit/SpriteKit template:
// in global scope
func makeBillboardNode(image: UIImage) -> SCNNode {
let plane = SCNPlane(width: 0.1, height: 0.1)
plane.firstMaterial!.diffuse.contents = image
let node = SCNNode(geometry: plane)
node.constraints = [SCNBillboardConstraint()]
return node
}
// inside ViewController
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// emoji to image based on https://stackoverflow.com/a/41021662/957768
let billboard = makeBillboardNode(image: "⛹ī¸".image())
node.addChildNode(billboard)
}
Finally, adding the animation for the bouncing ball:
let ballNode = makeBillboardNode(image: "🏀".image())
func startBouncing(time: TimeInterval) {
guard
let sceneView = self.view as? ARSCNView,
let first = anchors.first, let start = sceneView.node(for: first),
let last = anchors.last, let end = sceneView.node(for: last)
else { return }
if ballNode.parent == nil {
sceneView.scene.rootNode.addChildNode(ballNode)
}
let animation = CABasicAnimation(keyPath: #keyPath(SCNNode.transform))
animation.fromValue = start.transform
animation.toValue = end.transform
animation.duration = time
animation.autoreverses = true
animation.repeatCount = .infinity
ballNode.removeAllAnimations()
ballNode.addAnimation(animation, forKey: nil)
}
This time the animation code is a lot shorter than the SpriteKit version.
Here's how it looks in action.
Because we're working in 3D to start with, we're actually animating between two 3D positions — unlike in the SpriteKit version, the animation stays where it's supposed to. (And without the extra work for directly interpolating and animating attributes.)

Particle emitter not showing over image (swift spritekit)

This is a pretty basic question (Im new with swift and spritekit).
So anyway Im adding a particle emitter to a spritenode(call it ball) and adding the ball to my scene. This ball is on top of another spritenode that represents the background. When I run the app, the ball appears fine but the particle emitter doesn't show. The emitter shows fine when I remove the background, but not when I add the background. I will post a code example...
//Background
let background = SKSpriteNode(imageNamed:"Background")
background.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
background.zPosition = 0
self.addChild(background)
//Ball
let ball = SKSpriteNode(imageNamed: "ball")
ball.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
ball.zPosition = 1
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.frame.size)
ball.physicsBody?.usesPreciseCollisionDetection = true
//Emitter
let emitter = SKEmitterNode(fileNamed: "emitter")
emitter.targetNode = self
ball.addChild(emitter)
self.addChild(ball)
Like I said, this seems to be a pretty basic question. I just don't know what Im doing wrong. Just to reiterate, when I run this, the emitter doesn't show. But if I take away the background, the emitter shows.
Thanks For the help :)
Simple answer - you just need to set your emitter's zPosition to be higher than the background's (but less than the ball). zPosition determines the order in which things are rendered, and the higher the number the later it is rendered (thus putting it on top).
It's also possible that your emitter nodes and your background are the same color so it appears as though it isn't there.
So I tried changing the zPosition of emitter and it didn't change anything. But when I changed the zPosition of the background to -1 the emitter showed. I wish I knew why this works haha. But thanks for the suggestions :)
I had the same thing happen but when you said it doesn't happen when you comment out the background, I remembered that there is a command in GameViewController that you have to set false.
skView.ignoresSiblingOrder = false

How do I make my camera node focus on a tapped node? (SceneKit)

I created an empty node and added a set of nodes to the empty node and finally adding that to my root node. After which, I create a camera node as shown below. I am trying to add a feature to my scenekit app where by when a user tap a node, the camera moves and focus on that node. In my handleTap function, I am trying to get the position of the node and then fix the camera to that position but it is not working. Any suggestions?
//snippet
var emptyNode = SCNNode()
emptyNode.addChildNode(Node)
rootNode.addChildNode(emptyNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(1,0,10)
rootNode.addChildNode(cameraNode)
func handleTap(recognizer: UITapGestureRecognizer) {
let location = recognizer.locationInView(sceneView)
let hits = sceneView!.hitTest(location, options: nil)
if let tap = hits.first?.node {
tappNode = tap
myScene?.cameraNode.position = tappNode.position
}
EDIT
So, I am making a little progress but I still cannot figure out the position of my node. The Updated code is given below. What I have done is to create a new node and attached a camera to it. That node is then added to the tap node as a child.I still cannot get the position of the tap node within the context of empty node.
let cameraNode = SCNNode()
cameraNode.name = "cameraNode"
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(1,0,10)
tappNode.addChildNode(cameraNode)
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(3.0)
SCNTransaction.setCompletionBlock() {
print("done")
}
sceneView!.pointOfView = cameraNode
SCNTransaction.commit()
When you move the camera node to the tapped node (or create a new camera at the tapped node), you make it nearly impossible to see the tapped node from that camera.
How about
let lookAtConstraint = SCNLookAtConstraint.init(tappNode)
sceneView!.pointOfView.constraints = [lookAtConstraint]
Keep a reference to lookAtConstraint so that you can change its target on subsequent taps.
In my own app, I have one "default" point of view as SCNNode, which I define at startup, e.g.
defaultPoint
By double-tapping, the users resets to this point of view, simply so
scnView.pointOfView = self.defaultPoint
where scnView is the main SCNView with all nodes. In your case, I guess you have to define by trial-and-error one or more node-specific points-of-view nodes (which obviously don't have the same coordinates as the nodes themselves), and then set the scene view point-of-view accordingly.
PS my main SCNView has allowsCameraControl set to true.

SpriteKit - Adding a blur to the entire Scene

I am trying to blur my entire GameScene when my pause button is pressed. I have a method called blurSceen() but it doesn't seem to be adding the effect to the scene. Is there a way I can accomplish this or am I doing something wrong? I have viewed other posts about this topic but haven't been able to accomplish the effect.
func blurScreen() {
let effectsNode = SKEffectNode()
let filter = CIFilter(name: "CIGaussianBlur")
let blurAmount = 10.0
filter!.setValue(blurAmount, forKey: kCIInputRadiusKey)
effectsNode.filter = filter
effectsNode.position = self.view!.center
effectsNode.blendMode = .Alpha
// Add the effects node to the scene
self.addChild(effectsNode)
}
From the SKEffectNode docs:
An SKEffectNode object renders its children into a buffer and optionally applies a Core Image filter to this rendered output.
The effect node applies a filter only to its child nodes. Your effect node has no children, so there's nothing to apply a filter to.
Probably what you want is to add an effect node to your scene early on--but don't set the filter on it yet--and put all the nodes that you'll later want to blur in as its children. When it comes time to apply a blur, set the filter on the (already existing, already with children) effect node.
I had the same issue trying to blur the whole SKScene and it just wasn't working. The missing piece of the puzzle was this line:
shouldEnableEffects = true
Swift 4:
from gameScene:
let blur = CIFilter(name:"CIGaussianBlur",withInputParameters: ["inputRadius": 10.0])
self.filter = blur
self.shouldRasterize = true
self.shouldEnableEffects = true