How to Rotate Object in a Scene with Pan Gesture - Scenekit

How to rotate object in a scene with pan gesture - SceneKit

The problem is solved

if(gestureRecognize.state == UIGestureRecognizerState.Ended) {

let currentPivot = geometryNode.pivot
let changePivot = SCNMatrix4Invert( geometryNode.transform)

geometryNode.pivot = SCNMatrix4Mult(changePivot, currentPivot)

geometryNode.transform = SCNMatrix4Identity
}

SceneKit - rotate SCNNode with pan gesture based on direction

After a LOT of research and few hours of head-banging the wall, i finally found a working solution.

My working code:

func handlePan(sender: UIPanGestureRecognizer){

// get pan direction
let velocity: CGPoint = sender.velocity(in: sender.view!)
if self.panDirection == nil {
self.panDirection = GameHelper.getPanDirection(velocity: velocity)
}

// if selected brick
if self.selectedBrickNode != nil {

if sender.state == UIGestureRecognizerState.began{
lastAngle = 0.0 // reset last angle
}

let translation = sender.translation(in: sender.view!)
let anglePan = (self.panDirection == "horizontal") ? GameHelper.deg2rad(deg: Float(translation.x)) : GameHelper.deg2rad(deg: Float(translation.y))

let x:Float = (self.panDirection == "vertical" ) ? 1 : 0.0
let y:Float = (self.panDirection == "horizontal" ) ? 1 : 0.0

// calculate the angle change from last call
let fraction = anglePan - lastAngle
lastAngle = anglePan

// perform rotation by difference to last angle
selectedBrickNode.transform = SCNMatrix4Mult(selectedBrickNode.transform,SCNMatrix4MakeRotation(fraction, x, y, 0.0))


if(sender.state == UIGestureRecognizerState.ended) {

// calculate angle to snap to 90 degree increments
let finalRotation = GameHelper.rad2deg(rad:anglePan)
let diff = finalRotation.truncatingRemainder(dividingBy: 90.0)
var finalDiff = Float(0.0)

switch diff {
case 45..<90 :
finalDiff = 90 - diff
case 0..<45 :
finalDiff = -diff
case -45..<0 :
finalDiff = abs(diff)
case -90 ..< -45 :
finalDiff = -90 - diff
default:
print("do nothing")
}

// final transform to apply snap to closest 90deg increment
let snapAngle = GameHelper.deg2rad(deg: finalDiff)
selectedBrickNode.transform = SCNMatrix4Mult(selectedBrickNode.transform, SCNMatrix4MakeRotation(snapAngle, x, y, 0.0))

// remove highlight from node and deselect
self.selectedBrickNode?.geometry?.materials = [hlp.defaultMaterial]
self.selectedBrickNode = nil
}
}


}

and GameHelper.swift

class GameHelper {

static func rad2deg( rad:Float ) -> Float {
return rad * (Float) (180.0 / Double.pi)
}

static func deg2rad( deg:Float ) -> Float{
return deg * (Float)(Double.pi / 180)
}

static func getPanDirection(velocity: CGPoint) -> String {
var panDirection:String = ""
if ( velocity.x > 0 && velocity.x > abs(velocity.y) || velocity.x < 0 && abs(velocity.x) > abs(velocity.y) ){
panDirection = "horizontal"
}

if ( velocity.y < 0 && abs(velocity.y) > abs(velocity.x) || velocity.y > 0 && velocity.y > abs(velocity.x)) {
panDirection = "vertical"
}


return panDirection
}

}

Rotate a node using pan gesture in SceneKit iOS

I think you might be overcomplicating what you need to do here.

In my example, I have created an SCNNode with an SCNBox Geometry and set it's Euler Angles as per your example: (x: -90, y: 0, z: 0).

The 1st thing you need to do, is create a variable to store the rotationAngle around the YAxis:

var currentAngleY: Float = 0.0

Then try this function in order to rotate your node around the YAxis (which works fine by the way):

 /// Rotates An Object On It's YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
@objc func rotateObject(_ gesture: UIPanGestureRecognizer) {

guard let nodeToRotate = currentNode else { return }

let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY

nodeToRotate.eulerAngles.y = newAngleY

if(gesture.state == .ended) { currentAngleY = newAngleY }

print(nodeToRotate.eulerAngles)
}

Hope it helps...

SceneKit: pan gesture not storing previous rotation

Here we go, I figured it out after some major changes, info from other SO questions, and a different approach. Using this code you can perfectly rotate a node on any axis (y, in my case) and then "store" its rotation to continue rotating with another pan afterwards.

var previousRotation = SCNVector4(x: 0, y: 0, z: 0, w: 0)

@objc func pan(gesture: UIPanGestureRecognizer) {
if gesture.numberOfTouches == 2 {
let view = self.view as! SCNView
let node = view.scene!.rootNode.childNode(withName: "Node", recursively: false)
let translate = gesture.translation(in: view)

var newAngle = Float(translate.x) * Float(Double.pi) / 180.0

var rotationVector = SCNVector4()
rotationVector = SCNVector4(x: 0, y: previousRotation.y + Float(translate.y), z: 0, w: previousRotation.w - newAngle)

switch gesture.state {
case .began:
previousRotation = node!.rotation
break
case .changed:
node!.rotation = rotationVector
break
default: break
}
}
}

Hopefully this helps someone else in the future.

SceneKit cube rotation with multiple UIPanGestureRecognizers

Combine your current two gestures into one. Here's the relevant portion of the code I'm using:

func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)

var newAngleX = (Float)(translation.y)*(Float)(M_PI)/180.0
newAngleX += currentAngleX
var newAngleY = (Float)(translation.x)*(Float)(M_PI)/180.0
newAngleY += currentAngleY

baseNode.eulerAngles.x = newAngleX
baseNode.eulerAngles.y = newAngleY

if(sender.state == UIGestureRecognizerState.Ended) {
currentAngleX = newAngleX
currentAngleY = newAngleY
}
}

Here's a gesture for zooming as well:

func pinchGesture(sender: UIPinchGestureRecognizer) {
let zoom = sender.scale
var z = cameraNode.position.z * Float(1.0 / zoom)
z = fmaxf(zoomLimits.min, z)
z = fminf(zoomLimits.max, z)

cameraNode.position.z = z
}

Edit: I found a better way to rotate the model. In the panGesture code at the top, the x-axis pivots as you rotate about the y. This means if you rotate 180 about the y, rotation about the x is opposite your finger motion. The method also restricts motion to two degrees of freedom. The method linked to below, even though it doesn't directly affect the z-axis, somehow seems to allow three degrees of freedom. It also makes all vertical swipes rotate about the x in the logical direction.
How to rotate object in a scene with pan gesture - SceneKit

SceneKit: move camera towards direction its facing with pan gesture

In Objective C. The key part is the last three lines and specifically the order of multiplication of the matrices in the last line (causing the movement to happen in local space). If the transmat and cammat are switched it would behave again like you have now (moving in world space). The refactor part is just something that works for my specific situation where both perspective and orthographic camera is possible.

-(void)panCamera :(CGPoint)location {

CGFloat dx = _prevlocation.x - location.x;
CGFloat dy = location.y - _prevlocation.y;
_prevlocation = location;

//refactor dx and dy based on camera distance or orthoscale
if (cameraNode.camera.usesOrthographicProjection) {
dx = dx / 416 * cameraNode.camera.orthographicScale;
dy = dy / 416 * cameraNode.camera.orthographicScale;
} else {
dx = dx / 720 * cameraNode.position.z;
dy = dy / 720 * cameraNode.position.z;
}

SCNMatrix4 cammat = self.cameraNode.transform;
SCNMatrix4 transmat = SCNMatrix4MakeTranslation(dx, 0, dy);
self.cameraNode.transform = SCNMatrix4Mult(transmat, cammat);

}

How to use a pan gesture to rotate a camera in SceneKit using quaternions

I was able to get this working using quaternions. The full code is here: ThreeSixtyPlayer. A sample is here:

    let orientation = cameraNode.orientation

// Use the pan translation along the x axis to adjust the camera's rotation about the y axis (side to side navigation).
let yScalar = Float(translationDelta.x / translationBounds.size.width)
let yRadians = yScalar * maxRotation

// Use the pan translation along the y axis to adjust the camera's rotation about the x axis (up and down navigation).
let xScalar = Float(translationDelta.y / translationBounds.size.height)
let xRadians = xScalar * maxRotation

// Represent the orientation as a GLKQuaternion
var glQuaternion = GLKQuaternionMake(orientation.x, orientation.y, orientation.z, orientation.w)

// Perform up and down rotations around *CAMERA* X axis (note the order of multiplication)
let xMultiplier = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0)
glQuaternion = GLKQuaternionMultiply(glQuaternion, xMultiplier)

// Perform side to side rotations around *WORLD* Y axis (note the order of multiplication, different from above)
let yMultiplier = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0)
glQuaternion = GLKQuaternionMultiply(yMultiplier, glQuaternion)

cameraNode.orientation = SCNQuaternion(x: glQuaternion.x, y: glQuaternion.y, z: glQuaternion.z, w: glQuaternion.w)

SceneKit - Adding drift to a sphere rotated via a pan gesture

The way I've handled things like this before is to split the gesture handler into sections for the gesture state beginning, changing and ending. If the rotation is working for you all you need to do is add code to the bottom of your if (sender.state == .Ended) statement.

After lastHeightRatio = heightRatio you can find the velocity of the pan in the view using sender.velocityInView(sender.view!) and then find the horizontal component as before, then add an SCNAction to rotate the node with an EaseOut timing mode.

You'll likely need a multiplier to translate the velocity in the view (measured in points) to the angle you wish to rotate through (measured in radians). A velocity of 10 points, which is relatively small, would result in a very fast rotation.

Also you'll need to remove all actions from the node when the gesture begins again if you want a touch to stop the residual rotation.

Some sampel code would be:

if (sender.state == .Ended) {
lastWidthRatio = widthRatio
lastHeightRatio = heightRatio

// Find velocity
let velocity = sender.velocityInView(sender.view!)

// Create Action for whichever axis is the correct one to rotate about.
// The 0.00001 is just a factor to provide the rotation speed to pan
// speed ratio you'd like. Play with this number and the duration to get the result you want
let rotate = SCNAction.rotateByX(velocity.x * 0.00001, y: 0, z: 0, duration: 2.0)

// Run the action on the camera orbit node (if I understand your setup correctly)
cameraOrbit.runAction(rotate)
}

This should be enough to get you started.



Related Topics



Leave a reply



Submit