gaearon / ama

Ask me anything!
222 stars 5 forks source link

component variables access inside hooks. #165

Open sancelot opened 2 years ago

sancelot commented 2 years ago

I am trying to use correctly hooks with a 3d scene widget. I really don't understand how to deal with component variables

A 3d scene is created in useEffect() , that is fired only once. Some component variables are created , like scene, camera , camera controls inside useEffect() I will later need some references to these variable , camera or camera controls variable (eg, to zoom thanks an ui slider).

in this component example, a zoom value is changed thanks to an external zoom ui slider , I can retrieve the zoom value thanks to redux store .

I am notified of this change with useMemo zoomChanged function . first question : why zoomChanged does not work with a useCallback instead of useMemo ????

second question, how can I access to component variables controls or camera inside zoomChanged function ? they are undefined when zoomChanged is fired.


import React, {
    MutableRefObject,
    ReactElement,
    useCallback,
    useEffect,
    useLayoutEffect,
    useMemo,
    useRef,
    useState
} from 'react';
import { useSelector } from 'react-redux';

import * as THREE from 'three';
import { DDSLoader } from 'three/examples/jsm/loaders/DDSLoader';

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { PlayerBtn } from './sub_components/PlayerBtn';
import { SliderSpeed } from './sub_components/SliderSpeed';
import { SliderTimeline } from './sub_components/SliderTimeline';
import { SliderZoom } from './sub_components/SliderZoom';

import { RootState } from 'store';

import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';

import 'styles/SceneComponent.css';
import { DirectionalLight } from 'three';
import { RoomEnvironment } from 'three/examples//jsm/environments/RoomEnvironment';

type Props = {
    antialias?: boolean;
    engineOptions?: undefined; // FIXME
    adaptToDeviceRatio?: boolean;
    sceneOptions?: undefined; // FIXME
    onSceneReady: (scene: THREE.Scene) => void;
    onRender?: (scene: THREE.Scene) => void;
    id: string;
    mainLoop: (scene: THREE.Scene, setActualAxisState: (map: { [key: string]: number }) => void) => void;
    setActualAxisState: (map: { [key: string]: number }) => void;
};
type Nullable<T> = T | null;

export const SceneComponent = (props: Props): ReactElement => {
    const reactCanvas = useRef<HTMLCanvasElement>(null); // useRef<Nullable<HTMLCanvasElement>>(null);

    const zoom = useSelector((state: RootState) => state.simul_data.zoom);
    const [CameraZoom, setCameraZoom] = useState(0);
    let scene: THREE.Scene;
    let camera: THREE.PerspectiveCamera;
    let renderer: THREE.WebGLRenderer;
    let controls: OrbitControls | undefined;

    const {
        antialias,
        engineOptions,
        adaptToDeviceRatio,
        sceneOptions,
        onRender,
        onSceneReady,
        mainLoop,
        setActualAxisState,
        ...rest
    } = props;

    function RenderScene() {
        if (controls) {
            controls.update();
            controls.minDistance = 50;
            controls.maxDistance = 2000;

            const zoom = controls.target.distanceTo(controls.object.position);
            console.log('min distance ', controls.minDistance);
            console.log('max distance ', controls.maxDistance);
            console.log('distance ', zoom);
        }
        renderer.render(scene, camera);
    }

    useEffect(() => {
        // "This only happens ONCE.  But it happens AFTER the initial render."
        if (reactCanvas.current) {
            const width = reactCanvas.current.clientWidth;
            const height = reactCanvas.current.clientHeight;

            scene = new THREE.Scene();

            camera = new THREE.PerspectiveCamera(75, width / height, 1, 10000);
            camera.position.set(0, 0, 2000);
            renderer = new THREE.WebGLRenderer({ canvas: reactCanvas.current, antialias: true });
            controls = new OrbitControls(camera, renderer.domElement);

            controls.update();

            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1;
            renderer.outputEncoding = THREE.sRGBEncoding;

            const pmremGenerator = new THREE.PMREMGenerator(renderer);
            scene.environment = pmremGenerator.fromScene(new RoomEnvironment()).texture;

            renderer.setClearColor('#196696');
            renderer.setSize(width, height, true);

            const startRenderLoop = (e: Event) => {
                console.log('start render loop');
                // start the loop
                renderer.setAnimationLoop(() => {
                    console.log('render it');
                    RenderScene();
                });
            };
            const stopRenderLoop = (e: Event) => {
                console.log('stop render loop');
                renderer.setAnimationLoop(null);
            };
            const wheel = (e: Event) => {
                console.log('change render loop');
                renderer.setAnimationLoop(null);
            };
            controls.addEventListener('start', startRenderLoop);
            controls.addEventListener('end', stopRenderLoop);
            controls.addEventListener('change', startRenderLoop);

            props.mainLoop(scene, setActualAxisState);

            const handleResize = () => {
                if (reactCanvas.current) {
                    console.log('handleresize');
                    const width = reactCanvas.current.clientWidth;
                    const height = reactCanvas.current.clientHeight;
                    renderer.setSize(width, height);
                    camera.aspect = width / height;
                    camera.updateProjectionMatrix();
                    if (controls) controls.update();
                    RenderScene();
                }
            };

            const onMouseWheel = () => {
                RenderScene();
            };

            if (window) {
                window.addEventListener('resize', handleResize);
                window.addEventListener('wheel', onMouseWheel, { passive: false });
            }

            // animate();
            return () => {
                window.removeEventListener('resize', handleResize);
                window.removeEventListener('wheel', onMouseWheel);
                renderer.dispose();
            };
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const zoomMe = () => {
    };

    const zoomChanged = useMemo(() => {
        console.log('zoom has changed', zoom);
        console.log('controls variable ', controls);
        if (controls) {
            const MaxDistance = 2000;
            const MinDistance = 50;
            const delta = MaxDistance - MinDistance;

            const zoomDistance = MinDistance + (delta * zoom) / 100;
            const currDistance = camera.position.length();
            const factor = zoomDistance / currDistance;
            console.log('z factor', factor);
            controls.object.position.x *= factor;
            controls.object.position.y *= factor;
            controls.object.position.z *= factor;
            controls.update();
            //controls.dollyOut(zoom);
            if (renderer) RenderScene();
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [zoom]);

    return (
        <div className='scene-component'>
            <canvas className='canvas-holder' ref={reactCanvas} {...rest} />
        </div>
    );
};
Whoaa512 commented 2 years ago

Please try asking on https://stackoverflow.com/

Dan's AMA repo is not meant for personal debugging help