The Three.js display becomes warped and distorted until the user moves their mouse

Upon loading my three.js scene, it appears distorted until I interact with the mouse on the website. The distortion can be seen in the image below:

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

However, as soon as I move the mouse, the scene snaps back to normal. Surprisingly, the position of the cursor relative to the canvas where the scene is rendered doesn't seem to affect this issue at all. Here's how the scene looks after moving the mouse:

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

The dependencies related to three.js that are being used include:

  • "three": "^0.108.0"
  • "three-orbitcontrols": "^2.102.2"
  • "three.meshline": "^1.2.0"

I attempted updating "three" to the latest version (0.116.1), but unfortunately, that did not resolve the problem. This issue has been reproducible on Firefox and Edge, however, Chrome seems unaffected.

Some additional context: we utilize OffscreenCanvas for improved performance, sending mouse positions from the main thread to a web worker upon mousemove events. This information is then used to subtly adjust the camera and background positions (with offsets). Even with the mousemove handler logic temporarily removed from the web worker code, the issue persists, indicating an unrelated cause. Camera animations are made smooth using tween.js.

Highlighted snippets of code:

Scene setup:

const {scene, camera} = makeScene(elem, cameraPosX, 0, 60, 45);
const supportsWebp = (browser !== 'Safari');
imageLoader.load(backgroundImage, mapImage => {
   const texture = new THREE.CanvasTexture(mapImage);
   texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
   texture.minFilter = THREE.LinearFilter;
   // Repeat background so we don't run out of it during offset changes on mousemove
   texture.wrapS = THREE.MirroredRepeatWrapping;
   texture.wrapT = THREE.MirroredRepeatWrapping;
   scene.background = texture;
});

// Creating objects in the scene
let orbitingPlanet = getPlanet(0xffffff, true, 1 * mobilePlanetSizeAdjustment);
scene.add(orbitingPlanet);

// Ellipse class, which extends the virtual base class Curve
let curveMain = new THREE.EllipseCurve(
    0, 0, // ax, aY
    80, 30, // xRadius, yRadius
    0, 2 * Math.PI, // aStartAngle, aEndAngle
    false, // aClockwise
    0.2 // aRotation
 );
 let ellipseMainGeometry = new THREE.Path(curveMain.getPoints(100)).createPointsGeometry(100);
 let ellipseMainMaterial = new MeshLine.MeshLineMaterial({
      color: new THREE.Color(0xffffff),
      opacity: 0.2,
      transparent: true,
  });
  let ellipseMain = new MeshLine.MeshLine();
  ellipseMain.setGeometry(ellipseMainGeometry, function(p) {
     return 0.2; // Line width
  });
  const ellipseMainMesh = new THREE.Mesh(ellipseMain.geometry, ellipseMainMaterial );
  scene.add(ellipseMainMesh);
  // Create a halfish curve on which one of the orbiting planets will move
 let curveMainCut = new THREE.EllipseCurve(
     0, 0, // ax, aY
     80, 30, // xRadius, yRadius
     0.5 * Math.PI, 1.15 * Math.PI, // aStartAngle, aEndAngle
     false, // aClockwise
     0.2 // aRotation
  );
  let lastTweenRendered = Date.now();
  let startRotation = new THREE.Vector3(
      camera.rotation.x,
      camera.rotation.y,
      camera.rotation.z);
  let tweenie;

  return (time, rect) => {
      camera.aspect = state.width / state.height;
      camera.updateProjectionMatrix();
      let pt1 = curveMainCut.getPointAt(t_top_faster);
      orbitingPlanet.position.set(pt1.x, pt1.y, 1);

      t_top_faster = (t_top_faster >= 1) ? 0 : t_top_faster += 0.001;

      // Slightly rotate the background on mouse move
      if (scene && scene.background) {
          // The rotation mush be between 0 and 0.01
          scene.background.rotation =
              Math.max(-0.001,Math.min(0.01, scene.background.rotation + 0.00005 * target.x));
          let offsetX = scene.background.offset.x + 0.00015 * target.x;
          let offsetY = scene.background.offset.y + 0.00015 * target.y;
          scene.background.offset = new THREE.Vector2(
               (offsetX > -0.05 && offsetX < 0.05) ? offsetX : scene.background.offset.x,
               (offsetY > -0.05 && offsetY < 0.05) ? offsetY : scene.background.offset.y);
      }
      lastTweenRendered = tweenAnimateCamera(lastTweenRendered, tweenie, camera, startRotation, 200);

       renderer.render(scene, camera);
    };

makeScene function:

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(fieldOfView, state.width / state.height, 0.1, 100000000);
camera.position.set(camPosX, camPosY, camPosZ);
camera.lookAt(0, 0, 0);
scene.add(camera);
return {scene, camera};

Camera animation based on mouse positions:

function tweenAnimateCamera(lastTweenRendered, tween, camera, startRotation, period) {
    target.x = (1 - mouse.x) * 0.002;
    target.y = (1 - mouse.y) * 0.002;

    let now = Date.now();
    if ((
        // Don't let the camera go too far
        startRotation.x > -0.01 && startRotation.x < 0.01) &&
        now - lastTweenRendered  > (period / 2)) {

        if (tween) {
            tween.stop();
        }

        lastTweenRendered = now;

        let endRotation = new THREE.Vector3(
            camera.rotation.x +  0.005 * (target.y - camera.rotation.x),
            camera.rotation.y + 0.005 * (target.x - camera.rotation.y),
            camera.rotation.z);

        tween = new TWEEN.Tween(startRotation)
            .to(endRotation, period * 2)
            .easing(TWEEN.Easing.Quadratic.InOut)
            .onUpdate(function (v) {
                camera.rotation.set(v.x, v.y, v.z);
            })
            .onComplete(function(v) {
                startRotation = v.clone();
            });

        tween.start();
    }

    TWEEN.update();

    return lastTweenRendered
}

Mouse position receiver logic:

if (e.data.type === 'mousePosUpdate') {
    if (e.data.x !== -100000 && e.data.y !== -100000) {
        mouse.x = ( e.data.x - state.width / 2 );
        mouse.y = ( e.data.y - state.height / 2 );
        target.x = ( 1 - mouse.x ) * 0.002;
        target.y = ( 1 - mouse.y ) * 0.002;
    }
}

Render loop:

function render(time) {
    time *= 0.001;

    for (const {elem, fn, ctx} of sceneElements) {
        // get the viewport relative position of this element

        canvasesUpdatedPos.forEach( canvasUpdate => {
            if (canvasUpdate.id === elem.id) {
                elem.rect = canvasUpdate.rect;
            }
        });
        const rect = elem.rect;
        const bottom = rect.bottom;
        const height = rect.height;
        const left = rect.left;
        const right = rect.right;
        const top = rect.top;
        const width = rect.width;
        const rendererCanvas = renderer.domElement;

        const isOffscreen =
            bottom < 0 ||
            top > state.height ||
            right < 0 ||
            left > state.width;

        if (!isOffscreen && width !== 0 && height !== 0) {
            // make sure the renderer's canvas is big enough
            let isResize = resizeRendererToDisplaySize(renderer, height, width);

            // make sure the canvas for this area is the same size as the area
            if (ctx.canvas.width !== width || ctx.canvas.height !== height) {
                ctx.canvas.width = width;
                ctx.canvas.height = height;
                state.width = width;
                state.height = height;
            }

            renderer.setScissor(0, 0, width, height);
            renderer.setViewport(0, 0, width, height);

            fn(time, rect);

            // copy the rendered scene to this element's canvas
            ctx.globalCompositeOperation = 'copy';

            ctx.drawImage(
                rendererCanvas,
                0, rendererCanvas.height - height, width, height,  // src rect
                0, 0, width, height);                              // dst rect
        }
    }

    // Limiting to 35 FPS.
    setTimeout(function() {
        if (!stopAnimating) {
            requestAnimationFrame(render);
        }
    }, 1000 / 35);
}

Answer №1

It seems like the variables target and mouse are not initialized anywhere in your code. This could be causing issues with calculations involving target.x, target.y or mouse.x, mouse.y, leading to division by 0 or returning NaN. To solve this problem, make sure to initialize these vectors:

var target = new THREE.Vector2();
var mouse = new THREE.Vector2();

Answer №2

If you encounter similar issues, I recommend reviewing Marquizzo's answer and Ethan Hermsey's comment on the question as they offered a potential explanation for the problem. In my case, however, the issue was different.

The distortion we experienced was linked to the OffscreenCanvas. When our application initializes, we send the OffscreenCanvas and its dimensions to a web worker:

const rect = element.getBoundingClientRect();
const offScreenCanvas = element.transferControlToOffscreen();
worker.post({
    type: 'canvas',
    newCanvas: offScreenCanvas,
    width: rect.width,
    height: rect.height
}, [offScreenCanvas]);

The problem stemmed from an incorrect height value under certain conditions, specifically 1px off as shown in the question example. This discrepancy occurred due to a race condition caused by a separate script used to set the height of specific canvas containers:

$(".fullscreen-block").css({ height: var wh = $(window).height() });

To resolve this issue, we replaced the JavaScript code with a simple CSS rule:

.fullscreen-block {
   height: 100vh;
}

In conclusion, the problem was not related to our mouse event handling. It is possible that moving the mouse corrected the distortion because Firefox revalidates or recalculates DOM element sizes when the mouse is in motion, triggering a correction in the animation size and state.

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

What is preventing the installation of this Chrome extension?

I am struggling to install a few short and simple extensions on Chrome. The error message I receive about the 'manifest version' indicates that the issue might be due to outdated information. Since I lack experience in Chrome extensions and JavaS ...

If the size of the window changes, the button's functionality should adjust accordingly

$(document).ready(function(){ if ($(window).width < '700') { $(".fevent").removeAttr('data-toggle data-target aria-expanded aria-controls'); $(".fevent").attr({"data-toggle":"collapse", "data-target":"#bol", "aria-expanded": ...

Attempting to convert PHP tables into PDF format by utilizing jsPDF-auto-table to generate a beautifully structured PDF file containing the results of a PHP query with multiple records

I'm new to stackoverflow but I find myself visiting it regularly for helpful tips. I've taken some code from the simple.html file that comes with the jsPDF auto-table plugin. However, I'm having trouble making it work with data generated by ...

Accessing URL query parameters with axios

Currently, I am in the process of integrating APIs for my project where I am using React on the frontend and Django on the backend. To handle this integration, Axios is proving to be quite useful. On the frontend, I make a call to a specific URL structure ...

How can you reset a variable counter while inside a forEach loop?

I am currently working on resetting a counter that is part of a recursive forEach loop within my code. Essentially, the code snippet provided calculates the length of the innermost key-value pair as follows: length = (key length) + (some strings inserted) ...

Is there a straightforward way to upload a folder in a ReactJS application?

I am searching for a way to upload a folder in ReactJS. I have a folder with .doc and .docx files in it, and I want the user to be able to upload the entire folder when they click the browse button. I need to prevent the user from selecting individual fi ...

What options are available for labels in Bootstrap v5.0.0-beta1 compared to BS 3/4?

How can I use labels in Bootstrap v5.0.0-beta1? In Bootstrap 3, the labels are implemented like this: <link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" /> <span class="label label-success ...

"The issue seems to stem from a snippet of compact JavaScript

I am facing an issue with a list item that contains both an image and text. The text is overlapping the image, but when I hover over the text, it disappears and only the picture is visible. Here is the HTML code snippet: <ul> <li><img ...

How can I send a variable to a service using AngularJS?

I am currently working on developing an app for movie tracking, and I am fairly new to Angular. I am facing a challenge in passing a variable to this service. Instead of hardcoding the URL, I want it to be a variable. What is the best approach to achieve ...

Troubleshooting: Resolving the "Cannot read property '...' of undefined" error in Angular service method within a template

After adding new functions to a service in my project, I encountered an error when trying to call a service function from my component template. The error message displayed was "Cannot read property 'isCompanyEligible' of undefined." I attempted ...

Tips for modifying the color of a 3D object using the THREE.SceneUtils.createMultiMaterialObject function

My current project involves an object that has a unique combination of color and wireframe. I am looking to modify the base color and mesh material of this object in its final form. To achieve this, I have decided to utilize MeshBasicMaterial for the Mul ...

Utilizing onBlur and onFocus events in React to implement a dynamic menu that shows or hides

Currently, I am in the process of mastering React and developing a small online electronic store. Within my project, there is a tab labeled "View Store". My goal is for a small, hidden column to appear when a user clicks on this tab, revealing text or imag ...

Encountering difficulties in populating mongoose document following a model query

Hi everyone, this is my first time posting on SO so I'm hoping for some positive outcomes. I've been using Mongoose in a current project without any issues until now. The problem arises when trying to populate object references after querying fo ...

Add opening and closing HTML tags to enclose an already existing HTML structure

Is there a way to dynamically wrap the p tag inside a div with the class .description-wrapper using JavaScript or jQuery? This is the current html structure: <div class="coursePrerequisites"> <p> Lorem ipsum.. </p> </ ...

Creating a dropdown menu that functions for 24 hours non-stop by utilizing the Array.from( Array(n) )

Struggling to make my code less dense, I'm looking for the best way to implement a dropdown menu that allows users to choose between options 1-24 while the array is nested within an object. Thoughts? I'm working on a chrome extension that genera ...

Interacting Shadows with BufferGeometry in react-three-fiber

I've been working on my custom bufferGeometry in react-three-fiber, but I can't seem to get any shadows to show up. All the vertices, normals, and UVs are set correctly in my bufferGeometry, and I even tried adding indices to faces, but that just ...

Encountered a problem when incorporating delay functions into redux-saga testing using the Redux Saga Test Plan library

I am facing a challenge while trying to test my redux-saga functions using the Redux Saga Test Plan library. The issue arises due to delay functions present in my saga. All tests pass smoothly without any errors when I remove the line containing yield del ...

JavaScript code to verify if a checkbox is selected

Having some trouble making a text box value change based on checkbox status. I've attached a function to the onclick event of the checkbox, but it's not quite working as expected. <div> MyCheckbox <input type="checkbox" id="Check1" name ...

Instructions for creating a po file from a js file using poedit

Utilizing the Gettext.js library for localizing content created from a JS file has been my approach. The current challenge lies in the manual creation and writing of each po file. It is known that php files can be scanned for gettext strings using PoEdit ...

Having trouble with sending an AJAX request to a different domain?

I've been attempting to utilize the following API: However, upon making an AJAX call, I encounter this specific error: XMLHttpRequest cannot load /radius.json/30341/10/mile. No 'Access-Control-Allow-Origin' header is present on ...