如何使用可以通过轨道控制移动的正交相机在react-三/光纤中拖动x和z上约束在y上的对象?

问题描述 投票:0回答:2

我希望能够使用 React-三纤维和正交相机在画布中将对象拖动到平面上(想象一下棋盘上的棋子)。

这是一个使用固定相机位置的示例(不是我的):https://codepen.io/kaolay/pen/bqKjVz

但我也希望能够移动相机 - 所以我添加了轨道控件,当拖动对象时该控件将被禁用。

我这里有一个代码沙箱,我的尝试基于许多其他示例: https://codesandbox.io/s/inspiring-franklin-2r3ri?file=/src/Obj.jsx

主要代码位于两个文件中,App.jsx 以及画布、相机和轨道控件。 Obj.jsx 包含被拖动的网格以及使用手势 useDrag 函数内部的拖动逻辑。

应用程序.jsx

import React, { useState } from "react";
import { Canvas } from "@react-three/fiber";
import Obj from "./Obj.jsx";
import { OrthographicCamera, OrbitControls } from "@react-three/drei";
import * as THREE from "three";

export default function App() {
  const [isDragging, setIsDragging] = useState(false);

  return (
    <Canvas style={{ background: "white" }} shadows dpr={[1, 2]}>
      <ambientLight intensity={0.5} />
      <directionalLight
        intensity={0.5}
        castShadow
        shadow-mapSize-height={512}
        shadow-mapSize-width={512}
      />

      <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <planeBufferGeometry attach="geometry" args={[10, 10]} receiveShadow />
        <meshPhongMaterial
          attach="material"
          color="#ccc"
          side={THREE.DoubleSide}
          receiveShadow
        />
      </mesh>

      <Obj setIsDragging={setIsDragging} />

      <OrthographicCamera makeDefault zoom={50} position={[0, 40, 200]} />

      <OrbitControls minZoom={10} maxZoom={50} enabled={!isDragging} />
    </Canvas>
  );
}

Obj.jsx(在“使用拖动”功能中包含有问题的代码)

import React, { useState } from "react";
import { useDrag } from "@use-gesture/react";
import { animated, useSpring } from "@react-spring/three";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";

function Obj({ setIsDragging }) {
  const { camera } = useThree();
  const [pos, setPos] = useState([0, 1, 0]);
  const { size, viewport } = useThree();
  const aspect = size.width / viewport.width;
  const [spring, api] = useSpring(() => ({
    // position: [0, 0, 0],
    position: pos,
    scale: 1,
    rotation: [0, 0, 0],
    config: { friction: 10 }
  }));
  const bind = useDrag(
    ({ active, delta, movement: [x, y], velocity, timeStamp, memo = 0 }) => {
      if (active) {

 //// THIS IS THE CODE THAT I KNOW IS NOT WORKING /////

        let vDir = new THREE.Vector3();
        let vPos = new THREE.Vector3(
          (x / window.innerWidth) * 2 - 1,
          -(y / window.innerHeight) * 2 + 1,
          0.5
        ).unproject(camera);

        vDir.copy(vPos).sub(camera.position).normalize();
        let flDistance = -camera.position.z / vDir.z;
        vPos = vPos.copy(camera.position).add(vDir.multiplyScalar(flDistance));
        const arbitraryFactor = 1; // I suspect this has to reflect the distance from camera in all dims...
        setPos([vPos.x * arbitraryFactor, 1.5, -vPos.y * arbitraryFactor]);

 //// END /////
      }

      setIsDragging(active);

      api.start({
        // position: active ? [x / aspect, -y / aspect, 0] : [0, 0, 0],
        position: pos,
        scale: active ? 1.2 : 1,
        rotation: [y / aspect, x / aspect, 0]
      });
      return timeStamp;
    }
  );

  return (
    <animated.mesh {...spring} {...bind()} castShadow>
      <dodecahedronBufferGeometry
        castShadow
        attach="geometry"
        args={[1.4, 0]}
      />
      <meshNormalMaterial attach="material" />
    </animated.mesh>
  );
}

export default Obj;

一些有用的参考资料,但还没有让我到达那里! 鼠标/画布 X、Y 到 Three.js 世界 X、Y、Z

https://codesandbox.io/s/react- Three-Fiber-gestures-forked-lpfv3?file=/src/App.js:1160-1247

https://codesandbox.io/embed/react- Three-Fiber-gestures-08d22?codemirror=1

https://codesandbox.io/s/r3f-lines-capture-1gkvp

https://github.com/pmndrs/react- Three-Fiber/discussions/641

最后再次重新链接到我的代码沙箱示例: https://codesandbox.io/s/inspiring-franklin-2r3ri?file=/src/Obj.jsx:0-1848

three.js draggable coordinate-transformation react-three-fiber react-use-gesture
2个回答
9
投票

当我意识到 React-Three-Fiber 将事件信息传递给 useDrag 后,我采取了不同的方法,其中包含我需要的坐标和射线信息。

https://codesandbox.io/s/musing-night-wso9v?file=/src/App.jsx

应用程序.jsx

import React, { useState } from "react";
import { Canvas } from "@react-three/fiber";
import Obj from "./Obj.jsx";
import { OrthographicCamera, OrbitControls } from "@react-three/drei";
import * as THREE from "three";

export default function App() {
  const [isDragging, setIsDragging] = useState(false);
  const floorPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);

  return (
    <Canvas style={{ background: "white" }} shadows dpr={[1, 2]}>
      <ambientLight intensity={0.5} />
      <directionalLight
        intensity={0.5}
        castShadow
        shadow-mapSize-height={512}
        shadow-mapSize-width={512}
      />

      <mesh
        rotation={[-Math.PI / 2, 0, 0]}
        position={[0, -0.1, 0]}
        receiveShadow
      >
        <planeBufferGeometry attach="geometry" args={[10, 10]} receiveShadow />
        <meshPhongMaterial
          attach="material"
          color="#ccc"
          side={THREE.DoubleSide}
          receiveShadow
        />
      </mesh>

      <planeHelper args={[floorPlane, 5, "red"]} />

      <gridHelper args={[100, 100]} />

      <Obj setIsDragging={setIsDragging} floorPlane={floorPlane} />

      <OrthographicCamera makeDefault zoom={50} position={[0, 40, 200]} />

      <OrbitControls minZoom={10} maxZoom={50} enabled={!isDragging} />
    </Canvas>
  );
}

Obj.jsx

import React, { useState, useRef } from "react";
import { useDrag } from "@use-gesture/react";
import { animated, useSpring } from "@react-spring/three";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";

function Obj({ setIsDragging, floorPlane }) {
  const [pos, setPos] = useState([0, 1, 0]);
  const { size, viewport } = useThree();
  const aspect = size.width / viewport.width;

  let planeIntersectPoint = new THREE.Vector3();

  const dragObjectRef = useRef();

  const [spring, api] = useSpring(() => ({
    // position: [0, 0, 0],
    position: pos,
    scale: 1,
    rotation: [0, 0, 0],
    config: { friction: 10 }
  }));

  const bind = useDrag(
    ({ active, movement: [x, y], timeStamp, event }) => {
      if (active) {
        event.ray.intersectPlane(floorPlane, planeIntersectPoint);
        setPos([planeIntersectPoint.x, 1.5, planeIntersectPoint.z]);
      }

      setIsDragging(active);

      api.start({
        // position: active ? [x / aspect, -y / aspect, 0] : [0, 0, 0],
        position: pos,
        scale: active ? 1.2 : 1,
        rotation: [y / aspect, x / aspect, 0]
      });
      return timeStamp;
    },
    { delay: true }
  );

  return (
    <animated.mesh {...spring} {...bind()} castShadow>
      <dodecahedronBufferGeometry
        ref={dragObjectRef}
        attach="geometry"
        args={[1.4, 0]}
      />
      <meshNormalMaterial attach="material" />
    </animated.mesh>
  );
}

export default Obj;

这不应该花费我这么长时间,我希望这对其他人有帮助!


0
投票

要使用正交相机在 React- Three/Fiber 中拖动受 Y 约束的 X 和 Z 坐标中的对象,同时允许通过 OrbitControls 移动相机,您可以使用以下修改后的代码。我对 Obj.jsx 中 useDrag 函数内部的逻辑做了一些调整:

    import React, { useState } from "react";
import { useDrag } from "@use-gesture/react";
import { animated, useSpring } from "@react-spring/three";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";

function Obj({ setIsDragging }) {
  const { camera } = useThree();
  const [pos, setPos] = useState([0, 1, 0]);
  const { size, viewport } = useThree();
  const aspect = size.width / viewport.width;
  const [spring, api] = useSpring(() => ({
    position: pos,
    scale: 1,
    rotation: [0, 0, 0],
    config: { friction: 10 }
  }));

  const bind = useDrag(
    ({ active, delta, movement: [x, y], velocity, timeStamp }) => {
      if (active) {
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();
        mouse.x = (x / size.width) * 2 - 1;
        mouse.y = -(y / size.height) * 2 + 1;

        raycaster.setFromCamera(mouse, camera);

        // Define the plane in Y-axis at the current object position
        const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), pos[1]);

        // Intersect the ray with the plane
        const intersection = new THREE.Vector3();
        raycaster.ray.intersectPlane(plane, intersection);

        // Update the object position
        setPos([intersection.x, pos[1], intersection.z]);
      }

      setIsDragging(active);

      api.start({
        position: pos,
        scale: active ? 1.2 : 1,
        rotation: [y / aspect, x / aspect, 0]
      });

      return timeStamp;
    }
  );

  return (
    <animated.mesh {...spring} {...bind()} castShadow>
      <dodecahedronBufferGeometry
        castShadow
        attach="geometry"
        args={[1.4, 0]}
      />
      <meshNormalMaterial attach="material" />
    </animated.mesh>
  );
}

export default Obj;

此修改使用光线投射器从鼠标位置投射光线,并将其与当前对象位置的 Y 轴平面相交。这确保了对象将沿 X 和 Z 坐标拖动,同时保持在 Y 坐标。

© www.soinside.com 2019 - 2024. All rights reserved.