three.jsreact-three-fiberorbitcontrols

How do I keep a light in a scene fixed when using orbit controls in React Three Fiber?


As the title states, I want to keep a light in position while rotating an object using OrbitControls. I currently have a box with a light shining on one side of it. I want to be able to rotate the box and have the light stay in position, lighting the other sides as I rotate the box. :) Here is my current code

`import React, { Suspense, useEffect, useState } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Preload, useGLTF } from "@react-three/drei";

import CanvasLoader from "../Loader";

const Island = ({ isMobile }) => {
  const island = useGLTF("./block/scene.gltf");

  return (
    <mesh>
      <hemisphereLight intensity={0.15} groundColor='black' />
      {/* <spotLight
        position={[0, -10, 10]}
        angle={0.22}
        penumbra={1}
        intensity={2}
        castShadow
        shadow-mapSize={1024}
      /> */}


      <primitive
        object={island.scene}
        scale={isMobile ? 0.7 : 0.005}
        position={isMobile ? [0, -4, -2.2] : [0, -4.2, 0]}
        rotation={[0, 0, 0]}
      />





    </mesh>

  );
};

const IslandCanvas = () => {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    // Add a listener for changes to the screen size
    const mediaQuery = window.matchMedia("(max-width: 500px)");

    // Set the initial value of the `isMobile` state variable
    setIsMobile(mediaQuery.matches);

    // Define a callback function to handle changes to the media query
    const handleMediaQueryChange = (event) => {
      setIsMobile(event.matches);
    };

    // Add the callback function as a listener for changes to the media query
    mediaQuery.addEventListener("change", handleMediaQueryChange);

    // Remove the listener when the component is unmounted
    return () => {
      mediaQuery.removeEventListener("change", handleMediaQueryChange);
    };
  }, []);

  return (

    <Canvas

      frameloop='demand'
      shadows
      dpr={[1, 2]}
      camera={{ position: [20, 3, 5], fov: 25 }}
      gl={{ preserveDrawingBuffer: true }}
    >
      {/* <pointLight
      intensity={1}
      position={[0, -30, 1000]}
      /> */}

      <Suspense fallback={<CanvasLoader />}>
        <group>
          <pointLight
            intensity={1}
            position={[0, -30, 1000]}
          />
        </group>

        <group>
        <OrbitControls

enableZoom={false}
maxPolarAngle={Math.PI / 2}
minPolarAngle={Math.PI / 2}
/>
          <Island isMobile={isMobile} />

        </group>

      </Suspense>

      <Preload all />
    </Canvas>
  );
};

export default IslandCanvas;`

I have tried wrapping the light in a separate group on the canvas, I have tried putting orbit controls inside of the object mesh and not the canvas, none of this has worked so far and I'm a little lost. This is my first overflow question I've posted so lmk if I'm leaving anything out


Solution

  • From what I have seen, there are two options here:

    Option 1

    Attach the lights to the camera object, so that as the camera is moved around, the lights automatically follow the camera. For this to work, the camera must also be added to the scene.

    import { Canvas } from '@react-three/fiber';
    import { PointLight } from 'three';
    
    const light = new PointLight();
    light.position.set(1, 1, 0); // This position is relative to the camera's position
    
    function Scene() {
      return (
        <Canvas
          onCreated={({ camera, scene }) => {
            camera.add(light);
            scene.add(camera);
          }}
        >
          {/* your scene */}
        </Canvas>
      );
    }
    

    Option 2

    Add lights to the scene, but update their position when OrbitControls update the camera position.

    import { OrbitControls } from '@react-three/drei';
    import { Canvas } from '@react-three/fiber';
    import { useRef } from 'react';
    import { PointLight } from 'three';
    
    function Scene() {
      const lightRef = useRef<PointLight>(null);
      return (
        <Canvas>
          {/* your scene */}
    
          <pointLight ref={lightRef} position={[1, 1, 0]} />
    
          <OrbitControls
            onChange={(e) => {
              if (!e) return;
              const camera = e.target.object;
    
              if (lightRef.current) {
                // This sets the point light to a location above your camera
                // Note that this position is in world space, not relative to
                // the camera
                lightRef.current.position.set(0, 1, 0);
                lightRef.current.position.add(camera.position);
              }
            }}
          />
        </Canvas>
      );
    }