Rotating a path in Three.js: A beginner's guide

I am a beginner with three.js and I am trying to make an object move along an ellipse curve. However, when I rotate the ellipse using ellipse.rotation.y, the object no longer follows the path correctly. It continues to move along the original path instead. How can I solve this issue? I used the code on this website to replicate my problem:

http://jsfiddle.net/w9914420/krw8nwLn/14/

// GLOBALS - ALLOCATE THESE OUTSIDE OF THE RENDER LOOP - CHANGED
var cubes = [], marker, spline;
var matrix = new THREE.Matrix4();
var up = new THREE.Vector3( 0, 1, 0 );
var axis = new THREE.Vector3( );
var pt, radians, axis, tangent, path;

// the getPoint starting variable - !important - You get me ;)
var t = 0;

//This function generates the cube and chooses a random color for it 
//on intial load.

function getCube(){
    // cube mats and cube
    var mats = [];
    for (var i = 0; i < 6; i ++) {
        mats.push(new THREE.MeshBasicMaterial({color:Math.random()*0xffffff}));
    }

    var cube = new THREE.Mesh(
        new THREE.CubeGeometry(2, 2, 2),
        new THREE.MeshFaceMaterial( mats )
    );

    return cube
}

// Ellipse class, which extends the virtual base class Curve
function Ellipse( xRadius, yRadius ) {

        THREE.Curve.call( this );

        // add radius as a property
        this.xRadius = xRadius;
        this.yRadius = yRadius;

}

Ellipse.prototype = Object.create( THREE.Curve.prototype );
Ellipse.prototype.constructor = Ellipse;

// define the getPoint function for the subClass
Ellipse.prototype.getPoint = function ( t ) {

    var radians = 2 * Math.PI * t;

    return new THREE.Vector3( this.xRadius * Math.cos( radians ),
                              this.yRadius * Math.sin( radians ),
                              0 );

};

//

var mesh, renderer, scene, camera, controls;


function init() {

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

    // scene
    scene = new THREE.Scene();

    // camera
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( 20, 20, 20 );

    // controls
    controls = new THREE.OrbitControls( camera, renderer.domElement );
    controls.addEventListener( 'change', render ); // use if there is no animation loop
    controls.minDistance = 10;
    controls.maxDistance = 50;

    // light
    var light = new THREE.PointLight( 0xffffff, 0.7 );
    camera.add( light );
    scene.add( camera ); // add to scene only because the camera  has a child

    // axes
    scene.add( new THREE.AxisHelper( 20 ) );


    ////////////////////////////////////////
    //      Create the cube               //
    ////////////////////////////////////////

    marker = getCube();
    marker.position.set(0,0,0);
    scene.add(marker);


    ////////////////////////////////////////
    //      Create an Extruded shape      //
    ////////////////////////////////////////

    // path
    path = new Ellipse( 5, 10 );

    // params
    var pathSegments = 64;
    var tubeRadius = 0.5;
    var radiusSegments = 16;
    var closed = true;

    var geometry = new THREE.TubeBufferGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );

    // material
    var material = new THREE.MeshPhongMaterial( {
        color: 0x0080ff, 
    } );

    // mesh
    mesh = new THREE.Mesh( geometry, material );
    scene.add( mesh );

    //////////////////////////////////////////////////////////////////////////
    //       Create the path which is based on our shape above              //
    //////////////////////////////////////////////////////////////////////////

    //Please note that this red ellipse was only created has a guide so that I could  be certain that the square is true to the tangent and positioning.

    // Ellipse class, which extends the virtual base class Curve
        var curve = new THREE.EllipseCurve(
            0,  0,            // ax, aY
            6, 11,           // xRadius, yRadius
            0,  2 * Math.PI,  // aStartAngle, aEndAngle
            false,            // aClockwise
            0                 // aRotation
        );

        //defines the amount of points the path will have
        var path2 = new THREE.Path( curve.getPoints( 100 ) );
         geometrycirc = path2.createPointsGeometry( 100 );
        var materialcirc = new THREE.LineBasicMaterial( {
            color : 0xff0000
        } );

        // Create the final object to add to the scene
        var ellipse = new THREE.Line( geometrycirc, materialcirc );
        ellipse.position.set(0,0,0);
        scene.add( ellipse );

}

function animate() {
    requestAnimationFrame(animate);

    render();
}


function render() {

        // set the marker position
        pt = path.getPoint( t );

        // set the marker position
        marker.position.set( pt.x, pt.y, pt.z );

        // get the tangent to the curve
        tangent = path.getTangent( t ).normalize();

        // calculate the axis to rotate around
        axis.crossVectors( up, tangent ).normalize();

        // calcluate the angle between the up vector and the tangent
        radians = Math.acos( up.dot( tangent ) );

        // set the quaternion
        marker.quaternion.setFromAxisAngle( axis, radians );

        t = (t >= 1) ? 0 : t += 0.002;

        renderer.render( scene, camera );

}

init();
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Answer №1

To simplify achieving your desired result, consider adding the tube (mesh) and path tracking object (marker) as children of the ellipse, rather than directly to the scene. This way, transformations applied to the ellipse will also affect the marker and mesh:

ellipse.add(mesh);
ellipse.add(marker);
scene.add(ellipse);

View the example below where I have implemented these changes in your original code:

// GLOBALS - ALLOCATE THESE OUTSIDE OF THE RENDER LOOP - CHANGED
var cubes = [], marker, spline;
var matrix = new THREE.Matrix4();
var up = new THREE.Vector3( 0, 1, 0 );
var axis = new THREE.Vector3( );
var pt, radians, axis, tangent, path;

var ellipse;

// the getPoint starting variable - !important - You get me ;)
var t = 0;

//This function generates the cube and chooses a random color for it 
//on intial load.

function getCube(){
    // cube mats and cube
    var mats = [];
    for (var i = 0; i < 6; i ++) {
        mats.push(new THREE.MeshBasicMaterial({color:Math.random()*0xffffff}));
    }

    var cube = new THREE.Mesh(
        new THREE.CubeGeometry(2, 2, 2),
        new THREE.MeshFaceMaterial( mats )
    );

    return cube
}

// Ellipse class, which extends the virtual base class Curve
function Ellipse( xRadius, yRadius ) {

        THREE.Curve.call( this );

        // add radius as a property
        this.xRadius = xRadius;
        this.yRadius = yRadius;
}

Ellipse.prototype = Object.create( THREE.Curve.prototype );
Ellipse.prototype.constructor = Ellipse;

// define the getPoint function for the subClass
Ellipse.prototype.getPoint = function ( t ) {

    var radians = 2 * Math.PI * t;

    return new THREE.Vector3( this.xRadius * Math.cos( radians ),
                              this.yRadius * Math.sin( radians ),
                              0 );
};

var mesh, renderer, scene, camera, controls;

function init() {

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

    // scene
    scene = new THREE.Scene();

    // camera
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( 20, 20, 20 );

    // controls
    controls = new THREE.OrbitControls( camera, renderer.domElement );
    controls.addEventListener( 'change', render ); // use if there is no animation loop
    controls.minDistance = 10;
    controls.maxDistance = 50;

    // light
    var light = new THREE.PointLight( 0xffffff, 0.7 );
    camera.add( light );
    scene.add( camera ); // add to scene only because the camera  has a child

    // axes
    scene.add( new THREE.AxisHelper( 20 ) );

    ////////////////////////////////////////
    //      Create the cube               //
    ////////////////////////////////////////

    marker = getCube();
    marker.position.set(0,0,0);
    //scene.add(marker);


    ////////////////////////////////////////
    //      Create an Extruded shape      //
    ////////////////////////////////////////

    // path
    path = new Ellipse( 5, 10 );

    // params
    var pathSegments = 64;
    var tubeRadius = 0.5;
    var radiusSegments = 16;
    var closed = true;

    var geometry = new THREE.TubeBufferGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );

    // material
    var material = new THREE.MeshPhongMaterial( {
        color: 0x0080ff, 
    } );

    // mesh
    mesh = new THREE.Mesh( geometry, material );
    scene.add( mesh );

    //////////////////////////////////////////////////////////////////////////
    //       Create the path which is based on our shape above              //
    //////////////////////////////////////////////////////////////////////////

    //Please note that this red ellipse was only created has a guide so that I could  be certain that the square is true to the tangent and positioning.

    // Ellipse class, which extends the virtual base class Curve
    var curve = new THREE.EllipseCurve(
        0,  0,            // ax, aY
        6, 11,           // xRadius, yRadius
        0,  2 * Math.PI,  // aStartAngle, aEndAngle
        false,            // aClockwise
        0                 // aRotation
    );
  

    //defines the amount of points the path will have
    var path2 = new THREE.Path( curve.getPoints( 100 ) );
      geometrycirc = path2.createPointsGeometry( 100 );
    var materialcirc = new THREE.LineBasicMaterial( {
        color : 0xff0000
    } );
      

    // Create the final object to add to the scene
    ellipse = new THREE.Line( geometrycirc, materialcirc );
    ellipse.position.set(0,0,0);

    ellipse.add(mesh);
    ellipse.add(marker);
    scene.add(ellipse);
    window.onresize = resize;
}

function animate() {
    ellipse.rotation.y += Math.PI / 200
    requestAnimationFrame(animate);
    render();
}

function render() {

    // set the marker position
    pt = path.getPoint( t );

    // set the marker position
    marker.position.set( pt.x, pt.y, pt.z );

    // get the tangent to the curve
    tangent = path.getTangent( t ).normalize();

    // calculate the axis to rotate around
    axis.crossVectors( up, tangent ).normalize();

    // calcluate the angle between the up vector and the tangent
    radians = Math.acos( up.dot( tangent ) );

    // set the quaternion
    marker.quaternion.setFromAxisAngle( axis, radians );

    t = (t >= 1) ? 0 : t += 0.002;

    renderer.render(scene, camera);
}

function resize() {    
    var aspect = window.innerWidth / window.innerHeight;
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = aspect;
    camera.updateProjectionMatrix();
}

init();
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

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

Incorporate a Font Awesome icon link within ReactJS for enhanced design and functionality

I am using Typescript and ReactJS to work on adding a link to font awesome icons. Below is a snippet of my code: import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faRandom } from &apos ...

An error occurred due to a missing value within the forEach loop

In my JavaScript object, I am encountering an issue with a key. resolve: function () { var result = this.initialValue; console.log('initial value:',result); // 5 this.functions.forEach(function (element, index) { ...

What is the best way to extract an inner exception message from a JSON string using JavaScript?

I am struggling with error messages that have a nested structure like this: var data = {"message":"An issue has occurred.", "exceptionMessage":"An error occurred while updating the entries. Refer to the inner exception for more details.", "exceptionTyp ...

Carry out numerous commitments across a range of elements

I have a list of objectIds and I want to perform operations on different collections based on each Id. I prefer executing the operations sequentially. var removeOperation = function(objectified){ return Comps.findOne({reviews : objectified}).populate([ ...

Exploring the scope in JavaScript functions

Looking at this small code snippet and the output it's producing, I can't help but wonder why it's printing in this unexpected order. How can I modify it to achieve the desired output? Cheers, The desired result: 0 1 2 0 1 2 The actual r ...

What could be the reason behind encountering the UnhandledPromiseRejectionWarning error while executing npm build in my serverless application?

I am encountering an issue while attempting to execute npm build on my serverless aws-nodejs-typescript project. I am unsure of how to resolve it. Can someone provide guidance on how to troubleshoot this problem? npm build (node:44390) UnhandledPromiseRej ...

How to generate a dropdown menu using a deeply nested JSON array

I'm looking to create a series of drop-down lists based on the JSON array provided below. There will be five drop-down lists, and when I select an option in one list, the other four should populate accordingly. For example, if I choose "Hindi" in the ...

What is the process for fetching a texture from a MySql database using three.js?

Is it possible to load a model's texture from a MySql database using three.js and php, based on the logged-in user? My goal is to display the texture on the model that corresponds to the current user. Can I simply use "echo" to retrieve the column con ...

How can I simulate keyboard events in Angular using a button click?

I've included separate buttons for Ctrl+z and Ctrl+y functionalities. When these buttons are clicked, I want them to perform the respective undo and redo actions programmatically. Below is the code snippet for this functionality. undoText(event:MouseE ...

How come the Qunit counter doesn't seem to ever reach its expected target value during this test?

I'm currently stuck on a Qunit test related to jQuery Mobile's listview.filter extension. I can't figure out how the variable _refreshCornersCount ends up with a value of 3. Below is the section causing confusion. module( "Custom search ...

What is the solution to preventing Angular 1.3 ngMessages from affecting the size of my form?

Hey there, I'm diving into front-end design for the first time. My issue is with a form that I want to use ng-messages for validation error messages. Currently, my form's size changes depending on whether there are errors displayed or not, and it ...

Anime.js: Grid layout causing issues with displaying simple text animations

I'm attempting to create a text animation within a grid layout using the anime.js library, but unfortunately, the animation isn't displaying. The project consists of just one HTML file. The JavaScript code: <script> import anime from &ap ...

What is preventing my function from retrieving user input from an ngForm?

I'm currently working on my angular component's class. I am attempting to gather user input from a form and create an array of words from that input. The "data" parameter in my submit function is the 'value' attribute of the ngForm. Fo ...

React-router: Issue with ProtectedRoute authentication mechanism not functioning as expected

I'm currently working on setting up protected routes that can only be accessed after the user has logged in. There are two main routes to consider: /login, which is the Login component (public), and /, which is the Dashboard component (protected). Wh ...

Can you provide a list of factors that influence coverage? Additionally, is there a specific algorithm available for calculating

As part of my Angular project, I am currently focusing on writing unit test cases using the Jasmine Framework and Karma. To ensure thorough testing coverage, I am utilizing Coverage-html (Istanbul). When it comes to coverage, there are several important t ...

The Hover Effect on HTML Element Not Working on One Side Specifically

Scenario: Currently, I am working on creating a popover (tooltip) using Bootstrap 3.3.7. In order to achieve this, I have structured my HTML file as follows: popover { margin-left: 100%; padding: 5px 11px; border: solid 1px; border-radius: 25px ...

Partially extended texture in Three.js

Currently, I am using the Collada loader in Three.js r65 to load my 3D object. Upon loading, I apply a texture to all parts of the model using the following code snippet. var loader = new THREE.ColladaLoader(); loader.options.convertUpAxis = true; loader ...

The 'presenceUpdate' event in Discord.js seems to be inactive and not triggering as expected

I have a unique scenario with a "Special User" that I fetch using 'Client.users.fetch(Special User's ID)'. This user has two event listeners attached to it, 'message' and 'presenceUpdate'. The message event listener funct ...

changing the validation function from using if statements to utilizing switch statements

Currently, I am refactoring a form in react and planning to offload most of the state and logic to the parent component. This decision is made because the parent's state will be updated based on the form submission results. However, I encountered an i ...

Fix background transition and add background dim effect on hover - check out the fiddle!

I'm facing a challenging situation here. I have a container with a background image, and inside it, there are 3 small circles. My goal is to make the background image zoom in when I hover over it, and dim the background image when I hover over any of ...