4

I have an SCNBox in an SCNScene. Once the scene animates the SCNBox changes is orientation which can be seen by checking its presentationNode.orientation. This value is returned in an SCNVector4. How do I determine which side of the SCNBox is the up facing side from the values returned in the SCNVector4?

I have tried to normalize the data and came up with the following data

Side 1 = (-x, 0, 0, +x)
Side 2 = (0, -x, 0 +y)
Side 3 = (-x, +x, -y, y)
Side 4 = (x, x, y, y)
Side 5 = (-x, 0, +y, 0)
Side 6 = (-x, -y, +y, -x) 

Unfortunately this isn't always true and checking against it sometimes returns invalid sides occasionally.

Is there a reliable way to determine the up facing side of the SCNBox from its geometry's orientation property?

Edit: Based on Toyos answer, I have come up with the following code, which doesn't work. Hopefully this will help with coming closer to the end goal. I also used code found at Extracting vertices from scenekit to get the vertices of my SCNBoxNode.

- (NSNumber *)valueForRotation:(SCNVector4)rotation andGeometry:(SCNGeometry*)geometry {
    SCNVector4 inverse = SCNVector4Make(rotation.x, rotation.y, rotation.z, -rotation.w);

    CATransform3D transform = CATransform3DMakeRotation(inverse.w, inverse.x, inverse.y, inverse.z);

    GLKMatrix4 matrix = GLKMatrix4Make(transform.m11, transform.m12, transform.m13, transform.m14, transform.m21, transform.m22, transform.m23, transform.m24, transform.m31, transform.m32, transform.m33, transform.m34, transform.m41, transform.m42, transform.m43, transform.m44);

    GLKVector4 vector = GLKVector4Make(rotation.x, rotation.y, rotation.z, rotation.w);

    GLKVector4 finalVector = GLKMatrix4MultiplyVector4(matrix, vector);

    NSArray *vertexSources = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];

    SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources

    NSInteger stride = vertexSource.dataStride; // in bytes
    NSInteger offset = vertexSource.dataOffset; // in bytes

    NSInteger componentsPerVector = vertexSource.componentsPerVector;
    NSInteger bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
    NSInteger vectorCount = vertexSource.vectorCount;

    SCNVector3 vertices[vectorCount]; // A new array for vertices

    // for each vector, read the bytes
    NSLog(@"vetor count %i",vectorCount);
    float highestProduct = 0;
    int highestVector = -1;
    NSMutableArray *highVectors;
    for (NSInteger i=0; i<vectorCount; i++) {

        // Assuming that bytes per component is 4 (a float)
        // If it was 8 then it would be a double (aka CGFloat)
        float vectorData[componentsPerVector];

        // The range of bytes for this vector
        NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
                                        bytesPerVector);   // and read the lenght of one vector

        // Read into the vector data buffer
        [vertexSource.data getBytes:&vectorData range:byteRange];

        // At this point you can read the data from the float array
        float x = vectorData[0];
        float y = vectorData[1];
        float z = vectorData[2];

        // ... Maybe even save it as an SCNVector3 for later use ...
        vertices[i] = SCNVector3Make(x, y, z);

        // ... or just log it 
        NSLog(@"x:%f, y:%f, z:%f", x, y, z);
        float product = (x * finalVector.x) + (y * finalVector.y) +  (z * finalVector.z);
        if (product > highestProduct) {
            highestProduct = product;
            highestVector = i;
        }

    }

    NSLog(@"highestProduct = %f",highestProduct);
    NSLog(@"highestVector = %i",highestVector);
    NSLog(@"top verticy = %f, %f, %f",vertices[highestVector].x,vertices[highestVector].y,vertices[highestVector].z);

    return [NSNumber numberWithInt:highestVector];
}
Community
  • 1
  • 1
WhoaItsAFactorial
  • 3,538
  • 4
  • 28
  • 45

2 Answers2

9

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;
}
Toyos
  • 4,024
  • 15
  • 16
  • This does not work, 4 is returned for every side. This is how I create my node, maybe I'm doing something wrong here instead? `SCNNode *block = [SCNNode node]; block.position = SCNVector3Make(20, 0, 0); block.rotation = SCNVector4Make(0, 0, 0, 0); block.geometry = [SCNBox boxWithWidth:8 height:8 length:8 chamferRadius:0]; SCNPhysicsBody *body = [SCNPhysicsBody dynamicBody]; body.mass = 5; body.restitution = .7; body.friction = 0.5; block.physicsBody = body; [[scene rootNode] addChildNode:block];` – WhoaItsAFactorial Jun 12 '14 at 13:19
  • Edit: Figured it out, I needed to change `boxNode.rotation` to `boxNode.presentationNode.node`.Thank you so much! I wasn't even close with my code :( – WhoaItsAFactorial Jun 12 '14 at 13:25
2

node.orientation will give you a quaternion. You can also use node.eulerAngles or node.rotation (axis angle) if that makes your maths easier.

In all cases I think you need to apply the inverse rotation to the vector [0, 1, 0] (up vector). The result vector (V) will give you the direction of the up face. To then find the up face, compare the dot products of the box faces and V. The highest dot product is the one of the face that is facing up.

(An alternative way is to rotate the box's faces instead and do the dot products with [0,1,0].)

Toyos
  • 4,024
  • 15
  • 16
  • Could you provide a bit more on how I "apply the inverse rotation to the vector"? – WhoaItsAFactorial Jun 10 '14 at 02:03
  • The simplest is with axis angle (node.rotation). the inverse rotation of [x, y, z, angle] is [x, y, z, -angle]. then to apply this rotation to a vector first build the rotation matrix (by using GLKit, SIMD or CATransform3DMakeRotation(angle, x, y, z). Once you have your matrix, transform the vector with this matrix by multiplying the vector against the matrix (GLKit or SIMD are best here since CA has no built in function for this). Note that SceneKit provides utility functions to convert SCNVector3 to GLKit, CA or SIMD) – Toyos Jun 10 '14 at 13:45
  • I'm going to be completely honest, in that I am still completely lost. But, you clearly know what you are talking about so +1 for your answer. Thank you for taking the time to explain it even further for me. – WhoaItsAFactorial Jun 11 '14 at 03:23
  • Also, added an edit to my question with my current code progress (or I guess, lack there of.) – WhoaItsAFactorial Jun 11 '14 at 04:54
  • @Toyos: I would be grateful if you could help me with a similar situation. I have used your code to get the face pointing upwards, however, it doesn't work. https://stackoverflow.com/questions/62155439/get-the-index-of-the-face-pointing-up-of-scnnode – Amrit Sidhu Jun 02 '20 at 16:05