Limit the Y rotation of mesh A based on mesh B's Y rotation?

Hi guys - I am finding that I want to limit the Y rotation of one mesh, based on a 90° range around the Y rotation of another mesh. I am able to getWorldQuaternion() of both, and I have those vars (which we all know are pretty funky values) but I just don’t know how I would go about writing this. I know I need a min and max for the range of MeshA, and I know that I want to compare that range against the rotation of MeshB. I don’t need anything beyond conditional and getting the proper vars inside it. I imagine this is maybe something like bone limits, but I don’t do character animations, and I’m having trouble finding a good example that I can apply to my project.

So how would one make something like this work: if(rot >= minRot && rot <= maxRot){ , using the world rotations (quaternions or otherwise) of both meshes and let’s say a 90° range? Please help guys!

EDIT: I added an analogy in my next post below that really helps explain – here it is: Let’s use a locomotive train as an analogy, since you can’t have its train cars overlapping each other but each can rotate with limitations, this is perfect: If TrainCarA has a world Y rotation of 30°, TrainCarB can only be added/attached to TrainCarA (and have its Y rotation set) if the UI-controlled DirectionArrowMesh (that is always moved to end of the last train car) has a world Y rotation between 45° CW and 45° CCW of TrainCarA’s Y rotation (between 345°(-15°) and 75° in this case). I hope that makes sense.

Not sure if this is exactly what you’re looking for, but I worked on a tool that reads your phone’s accelerometer data and applies all those rotations to an Object3D. Then, I extract that object’s rotation around the Y axis to get its yaw. You can see a video of it in action here. The orange (bottom-right) arrow has all rotations, and the magenta arrow (top-left) only has the extracted y-axis rotations.

Is this something like what you’re looking for? If so, you can see the source code here: https://github.com/marquizzo/three-gimbal/blob/e4df915a55a705e8df475910c97ac59eac8ff091/src/Gimbal.js#L110 Line 110 is where the calculations are performed:

// Copy object rotations to my quaternion
this.quaternion.copy(this.sensorRotations.quaternion);

// Apply this quaternion to a forward (0, 0, 1) vector
this.vectorFwd.applyQuaternion(this.quaternion);

// Get the angle based on the x/z plane
// Basically, a top-down view of the forward vector
this.yaw = Math.atan2(this.vectorFwd.x, this.vectorFwd.z);

Once you have the yaw, you could perform a THREE.Math.clamp() to limit its angles.

Thank you so much marquizzo, but I’m fairly sure that is not what I am looking for… Sorry, I hope that didn’t take long to whip together…

Let me try and articulate this (hopefully much) better – Let’s use a locomotive train as an analogy, since you can’t have its train cars overlapping each other but each can rotate with limitations, this is perfect: If TrainCarA has a world Y rotation of 30°, TrainCarB can only be added/attached to TrainCarA (and have its Y rotation set) if the UI-controlled DirectionArrowMesh (that is always moved to end of the last train car) has a world Y rotation between 45° CW and 45° CCW of TrainCarA’s Y rotation (between 345°(-15°) and 75° in this case). I hope that makes sense.

I’ve written a lot of JS in my time, but never anything quite like this. And it really doesn’t help when my testing of rotations in the console is showing me that the radians are the same for the Y rotation of the DirectionArrowMesh at both 45° and 135° (0.7853981633974485)… Things like this are twisting my damn brains into a pretzel… Someone please help me get over this hump, and keep the train (project) moving! All aboard!..

Hi @FrankSilvaHM,
I think it depends on how you apply rotations. If you do

anyMesh.rotateY( yourAngleInRadian );

Then anyMesh.rotation.y will be somewhere between pi and -pi… In a nutshell if you tell it to be 270°, it will be -90°… I agree that it makes comparing angles a tedious operation.
But if to apply rotations, you set the parameters directly like this :

anyMesh.rotation.y += yourAngleInRadian

Then meshA.rotation.y will just be incremented (or decremented) beyond pi, it will look the same, and to limit the Y rotation of meshB you can do :

if ( meshB.rotation.y > meshA.rotation.y + ( Math.PI / 2 ) ) {
       meshB.rotation.y = meshA.rotation.y + ( Math.PI / 2 );
} else if ( meshB.rotation.y < meshA.rotation.y - ( Math.PI / 2 ) ) {
       meshB.rotation.y = meshA.rotation.y - ( Math.PI / 2 );
};

EDIT : But you would get wrong results if one of the two meshes revolved entirely compared to the other. If you have meshA.rotation.y = 0 and meshB.rotation.y = pi*2, the meshB’s rotation will be considered too wide and set to meshA.rotation.y + ( Math.PI / 2 ). It depends on your use case actually !

Thanks so much Felix! I need to table this part of the project for a couple days, and I will come back to it with a (hopefully) much clearer head. My brains have been turned to puddin’. Hopefully your stuff will get me closer…

But three things have occurred to me since yesterday:

[1] If when comparing rotations of these two, if we got the equivalent degrees of both, and added something much higher than 360 to each (like 1,000) and then compared those two values, could this not solve the the whole negative/positive stuff?.. just thinkin’… quite embarrassingly, I’m very math-challenged folks…

[2] If I temporarily attached the DirectionArrowMesh to a TrainCar, thus getting local rotation in relation to a TrainCar, and then attached it back to scene after comparing rotations in my function, could this possibly make this much easier and doable?.. I have some stuff going on with children and their lengths of said TrainCars, and having the DirectionArrowMesh be attached more than temporarily to any cars would complicate other parts of this project… But I think I could attach/detach real quick just for comparing rotations, without messing other parts up. Anyone have any thoughts on this?

[3] If all else fails, I imagine I could move to a hit-test solution. I’ve not used hit-test before, but I think that I if I was testing for a part (child, that sticks out some from where they are attached) of the DirectionArrowMesh that would only be touching the TrainCar if the DirectionArrowMesh rotation was too far, this might be the way to go…

Please advise, anyone who is interested in helping, or up for a challenge of figuring out the best solution to a scenario like this.

1 Like

Hi Franck,
I think I found a solution to your issue :

Given an angle A and an angle B that range from -180° to 180°.
Your angle A is the base, and you want to make sure that B is not greater than A + n (let’s say 90°).
The difference from A to B is B - A, so if you had A = 150° and B = -130°, then B to A = -280°.
But to be able to limit this difference whatever the angles, we want to give a limit value between -180° and 180°.
So we want to convert -280° to the -180° to 180° range.

This function can do it :

function toPiRange( rad ) {
	rad = rad % (Math.PI * 2) ;
	if ( rad > Math.PI ) {
		return ( - Math.PI ) - (Math.PI - rad) ;
	} else {
		return rad ;
	};
}; 

You input any radian angle, not limited to pi, and it returns a radian angle between -pi and pi (so our -280° becomes 80°, which is the same).
Once you have this it’s easier, and you can use this function to limit the difference :

function limitDiff( angleBase, angleToLimit, maxDiff ) {
	let diff = toPiRange( angleToLimit - angleBase );
	return Math.abs(diff) > maxDiff ? maxDiff * Math.sign(diff) : diff ;
};

I hope it does the trick :crossed_fingers:

So guys, after months away from this project I am back at it… I just spent like a day or so trying to get my app (that is like similar to working with “Train Cars”) to save/load positions and rotations of meshes with localStorage… After many, many hours of wrestling with quaternions and shit like that in an epic battle to get this effing thing working correctly and everything back to same state upon reload, I took a stab in the dark and tried saving my matrixWorld of each mesh and storing that as one my objects in an array of objects, and then hit each mesh with applyMatrix() in a loop upon reload, and what do you know… it worked beautifully. Perfectly… So here is the thing. I think someone, anyone, on the threejs team needs to make it very clear to folks in the documentation (and even call attention to the fact) that matrix and matrixWorld can be easily stored and retrieved to have everything restored, like in configurator apps and stuff like that. Really. Maybe I am stoopid for not knowing that this method exists and is such a breeze, but it never even occurred to me until I was throwing everything at the wall to see if anything would stick… man-o-man do I wish I had the last day back. Had I come across a blurb in the docs or in examples that this is doable, it would have been huge for me…