Copy link to clipboard
Copied
Hi!
I'd like a 3D Null to get the orientation of 3D a layer. (without parenting, it would be too easy )
I've tried a lot of solutions, through orientation, rotations, both combined... some results are close, but never perfect.
If I'm right, orientation is computed before rotations...
I think the simplest way would be with auto-orientation, or the lookAt() expression on orientation, wich gives me correct X and Y orientations, and then get an expression on zRotation to get a "full" orientation.
But what would it be?
How can I get the Z orientation in layer space?
Any idea will be appreciated!
François
Try this oreientation expression:
L = thisComp.layer("other layer");
u = fromWorldVec(L.toWorldVec([1,0,0]));
v = fromWorldVec(L.toWorldVec([0,1,0]));
w = normalize(fromWorldVec(L.toWorldVec([0,0,1])));
sinb = clamp(w[0],-1,1);
b = Math.asin(sinb);
cosb = Math.cos(b);
if (Math.abs(cosb) > .0005){
c = -Math.atan2(v[0],u[0]);
a = -Math.atan2(w[1],w[2]);
}else{
a = (sinb < 0 ? -1 : 1)*Math.atan2(u[1],v[1]);
c = 0;
}
[radiansToDegrees(a),radiansToDegrees(b),radiansToDegrees(c)]
Dan
Copy link to clipboard
Copied
It seems I asked a bit quickly...
Here's the solution in case somebody needs it:
for orientation:
a = thisComp.layer("master").toWorld([0,0,0]);
b = thisComp.layer("master").toWorld([0,0,100]);
lookAt(a,b)
then for zRotation:
a = fromWorld(thisComp.layer("master").toWorld([0,0,0]));
b = fromWorld(thisComp.layer("master").toWorld([100,0,0]));
c = a-b;
radiansToDegrees(Math.atan2(c[1],c[0]))+180;
Hope it can help!
François
Copy link to clipboard
Copied
Aaaaargh !
Nope, it's not perfect, even if it's close to the result...
Does anyone have a solution?
Dan?
Thanx.
Copy link to clipboard
Copied
Try this oreientation expression:
L = thisComp.layer("other layer");
u = fromWorldVec(L.toWorldVec([1,0,0]));
v = fromWorldVec(L.toWorldVec([0,1,0]));
w = normalize(fromWorldVec(L.toWorldVec([0,0,1])));
sinb = clamp(w[0],-1,1);
b = Math.asin(sinb);
cosb = Math.cos(b);
if (Math.abs(cosb) > .0005){
c = -Math.atan2(v[0],u[0]);
a = -Math.atan2(w[1],w[2]);
}else{
a = (sinb < 0 ? -1 : 1)*Math.atan2(u[1],v[1]);
c = 0;
}
[radiansToDegrees(a),radiansToDegrees(b),radiansToDegrees(c)]
Dan
Copy link to clipboard
Copied
Hi Dan!
Once again, you saved someone's day...
Thanx a lot!
François
Copy link to clipboard
Copied
Hey Dan,
the expression works perfectly, as long as the "Master" isn't stretched.
But when it's stretched, I get a slight difference between "Master" and "Follower".
The difference becomes huge when one of "Master"'s dimension is close to 0...
It's strange that there's no function similar to "lookAt()", but giving back the 3 dimensions.
Do you think there is a way to get the exact orientation?
Thanx a lot!
François
Copy link to clipboard
Copied
I can't seem to replicate that behavior, but it might help to normalize all the vectors (not sure why I didn't do that in the first place):
L = thisComp.layer("other layer");
u = normalize(fromWorldVec(L.toWorldVec([1,0,0])));
v = normalize(fromWorldVec(L.toWorldVec([0,1,0])));
w = normalize(fromWorldVec(L.toWorldVec([0,0,1])));
sinb = clamp(w[0],-1,1);
b = Math.asin(sinb);
cosb = Math.cos(b);
if (Math.abs(cosb) > .0005){
c = -Math.atan2(v[0],u[0]);
a = -Math.atan2(w[1],w[2]);
}else{
a = (sinb < 0 ? -1 : 1)*Math.atan2(u[1],v[1]);
c = 0;
}
[radiansToDegrees(a),radiansToDegrees(b),radiansToDegrees(c)]
Dan
Copy link to clipboard
Copied
Thanx Dan,
From what I see, the result of the expression isn't "linear".
If I rotate my "Master", the "Follower" will turn a bit less untill it reaches 90°, then turn more untill 180°, and so on...
Any idea?
Cheers,
François
Copy link to clipboard
Copied
I'm not seeing that behavior, and I'm sorry to say that I don't understand why you are.
Dan
Copy link to clipboard
Copied
Do you mind if I send you an .aep project?
Copy link to clipboard
Copied
That would be fine.
Dan
Copy link to clipboard
Copied
Here it is!
Copy link to clipboard
Copied
You may have to post it somewhere--it doesn't look like it made it through.
Dan
Copy link to clipboard
Copied
Copy link to clipboard
Copied
So are you talking about when you turn the Master's Scale expression on? As I recall, the orientation expression is derived from a simplified 3D rotation transform the assumes uniform scale. I think the correction I put in for non-uniform scaling is probably not correct/adequate and the original derivation of the expression needs to be revisited with non-uniform scaling in mind. I've got my original notes somewhere, but it could be a chunk of work and I'm not sure I have the time to dive into it.
Dan
Copy link to clipboard
Copied
You've done a lot for me already!
Now I have something working perfectly for uniform scale, and you told me where the issue comes from, I don't think I can complain
Thank you so much!
François
Copy link to clipboard
Copied
Hi François and Dan,
If I may jump in, I wrote to Dan about this last year and I used the following function to setup the rotation calculation in the expressions, based on Dan's first version on a Creative Cow forum:
function getOrientation(u, v, w)
{
u = normalize(u);
v = normalize(v);
w = normalize(w);
sinb = w[0];
b = Math.asin(sinb);
if (Math.abs(Math.cos(b)) > .0005)
{
c = -Math.atan2(v[0],u[0]);
a = -Math.atan2(w[1],w[2]);
}
else
{
a = ( (sinb < 0) ? -1 : 1 )*Math.atan2(u[1],v[1]);
c = 0;
}
return [radiansToDegrees(a), radiansToDegrees(b), radiansToDegrees(c)];
}
Then, calling it this way worked even with non-uniform scales as far as I can tell:
u = layer.toWorldVec([1,0,0]);
v = layer.toWorldVec([0,1,0]);
w = layer.toWorldVec([0,0,1]);
getOrientation(u, v, w);
I haven't checked these expressions for a while now, but you can see them in action in Elementary by using the Copy button: it creates a copy of the layer that is parented via expressions and I think I remember it worked properly, I can check if you want.
Kevin
Copy link to clipboard
Copied
Hi Kevin!
Thanx you for sharing this expression, it works great!!
I'm impressed
Thanx,
François
Copy link to clipboard
Copied
Well Dan did most of the work, I only changed a couple things.
Just to make sure, did this work fine with non-uniform scales? I remember I tested it last year but you know, I may have missed a situation where it didn't work and unfortunately I can't use AE right now.
Copy link to clipboard
Copied
Yes, it works perfectly with non-uniform scales.
The only exception is when you have X (or Y) negative, with Y (or X) positive. Then you have to make Z negative, and everything works fine.
Of course, scale values have to be non-null, but this is the case as soon as you compute something...
Thanx to you and Dan!
François
Copy link to clipboard
Copied
Ah, I don't think I tried negative scales.
It makes sense ... a negative scale on only one or two axes is going to invert the XYZ basis. I don't think that's a problem with the getRotation function though, you should leave it like that. And I don't think it's enough to invert Z if X and Y have oposite scales: you'll have the same problem with a different combinations of positive/negative scales, no?
We'd just need to make sure that the vectors u, v and w follow the rules u ^ v = w and v ^ w = u. The problem for me right now is: how can we detect a negative scale? I usually calculate the scale along e.g. X like this:
1/length(toWorldVec([1, 0, 0]));
... because this way it takes the scale of the parents into account, but the sign of the scale will disappear because of the "length" function.
If we can find the sign of the scale along each axis we'll just have to do:
u = layer.toWorldVec([1,0,0])*signX;
v = layer.toWorldVec([0,1,0])*signY;
w = layer.toWorldVec([0,0,1])*signZ;
getOrientation(u, v, w);
Copy link to clipboard
Copied
Well, I don't know any way to get scale's sign in World space (I don't think it's possible..)
But a loop can give us the sign of the parent(s) , and so of our layer :
if (layer.parent) {getParent's scale signs}
if (layer.parent.parent) {getParent's parent's scale signs}and so on...
And yes, it works with just turning Z negative.
In fact, the product of signs have to be positive.
So: Z *= sign(X*Y); and X*Y*Z will always be positive.
I think it's easier and more reliable than trying to get something close to a scale's sign in World's space, cos it would mean calculating from camera view...
Untill someone smart finds something else 😉
Cheers,
François
Copy link to clipboard
Copied
I agree with the loop, I can't find any other way.
But I still don't think what you're doing will work: what if the scale is [100, 100, -100] for example? It's still going to use a wrong XYZ basis for the calculation and your workaround for the X/Y scales won't do anything. I may be wrong though, tell me if I'm completely mistaken.
What I'll try is "prévenir plutôt que guérir" and recalculate the scale using a while loop, then multiply the u, v and w vectors I send to the getRotation functions by the computed scales' signs. This way 1) the parented object will have the same scale as it's parent and 2) the getRotation function should get a proper XYZ basis.
Copy link to clipboard
Copied
So here's what I tried:
- scale expression
{
var layer = thisComp.layer( /* name of the parent layer */ );
var scl = [1, 1, 1];
while (layer != null)
{
for (var j=0; j<3; j++)
{
scl
*= layer.scale /100; }
try
{
layer = layer.parent;
}
catch (err)
{
layer = null;
}
}
scl*100;
}
- orientation expression
{
function sign(x)
{
return x>=0 ? 1 : -1;
}
function getOrientation(u, v, w)
{
// still the same thing
}
layer = thisComp.layer( /* name of the parent layer */ );
u = layer.toWorldVec([1,0,0])*sign(scale[0]);
v = layer.toWorldVec([0,1,0])*sign(scale[1]);
w = layer.toWorldVec([0,0,1])*sign(scale[2]);
getOrientation(u, v, w);
}
I haven't tested it that much but the idea seems fine to me. In the scale expression note that I start the loop from the parent layer because in my case I want my parented layer to have the exact same scale.
Copy link to clipboard
Copied
I was about to share mine, you won!
I didn't write it the same way, but got the same result...
We're coming back to the problem we had with Dan's expression.
The vector is computed from the distorted layer, and I don't think we can really compensate (your solution is quite close though!)
In my case, it's not a big deal as I go through Nulls for what I need, and I can use their Z scale as I wish. So it's really easy to make it work...
Cheers,
François