Aligning a Three JS group's rotation with the camera for a View Cube or Orientation Cube

I am attempting to create a Blender-inspired orientation view using Three JS, as shown in the image linked below:

https://i.sstatic.net/7Ru4H.png

My approach involves rotating three vector points (representing X, Y, and Z bubbles) based on the camera rotation. I then plan to use these points to draw circles on a canvas, connecting them with lines to the center. The Z direction will determine the drawing order.

Currently, I am utilizing a secondary Three.js scene to visualize the position of these points while experimenting with different rotations to align them with the camera's orientation. So far, I have encountered challenges and attempted various strategies that have not yielded the desired results.

A demonstration of my current progress is available in this JS Fiddle:

https://jsfiddle.net/j9mcL0x4/4/ (works in Chrome but not loading in Brave)

While panning the camera, the movement affects the 3 cubes in the second scene, but I haven't been able to achieve the correct rotation.

The X, Y, and Z points are represented by a group of Vector3 objects structured as follows:

this.ref = [
  [new THREE.Vector3(1,0,0), 0xFF0000],
  [new THREE.Vector3(0,1,0), 0x00FF00],
  [new THREE.Vector3(0,0,1), 0x0000FF],
]

To represent each cube, I construct it and add it to a group:

this.group = new THREE.Group();
for(var pos of this.ref) {
  var geometry = new THREE.BoxGeometry(.25,.25,.25);
  var material = new THREE.MeshBasicMaterial({
    color: pos[1]
  });
  var cube = new THREE.Mesh(geometry, material);
  cube.position.copy(pos[0]);
  this.group.add(cube);
}

this.scene.add(this.group);

In my animate function, I am working on computing the necessary rotation for the group to align with the main view:

var quaternion = new THREE.Quaternion();
camera.getWorldQuaternion( quaternion );

let rotation = new THREE.Euler();
rotation.setFromQuaternion(quaternion);

var dir = new THREE.Vector3();
dir.subVectors( camera.position, controls.target ).normalize();

orientationHelper.group.rotation.set(dir.x, dir.y, dir.z);
orientationHelper.animate();

My current implementation is incorrect, and I have experimented with several approaches, including:

  • Calculating a point in front of the camera to direct the group using lookAt()
  • Utilizing orbit controls target to determine the viewing direction
  • Working with camera local rotation

Note Rather than rotating the camera in the second scene to match the primary view, I aim to rotate the vectors themselves. This would enable me to project these points onto a basic canvas for drawing circles and lines more efficiently than using sprites in 3D.

Examples of the desired output include:

https://i.sstatic.net/ALTfD.png

https://i.sstatic.net/voC7s.png

Update

Rabbid76 has successfully resolved this issue. For those interested in implementing a Blender Style orientation cube in their projects, the complete source code can be accessed here:

https://github.com/jrj2211/three-orientation-gizmo/

Alternatively, the package can be downloaded via npm:

https://www.npmjs.com/package/three-orientation-gizmo

Answer №1

When looking at the view space, remember that the X-axis points to the right, the Y-axis points upward, and the Z-axis points out of the canvas.
To start, you need to select an initial position for the OrientationHelper camera along the positive Z-axis:

class OrientationHelper {
    constructor(canvas) {
        // [...]

        this.camera = new THREE.PerspectiveCamera(75, size / size, 0.1, 1000);
        this.camera.position.set(0, 0, 2);

        // [...]

Next, it is recommended to set the orientation of the group within the OrientationHelper scene using a matrix:

class OrientationHelper {
    // [...]

    setCameraRotation(rotation) {
        this.group.setRotationFromMatrix(rotation);
    }

    // [...]

The view matrix is essentially the inverse matrix of the one defining the camera's position and orientation. In order to visualize the camera's orientation through the view matrix, create a rotation matrix from camera.rotation, compute the Inverse matrix, and then set the orientation of the OrientationHelper based on the computed matrix:

let rotMat = new THREE.Matrix4().makeRotationFromEuler(camera.rotation);
let invRotMat = new THREE.Matrix4().getInverse(rotMat); 

orientationHelper.setCameraRotation(invRotMat);
orientationHelper.update();

Take a look at the example below:

class OrientationHelper {
  constructor(canvas) {
    this.canvas = canvas;
    
    this.ref = [
      [new THREE.Vector3(1,0,0), 0xFF0000],
      [new THREE.Vector3(0,1,0), 0x00FF00],
      [new THREE.Vector3(0,0,1), 0x0000FF],
    ]

    var size = 200;
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(75, size / size, 0.1, 1000);
    this.camera.position.set(0, 0, 2);

    this.renderer = new THREE.WebGLRenderer({alpha: true});
    this.renderer.setSize(size, size);
    canvas.appendChild(this.renderer.domElement);
    
    var controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
    
    var geometry = new THREE.SphereGeometry( .1, 32, 32 );
    var material = new THREE.MeshBasicMaterial( {color: 0xffffff} );
    var sphere = new THREE.Mesh( geometry, material );
    this.scene.add( sphere );

    this.group = new THREE.Group();
    for(var pos of this.ref) {
      var geometry = new THREE.BoxGeometry(.25,.25,.25);
      var material = new THREE.MeshBasicMaterial({
        color: pos[1]
      });
      var cube = new THREE.Mesh(geometry, material);
      cube.position.copy(pos[0]);
      this.group.add(cube);
    }

    this.scene.add(this.group);
  }

  setCameraRotation(rotation) {
    this.group.setRotationFromMatrix(rotation);
  }
  
  update() {
  this.renderer.render(this.scene, this.camera);
  }
}

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);
camera.position.set(0, 3, 3);
controls.update();

//controls.autoRotate = true;

var size = 10;
var divisions = 10;

var gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);

var axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

 // material
  var material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    vertexColors: THREE.FaceColors
  });

var geometry = new THREE.BoxGeometry();

 // colors
  red = new THREE.Color(1, 0, 0);
  green = new THREE.Color(0, 1, 0);
  blue = new THREE.Color(0, 0, 1);
  var colors = [red, green, blue];
  
  for (var i = 0; i < 3; i++) {
    geometry.faces[4 * i].color = colors[i];
    geometry.faces[4 * i + 1].color = colors[i];
    geometry.faces[4 * i + 2].color = colors[i];
    geometry.faces[4 * i + 3].color = colors[i];
  }
  
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Orientation
var orientationHelper = new OrientationHelper(document.getElementById("orientation-helper"));

function animate() {
  requestAnimationFrame(animate);
  controls.update();

  let rotMat = new THREE.Matrix4().makeRotationFromEuler(camera.rotation);
  let invRotMat = new THREE.Matrix4().getInverse(rotMat); 
  orientationHelper.setCameraRotation(invRotMat);
orientationHelper.update();
  
  renderer.render(scene, camera);
}

animate();
body { margin: 0; }
canvas { display: block; }
#orientation-helper { position: absolute; top: 10px; left: 10px; background: #505050; }
<script src="https://rawcdn.githack.com/mrdoob/three.js/r113/build/three.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r113/examples/js/controls/OrbitControls.js"></script>
<div id='orientation-helper'></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble with querySelector or getElementById not functioning properly?

Currently, I am in the midst of developing an experimental web application that features a quiz component. For this project, I have implemented a Python file to handle the questions and quiz functionalities. However, I have encountered an issue with the Ja ...

Excessive use of the style attribute within an AngularJS directive

My AngularJS directive includes the replace: true and template: <span style="color: red;"></span> properties. However, when I use this directive in my code, it appears that the style attribute is duplicated in the rendered HTML: <span style= ...

Guide on incorporating pinching gestures for zooming in and out using JavaScript

I have been working on implementing pinch zoom in and out functionality in JavaScript. I have made progress by figuring out how to detect these gestures using touch events. var dist1=0; function start(ev) { if (ev.targetTouches.length == 2) {//checkin ...

Learn the process of adjusting opacity for a specific color in CSS

At the moment, this is the code I'm using to apply a color to an element using jss. const styleSheet = theme => ({ root: { backgroundColor: theme.colors.red, }, }) I am interested in finding out if there is a way to add opacity based o ...

Recommendations for Configuring VPS for Angular2 and .Net Application

My team and I are currently in the process of developing an application that combines Angular2 for the front-end and Web API ASP.NET for the back-end. We are currently at the stage of configuring a VPS for this application but unfortunately, we lack the ...

Verify if the arguments of a Discord bot command are in accordance with the format outlined in the commands configuration

I am looking to create a script that verifies if the arguments given for a command align with what the command expects them to be. For instance; When using the config command, the first argument should be either show, set, or reset Additionally, if se ...

Updating the scope in Angular when changing the image source using ng-src is not working

A snippet inside my controller looks like this: $scope.onFileSelect = function($files) { for(var i = 0; i < $files.length; i++) { var file = $files[i]; $scope.upload = $upload.upload({ url: '/smart2/api/files/profi ...

What are some strategies for efficiently locating partial matches in an array without having to loop through each element?

As I iterate through an array of English phrases, I check for exact matches and replace them with their corresponding translations from another array. This process is efficient and works flawlessly for exact matches. However, for partial matches, I need t ...

converting values to hexadecimal for array transmission in JavaScript

Looking to insert a hexadecimal value into an array in order to write to a device via modbus/TCP using socket communication. Currently facing an issue where I am unable to retrieve the hex value with the same data type as the elements present in byteArrayW ...

Do you need to finish the Subject when implementing the takeUntil approach to unsubscribing from Observables?

In order to prevent memory leaks in my Angular application, I make sure to unsubscribe from Observables using the following established pattern: unsubscribe = new Subject(); ngOnInit() { this.myService.getStuff() .pipe(takeUntil(this.unsubscr ...

Transfer or duplicate an SVG image from the bottom div to the top div

Is there a way to move a div from the chart_div to the movehere div? I've tried duplicating it below the current svg, but I'm having trouble targeting just the header row ('g' element) specifically. Here is the HTML: <script type= ...

Running "vue ui" with Node.js v17.2.0 - A step-by-step guide

After updating to Node.js v17.2.0, I am facing issues with running "vue ui" in my project. The error message I receive indicates a problem with node modules: at Object.readdirSync (node:fs:1390:3) at exports.readdir (/usr/local/lib/node_modules/@vu ...

Inaccurate data is being shown by the Ajax method

I have a question that I haven't been able to find a satisfactory answer for: Recently, I started learning about AJAX methods and I'm trying to post some information processed by a php page named page.php. In my HTML file, I've included th ...

Encountering a discord bot malfunction while on my Ubuntu server

My discord bot runs smoothly on my Windows 10 setup, but when deployed to the server running Ubuntu Server 20, it encounters a specific error. The issue arises when trying to handle incoming chat messages from the server. While I can read and respond to m ...

I need help figuring out how to retrieve the full path of an uploaded file in JavaScript. Currently, it only returns "c:fakepath" but I need

Is there a way to obtain the complete file path of an uploaded file in javascript? Currently, it only shows c:\fakepath. The file path is being directly sent to the controller through jquery, and I need the full path for my servlet. Are there any alte ...

Showing hierarchical objects retrieved from JSON response

I'm still learning React, so bear with me if this is a simple issue. I have some nested data that I want to show in a list format. The data is fetched and displayed below: data: [ { "id": 1, "domain_url": "localhost", "created_on": "2020-05-26" ...

Exploring the insights into React-hook-form Controller

I've inherited a website project that utilizes an unfamiliar method in the Controller part of react-hook-form. Here is an example of the code snippet: const CheckboxController = (props: CheckboxProps) => { return ( <Wrapper> < ...

Encountering a random MS JScript runtime error message after a few alerts

Encountering this issue when clicking the "Ok" button on an alert window. Error message: Unhandled exception at line 1, column 1 in a lengthy URI Error code: 0x800a138f - Microsoft JScript runtime error: Object expected This problem is occurring within ...

The function is not valid due to an angular-parse argument

This is my first experience with using angular and parse.com. I encountered the following error: Error: Argument 'eventList' is not a function, got undefined when attempting to execute the following angular code: var main_app = angular.mod ...

PHP variable missing "+" sign at the end after post operation

I have encountered a unique problem that I cannot seem to find anywhere else. My issue lies in adding grades ending with a plus sign, such as B+, C+ or D+. I am able to add grades like A-, B-, C- and D- without any problem, but when it comes to adding a g ...