Search code examples
typescriptthree.jsrotationraycastingreact-three-fiber

Casting multiple rays from a rotated mesh


I'm trying to shoot multiple rays from a rotating mesh. The rays are shooting in multiple directions (targeting points on a circle divided by the number of rays).

For debugging purposes I've added ArrowHelpers for every ray. The idea is that the arrows should turn red if they hit something, and white if not. They also change length based on the distance to the intersected object, when they are not intersecting their lengths stay at the ray's max length (far).

What I have so far is that every ray keeps checking the same (i think) forward direction of the rotating mesh. I believe I need to find the formula to calculate the new normalized vector based on the current rotation of the object. I've tried a lot of different things, like object3D.localToGlobal, Vector3.applyQuaternion etc... However my math skills are failing me

Code sandbox at: https://codesandbox.io/s/raycast-issue-bch05b

Raycasting code:

import {  RefObject } from "react";
import * as THREE from "three";
import React from "react";
import { useFrame, useThree } from "@react-three/fiber";

export type RayCastResult = {
  hit: boolean;
  angle: number;
  direction: THREE.Vector3;
  distance: number;
};

export const useRaycasts = ({
  count = 4,
  near = 1,
  far = 10,
  obj
}: {
  count?: number;
  near?: number;
  far?: number;
  obj: RefObject<THREE.Mesh>;
}): { rays: RayCastResult[] } => {
  const rays = React.useMemo(() => {
    const rays: RayCastResult[] = [];
    let angle = 0;
    const step = (2 * Math.PI) / count;

    for (let i = 0; i < count; i++) {
      rays.push({
        hit: false,
        angle: angle,
        direction: new THREE.Vector3(
          Math.cos(angle),
          0,
          Math.sin(angle)
        ).normalize(),
        distance: 10
      });
      angle += step;
    }

    return rays;
  }, [count]);

  const pos = React.useMemo(() => new THREE.Vector3(), []);
  const dir = React.useMemo(() => new THREE.Vector3(), []);

  const { scene, raycaster } = useThree();

  useFrame(() => {
    if (!obj.current) return;

    obj.current.getWorldDirection(dir);
    obj.current.getWorldPosition(pos);

    rays.forEach((direction, i) => {
      if (!obj.current) return;
      raycaster.set(
        pos,
        dir
          .applyAxisAngle(rays[0].direction, obj.current?.rotation.y)
          .normalize()
        //dir.applyAxisAngle(rays[i].direction, rays[i].angle),
        //dir.applyAxisAngle(rays[i].direction, Math.PI / 2)
        //dir.applyQuaternion(obj.current.quaternion).add(rays[i].direction)
      );

      raycaster.near = near;
      raycaster.far = far;

      const intersects = raycaster.intersectObjects(scene.children);

      // ONLY check first object
      if (intersects.length) {
        rays[i].hit = true;
        rays[i].distance = intersects[0].distance;
      } else {
        rays[i].hit = false;
        rays[i].distance = raycaster.far;
      }
    });
  });

  return { rays };
};



Solution

  • OK answering my own question, hopefully this may come in use to someone in the future...

    It seems I was right about the approach, I actually found the answer in another stackoverflow question, where the solution was in the question !

    The solution was to use a THREE.Matrix4 class that copies the rotating mesh's rotation. That rotation matrix should then be applied to the given ray's direction.

    In summery:

    // construct a Matrix4 class (do it once to save resources)
    const matrix = useMemo(() => new THREE.Matrix4(), []);
    // Later inside the loop:
    // get the object's current position
    obj.current.getWorldPosition(pos);
    // copy the objects rotation matrix to our matrix
    matrix.extractRotation(obj.current.matrix);
    // apply the rotation to the ray direction
    raycaster.set(pos, dir.copy(rays[i].direction).applyMatrix4(matrix));
    

    Checkout the updated sandbox