The ArrowHelper in THREE.js seems to be ignoring the natural rotation provided by Euler angles

Can someone help me with setting intrinsic rotations to a THREE.ArrowHelper in THREE.js? I'm trying to work with Tait-Bryan euler angles for 3D rotations.

In the snippet below, I define a unit vector for the x-axis as THREE.Vector3(1, 0, 0).

Then, I apply rotations around the Y and Z axes by some arbitrary angles.

Due to these Y and Z rotations, the X axis of the local coordinate system (which I assume aligns with the red vector) has also shifted.

Now, when I try to rotate around the X axis, I expect the arrow not to move, just to rotate in place (without visibly changing position).

However, what I actually observe is the arrow sweeping around, as if it's rotating around a different axis rather than its local x axis.

Any help on this issue would be greatly appreciated!

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 origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000);
arrow.rotation.order = 'XYZ';
arrow.rotation.y = 0.5;
arrow.rotation.z = 0.5;
scene.add(arrow);

camera.position.z = 5;

var animate = function () {
  requestAnimationFrame( animate );

arrow.rotation.x += 0.01;
  
  renderer.render( scene, camera );
};

animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

Answer №1

The issue lies in the fact that ArrowHelper utilizes its own unique rotation calculations. It generates a unit arrow pointing upwards in the +Y direction. Then, it applies custom math to establish the orientation to ensure that the line points in the specified direction in local space.

This becomes evident when you create the arrow and examine its rotation:

  var origin = new THREE.Vector3(0, 0, 0);
  var xDir = new THREE.Vector3(1, 0, 0);
  var length = 1;
  var arrow = new THREE.ArrowHelper(xDir, origin, length, color);
  console.log(arrow.rotation.x, arrow.rotation.y, arrow.rotation.z);

You will observe that rotation.z is already configured to rotate the +Y arrow to face +X. Therefore, modifying these rotations results in the arrow no longer being oriented towards +X.

Consequently, manipulating the arrow using arrow.rotation does not produce the expected outcome.

If you attach the arrow to an Object3D and then rotate that object, the arrow will behave as anticipated (or at least as I anticipate 😅)

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 );

scene.add(new THREE.GridHelper(10, 10));

function addArrow(x, ry, rz, color) {
  var origin = new THREE.Vector3(0, 0, 0);
  var xDir = new THREE.Vector3(1, 0, 0);
  var length = 1;
  var arrow = new THREE.ArrowHelper(xDir, origin, length, color);

  var ob = new THREE.Object3D();
  ob.position.x = x;
  ob.rotation.order = 'XYZ';
  ob.rotation.y = ry;
  ob.rotation.z = rz;
  scene.add(ob);  
  ob.add(arrow);
  return ob;
}

addArrow(-4,   0,   0, 0xFF0000);
addArrow(-2,   0, 0.5, 0x00FF00);
addArrow( 0, 0.5, 0.5, 0xFFFF00);
const arrow = addArrow( 2, 0.5, 0.5, 0x00FFFF);

camera.position.z = 6;

const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();

var animate = function () {
  requestAnimationFrame( animate );
  arrow.rotation.x += 0.01;

  renderer.render( scene, camera );
};
animate();
body { margin: 0; }
canvas { display: block; }
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2e5a465c4b4b6e1e001f1f1b001e">[email protected]</a>/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ef9b879d8a8aafdfc1dededac1df">[email protected]</a>/examples/js/controls/OrbitControls.js"></script>

My Expectations:

The rotations are relative to the local coordinate system. With the rotation order set to 'XYZ', the full matrix calculation would be:

matrix = projection * 
         view *
         obTranslation *
         obXAxisRotation * 
         obYAxisRotation * 
         obZAxisRotation * 
         obScale * 
         arrowOrientation;

In any scenario:

var origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000);
var ob = new THREE.Object3D();
scene.add(ob);
ob.add(arrow);
ob.rotation.order = 'XYZ';

When viewed from 0, 0, 5, this setup results in an arrow pointing to the right.

I prefer to visualize matrices as applied from right to left. Considering the formula above, the scale is applied first. Since it's 1,1,1, there is no change.

Next, the zAxisRotation is applied. With 0.5 radians (approximately 30 degrees), the arrow now points slightly upwards.

Subsequently, the yAxisRotation is applied, causing the slightly tilted arrow to point back into the distance.

Finally, the xAxisRotation is applied, causing this uniquely oriented arrow to spin around the x-axis.

Execute the code and drag on the provided sample to view from above. The result will align with the description provided.

Therefore, you have the choice to either create a +X facing ArrowHelper and attach it to an Object3D or adjust the rotations accordingly, understanding that an ArrowHelper actually creates a +Y arrow initially.

Answer №2

Honestly, I have not had the chance to work with THREE.js before, but I will attempt to illustrate my point through this response. The concept of the imaginary or world axis is symbolized by the gray arrow. When you adjust the values of the Y and Z axes, you will notice that it impacts the red arrow but not the other gray arrow. This illustrates that the imaginary X axis remains stationary.

Even though the red arrow rotates around the X axis, it is not rotating around its X axis, but rather the X axis of the world. This is why altering the Y and Z axes of the arrow creates the illusion of it sweeping around, when in reality, it is continuously rotating around the same fixed axis it started with.

Hopefully, I have not muddied the waters with my explanation.

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 origin = new THREE.Vector3(0, 0, 0);
var xDir = new THREE.Vector3(1, 0, 0);
var length = 1;
var arrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000);
var imaginaryXAxis = new THREE.ArrowHelper(xDir, origin, length+100, 0xffffff);
arrow.rotation.order = 'XYZ';
/*arrow.rotation.y = 0.5;
arrow.rotation.z = 0.5;*/
scene.add(arrow);
scene.add(imaginaryXAxis);

camera.position.z = 2;

var animate = function () {
  requestAnimationFrame( animate );
  arrow.rotation.x += 0.01;

  renderer.render( scene, camera );
};
animate();

const yValue = arrow.rotation.y, zValue = arrow.rotation.z;

document.querySelector('button').addEventListener('click', (e) => {
    e.target.classList.toggle('affected')
    if(e.target.classList.contains('affected')){
      arrow.rotation.y=.5;
      arrow.rotation.z=.5;
      e.target.textContent = "Reset Y and Z to zero";
    } else {
      arrow.rotation.y=yValue;
      arrow.rotation.z=zValue;
      e.target.textContent = "Affect Y and Z";
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

<button>Affect Y and Z</button>

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

Is it possible for npm to assist in determining the appropriate version of Primeng that is compatible with Angular 16 dependencies

While trying to add primeng to my project, I ran into an error message: npm i primeng npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: <a href="/cdn-cgi/l/email-protection" class="__cf_email__ ...

Vue3 CheckBox Component: A versatile option for interactive user interfaces

Struggling with incorporating checkboxes into a separate component. I have a child component with a basic card template and checkbox - essentially, a product list where each card should have a checkbox for deletion. Currently, as I loop through an array to ...

Error: Unable to locate module - The specified file cannot be resolved when utilizing an external JavaScript library

I am currently integrating a WYSIWYG editor (TUI Editor) into my Angular2+ application. Since there is no official Angular wrapper available, I have decided to create my own based on an existing wrapper. Due to some installation issues with npm, I saved t ...

Error in Angular 2: The app.component.html file triggered an exception at line 1, character 108 due to excessive recursion

After successfully setting up the Angular 2 quickstart and connecting to an express server, I encountered a problem when switching from using the template property to component templateUrl. I have created a Plunker to showcase this issue: Plunker Demo imp ...

Is there a way to effortlessly refresh a viewpage with fresh data directly from Firebase?

I am trying to update my HTML page with new data from Firebase in real time without having to refresh the entire page. However, I am facing issues where the view is not updating automatically. How can I achieve this? In my JavaScript file, I query Firebas ...

Tips for assessing JSON response data from an AJAX form

I am struggling with how to properly structure a question, but I would like to explain my situation. I am using AJAX to send data and receiving JSON in return. Each value in the JSON response represents a status, and I need to test and react based on these ...

Get the page downloaded before displaying or animating the content

Is there a method to make a browser pause and wait for all the content of a page to finish downloading before displaying anything? My webpage has several CSS3 animations, but when it is first loaded, the animations appear choppy and distorted because the ...

Executing a Data Factory Pipeline using JavaScript

In Azure Data Factory, I constructed a pipeline to transfer data from an Azure Storage Table to an Azure SQL database Table. The Azure Storage Table receives data from a JavaScript chatbot that captures responses and saves them in the table. I want to ini ...

The array of objects has vanished into thin air

I am receiving a JSON string from my server through a PHP file, which contains 25 meals. The PHP script is initiated by a JavaScript function. My goal is to convert the JSON into an array of objects. The response stream displays the correct values and eac ...

Error message in React/ Ruby on Rails: Encountered Uncaught TypeError when trying to access properties of undefined (specifically, 'id') while passing down a mapped prop

I have been grappling with this bug for the past few days and haven't found a solid solution yet. My objective is to route to an individual component, [http://localhost:4000/hris/employees/1], and display that specific 'employee' card on a ...

Utilizing recursive methods for discovering routes in a two-dimensional matrix

Currently in the process of my A-level project, I am focused on the task of determining the maximum flow of a network using javascript. My approach involves working with a 2D array where the values in the array represent distances between two points. For ...

The process of departing a SocketIO room and switching to a different room logic

I am wondering how I can leave the Room when I click on a new Room Here is what my page looks like: https://i.sstatic.net/vuwv0.png The list on the left side is from the MySQL Server and it displays a list of my chats. Each Room name has an id value whi ...

Generating various fields within a single row

I am in the process of creating a dynamic form that should generate two new fields in the same line when the user clicks on the plus icon. Currently, the code I have only creates a single field. I attempted to duplicate the code within the function, but i ...

"Enhance Your Text Fields with Angular2 Text Masks for Added Text Formatting

Is there a way to combine text and numbers in my mask? This is what I am currently using: [/\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/] The above code only allows for numbers. How can I modify it to allow f ...

Testing the onClick event in React components using unit testing

I'm facing an issue with testing a Button wrapper component that utilizes a material-ui button. I tried writing some test code, but it's failing when trying to test the onClick event. index.tsx (ButtonWrapper Component) import React from &ap ...

Can the outcomes be showcased again upon revisiting a page?

Whenever I navigate away from and return to my filter table/search page, the results vanish. I'm looking for a way to preserve the results without reloading the page. Essentially, I want the page to remain as it was originally, with the search results ...

Tips for updating state and rendering in a function component in React Native:

I am attempting to update the state before rendering the component in a function component. While I have found suggestions to use the useEffect hook for this purpose, I am finding the information on the React official site somewhat perplexing. The docume ...

How can I set it up so that clicking on a specific number within the table will direct me to various pop-ups or additional tables?

// Sending an HTTP request to fetch JSON data $http({ method: 'GET', url: '/Home/GetJson' }).then(function successCallback(response) { console.log(response); $scope.jsonData = response; var data = response.data ...

Displaying search results in various Angular components

On my home page (homePageComponent), I have a search feature. When the user clicks on the search button, they are redirected to a different page called the search list page (searchListComponent). Within the searchListComponent, there is another component c ...

When trying to make a jQuery call using ajax, there is no response being received

I'm facing a slight issue with a gift list that is generated from SQL. My objective is to display each row as a form with a textbox and a button. When the button is clicked, I want to send the textbox value and an ID number (hidden field value) to a f ...