Вопрос

I am working on understanding the semantics of Scene Kit's use of transform related properties for nodes. In particular, the pivot property on a node is not a single point, but a full transform matrix, and I haven't found reference to anything comparable in the Collada spec or other documentation.

Does anyone have experience is with using the pivot matrix in Scene Kit, or a property with similar semantics? can someone provide a brief explanation / sample of how this is used or a pointer to related documentation? It looks like a potentially useful approach, but I'd rather not have to reverse engineer it if I can avoid it.

Edit: Based on OMZ's response, it would seem like the pivot might just be another layer in the transform tree. However, if th this is so, how would it be different than just adding an intermediate node? What would be the bais for determining where to appy which transforms?

Это было полезно?

Решение

I tried out @omz's idea. but go some conflicting results, so I dug into this some more with some interesting results.

The answer is the inverse of the pivot is applied as a child of the main transformation. This provides quite different semantics than supplying an intermediate parent node.

Specifically, if I start with a parent node with transform P, and a child node with transform C, they combine to make a world transform W = CATransform3DConcat(C, P). The same world transform W is obtained by setting the parent node's pivot to the inverse of C, and not setting a transform on the child.

This approach does appear to provide some additional utility as it cannot be duplicated by interposing an intermediate node, as well as being inverted before being applied. As Hal Mueller pointed out, the documentation does say the pivot influences rotation and scale, as well as position, but is silent on the nature of the influence.

Here are the result of testing this conclusion:

Set parent rotation and node position.

Transform:
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  1.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  1.00,  2.00,  3.00,  1.00 }

Pivot:
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  1.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  0.00,  0.00,  0.00,  1.00 }

World Transform:
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  0.00, -1.00,  0.00,  0.00 }
{  1.00, -3.00,  2.00,  1.00 }
2013-04-04 13:49:55.453 Model Importer[43504:303] 

CATransform3DConcat(node.transform, node.parentNode.transform)]
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  0.00, -1.00,  0.00,  0.00 }
{  1.00, -3.00,  2.00,  1.00 }
2013-04-04 13:49:55.454 Model Importer[43504:303] 

Set parent rotation and parent.pivot to inverse position.

Transform:
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  1.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  0.00,  0.00,  0.00,  1.00 }

Pivot:
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  1.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  0.00,  0.00,  0.00,  1.00 }

World Transform:
{  1.00,  0.00,  0.00,  0.00 }
{  0.00,  0.00,  1.00,  0.00 }
{  0.00, -1.00,  0.00,  0.00 }
{  1.00, -3.00,  2.00,  1.00 }

Here's the code:

    // Verification
    SCNVector3 v3 = {1.0, 2.0, 3.0};
    SCNVector4 v4 = {1.0, 0, 0, M_PI/2.0};
    node.parentNode.rotation = v4;
    node.position = v3;
    NSLog(@"\n\nSet parent rotation and node position.%@",
          [self transformsForNodeToString:node]);
    //
    // Verify match with CATransform3DConcat
    NSLog(@"\n\nCATransform3DConcat(node.transform, node.parentNode.transform)]%@",
          [self transformToString:CATransform3DConcat(node.transform, node.parentNode.transform)]);
    //
    // Clear the child node transform and put the inverse in the parent's pivot.
    CATransform3D position = node.transform;
    CATransform3D inversePosition = CATransform3DInvert(position);

    node.transform = CATransform3DIdentity;
    node.parentNode.pivot = inversePosition;
    NSLog(@"\n\nSet parent rotation and parent.pivot to inverse position.%@",
          [self transformsForNodeToString:node]);
    node.parentNode.pivot = CATransform3DIdentity;
    node.parentNode.transform = CATransform3DIdentity;


+ (NSString*)transformsForNodeToString: (SCNNode*)node {
    NSString* result = @"\n";
    result = [result stringByAppendingFormat:
          @"\nTransform:%@\nPivot:%@\nWorld Transform:%@",
          [self transformToString:node.transform],
          [self transformToString:node.pivot],
          [self transformToString:[node worldTransform]]];
    return result;
}

+ (NSString*)transformToString: (CATransform3D)transform {
    NSString* result = @"\n";
    result = [result stringByAppendingFormat:
              @"{ % .2f, % .2f, % .2f, % .2f }\n", transform.m11, transform.m12, transform.m13, transform.m14];
    result = [result stringByAppendingFormat:
              @"{ % .2f, % .2f, % .2f, % .2f }\n", transform.m21, transform.m22, transform.m23, transform.m24];
    result = [result stringByAppendingFormat:
              @"{ % .2f, % .2f, % .2f, % .2f }\n", transform.m31, transform.m32, transform.m33, transform.m34];
    result = [result stringByAppendingFormat:
              @"{ % .2f, % .2f, % .2f, % .2f }\n", transform.m41, transform.m42, transform.m43, transform.m44];
    return result;
}

Comments welcome!

Другие советы

I don't have much experience with SceneKit, but it's quite common in 3D modeling apps that the pivot is a point that can be rotated (sometimes called a locator). That allows you for example to make an object rotate around its own axis more easily (which doesn't necessarily correspond to a world axis).

For a pivot that isn't rotated, the matrix would simply be CATransform3DMakeTranslation(x, y, z).

EDIT: I think the fact that the pivot is specified as a full matrix is just because it's a convenient data structure and consistent with the rest of the model. I can't think of a situation where you'd want to scale a pivot, but representing it as a matrix makes it easy to operate on it with the usual matrix manipulation functions.

You could of course represent a pivot as just another intermediate node and get the same results, but the pivot is a more "semantic" way of expressing an object's center, and more suitable for interactive editing. When you select an object in a modeling app, it's common to locate the transformation tool handles where the pivot is, so that you can rotate/scale an object around its "natural" center without having to navigate up in the node hierarchy.

I can't really find any good reference material on this. Most of the things that turn up seem to deal with specific implementation details in various software packages.

I think you'll find some relevant samples in Core Animation material. The pivot of an SCNNode is defined as a CATransform3D, which is used throughout Core Animation on both Mac and iOS.

I haven't dug into Scene Kit enough myself to be authoritative. But I found this post by Brad Larson: "the CATransform3D struct provided by Core Animation for doing manipulation of CALayers is identical in structure to the model view matrix in OpenGL".

There's also a cool visualization tool on GitHub, https://github.com/honcheng/CATransform3D-Test. Honcheng's demo is iPad only, but I think still helpful.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top