Difference Between Orientation and Rotation in Scnnode

What is the difference between Orientation and Rotation in SCNNode

There's some obvious difference between Orientation and Rotation in SceneKit framework. Orientation is expressed as a quaternion (whose result of the equation must be 1).

According to Apple documentation:

Orientation – The node’s orientation, expressed as a quaternion. Animatable.

A quaternion is a mathematical construct useful for describing rotations in three-dimensional space. Although its implementation differs from that of a 4-component vector, you specify a quaternion value using the same fields as an SCNVector4 structure.

SceneKit uses unit quaternions – those whose components satisfy the equation:

x² + y² + z² + w² == 1

for the orientation property of nodes.

var orientation: SCNQuaternion { get set }  // instance property

// There are four elements (x, y, z, w) in Structure `float4`
var orientation: SCNQuaternion = SCNQuaternion(v: float4)

typealias SCNQuaternion = SCNVector4

Rotation – The node’s orientation, expressed as a rotation angle about an axis. Animatable. The four-component rotation vector specifies the direction of the rotation axis in the first three components and the angle of rotation (in radians) in the fourth. The default rotation is the zero vector, specifying no rotation. Rotation is applied relative to the node’s pivot property.

var rotation: SCNVector4 { get set }  // instance property

var rotation: SCNVector4 = SCNVector4(x: CGFloat,
y: CGFloat,
z: CGFloat,
w: CGFloat)

As you can see orientation instance property is expressed in SCNQuaternion type but rotation instance property is expressed in SCNVector4 type.

SCNNode orientation instead of eulerAngles

Here's one of an examples how to implement UIRotationGestureRecognizer.

I copied it from How can I rotate an SCNNode.... SO post.

private var startingOrientation = GLKQuaternion.identity
private var rotationAxis = GLKVector3Make(0, 0, 0)

@objc private func handleRotation(_ rotation: UIRotationGestureRecognizer) {
guard let node = sceneView.hitTest(rotation.location(in: sceneView), options: nil).first?.node else {
return
}
if rotation.state == .began {
startingOrientation = GLKQuaternion(boxNode.orientation)
let cameraLookingDirection = sceneView.pointOfView!.parentFront
let cameraLookingDirectionInTargetNodesReference = boxNode.convertVector(cameraLookingDirection,
from: sceneView.pointOfView!.parent!)
rotationAxis = GLKVector3(cameraLookingDirectionInTargetNodesReference)
} else if rotation.state == .ended {
startingOrientation = GLKQuaternionIdentity
rotationAxis = GLKVector3Make(0, 0, 0)
} else if rotation.state == .changed {
let quaternion = GLKQuaternion(angle: Float(rotation.rotation), axis: rotationAxis)
node.orientation = SCNQuaternion((startingOrientation * quaternion).normalized())
}
}

Hope this helps.

How to calculate the difference between 2 rotations in SceneKit / ARKit

So I ultimately figured this out.
I turn each rotation into a rotation matrix - in my case I use a simd_float4x4 because I later combine it with a translation (as 4th column) into a transform matrix.
I do something like this:

let newRotationInTransformMatrix = simd_float4x4(SCNMatrix4MakeRotation(currentRotation!.w, currentRotation!.x, currentRotation!.y, currentRotation!.z))

(If anyone knows how to create such a matrix directly in the simd world, please comment. I didn't know so I first created it in the SCN notation and then converted to the sims format).

I do the same with the other rotation.
Then to find the difference between the two, I multiply one matrix by the transpose of the other:

let pivotRotationMatrix = lastMazeRotationInTransformMatirx * newRotationInTransformMatrix.transpose

This worked for me. I hope it can help others as well!

SceneKit: Apply multiple rotations to SCNNode but keep the original axes constant

What I needed was the SCNNode method rotate(by:aroundTarget:)

https://developer.apple.com/documentation/scenekit/scnnode/2867399-rotate

Rotate SCNNode then move relative to rotation?

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)
*/

Change SCNNode position without changing orientation

So after testing many things for a few days, I found out that the rotation is never actually getting set on the node; it is only getting set on the presentation node. Because of this, whenever I was setting the position, the transform of the node was automatically being recalculated based on my new position and the current rotation (which it thought was [0,0,0]), effectively resetting the node's rotation.

So, in order to solve this issue, you have to change the rotation of the node to the presentationNode's rotation.

This is what I am currently doing in my panGestureRecognizer's .Began state (after setting the touched node to currentlyMoving:

if let currentlyMoving = _currentlyMoving {
if let presentationNode = currentlyMoving.presentationNode() {
currentlyMoving.rotation = presentationNode.rotation
}
currentlyMoving.position = newLocation
}

This just sets the node's rotation to its presentation rotation before moving it so that the transform is set correctly based on what the user can see.

A problem that I was having with this was that some of the models that I downloaded were still being rotated strangely by tiny amounts, although all of the models I created myself were working fine. I had to create my own versions of the other models, after which everything has been working fine. So, I still don't quite know what was wrong with the first models that caused them to shift slightly when I set their rotations to their presentation rotations (I don't have any experienced with 3D modeling).

Get up side of SCNNode from Orientation

Here is a method that returns the index of the face that's facing up. It assumes that "boxNode" is a box made of 6 faces with the following (arbitrary) order: front / right / back / left / up / bottom. It returns the index of the face that is facing up.
Don't forget to import then .
For an arbitrary mesh, you would have to use the face normals instead of "boxNormals" (which is not obvious to compute since SceneKit meshes have one normal per vertex, not one normal per face, so you would have to compute the normals per face yourself).

- (NSUInteger) boxUpIndex:(SCNNode *)boxNode
{
SCNVector4 rotation = boxNode.rotation;
SCNVector4 invRotation = rotation; invRotation.w = -invRotation.w;

SCNVector3 up = SCNVector3Make(0,1,0);

//rotate up by invRotation
SCNMatrix4 transform = SCNMatrix4MakeRotation(invRotation.w, invRotation.x, invRotation.y, invRotation.z);
GLKMatrix4 glkTransform = SCNMatrix4ToGLKMatrix4(transform);
GLKVector3 glkUp = SCNVector3ToGLKVector3(up);
GLKVector3 rotatedUp = GLKMatrix4MultiplyVector3(glkTransform, glkUp);

//build box normals (arbitrary order here)
GLKVector3 boxNormals[6] = {{{0,0,1}},
{{1,0,0}},
{{0,0,-1}},
{{-1,0,0}},
{{0,1,0}},
{{0,-1,0}},
};

int bestIndex = 0;
float maxDot = -1;

for(int i=0; i<6; i++){
float dot = GLKVector3DotProduct(boxNormals[i], rotatedUp);
if(dot > maxDot){
maxDot = dot;
bestIndex = i;
}
}

return bestIndex;
}

SCNNode Z-rotation axis stays constant, while X and Y axes change when node is rotated

As I wrote earlier in your post it's a Gimbal Lock issue. Gimbal Lock is the loss of one DoF in a three-dimensional mechanism. There are two variables in SceneKit: eulerAngles (intrinsic euler angles, expressed in radians, represent a sequence of 3 rotations about the x-y-z axis with the following order: Z-Y-X or roll, yaw, pitch) and orientation (node’s orientation, expressed as quaternion).

To get rid of gimbal lock in ARKit and SceneKit you need to use unit quaternions, whose components satisfy the equation:

(x * x) + (y * y) + (z * z) + (w * w) == 1

For applying quaternions correctly you need to use the fourth component of this structure too (w). Quaternions are expressed in SCNVector4, where x-y-z values are normalized components, and the w field contains the rotation angle, in radians, or torque magnitude, in newton-meters).

Sample Image

Gimbal Lock occurs when the axes (of two of the three gimbals) are driven into a parallel configuration.



Related Topics



Leave a reply



Submit