Разрешить полное вращение модели в Three с React

Я только начал с «три», «@react-three/drei» и «@react-three/fiber», которые помогают мне визуализировать челюсть/зубы в 3D. Результат такой, как я ожидал, за исключением 2-х точек, как показано на видео, я застрял в одной точке на одной оси для вращения. и второй момент заключается в том, что исходное положение не обращено к зубам, как я ожидаю.

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

Это GIF-изображение начальной позиции и той части, где я застрял при вращении модели.

И вот мой текущий код:

/* eslint-disable */
import React, { useState, useRef, useEffect, useMemo } from 'react';
// libraries
import { Canvas, useThree, useLoader } from '@react-three/fiber';
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
import * as THREE from 'three';
// mui
import { Button, Stack } from '@mui/material';

// ----------------------------------------------------------------------

const STLModel = ({ geometry }) => {
  useEffect(() => {
    if (geometry) {
      geometry.computeVertexNormals();
    }
  }, [geometry]);

  return (
    <mesh geometry = {geometry} receiveShadow castShadow>
      <meshPhongMaterial
        color = "#078DEE"
        specular = "#ffffff"
        shininess = {100}
        side = {THREE.DoubleSide}
        receiveShadow 
        castShadow
      />
    </mesh>
  );
};

// ----------------------------------------------------------------------

const Scene = ({ geometries, currentFileIndex, cameraPosition, onCameraChange }) => {
  const controlsRef = useRef();
  const { camera } = useThree();

  useEffect(() => {
    if (controlsRef.current) {
      controlsRef.current.addEventListener('change', () => {
        onCameraChange({
          position: camera.position.toArray(),
          target: controlsRef.current.target.toArray(),
        });
      });
      // I Removed this two line to allow full rotation, but it did not work
      // controlsRef.current.maxPolarAngle = Math.PI;
      // controlsRef.current.minPolarAngle = 0;
      controlsRef.current.enablePan = true;
    }
  }, [camera, onCameraChange]);

  useEffect(() => {
    if (cameraPosition && controlsRef.current) {
      camera.position.fromArray(cameraPosition.position);
      controlsRef.current.target.fromArray(cameraPosition.target);
      controlsRef.current.update();
    } else {
      camera.position.set(0, 0, 100);
      controlsRef.current.target.set(0, 0, 0);
      controlsRef.current.update();
    }
  }, [camera, cameraPosition]);

  return (
    <>
      <PerspectiveCamera makeDefault position = {[0, 0, 100]} />
      <STLModel geometry = {geometries[currentFileIndex]} />
      <OrbitControls ref = {controlsRef} />
      <ambientLight intensity = {0.5} />
      <pointLight position = {[10, 10, 10]} intensity = {0.8} />
      <pointLight position = {[-10, -10, -10]} intensity = {0.5} />
      <directionalLight position = {[0, 0, 5]} intensity = {0.5} />
      <axesHelper scale = {[10, 10, 10]} />
    </>
  );
};

// ----------------------------------------------------------------------

const TreatmentPlan = () => {
  const [currentFileIndex, setCurrentFileIndex] = useState(0);
  const [cameraPosition, setCameraPosition] = useState(null);

  const url = "https://myurl/";
  const fileUrls = useMemo(() => Array.from({ length: 15 }, (_, i) => `${url}file${i + 1}.stl`), [url]);

  const geometries = useLoader(STLLoader, fileUrls);

  const handleNext = () => {
    setCurrentFileIndex((prevIndex) => (prevIndex + 1) % geometries.length);
  };

  const handlePrevious = () => {
    setCurrentFileIndex((prevIndex) => (prevIndex - 1 + geometries.length) % geometries.length);
  };

  return (
    <div>
      <Stack direction = "row" spacing = {2} mb = {2}>
        <Button variant = "contained" onClick = {handlePrevious} disabled = {geometries.length === 0}>Previous</Button>
        <Button variant = "contained" onClick = {handleNext} disabled = {geometries.length === 0}>Next</Button>
      </Stack>
      {geometries.length > 0 && (
        <div style = {{ width: '50vw', height: '50vh' }}>
          <Canvas>
            <Scene
              geometries = {geometries}
              currentFileIndex = {currentFileIndex}
              cameraPosition = {cameraPosition}
              onCameraChange = {setCameraPosition}
            />
          </Canvas>
        </div>
      )}
    </div>
  );
};

export default TreatmentPlan;

Заранее благодарю за любую помощь/совет

🤔 А знаете ли вы, что...
JavaScript поддерживает асинхронное программирование с использованием промисов и асинхронных функций.


81
1

Ответ:

Решено

Проблема, с которой вы столкнулись, связана не с вашим кодом, а только с ограничениями и философией OrbitControls. OC поддерживает постоянный восходящий вектор, отсюда и ограничение. То, о чем вы пишете, или конкретно то, что вы ожидаете, можно найти в TrackballControls, где этот вектор не является постоянным. Кроме того, как показывает ваш код, не имеет значения, удаляете ли вы закомментированные строки, поскольку они являются значениями по умолчанию.

TrackballControls похож на OrbitControls. Однако это не поддерживать постоянный вектор камеры вверх. Это означает, что если камера вращается по орбите над «северным» и «южным» полюсами он не переворачивается, чтобы оставаться «правым» стороной вверх".

В этой теме Pailhead довольно... наглядно объяснил эту проблему.

https://discourse.threejs.org/t/solved-orbitcontrols-max-polar-angle-infinity/2629

Взгляните на разницу

ОрбитаУправление

<script type = "importmap">
  {
"imports": {
  "three": "https://unpkg.com/[email protected]/build/three.module.js",
  "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
  }
</script>

<script type = "module">
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const planeGeometry = new THREE.PlaneGeometry(5, 5);
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);

camera.position.z = 5;

const controls = new OrbitControls(camera, renderer.domElement);

function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}

animate();

</script>

ТрекболУправление

<script type = "importmap">
  {
"imports": {
  "three": "https://unpkg.com/[email protected]/build/three.module.js",
  "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
  }
</script>

<script type = "module">
import * as THREE from "three";

import { TrackballControls } from 'three/addons/controls/TrackballControls.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const planeGeometry = new THREE.PlaneGeometry(5, 5);
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);

camera.position.z = 5;

const controls = new TrackballControls(camera, renderer.domElement);

function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}

animate();

</script>

Просто попробуйте переключить OrbitControls на TrackballControls, возможно, вы получите то, что ожидаете.

import { TrackballControls } from '@react-three/drei';

//…
<TrackballControls />

Интересные вопросы для изучения

Я получаю сообщение об ошибке: AxiosError {сообщение: «Запрос не выполнен с кодом состояния 500», имя: «AxiosError», код: «ERR_BAD_RESPONSE»}Реагировать на обновление значения в сопоставленном объекте из дочернего компонентаКак узнать, содержит ли массив вложенный массив с определенными значениями?FsFile.readable от Deno автоматически закрывает файл, когда поток исчерпан?Как предотвратить кэширование ответов с помощью Astro на Vercel?Я получаю сообщение об ошибке: AxiosError {сообщение: «Запрос не выполнен с кодом состояния 500», имя: «AxiosError», код: «ERR_BAD_RESPONSE»}Состояние React обновляется, но изменения не отражаются в прослушивателе событий, вызываемом программноРеагировать на обновление значения в сопоставленном объекте из дочернего компонентаШаблон ожидания/успеха/неудачи Redux Saga – как справиться с прерываниемКак предотвратить кэширование ответов с помощью Astro на Vercel?