bohdanbirdie/react-stl-obj-viewer

Is it possible to not render shadows or move the light while I rotate the object?

ambev7labs opened this issue · 3 comments

My rendered object has a lot of details and the shadows are hiding them

same problem

Been looking to solve this myself. Here is an excerpt with the lighting issue fixed and a number of the modern react issues solved. Still looking to port the entire thing over to FC but haven't gotten that far yet.

import React, {Component} from 'react';
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom';
import * as THREE from 'three';
import STLLoaderModule from 'three-stl-loader'
import OrbitControlsModule from 'three-orbit-controls'
import {ScaleLoader} from 'react-spinners';

const STLLoader = STLLoaderModule(THREE);
const OrbitControls = OrbitControlsModule(THREE);

function lightOnScene (scene, x, y, z, brightness) {
    const directionalLight = new THREE.DirectionalLight(0xffffff, brightness);
    directionalLight.position.x = x;
    directionalLight.position.y = y;
    directionalLight.position.z = z;
    directionalLight.position.normalize();
    scene.add(directionalLight);
    return directionalLight
}

function ambientLightOnScene(scene, brightness) {
    const ambientLight = new THREE.AmbientLight(0x404040, brightness); // soft white light
    scene.add(ambientLight);
}

class STLViewer extends Component {
    static propTypes = {
        className: PropTypes.string,
        url: PropTypes.string,
        file: PropTypes.object,
        width: PropTypes.number,
        height: PropTypes.number,
        backgroundColor: PropTypes.string,
        modelColor: PropTypes.string,
        sceneClassName: PropTypes.string,
        onSceneRendered: PropTypes.func,
        lightIntensity: PropTypes.number,
    };

    static defaultProps = {
        backgroundColor: '#EAEAEA',
        modelColor: '#B92C2C',
        lineColor: '#000000',
        height: 400,
        width: 400,
        rotate: true,
        orbitControls: true,
        sceneClassName: '',
        lightIntensity: 0.4,
    };

    componentDidMount() {
        this.renderModel(this.props);
    }


    renderModel(props) {
        let camera, scene, renderer, mesh, distance, controls;
        const {url, file, width, height, modelColor, backgroundColor, orbitControls, sceneClassName, onSceneRendered, lineColor, lightIntensity} = props;
        let xDims, yDims, zDims;

        scene = new THREE.Scene();
        distance = 10000;

        // Light all sides
        // lightOnScene(scene, 0, 1, 0, lightIntensity)
        // lightOnScene(scene, 0, -1, 0, lightIntensity)

        // lightOnScene(scene, 1, 0, 0, lightIntensity)
        // lightOnScene(scene, -1, 0, 0, lightIntensity)

        // lightOnScene(scene, 0, 0, 1, lightIntensity)
        // lightOnScene(scene, 0, 0, -1, lightIntensity)

        // BG Lighting
        ambientLightOnScene(scene, lightIntensity)

        // When We load show model
        const onLoad = geometry => {
            // geometry.computeFaceNormals();
            geometry.computeVertexNormals();
            geometry.center();
            
            // Edge Lines
            const edges = new THREE.EdgesGeometry( geometry );
            const line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { 
                color: lineColor,
                linewidth: 3,
            } ) );

            // Mesh
            mesh = new THREE.Mesh(
                geometry,
                new THREE.MeshLambertMaterial({
                        // overdraw: true,
                        color: modelColor,
                    }
                ));

            
            // Some extra stuff
            geometry.computeBoundingBox();
            xDims = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
            yDims = geometry.boundingBox.max.y - geometry.boundingBox.min.y;
            zDims = geometry.boundingBox.max.z - geometry.boundingBox.min.z;
            
            // Add items to Scene
            scene.add(mesh);
            scene.add(line);

            // Set the Camera
            camera = new THREE.PerspectiveCamera(30, width / height, 1, distance);
            camera.position.set(0, 0, Math.max(xDims * 3, yDims * 3, zDims * 3));
            scene.add(camera);
            
            // Set Render 
            renderer = new THREE.WebGLRenderer({
                preserveDrawingBuffer: true,
                antialias: true
            });
            renderer.setSize(width, height);
            renderer.setClearColor(backgroundColor, 1);
            renderer.domElement.className = sceneClassName;

            // Set controlls
            if (orbitControls) {
                controls = new OrbitControls(camera, this.container.current);
                controls.enableKeys = false;
                controls.addEventListener('change', orbitRender);
            }

            // hack instead of state managment becase reasons?
            this.container.current.replaceChild(renderer.domElement, this.firstChild.current)
            
            // Call render
            render();

            // Run call back on render changed
            if (typeof onSceneRendered === "function") {
                onSceneRendered(this.container.current)
            }
        };

        const onProgress = (xhr) => {
            if (xhr.lengthComputable) {
                let percentComplete = xhr.loaded / xhr.total * 100;
            }
        };

        // Load model in and then show it
        const loader = new STLLoader();

        if (file) {
            loader.loadFile(file, onLoad, onProgress);
        } else {
            loader.load(url, onLoad, onProgress);
        }

        const render = () => {
            renderer.render(scene, camera);
        };

        const orbitRender = () => {
            render();
        };
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (JSON.stringify(nextProps) === JSON.stringify(this.props)) {
            return false
        }
        return true
    }

    componentDidUpdate(nextProps, nextState) {
        this.renderModel(nextProps);
    }

    componentDidCatch(error, info) {
        console.log(error, info)
    }

    constructor (props) {
        super(props);
        // Refs
        this.container = React.createRef();
        this.firstChild = React.createRef();
    }

    render() {
        return (
            <div
                ref={this.container}
                className={this.props.className}
                style={{
                    width: this.props.width,
                    height: this.props.height,
                    overflow: 'hidden',
                }}
            >
                <div
                    ref={this.firstChild} 
                    style={{
                        height: '100%',
                        display: 'flex',
                        justifyContent: 'center',
                        alignItems: 'center',
                    }}>
                    <ScaleLoader
                        color={'#123abc'}
                        loading={true}
                    />
                </div>

            </div>
        );
    };
};

export {
    // OBJViewer,
    STLViewer
}

<STLViewer
      url={`${window.location.origin}/${publicPath}`}
      modelColor="#ffa500"
      className='wrapper'
      sceneClassName='canvas'
      height={800}
      width={800}
      lightIntensity={4}
  />

Basically the same thing but in an FC and without having to replace the dom.

import React, {Suspense, useRef, useState} from 'react';
import * as THREE from 'three';
import { 
    Canvas, ambientLight, 
    lineSegments, edgesGeometry, lineBasicMaterial, 
    pointLight, meshStandardMaterial, color 
} from '@react-three/fiber'
import { OrbitControls } from "@react-three/drei"
import {STLLoader} from 'three/examples/jsm/loaders/STLLoader'
import { useLoader } from '@react-three/fiber'

function StlGeometry(props) {
    // Props
    const {
        url, file, width, height, 
        modelColor, backgroundColor, className, orbitControls,
        onSceneRendered, lineColor, linewidth, brightness,
        onGeometryLoaded
    } = props


    // State
    const bufferedGeometry = useLoader(STLLoader, url)

    // Computed
    if (bufferedGeometry) {
        bufferedGeometry.computeVertexNormals()
        bufferedGeometry.center()
        bufferedGeometry.computeBoundingBox()
        onGeometryLoaded(bufferedGeometry)
    }

    // ..
    return <>
        {/* The 3D object  */}
        <mesh
            castShadow
            receiveShadow
            position={[0, 0, 0]}
            geometry={bufferedGeometry}
        >
            <meshStandardMaterial color={modelColor} />
        </mesh>

        {/* The edge lines in a color */}
        <lineSegments>
            <edgesGeometry 
                attach="geometry" 
                args={[bufferedGeometry]} 
            />
            <lineBasicMaterial 
                color={lineColor || 'black'} 
                attach="material"
                linewidth={linewidth || 1}
            />
        </lineSegments>
    </>
}

export const STLViewerFC = (props) => {
    // Props
    const {
        url, file, width, height, 
        modelColor, backgroundColor, className, orbitControls,
        onSceneRendered, lineColor, linewidth, brightness,
    } = props
    
    // Ref
    const container = useRef()

    // State 
    const distance = 10000;
    const [camera, setCamera] = useState(
        new THREE.PerspectiveCamera(30, width / height, 1, distance)
    )

    // When the obj loads move the camera to a good position
    const onGeometryLoaded = (geometry) => {
        const xDims = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
        const yDims = geometry.boundingBox.max.y - geometry.boundingBox.min.y;
        const zDims = geometry.boundingBox.max.z - geometry.boundingBox.min.z;
        camera.position.set(0, 0, Math.max(xDims * 3, yDims * 3, zDims * 3));
        setCamera(camera)
    }

    // On update pass up ref
    if (typeof onSceneRendered === "function") {
        onSceneRendered(container)
    }

    // ..
    return <>
        {/* Make sure we wait for the 3D obj to load */}
        <Suspense fallback={null}>
            <Canvas 
                ref={container}
                className={className} 
                style={{
                    width: width || '400px',
                    height: height || '400px',
                    overflow: 'hidden',
                }}
                camera={camera}
                backgroundColor={backgroundColor || '#EAEAEA'}
            >
                {/* Light up the entire scene */}
                <ambientLight  
                    brightness={brightness || 5}
                />

                {/* The 3D object */}
                <StlGeometry {...props} onGeometryLoaded={onGeometryLoaded} />
                {/* Background */}
                <color attach="background" args={[backgroundColor || "#EAEAEA"]} />
                
                {/* Enable Orbit Controls */}
                {orbitControls === true && <>
                    <OrbitControls />
                </>}
               
                
            </Canvas>
        </Suspense>
        
    </>
}

export {
    // OBJViewer,
    // STLViewer,
    // STLViewerFC
}