Keep track of animation in scenekit (swift) - swift

I am trying to rotate a geometry on the right as long as I am touching the right side of the screen and left when touching the left side. When not touching the screen, the animation should stop.
For this I came up with the following code which works until I press again the screen and the geometry goes back to its original state instead of starting the rotatation from the previous position.
I also need to keep track of the angle for future development and node.rotation.w always returns 0.
func respondToLPGesture(gestureRecognize: UILongPressGestureRecognizer)
{
let scnView = self.view as SCNView
let spin = CABasicAnimation(keyPath: "rotation")
spin.duration = 2
spin.repeatCount = .infinity
let p = gestureRecognize.locationInView(scnView)
switch gestureRecognize.state
{
case .Began:
//Determine rotation direction
if p.x < scnView.frame.width / 2 {
//spin.fromValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: angle))
//spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: angle + 2*Float(M_PI)))
spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(M_PI)))
scnView.scene.rootNode.childNodes[1].addAnimation(spin, forKey: "spin around")
}
else {
spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(-M_PI)))
scnView.scene.rootNode.childNodes[1].addAnimation(spin, forKey: "spin around")
}
case .Ended:
println(scnView.scene.rootNode.childNodes[1].rotation.w) // Always returns 0.
scnView.scene.rootNode.childNodes[1].pauseAnimationForKey("spin around")
default:
println("Inside default")
}
}

Watching WWDC 2014 "What's New in SceneKit" helped me to find the answer.
To track a node position/rotation in Scenekit use "presentationNode".
Example below:
let angle = scnView.scene.rootNode.childNodes[1].presentationNode().rotation.w
Using the following will return the value of the targeted end of the animation which is always 0 in my code example:
let angle = scnView.scene.rootNode.childNodes[1].rotation.w

Related

SCNNode not moving due to animation

I created SCNNode object and added simple CABasicAnimation that just spins my object. I used to set velocity to the physics body of my node, but it’s not moving while animation is playing. However other animations which were created in blender are working.
Example:
self.node = SCNNode()
let obj = SCNScene(named: "art.scnassets/player.scn")!
self.node = (obj.rootNode.childNode(withName: "Body", recursively: true))!
let spin = CABasicAnimation(keyPath: "rotation")
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: Float(CGFloat(2 * Double.pi))))
spin.duration = 1
spin.repeatCount = .infinity
self.node.addAnimation(SCNAnimation(caAnimation: spin), forKey: "spin")
scene.rootNode.addChildNode(self.node)
self.player.node.physicsBody?.velocity.x = 2.0 // not working together with animation

How can I rotate 3d object using right and left arrows in Swift? (SceneKit)

How can I enable the user to move the object using the right and left arrows I put below, instead of manually moving it?
"allowscameracontrol" allows the user to rotate the object with their hand. But I just want it to be rotated using arrows.
** -> sceneView.allowsCameraControl = true**
import UIKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet weak var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
// 1: Load .obj file
let scene = SCNScene(named: "converse_obj.obj")
//Add camera node
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
//Place camera
cameraNode.position = SCNVector3(x: 0, y: 10, z: 35)
//*Set camera on scene
scene?.rootNode.addChildNode(cameraNode)
//Adding light to scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 35)
scene?.rootNode.addChildNode(lightNode)
// 6: Creating and adding ambien light to scen
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene?.rootNode.addChildNode(ambientLightNode)
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
// Set background color
sceneView.backgroundColor = UIColor.white
// Allow user translate image
sceneView.cameraControlConfiguration.allowsTranslation = false
// Set scene settings
sceneView.scene = scene
}
}
I haven't used SceneKit before.
I'm not sure if you would want to rotate your camera or rotate the node that contains the object. From the way it's named, it makes it sound like allowsCameraControl rotates the camera. In any case you would use rotationMatrixAroundZ to create a rotation matrix, and then multiply that by the matrix for whichever thing you want to rotate.
If rotating the camera is what you want to do, your code might look like this:
func rotateCameraZByDegrees(_ zRotation: Double) {
let radians = zRotation / 180.0 * Double.pi
let rotationZ = rotationMatrixAroundZ(radians: radians)
cameraNode.transform = SCNMatrix4(simd_mul(simd_float4x4(cameraNode.transform), rotationZ))
}
If you instead want to rotate your object's node, and your object's node is named sneakersNode, change the last line to:
sneakersNode.transform = SCNMatrix4(simd_mul(simd_float4x4(sneakersNode.transform), rotationZ))
This post [31881911] in stackO shows you some examples for arrow keys.
Turn allowsCameraControl = false
This post [68506605] see my answer - creating a camera control class makes things a lot easier.
For your rotation, just rotate the object directly - several ways to do this.
func resetRotation()
{
for (_, vNode) in displayNodes
{
vNode.node.removeAllActions()
}
vRotate0 = SCNAction.rotateTo(x: 0, y: 0, z: 0, duration: 2)
vRotate1 = SCNAction.rotateBy(x: 0, y: CGFloat(GLKMathDegreesToRadians(450)), z: 0, duration: 3)
vRotate2 = SCNAction.rotateBy(x: 0, y: CGFloat(GLKMathDegreesToRadians(-90)), z: 0, duration: 1)
vRotate3 = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(-45)), y: 0, z: 0, duration: 1)
vRotate4 = SCNAction.rotateBy(x: CGFloat(GLKMathDegreesToRadians(45)), y: 0, z: 0, duration: 1)
seq = SCNAction.sequence([vRotate0, vRotate1, vRotate2, vRotate3, vRotate4, vRotate3, vRotate4])
allSeq = SCNAction.repeatForever(seq)
for (_, vNode) in displayNodes
{
vNode.node.runAction(allSeq)
}
}
You could use some sequences - so your object could do a 360 and stop, come back 90 at a slower pace, whatever you want. The above repeats forever, but you can take that out.
Or just on Left key press, rotate Y += n degrees.

SWIFT problems with CGAffineTransform... button always go to origin

I got a problem with the animations from CGAffineTransform... I want to move the button and scale it up at the same time.
But if I do one animation alone everything works pretty straight forward. It scales up or moves the button by 50px.
But if I put both in the same animation its messed up. The button starts from far outside the screen to move in and doesn't scale as it should.
Also, after the movement of the button. The buttons scales up to the correct size i expected, but moves back to origin position before...
What do I miss here?
let shape = CAShapeLayer()
//shape.path = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 30, startAngle: 0, endAngle: .pi/2, clockwise: true).cgPath
shape.lineWidth = 5
shape.strokeColor = UIColor.black.cgColor
timerButton.layer.addSublayer(shape)
UIView.animate(withDuration: 2, animations: {
self.timerButton.transform = CGAffineTransform(translationX: -50, y: 0)
//self.timerButton.transform = CGAffineTransform(scaleX: 3, y: 3)
}) { (true) in
UIView.animate(withDuration: 1, animations: {
self.timerButton.transform = CGAffineTransform(scaleX: 3, y: 3)
}) { (true) in
print("later")
}
}
At completion of the transform of the x position, you need to change the constraint to make it permanent, the top constraint, after that also in the completion you need to remove the x position transformation,
If you follow these steps and after them animate your new transformation it should work

Rotate SCNNode then move relative to rotation?

My node setup:
let node = SCNNode()
node.position = SCNVector3(0.0, 0.0, 0.0)
I then wish to rotate the node by 90 degrees to the left which can be achieved with:
node.transform = SCNMatrix4Rotate(node.transform, .pi/2, 0.0, 1.0, 0.0)
Next, I want to translate the node forward one unit along the negative z axis relative to current rotation and end up with:
node.position = SCNVector3(-1.0, 0.0, 0.0)
I have no idea how to get from the rotation of the node to the position of the node programmatically.
Basically node begins at (0, 0, 0), its forward vector is the -z axis, node turns left and moves one unit forward to end up at (-1, 0, 0).
This isn't working:
func move(_ direction: moveDirection) {
switch direction {
case .forward: characterNode.position = SCNVector3(characterNode.position.x, characterNode.position.y, characterNode.position.z - 1.0)
case .left: characterNode.pivot = SCNMatrix4Rotate(characterNode.pivot, -.pi/32, 0.0, 1.0, 0.0)
case .backward: characterNode.position = SCNVector3(characterNode.position.x, characterNode.position.y, characterNode.position.z + 1.0)
case .right: characterNode.pivot = SCNMatrix4Rotate(characterNode.pivot, .pi/32, 0.0, 1.0, 0.0)
}
}
Assuming I have understood you correctly, once you have rotated your SCNNode, you want to move it forward it in that direction.
This can be done by using the worldFront value which is simply:
The local unit -Z axis (0,0,-1) in world space.
As such this may be the answer you are looking for:
//1. Create A Node Holder
let nodeToAdd = SCNNode()
//2. Create An SCNBox Geometry
let nodeGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
nodeGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
nodeGeometry.firstMaterial?.lightingModel = .constant
nodeToAdd.geometry = nodeGeometry
//3. Position It 1.5m Away From The Camera
nodeToAdd.position = SCNVector3(0, 0, -1.5)
//4. Rotate The Node By 90 Degrees On It's Y Axis
nodeToAdd.rotation = SCNVector4Make(0, 1, 0, .pi / 2)
//5. Add The Node To The ARSCNView
augmentedRealityView.scene.rootNode.addChildNode(nodeToAdd)
//6. Use The World Front To Move The Node Forward e.g
/*
nodeToAdd.simdPosition += nodeToAdd.simdWorldFront * 1.2
*/
nodeToAdd.simdPosition += nodeToAdd.simdWorldFront
//7. Print The Position
print(nodeToAdd.position)
/*
SCNVector3(x: -0.999999881, y: 0.0, z: -1.50000024)
*/

SCNVector4: Rotate on x-axis

I have a sphere and managed to rotate it. But unfortunately along the wrong axis.
My goal is a rotation like the earth along the x axis. Can you help me to apply this?
Here is my existing code:
let spin = CABasicAnimation(keyPath: "rotation")
// Use from-to to explicitly make a full rotation around z
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: Float(CGFloat(-2 * Double.pi))))
spin.duration = 30
spin.repeatCount = .infinity
sphereNode.addAnimation(spin, forKey: "spin around")
It will be something like this
self.imageView.layer.transform = CATransform3DConcat(self.imageView.layer.transform, CATransform3DMakeRotation(M_PI,1.0,0.0,0.0));
You're already have working solution:
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: CGFloat.pi * 2))
If you wanna change direction, just change vectors :)
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: CGFloat.pi * 2))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
You can rotate your Earth model about X-axis using SceneKit's Transaction:
let scene = SCNScene(named: "art.scnassets/model.scn")!
let modelEarth = scene.rootNode.childNode(withName: "model",
recursively: true)!
SCNTransaction.begin()
SCNTransaction.animationDuration = 500 // animation in seconds
SCNTransaction.animationTimingFunction = .init(name: .default)
modelEarth.rotation.x = 1
modelEarth.rotation.y = 0
modelEarth.rotation.z = 0
modelEarth.rotation.w = 100 * Float.pi // fourth component
SCNTransaction.commit()