Three.js and React-three-fiber model is completely black? - reactjs

This model seems to load completely black - but others do not. https://d1iczm3wxxz9zd.cloudfront.net/e4a5c9ee-9fa0-46b2-9ff5-8e52e6286a3b/5ee3baf2-2524-4617-99a7-2a91b4bd20c1/11/ITEM_PREVIEW1.glb
Anyone happen to know why?
Other GLB files have no issues with textures. This one seems to have an issue. But when loaded in other online GLB viewers it loads fine.
Using reactjs, react-three-fibre and three.js.
Here is the three.js code:
import * as THREE from "three";
import React, { useRef, Suspense } from "react";
import { Canvas, useThree, useFrame, extend } from "react-three-fiber";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { Box } from "./Box";
import { Model } from "./Model";
extend({ OrbitControls });
const Controls = (props) => {
const { gl, camera } = useThree();
const ref = useRef();
//#ts-ignore
useFrame(() => ref.current.update());
//#ts-ignore
return <orbitControls ref={ref} args={[camera, gl.domElement]} {...props} />;
};
const ThreeJSComponent = (props) => {
const {
width,
height,
fallback,
modelSuccess,
setBackground,
background,
} = props;
return (
<div
style={{
width: width,
height: height,
position: "relative",
}}
>
<div
onClick={() => setBackground("black")}
style={{
position: "absolute",
top: "0",
right: "0",
width: "25px",
borderRadius: "5px",
height: "25px",
background: "black",
zIndex: 50,
border: "white 1px solid",
}}
></div>
<div
onClick={() => setBackground("white")}
style={{
position: "absolute",
top: "0",
right: "50px",
width: "25px",
height: "25px",
borderRadius: "5px",
background: "white",
border: "black 1px solid",
zIndex: 50,
}}
></div>
<Canvas
style={{
width: "100%",
height: "100%",
background: background,
}}
onCreated={({ gl }) => {
gl.shadowMap.enabled = true;
gl.shadowMap.type = THREE.PCFShadowMap;
//#ts-ignore
gl.outputEncoding = false;
gl.toneMappingExposure = 0;
}}
camera={{
position: [0, 0, 10],
isPerspectiveCamera: true,
}}
//shadowMap
>
{" "}
<ambientLight intensity={0x222222} />
<Suspense fallback={fallback}>
<Model url={props.modelURL} modelSuccess={modelSuccess} />
</Suspense>
<Controls
autoRotate
enablePan={true}
enableZoom={true}
enableDamping
dampingFactor={0.5}
rotateSpeed={1}
/>
</Canvas>
</div>
);
};
export default ThreeJSComponent;
and Model:
import React, { useState, useEffect } from "react";
import { useLoader } from "react-three-fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
export const Model = (props) => {
const { url, modelSuccess } = props;
const [model, setModel] = useState();
const loadedModel = useLoader(GLTFLoader, url, (loader) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco-gltf/");
loader.setDRACOLoader(dracoLoader);
});
useEffect(() => {
if (!model) {
setModel(loadedModel);
}
}, []);
if (model) {
modelSuccess();
return (
<primitive
position={[0, 0, 0]}
scale={[1, 1, 1]}
object={model.scene}
dispose={null}
></primitive>
);
} else return null;
};

you dont need setState, useLoader uses suspense, loadedModel will contain the model guaranteed, or the component will throw.
const { scene } = useLoader(GLTFLoader, url, ...)
return <primitive object={scene} dispose={null} />
ambientlight intensity is also wrong, 0 is no light, 1 is the default, then it goes higher. you're giving it an impossibly high value.
other than that it could be that the model is outside the cameras frustrum (too big), it could also be too small. or the path is wrong (it should either be in /public, or you need to it import modelUrl as "./model.glb".
here's a working example: https://codesandbox.io/s/r3f-ibl-envmap-simple-k7q9h?file=/src/App.js
import React, { Suspense } from 'react'
import { Canvas, useLoader } from 'react-three-fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls, Html, draco } from 'drei'
function Model({ url }) {
const { scene } = useLoader(GLTFLoader, url, draco())
return <primitive object={scene} dispose={null} />
}
export default function App() {
return (
<Canvas camera={{ position: [0, 5, 1] }}>
<directionalLight position={[10, 10, 5]} intensity={2} />
<directionalLight position={[-10, -10, -5]} intensity={1} />
<OrbitControls />
<Suspense fallback={<Html>loading..</Html>}>
<Model url="/suzanne-draco.glb" />
</Suspense>
</Canvas>
)
}

Related

Sharpness is lost when moving the React-Three-Fiber/Drei component

I am trying to make a 3D terminal for my portfolio. Currently developing the project in Next.js.
The idea is to use an existing package (https://www.npmjs.com/package/crt-terminal) and put inside the tag of the Drei library for react-three-fiber.
This is the code for model component:
import React, { useState, useEffect } from "react";
import { Canvas } from "#react-three/fiber";
import { OrbitControls, Stats } from "#react-three/drei";
import { useTheme } from "next-themes";
import TestComponent from "./TestComponent";
import { motion } from "framer-motion";
function Model() {
const { theme, setTheme } = useTheme();
const [initialZoom, setInitialZoom] = useState(null);
useEffect(() => {
if (window.innerWidth < 1000) {
setInitialZoom(0.5);
} else {
setInitialZoom(1);
}
}, []);
return (
<motion.div
id="canvas-container"
initial={{ y: 50 }}
animate={{ y: 0 }}
transition={{ duration: 0.5 }}
>
{initialZoom && (
<Canvas
camera={{ zoom: initialZoom }}
style={{ width: "100%", height: 700 }}
>
<OrbitControls
enablePan={true}
enableZoom={true}
enableRotate={true}
minPolarAngle={Math.PI / 2.2}
maxPolarAngle={Math.PI / 1.8}
minAzimuthAngle={-Math.PI / 12}
maxAzimuthAngle={Math.PI / 12}
/>
<TestComponent />
<Stats />
</Canvas>
)}
</motion.div>
);
}
export default Model;
And this is the Html component to render.
import React, { useEffect, useState, useRef } from "react";
import { Terminal, useEventQueue, textLine, textWord } from "crt-terminal";
import { IoTerminal } from "react-icons/io5";
import { VscChromeMaximize, VscChromeMinimize } from "react-icons/vsc";
import { CgClose } from "react-icons/cg";
import { OrbitControls, Html, Icosahedron } from "#react-three/drei";
import { Canvas, useFrame } from "react-three-fiber";
import { useThree } from "#react-three/fiber";
const bannerText = `
Welcome to my CLI. Kind of
`;
export default function TestComponent(props) {
const eventQueue = useEventQueue();
const [screenDimensions, setScreenDimensions] = useState([]);
const { print } = eventQueue.handlers;
const { size } = useThree();
useEffect(() => {
const currentDim = [size.width, size.height];
setScreenDimensions(currentDim);
}, []);
return (
<group>
<mesh position={[0, 0, 0]}>
<Html transform position={[0, 0, 0]}>
<div
style={{
width: screenDimensions[0] * 0.5,
height: screenDimensions[1] * 0.3,
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}
className="html-transform-div"
>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<IoTerminal style={{ fontSize: 8, color: "black" }} />
<h1 style={{ fontSize: 6, marginLeft: 3, color: "black" }}>
Command Line
</h1>
</div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<div className="canvas-minimize-div">
<VscChromeMinimize
style={{ fontSize: 8, marginTop: 4, color: "black" }}
/>
</div>
<div className="canvas-maximize-div">
<VscChromeMaximize style={{ fontSize: 8, color: "black" }} />
</div>
<div className="canvas-close-div">
<CgClose
style={{
fontSize: 8,
color: "#010308",
}}
/>
</div>
</div>
<Terminal
queue={eventQueue}
banner={[
textLine({ words: [textWord({ characters: bannerText })] }),
]}
className="Terminal"
onCommand={(command) =>
print([
textLine({
words: [
textWord({ characters: "You entered command: " }),
commandWord({ characters: command, prompt: ">" }),
],
}),
])
}
/>
</div>
</Html>
</mesh>
</group>
);
}
The component renders fine, appearing sharp, but then when I use the controls to move the characters all get blurred. This problem only happens for desktop computers, everything works perfectly for mobile devices.
Before move
After move
Any ideia how to fix?
Thank you

Video element in react does not play the stream

The html video element below does not play the webcam stream.
setState works fine since the browser notifies me that the site is accessing the camera.
It still invokes the webcam but the html video element is not activated after state change.
What i see is the black screen even if the webcam is active.
There are no error messages on browser console
any help appreciated
import React, {useState,useEffect} from 'react';
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import { connect } from "react-redux";
const styles = () => ({
root: {
display: "flex",
flexDirection: "column",
width: "20%",
height: "25%",
overflowY: "auto",
},
videoPreview:{
alignSelf: "center",
width: "30%",
backgroundColor: "rgba(0, 0, 0, 0.25)",
marginTop: 20,
},
});
const Preview = (props) => {
const {classes} = props;
const [videoPreviewTrack, setVideoPreviewTrack] = useState(navigator.mediaDevices.getUserMedia({video:true}) );
useEffect(() => {
//something here maybe?
});
return (
<div className={classes.videoPreview}>
<video src={videoPreviewTrack} autoPlay={true} id={"videoPreviewElement"}>
</video ></div>
);
};
Preview.propTypes = {
classes: PropTypes.object.isRequired,
};
export default connect()(withStyles(styles)(Preview));
import React, {useState,useEffect,useRef} from 'react';
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import { connect } from "react-redux";
import Select from '#material-ui/core/Select';
const styles = () => ({
root: {
display: "flex",
flexDirection: "column",
width: "20%",
height: "25%",
overflowY: "auto",
},
previewBackground:{
alignSelf: "center",
backgroundColor: "rgba(0, 0, 0, 0.25)",
marginTop: 20,
},
videoPreview:{
alignSelf: "center",
width: "15%",
backgroundColor: "rgba(0, 0, 0, 0.25)",
marginTop: 5,
},
});
const Preview = (props) => {
const {classes} = props;
const videoRef = useRef(null);
useEffect(() => {
getVideo();
}, [videoRef]);
const getVideo = () => {
navigator.mediaDevices
.getUserMedia( {video: true} )
.then(stream => {
let video = videoRef.current;
video.srcObject = stream;
video.play();
})
.catch(err => {
console.error("error:", err);
});
};
return (<div align={"center"} className={classes.previewBackground} >
<div >
<video className={classes.videoPreview} ref={videoRef} >
</video >
</div>
</div>
);
};
Preview.propTypes = {
classes: PropTypes.object.isRequired,
};
export default (withStyles(styles)(Preview));

Unable to set state in the function of fullscreenchange event while exiting fullscreen

I want to toggle fullscreen mode by changing state when fullscreenchange event fires but state is not being changed when exiting fullscreen mode.
Here is the code:
import React, { useEffect, useState } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { FullscreenExit, Fullscreen } from '#mui/icons-material';
import { Fab } from '#mui/material';
import Test1 from './components/Test1';
import Test2 from './components/Test2';
const App = () => {
const [fullscreen, setFullscreen] = useState(false);
useEffect(() => {
document.addEventListener('fullscreenchange', onFullscreenChange, false);
return () => {
document.removeEventListener(
'fullscreenchange',
onFullscreenChange,
false,
);
};
}, []);
const onFullscreenChange = () => {
console.log('here!');
setFullscreen(!fullscreen);
};
const openFullscreen = () => {
const elem = document.documentElement;
if (elem?.requestFullscreen) {
elem.requestFullscreen();
} else if (elem?.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem?.msRequestFullscreen) {
elem.msRequestFullscreen();
} else if (elem?.mozRequestFullScreen) {
elem.msRequestFullscreen();
}
};
function closeFullscreen() {
if (document?.exitFullscreen) {
document.exitFullscreen();
} else if (document?.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document?.msExitFullscreen) {
document.msExitFullscreen();
} else if (document?.mozsExitFullscreen) {
document.msExitFullscreen();
}
}
return (
<>
<BrowserRouter>
{console.log(fullscreen, 'fullscreen')}
<Routes>
<Route path='/' element={<Test1 />} />
<Route path='/home' element={<Test2 />} />
</Routes>
</BrowserRouter>
{!fullscreen ? (
<Fab
style={{
position: 'fixed',
bottom: '30px',
right: '30px',
backgroundColor: '#9e9e9e',
opacity: '0.5',
}}
onClick={openFullscreen}>
<Fullscreen style={{ color: '#183569' }} />
</Fab>
) : (
<Fab
style={{
position: 'fixed',
bottom: '30px',
right: '30px',
backgroundColor: '#9e9e9e',
opacity: '0.5',
}}
onClick={closeFullscreen}>
<FullscreenExit style={{ color: '#183569' }} />
</Fab>
)}
</>
);
};
export default App;
Try using this
setFullscreen(p => !p)

NativeBase showing error while using in Jest testing

I want to test a basic component name TitleHeader which uses HStack and VStack from native base.
The component:-
import {Center, HStack, VStack} from 'native-base';
import React from 'react';
import {View, Text} from 'react-native';
import appColors from '../../constants/appColors';
//Icon Imports
import Ionicons from 'react-native-vector-icons/Ionicons';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {useNavigation} from '#react-navigation/core';
const TitleHeader = ({
title = 'Header Title',
navigationBack = false,
center = false,
onBackPress,
drawer = false,
style = {},
}) => {
console.log(title);
const navigation = useNavigation();
const handleGoBack = () => {
if (onBackPress && typeof onBackPress === 'function') {
onBackPress();
} else if (drawer) {
navigation.goBack();
} else {
navigation.pop();
}
};
return (
<HStack
bg={appColors.primaryBlue}
style={[
{
height: 55,
position: 'relative',
},
style,
]}
alignItems="center"
px={3}>
{navigationBack ? (
<View style={{position: 'absolute', zIndex: 10, left: 10}}>
<TouchableOpacity
onPress={handleGoBack}
style={{
width: 35,
height: 35,
justifyContent: 'center',
alignItems: 'center',
}}>
<Ionicons name="arrow-back" size={26} color="white" />
</TouchableOpacity>
</View>
) : null}
<VStack flex={1} alignItems="center" pl={2}>
<Text
color="white"
fontSize="lg"
numberOfLines={1}
ellipsizeMode="tail"
style={
center
? {}
: {
width: '80%',
}
}>
{title}
</Text>
</VStack>
</HStack>
);
};
export default TitleHeader;
Following is my test case which uses jest for testing:-
import React from 'react';
import {render} from '#testing-library/react-native';
import TitleHeader from '../src/components/AppHeaders/TitleHeader';
import renderer from 'react-test-renderer';
import {NavigationContainer} from '#react-navigation/native';
import {NativeBaseProvider} from 'native-base';
jest.mock('native-base');
const wrapper = ({children}) => (
<NativeBaseProvider
initialWindowMetrics={{
frame: {x: 0, y: 0, width: 0, height: 0},
insets: {top: 0, left: 0, right: 0, bottom: 0},
}}>
{children}
</NativeBaseProvider>
);
describe('Testing Title Header for screens', () => {
test('should render title header', () => {
const tree = renderer
.create(
<NavigationContainer>
<TitleHeader title={'HEADER TEST'} />
</NavigationContainer>,
{wrapper},
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
But I am getting this error inspite of using jest.mock('native-base') in my jest.setup.js.
I am new to testing please help me render this component first and test it using jest. Also, one more thing to add is that if I do not use the wrapper function it throws error telling me that "theme is not defined. Did you forget to wrap your app inside NativeBaseProvider?".
If the component that you trying to test has the theme coming from NativeBase you need to provide the <ThemeProvider/> (Spelling might differ depending on the library, in your case <NativeBaseProvider/>
If you do jest.mock('native-base') and you do not return anything will cause Nothing was returned from render error.
Did you add native-base library to transformIgnorePatterns of jest.config as suggested in this issue?

Fit canvas to screen in React

So I have this code base:
import React, { useEffect, useRef, useState } from 'react';
function App() {
const container = useRef(null);
const canvas = useRef(null);
const [ctx, setCtx] = useState(undefined);
useEffect(() => {
setCtx(canvas.current.getContext("2d"));
}, []);
useEffect(() => {
if (!ctx) return;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, canvas.current.width, canvas.current.height);
}, [ctx]);
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
padding: 8,
position: "relative",
}}
>
<header>
Some Header
</header>
<div style={{ margin: 40, flex: '1 1' }} ref={container}>
<canvas ref={canvas} />
</div>
</div>
);
}
export default App;
It's pretty basic example for a canvas element placed inside a container div.
What I want to do is to resize the canvas width and height according to the user's screen ( and make it responsive ).
So I found out two options:
To use window.addEventListener('resize', ...) or to use ResizeObserver.
I tried them both, but without any success, thats what I tried to do:
import React, { useEffect, useRef, useState } from 'react';
function App() {
const container = useRef(null);
const canvas = useRef(null);
const [ctx, setCtx] = useState(undefined);
const [size, setSize] = useState([0, 0]);
useEffect(() => {
const resize = () => {
const { offsetWidth, offsetHeight } = container.current;
canvas.current.width = offsetWidth;
canvas.current.height = offsetHeight;
setSize([offsetWidth, offsetHeight]);
setCtx(canvas.current.getContext('2d'));
};
resize();
window.addEventListener('resize', resize);
return () => window.removeEventListener('resize', resize);
}, []);
useEffect(() => {
if (!ctx) return;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, size[0], size[1]);
}, [ctx, size]);
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
padding: 8,
position: "relative",
}}
>
<header>
Some Header
</header>
<div style={{ margin: 40, flex: '1 1' }} ref={container}>
<canvas ref={canvas} style={{ width: size[0], height: size[1] }} />
</div>
</div>
);
}
export default App;
But from some reason it makes the height of the canvas greater in every resize cycle.
Whys that and how can I fix that?
If you don't have a problem using a package for the canvas you can use react-konva which will take care of the responsive nature really well.
So all you have to do is change the height and width as the window is resized and send them as attributes.
...
useEffect(() => {
const checkSize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener("resize", checkSize);
return () => window.removeEventListener("resize", checkSize);
}, []);
...
return (
<Stage
width={size.width}
height={size.height}
>
...
</Stage>
)
...
Demo: codesandbox

Resources