Determine the angle needed to rotate a point in order to align it with another point in three-dimensional space

Given two vectors:

V1 = { x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968 }

V2 = { x: 2.3652551657790695, y: -16.732085083053185, z: 8.945905454164146 }

How can I determine the angle at which V1 needs to be rotated in order to face directly towards V2?

To simplify, imagine knowing your exact position in space and that of another individual elsewhere... How could you calculate the angles necessary to point directly at them using mathematics?

Visual representation of my axis here

The formula I am currently using (which is incorrect)

v2.x -= v1.x;
v2.y -= v1.y;
v2.z -= v1.z;

v1.x = 0;
v1.y = 0;
v1.z = 0;

var r = Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.y,2) + Math.pow(v2.z,2));
var θ = Math.acos((Math.pow(v2.x,2) + Math.pow(v2.z,2))/(r*Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.z,2))));
var ϕ = Math.acos(v2.x/Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.z,2)));

My current process involves rotating V1 based on the calculated theta and phi angles.

v1.rotation.y = θ;
v2.rotation.x = ϕ;

However, this method results in inaccurate rotations.

θ = 0.6099683401012933

ϕ = 1.8663452274936656

Conversely, when utilizing THREE.js and the lookAt function, the following correct rotations are produced:

y/θ: -0.24106818240525682

x/ϕ: 2.5106584861123644

I appreciate all assistance provided! While THREE.js works well, I aim to achieve this functionality using pure vanilla JS for portability across different languages.

Answer №1

Consider using a matrix for orientation calculation. The viewpoint v1 is where you are looking towards v2. A transformation matrix can help show orientation, while Euler Angle provides another interpretation.

To achieve what you want, you can build a transformation matrix from object space to world space in three steps:

  1. Start with the camera at the origin of world space, with a rotation of (0,0,0), where world space aligns with object space: v1'(0,0,0).
  2. Translate the camera to
    v1(3.296372727813439,-14.497928014719344,12.004105246875968)
    . Object space may have an offset from world space, but their axes remain parallel, and the camera rotation remains (0,0,0).
  3. Adjust the camera to look at v2, causing a change in camera rotation.

By constructing a transformation matrix representing these actions, we can determine the orientation.

  1. Create the translation matrix first: As translation is an affine transformation, a 4x4 matrix is required. The matrix can be obtained easily:
  2. Use basis axes to obtain the rotation matrix.

    You may need to set the camera's up vector, defaulting to (0,1,0). In object space, calculate the basis z axis by subtracting v1 from v2:

    z = (v1.x-v2.x,v1.y-v2.y,v1.z-v2.z).normalize()

    Determine the basis x vector by taking the cross product of up and z:

    x = up.crossproduct(z)

    The basis y vector, perpendicular to z-x plane, is found by crossing z and x:

    y = z.product(x)

    Construct the rotation matrix as a 3 x 3 matrix:

    Next, derive the transformation matrix:

    Different ways exist to convert between matrices, Euler Angles, and Quaternions; details can be explored in this book: 3D Math Primer for Graphics and Game Development

    Three.js incorporates the LookAt() function similarly to this approach.

    An example in three.js source code with added comments:

    function lookAt( eye, target, up ) //eye : your camera position; target : which point you want to look at; up : camera up vector
    {  
        if ( x === undefined ) {
            x = new Vector3();
            y = new Vector3();
            z = new Vector3();
        }
        var te = this.elements;
    
        z.subVectors( eye, target ).normalize();
    
        if ( z.lengthSq() === 0 ) {
            z.z = 1;
        }
    
        x.crossVectors( up, z ).normalize();
    
        if ( x.lengthSq() === 0 ) {
            z.z += 0.0001;
            x.crossVectors( up, z ).normalize();
        }
    
        y.crossVectors( z, x );
    
        te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x; 
        te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y;
        te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z;
    
        return this;
    };
    

Implement Matrix using a list structure like three.js does.

An alternative idea involves Spherical Polar Coordinates System. Utilizing coordinates expressed as (r,Θ,Φ), where Θ represents the heading angle and Φ denotes the pitch angle. Convert the Cartesian coordinates of v1 and v2 to spherical ones. Calculate the angular displacement between v1 and v2, then apply it to adjust the camera rotation.

A possible code snippet assuming the camera is at the world origin (0,0,0):

//convert v1 and v2 Cartesian coordinates to Spherical coordinates
var radiusV1 = Math.sqrt( Math.pow(v1.x) + Math.pow(v1.y) + Math.pow(v1.z));
var headingV1 = Math.atan2(v1.x , v1.z);
var pitchV1 = Math.asin(-(v1.y) / radiusV1);

var radiusV2 = Math.sqrt( Math.pow(v2.x) + Math.pow(v2.y) + Math.pow(v2.z));
var headingV2 = Math.atan2(v2.x , v2.z);
var pitchV2 = Math.asin(-(v2.y) / radiusV2);

//calculate angular displacement
var displacementHeading = headingV2 - headingV1;
var displacementPitch = pitchV2 - pitchV1;

//adjust camera rotation based on displacement
camera.rotation.x += displacementPitch;
camera.rotation.y += displacementHeading;

Knowledge of 3D math proves valuable and learning-worthy. All referenced formulas and concepts can be explored further in the recommended book.

Hopefully, this information aids your understanding.

Answer №2

Here are the corrected formulas:

 var dx = v2.x-v1.x; //-0.93
 var dy = v2.y-v1.y; //-31.22
 var dz = v2.z-v1.z;
 var rxy = Math.sqrt( Math.pow(dx,2) + Math.pow(dy,2) );
 var lambda = Math.atan(dy/dx);
 var phi = Math.atan(dz/rxy)

To ensure accuracy, adjust the formulas for phi and lambda based on the vector's quarter. Here is a simplified guide:

 //if the calculations are in degrees, add 180 instead of PI
 if (dx < 0) phi = phi + Math.PI;
 if (dz < 0) lambda = -1 * lambda;

Answer №3

When x/ϕ is rotated around the x-axis, it equals the angle between y and z. Similarly, for y/θ, we need to calculate the angle between x and z.

V1 = { x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968 }
V2 = { x: 2.3652551657790695, y: -16.732085083053185, z: 8.945905454164146 }
var v={dx:V2.x-V1.x, dy:V2.y-V1.y, dz:V2.z-V1.z}
testVector(v);
 
function testVector(vec){
   console.log();
   var angles=calcAngles(vec);
   console.log("phi:"+angles.phi+" theta:"+angles.theta);
}
function calcAngles(vec){
   return {
      theta:(Math.PI/2)+Math.atan2(vec.dz, vec.dx),
      phi:(3*Math.PI/2)+Math.atan2(vec.dz, vec.dy)
   };
}

Answer №4

After carefully analyzing the latest version of THREE.js (r84), I have extracted the relevant code that I believe will help you achieve your desired outcome.

// Here is the code extracted from the latest version of THREE.js (r84)
// Source: https://github.com/mrdoob/three.js/tree/master
// THREE.js is licensed under MIT (Copyright © 2010-2017 three.js authors)
// 
// Some modifications were made by K Scandrett to adapt the functions for this purpose, but not the calculations.
// Any errors are my responsibility and not those of the three.js authors. 
// I cannot guarantee that there are no bugs in the adapted code,
// so please use it at your own risk. Enjoy the pizza!



var v1 = {x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968};
var v2 = {x: 2.3652551657790695, y: -16.732085083053185,z: 8.945905454164146};

var startVec = {x: v1.x, y: v1.y, z: v1.z, w: 0};
var endVec = {x: v2.x, y: v2.y, z: v2.z, w: 0};

var upVec = {x: 0, y: 1, z: 0}; // y up

var quat = lookAt(startVec, endVec, upVec);
var angles = eulerSetFromQuaternion(quat);

console.log(angles.x + " " + angles.y + " " + angles.z);

/* KS function */
function magnitude(v) {
  return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}

/* KS function */
function normalize(v) {
  var mag = magnitude(v);
  return {
    x: v.x / mag,
    y: v.y / mag,
    z: v.z / mag
  };
}

function subVectors(a, b) {
  return {
    x: a.x - b.x,
    y: a.y - b.y,
    z: a.z - b.z
  };
}

function crossVectors(a, b) {
  var ax = a.x,
    ay = a.y,
    az = a.z;
  var bx = b.x,
    by = b.y,
    bz = b.z;
  return {
    x: ay * bz - az * by,
    y: az * bx - ax * bz,
    z: ax * by - ay * bx
  };
}

function lengthSq(v) {
  return v.x * v.x + v.y * v.y + v.z * v.z;
}


function makeRotationFromQuaternion(q) {

  var matrix = new Float32Array([

    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1

  ]);

  var te = matrix;

  var x = q.x,
    y = q.y,
    z = q.z,
    w = q.w;
  var x2 = x + x,
    y2 = y + y,
    z2 = z + z;
  var xx = x * x2,
    xy = x * y2,
    xz = x * z2;
  var yy = y * y2,
    yz = y * z2,
    zz = z * z2;
  var wx = w * x2,
    wy = w * y2,
    wz = w * z2;

  te[0] = 1 - (yy + zz);
  te[4] = xy - wz;
  te[8] = xz + wy;

  te[1] = xy + wz;
  te[5] = 1 - (xx + zz);
  te[9] = yz - wx;

  te[2] = xz - wy;
  te[6] = yz + wx;
  te[10] = 1 - (xx + yy);

  // last column
  te[3] = 0;
  te[7] = 0;
  te[11] = 0;

  // bottom row
  te[12] = 0;
  te[13] = 0;
  te[14] = 0;
  te[15] = 1;

  return te;

}

// More functions follow...

To view this code in action on Plunker, click here.

Answer №5

If you're looking for a straightforward answer to your question, using Euler angles may not be the best approach. The Euler coordinate system is designed for coordination purposes and can lead to issues with orientation and rotation. While it may be easy for humans to understand, it can result in Gimbal lock, causing incorrect outcomes.

When it comes to orientation, there are two common systems used: Transform matrix and Quaternions. For example, three.js utilizes quaternions for its lookAt() function, while Crag.Li's recommendation involves using a Transform Matrix.

It's important to highlight the complexity of 3D transformation, as I once learned the hard way by attempting to simplify the process and ultimately wasting time on ineffective methods. Learning the math behind 3D transformations, such as through resources like the "3D Math Primer," is essential if you truly want to succeed in this area.

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

Utilizing object deconstruction in function parameters

const sampleObj = { apple: 1, orange: 2 }; let newFunction = (...objects) => { console.log(apple,orange); // ReferenceError: apple is not defined }; newFunction(sampleObj); I am looking to access the object properties directly within the f ...

Guide on the correct way to register plugins in Hapi.js

I attempted to create a simple server following the instructions on hapi's official website, but encountered an issue: I couldn't register plugins. var Hapi = require('hapi'); var server = new Hapi.Server(); server.connection({port: ...

I am unable to pass a variable through a callback, and I cannot assign a promise to a

Currently, I am facing a challenge with my code where I need to loop through a hard-coded data set to determine the distance from a user-entered location using Google's web API. The issue lies in passing an ID variable down through the code so that I ...

Child_process module spawn method in Node.js

When I attempt to play an audio stream using the mpg123 command, everything works perfectly fine. I have also implemented a method to terminate the process successfully. However, I am struggling to retrieve output from the executed command. Despite follow ...

Converting JSON information into a JavaScript array

var test=[]; $(document).ready(function(){ $.getJSON("data.json",function(data){ $.each(data,function(key,value){ test.push(value.topic); }); }); }); I have written some j ...

Alter the color scheme of the website depending on the information provided by the user on the previous page

On the previous page, I have created a form with a checklist containing options for colors: red, green, and blue. If the user selects red, the background color on the next page should change to red. If green is selected, the background color will be green ...

The issue with ngFileUpload causing empty file posts on Safari

Currently, I am utilizing ngFileUpload to transmit images to the Cloudinary service. My application is constructed on Ionic and is meant to be functional on both iOS and Android platforms. The code snippet below showcases my image uploading process: .se ...

Positioning a div on the right side of its parent div

Hi there! I'm currently working on a small navbar that has two sections: the left section with an arrow icon and the right section with two icons - an envelope and an exclamation triangle. I've been trying to position the right section in the top ...

Guide on closing a Bootstrap modal by clicking within the UI Grid in Angular

I have an Angular app written in TypeScript where I am utilizing the ui grid to present data within a bootstrap modal. My goal is to have the modal close when any of the columns in a grid row are clicked. Currently, I am not relying on $modal or $modalIn ...

How to update newly added information through the existing form in ReactJS

Currently, I am working on a CRUD operation in my project. So far, I have successfully implemented the functionalities to add, delete, and display data. However, I am facing challenges with updating existing data. Despite trying various methods, none of th ...

What is the best way in React to handle large operations, such as filtering and mapping a 10000-row array, asynchronously in order to prevent the UI from being blocked?

Currently, my table contains a large amount of data. Whenever I apply a filter, the data needs to be filtered and remapped, causing a delay of approximately 1-2 seconds. However, during this processing time, the UI becomes unresponsive. Is there a method ...

Array buffers are scheduled by BinaryJs and the Audio API

My node.js server is streaming some ArrayBuffers: var BinaryServer = require('binaryjs').BinaryServer; var fs = require('fs'); var server = BinaryServer({port: 2000}); server.on('connection', function(client){ var file = f ...

Transforming a set of properties into an organized array

Looking to transform an object literal that contains inner objects with a "rank" key holding floating point values into an array of these inner objects, sorted by the "rank" value. Input Object: { 452:{ bla:123, dff:233, rank:2 }, 234:{ ...

What is the best way to integrate AngularJS with jQuery?

Lately, I've been incorporating AngularJS into my project. In the past, I've written various prototypes and standalone JavaScript functions, such as (Node.js + socket.io functionality). However, I am currently facing some challenges when it come ...

What is the best way to target a specific item in a list using JavaScript in order to modify its appearance?

How can I modify the appearance of a specific li element when it is clicked? ...

What is the best way to ensure that a specific number of XHR callbacks have successfully completed before proceeding with further actions?

I have four requests that each have their own callback and can fire in any order. However, I need all the callbacks to finish successfully before executing mergeData. The issue with my current approach is that the initial parameter values do not refresh ...

Generating a computer selection at random, while ensuring it is different from the choice you've already made

I'm currently developing a boxing game that allows users to select from three different boxers. The computer will then choose one of the remaining two boxers that the user did not select. https://jsfiddle.net/1ehpxto6/ My HTML code looks like this: ...

Is it possible to activate the :active pseudoclass by pressing the 'enter' key on the keyboard using solely CSS?

The CSS: a:focus { opacity: .75 } a:active { transform: translateY(4px) } The purpose: When keyboard users tab to the link, the :focus style serves as a visual indicator Upon pressing the enter key to activate the link, the :active style provides ...

Troubleshooting: Issue with displaying PHP data on an AngularJS table using JSON through jQuery AJAX

I've been encountering an issue while trying to display data from a database on my HTML page using Angular and JSON. Despite the browser being able to read the database, it fails to show the data. Can anyone shed some light on why this might be happen ...

Non-responsive Click Event on Dynamically Created Div Class

I am facing some uncertainty in approaching this problem. In the HTML, I have created buttons with additional data attributes. These buttons are assigned a class name of roleBtn. When clicked, they trigger the jQuery function called roleBtnClicked, which ...