How to affect background brightness based on camera rotation using OrbitControls.js - three.js

(Hello, it's my first ever post here)
here's what I'd like to incorporate in this simple example:
I would like to make the background turn from light to dark gradually when the user is closer to a particular orientation – in this case (example above) the desired orientation is a steep angle so that the foreshortened anamorphic image looks like a regular skull (the value of the background indicating the angle user should aim for – kind of like playing Hot and Cold)
when the user reaches the desired orientation (the background is then accordingly 100% dark) I would like it to lock rotation and trigger a video file in the background or a pop up window.
I assume it has to do with accessing the camera rotation values inside OrbitControls and setting some kind of an Event?? i have no idea how to access it.
Any kind of help, suggestions to edit the thread or explanation would be greatly appreciated, thank you so much in advance!

You could use camera.position to calculate the best vantage point. First, you have to figure out what the desired position is (I'm not sure how the wooden board is being placed, but this position seems to be close to: { x: 6.8, y: 0.6, z: -1.8})
var vantagePoint = new THREE.Vector3(6.8, 0.6, -1.8);
var distance = 100;
var normalized = 1;
var endColor = new THREE.Color(0xff9900);
var startColor = new THREE.Color(0x0099ff);
scene.background = startColor;
animate() {
distance = vantagePoint.distanceTo(camera.position);
normalized = THREE.Math.smoothstep(distance, 5, 100); // Converts [1, 100] => [0, 1]
// Resets the color on each frame
startColor.set(0x0099ff);
startColor.lerp(endColor, normalized);
}
The closer to 0 you are, the closer you are to seeing the skull. You can then use that value to change the color of scene.background. Anything beyond 10 and you're 'cold', and you get hotter as you approach 0.
https://threejs.org/docs/#api/en/math/Vector3.distanceTo
Update:
You can then transform the distance to a normalized value in the range of [0, 1] by using Math.smoothstep(). Then interpolate the value of the colors with this normalized value using Color.lerp

Related

Efficient way to translate a camera dolly

I currently have a VR camera attached to a dolly in order to allow for translation and rotation.
I'm trying to translate the dolly, based on gamepad inputs, relative to the orientation of the camera (which is linked to a VR headset.)
I'm also trying to avoid letting the dolly pitch up or down relative to the camera.
My current code looks something like this:
this.camerDirectionVector = new THREE.Vector3()
this.camera.getWorldDirection(this.cameraDirectionVector)
this.moveVec.y = 0
this.dolly.translateOnAxis(this.cameraDirectionVector, this.gamepad.axes[0] * this.moveSpeed)
This works great for moving the dolly in the direction the camera is pointing (minus y rotation).
What I can't figure out is how to also translate the dolly "left and right" relative to the camera based off an additional gamepad input.
Based on the comments on the question, I think I understand. If I don't, please leave a comment, and I'll update this answer.
My understanding is that you want to be able to move left and right, with respect to the camera, all without altering the dolly's up direction.
This is actually easier than it sounds, and is even easier because you are already comfortable translating along an axis.
First, understand that the camera has its own spatial frame of reference, where it sits at the origin, with a +Y up direction, and it looks down the -Z axis. With this in mind, you already know the "left" and "right" axes: -X (-1, 0, 0) and +X (1, 0, 0).
But the camera (especially in VR) might not be so nicely aligned in world space, so you need to convert these nice uniform axes into world axes. Three.js makes this very easy using Object3D.localToWorld.
(Note: Object3D.localToWorld is destructive to the input Vector3.)
Here's a function to get the world-aligned left axis:
const getLeftWorld = (function(){
const localLeft = new THREE.Vector3(-1, 0, 0);
return function(vectorRef){ // you can give it a vector to overwrite
let out = vectorRef || new THREE.Vector3();
out.copy(localLeft);
camera.localToWorld(out);
return out;
};
))();
You can create a similar function for the "right" axis.
With your new world-aligned left axis in hand, you can translate the dolly along it, using the "speed" given by your controller input. Translation won't change the pitch of the dolly, though it may change the elevation, depending on how the camera is tipped at the time of computation (but you can just zero-out the y component like you did before, if you want).
This is the solution that ended up working best for me. I've adapted it from Brian Peiris code here: https://github.com/brianpeiris/three-firstperson-vr-controls/blob/master/FirstPersonVRControls.js#L125
// Create a dolly
this.dolly = new THREE.Group()
this.dolly.add(this.camera)
this.scene.add(this.dolly)
// Some variables for movement translations
this.dummy = new THREE.Object3D()
this.dummyDirection = new THREE.Vector3()
this.ZAXIS = new THREE.Vector3(0, 0, 1)
this.moveSpeed = 0.075
// Get controller stick positions
const stickForwardBack = this.leftStick[3]
const stickLeftRight = this.leftStick[2]
// In THREE.js when using the WebXR API, the camera rotation will not update to match the headset orientation.
// You'll need to get pose information from the XRSession or get the xr camera using the following method.
const xrCamera = globals.renderer.xr.getCamera(this.camera)
this.dummy.position.set(0, 0, 0)
this.dummy.quaternion.copy(xrCamera.quaternion)
this.collapseY(this.dummy.quaternion)
// Translate the dummy object forwards/backwards left/right relative to the direction the camera is facing
this.dummy.translateZ(stickForwardBack * this.moveSpeed)
this.dummy.translateX(stickLeftRight * this.moveSpeed)
// Add the dummy position to the dolly
this.dolly.position.add(this.dummy.position)
// Flatten out up and down rotation
collapseY(quaternion) {
this.dummyDirection.set(0, 0, 1)
this.dummyDirection.applyQuaternion(quaternion)
this.dummyDirection.y = 0
this.dummyDirection.normalize()
quaternion.setFromUnitVectors(this.ZAXIS, this.dummyDirection)
}

How to programmatically undo positional translation to pivot point?

I think this is ultimately a pretty simple question, but it's hard to describe, thus, I provide a working example here (in the sample press 'z' to see rotation with unwanted translation and 'x' keys to rotate with a compensating re-position).
Basically, I am trying to rotate an object (a thumbstick) about the z-axis of a complex model loaded via gltf (a model of the oculus rift touch controller). It's easy to rotate about the x-axis because it's 90 deg. orthogonal to the x-axis. About the z-axis, it's harder because the plane the thumbstick is attached to is angled at 30 deg. I realize that if the thumbstick were using local coordinates, this wouldn't be a problem, but 'thumb.rotation.z' does not seem to be using local coordinates and is rotating about the model's (as a whole), or maybe even the scene's global y and z (?). Anyway, after a bunch of futzing around, I was able to get things to work by doing the following:
// occulus plane is angle at 30 deg, which corresponds to
// 5 units forward to 3 units down.
var axis = new THREE.Vector3(0, 5, -3).normalize();
factory.thumbstick.geometry.center();
var dir = (evt.key === 'x' ? 1 : -1);
thumb.rotateOnAxis(axis, factory.ONE_DEG * 5.0 * dir);
Basically, I'm rotating about a "tilted" axis, and then calling 'center' to make thumbstick centered on the pivot point, so it rotates about the pivot point, rather than around the pivot point (like the earth orbiting the sun).
Only problem is that when you call 'geometry.center()' and then call 'rotateOnAxis', it translates the thumbstick to the pivot point:
Note: the position on the thumbstick object is (0,0,0) before and after the calls.
I have empirically determined that if I alter the position of the thumbstick after the translation like so:
// magic numbers compensating position
var zDisp = 0.0475;
var yDisp = zDisp / 6.0
thumb.position.x = 0.001;
thumb.position.y = -yDisp;
thumb.position.z = zDisp;
Then it (almost) returns back to it's original position:
Problem is these numbers were just determined by interactively and repeatedly trying to re-position the thumbstick i.e. empirically. I simply cannot find a programmatic, analytical, api kind of way to restore the original position. Note: saving the original position doesn't work, because it's zero before and after the translation. Some of the things I tried were taking the difference between the bounding spheres of the global object and the thumbstick object, trying to come up with some 'sin x- cos x' relation on one distance etc. but nothing works.
My question is, how can I progammatically reverse the offset due to calling 'geometry.center()' and rotateOnAxis (which translates to the pivot point), without having to resort to hacked, empircal "magic" numbers, that could conceivably change if the gltf model changes.
Of course, if someone can also come up with a better way to achieve this rotation, that would be great too.
What's throwing me is the (peceived?) complexity of the gltf model itself. It's confusing because I have a hard time interpreting it and it's various parts: I'm really not sure where the "center" is, and in certain cases, it appears with the 'THREE.AxesHelper' I'm attaching that what it shows as 'y' is actually 'z' and sometimes 'up' is really 'down' etc, and it gets confusing fast.
Any help would be appreciated.
The breakthrough for me on this was to re-frame the problem as how do I change the pivot point for the thumbstick, rather than how do I move the thumbstick to the (default and pre-existing) pivot point. To paraphrase JFK, "ask not how you can move to the pivot, but ask how the pivot can move to you" :-)
After changing my angle of attack, I pretty quickly found the aforementioned link, which yielded my solution.
I posted an updated glitch here, so now pressing z works as I expected. Here is the relevant code portion:
factory.onModelLoaded = function(evt) {
console.log(`onModelLoaded: entered`);
factory.thumbstick = this.scene.children[1].children[2]
let thumb = factory.thumbstick;
// make the thumb red so it's easier to see
thumb.material = (new THREE.MeshBasicMaterial({color: 0xFF7777}));
// use method from https://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center/28860849#28860849
// to translate the pivot point of the thumbstick to the the thumbstick center
factory.thumbParent = thumb.parent;
let thumbParent = factory.thumbParent;
thumbParent.remove(thumb);
var box = new THREE.Box3().setFromObject( thumb );
box.getCenter( thumb.position ); // this basically yields my prev. "magic numbers"
// thumb.position.multiplyScalar( - 1 );
var pivot = new THREE.Group();
thumbParent.add( pivot );
pivot.add( thumb );
thumb.geometry.center();
// add axeshelp after centering, otherwise the axes help, as a child of thumb,
// will increase the bounding box of thumb, and positioning will be wrong.
axesHelper = new THREE.AxesHelper();
thumb.add(axesHelper);
}
Which allows my "z" handler to just rotate without having to do translation:
case 'z':
case 'Z':
var axis = new THREE.Vector3(0, 5, -3).normalize();
var dir = (evt.key === 'z' ? 1 : -1);
thumb.rotateOnAxis(axis, factory.ONE_DEG * 5.0 * dir);
break;
Interestingly, it's the call to box.getCenter() that generates numbers very close to my "magic numbers":
box.getCenter()
Vector3 {x: 0.001487499801442027, y: -0.007357006114165027, z: 0.04779449797522323}
My empirical guess was {x: 0.001, y: -0.00791666666, z: 0.0475} which is %error {x: 32.7%, y: 7.6%, z: 0.61%}, so I was pretty close esp. on the z component, but still not the "perfect" numbers of box.getCenter().

SceneKit. How to rotate SCNNode around its axis

I'm trying to achieve same rotation as in scene editor but with code so object always rotate around selected axis, however if I look at angles(x,y,z) in editor they changes quite randomly
![Local node axis][1]
I've tried to use quaternions, but can't get it working
PS. my bad was using rotation property instead of orientation both SCNVector4, have read doc properly)
Seems you was really close, you had to swap parameters in GLKQuaternionMultiply call. I used solution in https://stackoverflow.com/a/39813058/4124265 to achieve rotation only by Z axis:
let orientation = modelNode.orientation
var glQuaternion = GLKQuaternionMake(orientation.x, orientation.y, orientation.z, orientation.w)
// Rotate around Z axis
let multiplier = GLKQuaternionMakeWithAngleAndAxis(0.5, 0, 0, 1)
glQuaternion = GLKQuaternionMultiply(glQuaternion, multiplier)
modelNode.orientation = SCNQuaternion(x: glQuaternion.x, y: glQuaternion.y, z: glQuaternion.z, w: glQuaternion.w)
To rotate arround Y:
// Rotate around Y axis
let multiplier = GLKQuaternionMakeWithAngleAndAxis(0.5, 0, 1, 0)
glQuaternion = GLKQuaternionMultiply(glQuaternion, multiplier)
By setting the rotation property, the absolute rotation of the object is changed, rather than the relative rotation.
Here's some pseudo code
Compute the quaternion represents the relative rotation. I would use the GLKQuaternionMakeWithAngleAndAxis function to do so.
Apply the rotation to the orientation property:
let initial_object_orientation = rotateNode.orientation;
new_orientation = GLKQuaternionMultiply(rotation_quaternion, initial_object_orientation)
Assign the new orientation
rotatNode.orientation = new_orientation
Hope it helps.

Finding World Space Coordinates of a Unity UI Element

So according to the Unity documentation RectTransform.anchoredPosition will return the screen coordinates of a UI element if the anchors are touching at the pivot point of the RectTransform. However, if they are separated (in my case positioned at the corners of the rect) they will give you the position of the anchors relative to the pivot point. This is wonderful unless you want to keep appropriate dimensions of a UI object through multiple resolutions and position a different object based on that position at the same time.
Let's break this down. I have object1 and object2. object1 is positioned at (322.5, -600) and when the anchor points meet at the center (pivot) of the object anchoredPosition returns just that and object2 is positioned just fine. On the other hand once I have placed the anchors at the 4 corners of object1 anchoredPosition returns (45.6, -21). Thats just no good. I've even tried using Transform.position and then Camera.WorldToScreenPoint(), but that does just about as much to getting me to my goal.
I was hoping that you might be able to help me find a way to get the actual screen coordinates of this object. If anyone has any insight into this subject it would be greatly appreciated.
Notes: I've already attempted to use RectTranfrom.rect.center and it returned (0, 0)
I've also looked into RectTransformUtility and those helper functions have done all of squat.
anchoredPosition returns "The position of the pivot of this RectTransform relative to the anchor reference point." It has nothing to do with screen coordinates or world space.
If you're looking for the screen coordinates of a UI element in Unity, you can either use rectTransform.TransformPoint or rectTransform.GetWorldCorners to get any of the Vector3s you'd need in world space. Which ever you decide to go with, you can then pass them into Camera.WorldToScreenPoint()
Here's a glimpse on how finding world space coordinates of UI elements works if your stuck and need to roll your own transformations from view-space to world-space.
This may be beneficial if say you need something more than rectTransform.TransformPoint or want to know how this works.
Ok, so you want to do a transformation from normalised UI coordinates in the range [-1, 1], and de-project them back into world space coordinates.
To do this you could use something like Camera.main.ScreenToWorldPoint or Camera.main.ViewportToWorldPoint, or even rectTransform.position if your a lacker.
This is how to do it with just the camera's projection matrix.
/// <summary>
/// Get the world position of an anchor/normalised device coordinate in the range [-1, 1]
/// </summary>
private Vector3 GetAnchor(Vector2 ndcSpace)
{
Vector3 worldPosition;
Vector4 viewSpace = new Vector4(ndcSpace.x, ndcSpace.y, 1.0f, 1.0f);
// Transform to projection coordinate.
Vector4 projectionToWorld = (_mainCamera.projectionMatrix.inverse * viewSpace);
// Perspective divide.
projectionToWorld /= projectionToWorld.w;
// Z-component is backwards in Unity.
projectionToWorld.z = -projectionToWorld.z;
// Transform from camera space to world space.
worldPosition = _mainCamera.transform.position + _mainCamera.transform.TransformVector(projectionToWorld);
return worldPosition;
}
I've found out that you can multiply your coordinate by the 2 times the camera size and divide it to screen height.
I have a panel placed at (0, 1080) on a fullHD screen (1920 x 1080), camera size is 7. So the Y coordinate in world space will be 1080 * 7 * 2 / 1080 = 14 -> (0, 14).
ScreenToWorldPoint convert canvas position to world position :
Camera.main.ScreenToWorldPoint(transform.position)

How can I draw a scale meter?

I want to draw (on the renderer) a fixed vertical line (or a bar) that gives info about size/distance or zooming factor like the one in Google Maps (see the image) (supposing to use a Orbit Control or a Trackball Control). Is it possibile?
why yes, good sir! You could find out the distance quite easily if you have THREE.OrbitControls object and a THREE.Camera:
var controls = new THREE.OrbitControls(...);
var camera = ....;
//get distance from camera to 'target' of OrbitControls
var distance = camera.position.distanceTo(controls.target);
luckily for us, THREE.OrbitControls also has minDistance and maxDistance attributes which control how close and how far away the camera can move from the target point. So, to get relative position of our camera between minDistance and maxDistance we can use the following code:
var normalizedDistance = (distance- controls.minDistnace) / (controls.maxDistance - controls.minDistance);
Now our new normalizedDistance variable will store a value between 0 and 1 relative to how far we are from the target, if we are at the minDistance - we can't get any closer and normalizedDistance === 0, if we are at maxDistance and can't get any further, out normalizedDistance === 1.
Also, be careful, by default THREE.OrbitControls.maxDistance is set to infinity, so to get a meaningful normalizedDistance you would have to set maxDistance to some reasonable non-infinite value, like say 100 or 1000.

Resources