0

Quick abstract: I don't know how to give to an object, attached to the camera, a local rotation so that it is rotated as if it is attached to the scene.

Background: I am the creator of this website which offers to players of the related game the ability to build levels on a 3D Builder, in a web browser. Everything work fine with it but some parts are not well optimized, specially the toolbar (or dashboard), where objects can be selected to be added to the main scene.

Those toolbar objects need to be sticked at the bottom of the screen and always above all layers but their rotation has to be relative to the main axis of the scene. That's where I face some weird behaviors on browsers other than Firefox and Chrome.

If first thought this other thread was saving my life on this matter by implementing the Object3D.onBeforeRender function, but I faced other issues.

Currently those objects are attached to the scene and their position is updated relatively to the camera with the onBeforeRender function of each object. On such way I noticed some flickering results with Edge and iPhone browser, despite everything ends up fine in the rendering. Not nice on the scene rotation...

So I tried something else by attaching each object to the camera and giving them their supposed world rotation. Now when testing on Edge there is no flickering and the results is much better but I can't get how to give them the same rotation than when they are attached to the scene.

Here is a visual:

Visual situation

and an animated gif (you can see a little how the red background and cube at the right part are moving/flickering, but it is awfull if done quicklier):

Animation

For the #1 (at the right) there's a good rendering but a bad experience on some browsers and for the #2 (at the left) the rotation, based on the camera, is not good but the experience is fine.

Here are some code abstracts I've implemented:

  • For #1:
rightCube.relPos = new THREE.Vector3(500, 0, 0); // a new property to be used
rightCube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
    camReference.position.copy(this.relPos);
    var pos = camReference.getWorldPosition();
    this.position.set( pos.x, pos.y, pos.z );
};
scene.add(rightCube);

And camReference is a simple Object3D representing the camera origin

  • For the #2:
leftCube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
    this.rotation.copy(camera.getWorldRotation());
};
camera.add(leftCube);

I admit on this last part, that getting the camera world rotation is not sufficient but that's where I'm stuck and need some help: I don't know how to give to an object, attached to the camera, a local rotation so that it is rotated as if it is attached to the scene.

This post is pretty long but I hope my problem can be well understood... and that someone will give me the key to make it work.

Three.js r82

Gepeto
  • 188
  • 1
  • 1
  • 11
  • See if [this SO answer](https://stackoverflow.com/questions/16226693/three-js-show-world-coordinate-axes-in-corner-of-scene/16227714#16227714) helps. – WestLangley Mar 12 '18 at 15:47
  • @WestLangley thanx for answering. Actually the jsfiddle mentionned in your link is the one I partly used to implement a 2nd scene for this demo (the "yellow" ribbon). On [this link](https://i.imgur.com/XAUN7Dd.png) here is a pic where the red ribbon, part of the main scene currently, is not in the same scene than the right objects but follow it because both are updated with relative position. The left objects really don't move at all staying perfectly stuck to the camera. But only their rotation is not fine. I'm kind of desperate to only find the right rotation for the left objects... – Gepeto Mar 12 '18 at 17:10

1 Answers1

0

I've found out how to deal with that problem. I had to go into some calculus and I am surprised that it is not yet implemented in Three.js. If I have missed something @WestLangley please let me know!

Basically my request is to have a rotateToWorld functionnality from the camera coordinate. Getting the rotation of the camera is not enought because of the orbit control position offset which could be different than the Scene Axis. From the camera coordinate, rotating to the World can be seen as a rotation around x then y (Euler rotations), z being directed to the viewer. Here is a figure I tried to draw as a better explanation:

rotateToWorld

Here is the function I have coded to do that:

var rotateToWorld = function (camera, controls) {
    var p = camera.position.clone();
    var o = controls.target.clone();

    // position relative to the control
    p.sub(o);

    var Dxyz = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
    var Dxz = Math.sqrt(p.x*p.x + p.z*p.z);

    return {
        x: Math.acos(Dxz/Dxyz) * (p.y>0?1:-1), // Angle Rotation on x axis camera
        y: Math.acos(p.z/Dxz) * (p.x>0?-1:1), // Angle Rotation on y axis camera
        z: 0 // No rotation around z axis
    };
};

Then from a cube object attached to the camera (not to the scene) it can be rendered like that:

cube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
    var R = rotateToWorld(camera, controls); // controls defined elsewhere in the code
    this.rotation.set(R.x, R.y, 0);
};

As a result this is what I got with a perspective camera:

rotateToWorld

And now it works with Microsoft Edge, Safari and other browsers despite when attaching to the scene and updating the position of the object (to keep it in front of the camera) it was only workable with Firefox and Chrome (as for today).

I wonder now if this is a functionnality that could be added to Three.js...


EDIT:

@WestLangley pointed out in comments that the clone() function of Vector3 instantiate a new object each time it is called, so better is to use a closure function to initialize once p and o as Vector3 objects then update them with the copy() function.

So here is a better version:

var rotateToWorld = function() {
    var p = new THREE.Vector3();
    var o = new THREE.Vector3();
    return function rotateToWorld(camera, controls) {
        p.copy(camera.position);
        o.copy(controls.target);

        // position relative to the control
        p.sub(o);

        var Dxyz = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
        var Dxz = Math.sqrt(p.x*p.x + p.z*p.z);

        return {
            x: Math.acos(Dxz/Dxyz) * (p.y>0?1:-1), // Angle Rotation on X axis camera
            y: Math.acos(p.z/Dxz) * (p.x>0?-1:1), // Angle Rotation on Y axis camera
            z: 0 // No rotation around z axis
        };
    }
}();

Again, thanks @WestLangley! :)

Gepeto
  • 188
  • 1
  • 1
  • 11
  • Tip: Learn how to use closures so you don't instantiate new `Vector3`s each call. Or write your method without using `Vector3`s at all. – WestLangley Mar 21 '18 at 00:28
  • Thanks to point it out @WestLangley. Now I understand why there's so much closures in Three.js code ;) In such a case I'd rather use the `clone()` function of `Vector3` objects for `p` and `o` and define the resulted rotation parameter from scratch, as there's no real variable initialisation needed. I am going to update the answer itself. Indeed, that was a great tip! – Gepeto Mar 21 '18 at 22:41
  • `clone()` calls `new`. Don't use `clone()` either. – WestLangley Mar 21 '18 at 22:43
  • Oh, that's true... OK then I'll leave it with some modifications (as that still more straight as an explanation) then will add an update with closures as an enhancement. Thanks. – Gepeto Mar 21 '18 at 23:21