Spin a Collada model on its y-axis

I am struggling with a three.js scene that features a 3D Homer Simpson standing on a plane. My goal is to rotate him around his y-axis using the mouse.

The code I have put together is almost there, with pieces borrowed from various sources. However, I am facing an issue where I cannot rotate him around his y-axis that should be running through his core.

Currently, when I use the frame as a reference, he spins in a circular motion with the radius equalling the size of the plane. But, if I rotate using the model instead of the frame, he almost rotates around his y-axis, although slightly offset.

I am utilizing the following model. The rotation I am trying to achieve is similar to the one demonstrated in the 3D Warehouse example. By clicking on 3D View, you can see the desired rotation.

// declaring necessary variables
var camera;
var frame;
var loader = new THREE.ColladaLoader();
var model;
var plane;
var renderer;
var scene;

var mouseX = 0;
var mouseXOnMouseDown = 0;
var targetRotation = 0;
var targetRotationOnMouseDown = 0;
var windowHalfX = 800 / 2;

// loading the model
loader.load('Homer/models/body.dae', function(collada) {
    model = collada.scene;

    model.position.x = 0;
    model.position.y = 0;
    model.position.z = 100;

    model.rotation.x = - Math.PI / 2;       // adjusting Homer's initial rotation

    init();
    animate();
});

// initializing the scene
function init()
{
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(800, 800);

    document.body.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(45, 1, 100, 10000);

    camera.position.x = 50;
    camera.position.y = 120;
    camera.position.z = 600;

    scene = new THREE.Scene();
    scene.add(model);

    // Is a frame of reference necessary for rotating Homer on his own Y-axis?
    frame = new THREE.Object3D();
    frame.add(model);
    scene.add(frame);

    plane = new THREE.Mesh( new THREE.PlaneGeometry( 400, 400 ), new THREE.MeshBasicMaterial( { color: 0xe0e0e0 } ) );
    plane.geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
    scene.add( plane );

    var light = new THREE.SpotLight();
    light.position.set(200, 500, 4000);
    scene.add(light);

    renderer.render(scene, camera);

    // Implementing mouse movement to rotate Homer
    document.addEventListener( 'mousedown', onDocumentMouseDown, false );
    document.addEventListener( 'touchstart', onDocumentTouchStart, false );
    document.addEventListener( 'touchmove', onDocumentTouchMove, false );
}


function onDocumentMouseDown( event ) {
    event.preventDefault();
    document.addEventListener( 'mousemove', onDocumentMouseMove, false );
    document.addEventListener( 'mouseup', onDocumentMouseUp, false );
    document.addEventListener( 'mouseout', onDocumentMouseOut, false );

    mouseXOnMouseDown = event.clientX - windowHalfX;
    targetRotationOnMouseDown = targetRotation;
}

function onDocumentMouseMove() {
    mouseX = event.clientX - windowHalfX;
    targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
}

function onDocumentMouseUp() {
    document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
    document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
    document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}

function onDocumentMouseOut() {
    document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
    document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
    document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}

function onDocumentTouchStart( event ) {
    if ( event.touches.length === 1 ) {
        event.preventDefault();
        mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
        targetRotationOnMouseDown = targetRotation;
    }
}

function onDocumentTouchMove( event ) {
    if ( event.touches.length === 1 ) {
        event.preventDefault();
        mouseX = event.touches[ 0 ].pageX - windowHalfX;
        targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
    }
}

// Rotating Homer around the Y-Axis
function animate() {
    requestAnimationFrame( animate );

    // When using the frame, Homer rotates in a circular motion (coincidentally matching the plane's size)
    frame.rotation.y += ( targetRotation - frame.rotation.y ) * 0.03;

    // Removing the frame and using the following line rotates Homer around the y-axis
    // However, the y-axis runs along his leg and arm, not through his core as desired
    // model.rotation.z += ( targetRotation - model.rotation.z ) * 0.03;
    renderer.render( scene, camera );
}

Answer №1

This problem is quite challenging. The issue arises from the fact that Homer's geometry places him in his local coordinate system with his right foot positioned next to the origin.

In a typical scenario like this, the solution would involve

geometry.applyMatrix( new THREE.Matrix4().makeTranslation( -distance, 0, 0 ) );

which would move the geometry along the local x-axis to center Homer, effectively resolving the issue.

If you are able to understand the Collada data structure, that would also be a viable approach.

Alternatively, you can use the following workaround: (1) make your model a child of a parent object, (2) set the parent as a child of another object, and (3) offset the parent by a certain distance:

Within your init() function, include the following code snippet

scene = new THREE.Scene();

renderer = new THREE.WebGLRenderer();
renderer.setSize(800, 800);
document.body.appendChild( renderer.domElement );

camera = new THREE.PerspectiveCamera( 45, 1, 100, 10000 );
camera.position.x = 50;
camera.position.y = 120;
camera.position.z = 600;
scene.add( camera ); // important to add this

scene.add( new THREE.AxisHelper() ); // provides a reference frame

// //////////////////////////////////////////////////
// workaround for handling geometry offset
object = new THREE.Object3D();
scene.add( object );

parent = new THREE.Object3D();
parent.position.x = -39;
object.add( parent );

parent.add( model );
// //////////////////////////////////////////////////

Then, in your animate() function:

object.rotation.y += ( targetRotation - object.rotation.y ) * 0.03;

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

Repair the masthead background during overscroll

The Dilemma At the top of my webpage, I have a sleek masthead with a captivating background image that scrolls along with the page. However, there is an issue when users overscroll upwards, causing an undesirable white overflow to appear. To rectify this ...

Illumination causes surfaces to transform

In my simple scene, I have a ground and an interesting light source. However, when the light hits certain meshes, it creates some strange effects. The shadows are being cast correctly, but the other meshes affected by the light are showing unusual results. ...

Unable to open a modal in Angular UI after closing the initial modal

Hey there! I've been attempting to open a modal after closing the first one, but I'm encountering an issue where the second modal appears to be opened but is actually not. I'm struggling to understand what's causing this problem. Could ...

Adjust the content of a table cell using jQuery

<td>09 Mars 2020</td> Using the string above, I have created a div. Here are the Jquery variables I am working with: date1 = 09 Mars 2020 date2 = 18 Mars 2020 My goal is to combine the content from another date with the existing date to ach ...

Mapping object data within an object in React: A step-by-step guide

Within my React project, I am retrieving data from a JSON source like so: https://i.sstatic.net/fEZFL.jpg The simplified JSON data appears as: const listData = [ { "_id": "abscdf456", "bucket": { code: "videos" }, "contents": [{}, {} ...

The compilation of the Angular application is successful, however, errors are arising stating that the property does not exist with the 'ng build --prod' command

When compiling the Angular app, it is successful but encountered errors in 'ng build --prod' ERROR in src\app\header\header.component.html(31,124): : Property 'searchText' does not exist on type 'HeaderComponent&apo ...

Creating unique geometric designs with three.js

Attempting to construct a polygon in three.js and here is the code used for it. function DeployZone(coordinatesList) { // Forming the polygon Shape { var material = new THREE.MeshLambertMaterial({ color: 0x00ffcc }); var faces = [0, 1, 2, 3, 4] ...

Fade in text effect using jQuery on a Mac computer

Trying to create a smooth text fading effect across different browsers using jQuery. In the example below, you may notice that during the last part of the animation when the text fades in, the font weight suddenly increases causing a jerky/clunky appearan ...

Dynamic text input and selection menu with AJAX (PHP and JavaScript)

As a student who is new to Javascript and PHP, I am trying to create a login page for my website that can verify user input in the database using AJAX. For example, when a user enters their username and password, the system should automatically check if t ...

Using AngularJS to incorporate ng-include with ng-click functionality

I am trying to figure out a way to insert HTML that is specifically optimized for the controller into an alert div. Unfortunately, I have been unsuccessful so far... <script type="text/ng-include" id="login.html"> <form data-select="exepti ...

Get only the text content from a hyperlink using Tinymce-4

Within tinymce.activeEditor, I currently have this line of innerHTML code (part of a ul-list): <li><a href="#">Important words</a></li> When I place the caret within the sentence "Important words," and click a button with the foll ...

Send a collection of items to directives using a function

I need assistance in creating a directive that can dynamically insert buttons into a div. I have set up a json to store the button text, class, and action, but for some reason, the function does not trigger when the button is clicked. Can anyone point out ...

Pass the JavaScript variable to the Laravel controller for updating VueJS

Trying to send data from my modal to a controller to update data, but I'm not sure how to declare my variable and how to send this variable... Here is my current code: Vue.js <script> export default { data() { return { ...

A function with a specific name for sorting a multidimensional object based on a specified sub-key's value, without anonymity

My data consists of a collection of objects, not an array: var people = {}; people['Zanny'] = {date: 447, last: 'Smith'}; people['Nancy'] = {date: 947, last: 'William'}; people['Jen'] = {date: 147, last: &a ...

AngularJS: Sending form data to an external server results in an error due to restricted URI access

I am currently diving into the world of AngularJs and putting effort into mastering it. In this process, I've created a demo application that attempts to send form data to a localhost site. The code for this endeavor is provided below. index.html &l ...

Trouble with jQuery addClass function when trying to add to a specific object variable

I'm attempting to give an error class to an input textbox to inform the user that their input is invalid. Within the change event, I am referencing what seems to be the input field and storing it in a variable. However, calling addClass on the var ...

Experiencing difficulties with a cross-domain jQuery/AJAX service request

After extensively researching various threads both on this platform and elsewhere, I have been trying to successfully execute a cross-domain AJAX call. The scenario involves a Restful WCF service that simply returns a boolean value. The service is configur ...

ReactJS encountered an error: [function] is not defined, July 2017

Attempting to convert a JSON file into an array and then randomly selecting 5 items from it. I suspect the issue lies in my render/return statement at the end of ImageContainer.js, but as a newbie in ReactJS, it could be anything. Any assistance or guida ...

Struggling with making changes to a instantiated "this" object within a pseudo javascript class

If you scroll down to the bottom of this post, you'll find a workaround or possible solution. I've been grappling with understanding how pseudo classes work together to achieve the task I'm attempting (explained in the code below). It might ...

Why is my Javascript for-loop returning "null" instead of the expected value?

Having trouble with the function below that is supposed to calculate the average of all elements in array1. Unfortunately, the function keeps returning null as the result. I can't seem to pinpoint the issue. var array1 = [46,73,-18,0,-442,779,5,1400] ...