3. js сцена искажается, пока не будет перемещена мышь - PullRequest
0 голосов
/ 06 мая 2020

Мои три. js сцена полностью искажена, пока я не наведу указатель мыши куда-нибудь на сайте. Вы можете увидеть характер искажения на изображении ниже:

enter image description here

Когда я двигаю мышью, сцена внезапно появляется, и все в порядке. Кажется, не имеет значения, где именно находится курсор на сайте, он не обязательно должен находиться над холстом, на котором отображается моя сцена. Вот как сцена выглядит после перемещения мыши:

enter image description here

Используются следующие три. js зависимости:

  • "три": "^ 0.108.0"
  • "три орбиты": "^ 2.102.2"
  • "three.meshline": "^ 1.2.0"

Я пробовал обновить три до последней версии (0.116.1), но это тоже не решило проблему. Мне удалось воспроизвести эту проблему на Firefox и Edge, но не на Chrome.

Некоторый дополнительный контекст: мы используем OffscreenCanvas для повышения производительности, позиции мыши отправляются из основного потока в веб-воркер при событии mousemove мы используем эту информацию для небольшого перемещения камеры и фона (со смещениями). Я временно удалил logi c обработчика mousemove из кода веб-воркера, и проблема все еще возникала, так что, вероятно, она не связана. Мы используем tween. js, чтобы сделать анимацию камеры плавной.

Соответствующие фрагменты кода:

Настройка сцены :

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 :

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

Анимация камеры в зависимости от положения мыши:

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
}

Logi приемника положения мыши c:

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

Отрисовка l oop:

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

Ответы [ 2 ]

1 голос
/ 07 мая 2020

Я нигде не понимаю, где вы начинаете target и mouse. Мое лучшее предположение состоит в том, что target.x, target.y или mouse.x, mouse.y не определены или 0, и это, вероятно, вызывает деление на 0 или вычисление, которое возвращает NaN, что дает вам эту бесконечно растянутую текстуру. Вы сможете это исправить, если инициируете эти векторы:

var target = new THREE.Vector2();
var mouse = new THREE.Vector2();
0 голосов
/ 08 мая 2020

Прежде, чем кто-либо столкнется с подобными проблемами, пожалуйста, взгляните на ответ Маркиццо и комментарий Итана Хермси (по вопросу), так как они предоставили хорошую возможную причину для этой проблемы, хотя в моем случае проблема была другой.

В нашем случае искажение было связано с OffscreenCanvas. Когда приложение запускается, мы отправляем OffscreenCanvas вместе с его размером веб-воркеру:

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

Причиной проблемы была высота, которая в некоторых случаях была неправильной, 1 пиксель, чтобы быть точным в изображенном примере в вопросе. Неправильная высота выскочила из-за состояния гонки, в отдельном скрипте, который мы использовали для установки высоты определенных элементов контейнера холста, с помощью следующего скрипта:

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

Однако мы обычно отправляем размер холст рабочему до того, как это произошло. Замена этого кода JS простым правилом CSS решила эту проблему:

.fullscreen-block {
   height: 100vh;
}

Итак, в конце концов, проблема не была связана с обработанным нами событием мыши, я могу только догадываться, почему перемещение мыши исправило искажение. Я бы сказал, что Firefox, вероятно, повторно проверяет / пересчитывает размер элементов DOM при перемещении мыши, и мы были уведомлены об изменениях размера, в результате чего анимация выскакивала до нужного размера и состояния.

...