I'm working on a website that for now, only has a text and a floating 3D logo on the right. The thing is, when I'm watching the object, the animation runs at 60fps and the website runs smoothly, but when I'm not watching the object, the website has a lot of lag and it's unusable so, I wanted to know why that happens and how I can fix it.
Here's my code:
index.js
<div className={styles.logo}>
<Suspense fallback={null}>
<Canvas gl={{antialias: true, powerPreference: 'high-performance'}} dpr={[0.8, 1.8]}>
<Logo />
<Environment background={false} files="/images/env2.hdr" near={1} far={1000} resolution={256} />
</Canvas>
</Suspense>
</div>
logo.js
export default function Logo({ ...props }) {
const group = useRef()
const { nodes, materials } = useGLTF('/images/RB_Maquillaje_2-transformed.glb')
useFrame((state) => {
const t = state.clock.getElapsedTime()
group.current.position.y = Math.sin(t) * 0.2;
group.current.rotation.y = Math.sin(t / 1.5) / 15
})
return (
<group ref={group} {...props} dispose={null}>
<motion.mesh initial={{scale: 0}} animate={{scale: 1}}
transition={{duration: 0.5, type: 'spring', stiffness: 200, damping: 20}}
geometry={nodes.BezierCurve004.geometry} material={materials['Material.001']} rotation={[0, -0.2, 0]} position={[0, -0.2, 1]} />
</group>
)
}
useGLTF.preload('/images/RB_Maquillaje_2-transformed.glb')
Related
I'm trying to implement this Water Shader in my project but the water doesn't show up. I get no errors in the console only a warning:
React Hook useMemo has a missing dependency: 'gl.encoding'. Either include it or remove the dependency array
I've tried removing it but no luck. I'm not exactly sure what this might mean, at first I thought the water wasn't appearing because of the texture I was loading in but this doesn't seem to be the case.
My code is here:
function Ocean() {
const ref = useRef()
const gl = useThree((state) => state.gl)
const waterNormals = useLoader(THREE.TextureLoader, 'https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/waternormals.jpg')
waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping
const geom = useMemo(() => new THREE.PlaneGeometry(10000, 10000), [])
const config = useMemo(
() => ({
textureWidth: 512,
textureHeight: 512,
waterNormals,
sunDirection: new THREE.Vector3(),
sunColor: 0xFFF05D,
waterColor: 0x7F7F7F,
distortionScale: 3.7,
fog: false,
format: gl.encoding
}),
[waterNormals]
)
useFrame((state, delta) => (ref.current.material.uniforms.time.value += delta))
return <water ref={ref} args={[geom, config]} rotation-x={-Math.PI / 2} position={[0, 0, 0]} />
}
And the code when calling it:
export default function App() {
return (
<Canvas camera={{ position: [0, 5, 100], fov: 55, near: 1, far: 20000 }}>
<pointLight position={[100, 100, 100]} />
<pointLight position={[-100, -100, -100]} />
<Suspense fallback={null}>
<Ocean />
<Box />
</Suspense>
<Sky scale={1000} sunPosition={[500, 150, -1000]} turbidity={0.1} />
<OrbitControls />
</Canvas>
)
}
Hi I have a simple Cube component made with react three fibre.
import {useState,useRef} from 'react';
import { useFrame } from '#react-three/fiber';
const Box = ({props}) =>{
const ref = useRef()
const [hovered, hover] = useState(false)
const boxClick = () =>{
alert('clicked the object')
}
return (
<mesh
{...props}
ref={ref}
onClick={boxClick}
scale={2}
onPointerOver={(event) => hover(true)}
onPointerOut={(event) => hover(false)}>
<boxGeometry args={[1.5, 1.5, 1.5]} />
<meshStandardMaterial color={hovered ? 'yellow' : 'orange'} />
</mesh>
)
}
export default Box;
I am importing the Box compoent in App.js and using it this way-
function App() {
const [boxes,setBoxes] = useState(0);
const box1Click = () =>{
setBoxes(() =>boxes+1)
alert(boxes)
}
return (
<Canvas className='main-canvas'>
<ambientLight intensity={0.5} />
<spotLight position={[5, 155, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-100, -200, -100]} />
<Box position = {[0 ,0,0]} />
</Canvas>
);
}
export default App;
Right now I can click the cube and it runs the boxClick function properly. What I want to assign different click function to each face of the cube.
You can draw six planes and change the rotation or the position each time to construct a cube..
const Plane = (props) => {
const ref = useRef();
const texture = useLoader(THREE.TextureLoader, "/images/wood.png");
useFrame((state) => {
if (props.rotateX) ref.current.rotation.x = props.rotateX;
if (props.rotateY) ref.current.rotation.y = props.rotateY;
if (props.rotateZ) ref.current.rotation.z = props.rotateZ;
// ref.current.rotation.x += 0.01;
// ref.current.rotation.y += 0.01;
});
return (
<mesh ref={ref} {...props}>
<planeGeometry />
<meshBasicMaterial
color="red"
side={THREE.DoubleSide}
opacity={props.opacity}
transparent
visible={props.visible}
map={texture}
/>
</mesh>
);
};
And then add them to your scene
<Suspense fallback={null}>
<Box position={[3, 3, 0]} />
<Plane
args={[2, 2]}
position={[0.5, 0, 0.5]}
rotateX={Math.PI / 2}
opacity="0.5"
visible={true}
/>
<Plane
args={[2, 2]}
position={[0.5, 0.5, 1]}
opacity="0.8"
visible={true}
/>
<Plane
args={[2, 2]}
position={[0, 0.5, 0.5]}
rotateY={Math.PI / 2}
opacity="1"
visible={true}
/>
{/** ... */}
</Suspense>
I'm trying to create a d20 (a dice with 20 sides or icosahedron).
So far, I've been doing it like this:
const IcosahedronDice = (props) => {
const [ref] = useBox(() => ({ mass: 1, position: [0, 10, 0] }));
return (
<mesh ref={ref} position={[0, 2, 0]} castShadow receiveShadow>
<icosahedronBufferGeometry attach="geometry" args={[1, 0]}/>
<meshStandardMaterial attach="material" color="#802d2d" />
</mesh>
)
}
The issue with that is that I'm using "useBox", which collides on my plane (code below) and behaves like a box should, even tough my object still looks like a icosahedron as ref, it rolls and rotates like a box, but as soon as I try using "useConvexPolyhedron" (which I guess is the right ref for a icosahedron), my icosahedron goes right trough the floor (my plane) without collision.
Plane:
const Plane = () => {
const [ref] = usePlane(() => ({ rotation: [-Math.PI / 2, 0, 0] }));
return (
<mesh ref={ref} position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
<planeBufferGeometry attach="geometry" args={[100, 100]} />
<meshStandardMaterial attach="material" color="#49687a" />
</mesh>
)
}
I tried finding a solution on the docs, but so far, nada! Not sure if I'm missing something or the docs are actually too confusing.
Edit:
The whole code:
import './App.css';
import { Canvas } from '#react-three/fiber';
import { OrbitControls, Stars, Icosahedron } from '#react-three/drei';
import * as THREE from 'three'
import { Physics, useBox, usePlane, useConvexPolyhedron } from '#react-three/cannon';
const IcosahedronDice = (props) => {
const icosahedron = new THREE.IcosahedronGeometry(4)
const [ref] = useBox(() => ({ mass: 1, position: [0, 10, 0] })); // THIS should be useConvexPolyhedron
return (
<mesh ref={ref} position={[0, 2, 0]} castShadow receiveShadow>
<icosahedronBufferGeometry attach="geometry" args={[1, 0]}/>
<meshStandardMaterial attach="material" color="#802d2d" />
</mesh>
)
}
const Plane = () => {
const [ref] = usePlane(() => ({ rotation: [-Math.PI / 2, 0, 0] }));
return (
<mesh ref={ref} position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
<planeBufferGeometry attach="geometry" args={[100, 100]} />
<meshStandardMaterial attach="material" color="#49687a" />
</mesh>
)
}
const DiceCanvas = () => {
return (
<Canvas>
<Stars />
<OrbitControls />
<ambientLight intensity={0.5} />
<spotLight
position={[10, 10, 10]}
intensity={0.5}
penumbra={1}
castShadow
/>
<Physics>
<IcosahedronDice position={[0, 0, 4]} rotation={[0, 1, 0]}/>
<Plane />
</Physics>
</Canvas>
);
}
export default DiceCanvas;
I've actually also struggled a bit with finding the resources to get a Rhombicubeoctahedron (26th sided die) to work. My use case might be a little different to yours, since I'm importing a GLTF for my shape, but I'm sure you can extract the necessary bits for it.
const D26 = (props) => {
// I'm importing my shape here.
const { nodes, materials } = useGLTF('/beveled_die.glb');
// Memoizing the calculations for the `Cube` object geometry (Even if it says cube, it's a 26 sided polyhedron.
const geo = useMemo(() => toConvexProps(nodes.Cube.geometry), [nodes]);
// Getting the reference from the `useConvexPolyhedron` hook. Notice how I'm passing in the goe object.
const [ref, api] = useConvexPolyhedron(() => ({ mass: 1, ...props, args: geo }));
I'm passing in the geo memoized object to the args of the ConvexPolyhedron. The function toConvexProps is the following:
const toConvexProps = (bufferGeometry) => {
const geo = new Geometry().fromBufferGeometry(bufferGeometry);
// Merge duplicate vertices resulting from glTF export.
// Cannon assumes contiguous, closed meshes to work
geo.mergeVertices();
return [geo.vertices.map((v) => [v.x, v.y, v.z]), geo.faces.map((f) => [f.a, f.b, f.c]), []]; // prettier-ignore
};
Here's the D26 jsx:
<group
ref={ref} // This is the ref we obtained from the useConvexPolyhedron
onClick={(e) => {
...
{...props}
dispose={null}
>
<mesh
castShadow
material-color="#1e293b"
receiveShadow
geometry={nodes.Cube.geometry} // These are the geometries from the GLTF object loaded.
material={materials.Material}
/>
...
</group>
At the same time, here's my Canvas jsx:
<Canvas gl={{ alpha: false }} camera={{ position: [0, -12, 16], zoom: 3 }}>
<hemisphereLight intensity={0.35} />
<spotLight position={[30, 0, 30]} angle={1} penumbra={1} intensity={1} castShadow />
<Suspense fallback={null}>
<Physics gravity={[0, 0, -30]}>
<Plane color={'#334155'} />
<Rhombicuboctahedron position={[4, -4, 0]} />
</Physics>
</Suspense>
</Canvas>
The Plane objects you're using are the same as the ones as I'm using, so that should be fine.
Sorry for the lenghty answer lol. Hope that this will help you resolve your issue!
So I explore 2 cubes sample. I want to click only on one cube at a time. Yet when I click on a cube that has a cube right behind the current one both get clicked. How to force click and hover only on the nearest one?
In other words clicking\hovering on cube A I want cube B not to be clicked
My code sample:
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from '#react-three/fiber'
function Box(props) {
// This reference will give us direct access to the mesh
const mesh = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
// Rotate mesh every frame, this is outside of React without overhead
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01
})
return (
<mesh
{...props}
ref={mesh}
scale={active ? 1.5 : 1}
onClick={(e) => setActive(!active)}
onPointerOver={(e) => setHover(true)}
onPointerOut={(e) => setHover(false)}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
export default function App() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-10, -10, -10]} />
<Box position={[1.2, 0.5, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>
)
}
Solved it here
main diff:
function intersectionsFilter(intersections) {
return intersections?.length ? [intersections[0]] : intersections
}
export default function App() {
return (
<Canvas raycaster={{ filter: intersectionsFilter }}>
I'm trying to use the texture for my ThreeJS object.
I'm getting error:
index.js:1 Error: Earth suspended while rendering, but no fallback UI was specified.
Add a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.
in Earth
Earth.js:
export const Earth = () => {
const texture = useLoader(THREE.TextureLoader, earthImg)
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<mesh>
<sphereBufferGeometry attach="geometry" args={[5, 32, 32]} />
<meshStandardMaterial attach="material" roughness={1} fog={false} />
</mesh>
</Suspense>
)
}
index.js:
export default function App() {
return (
<Canvas
style={{height:'100vh',width:'100vw'}}
camera={{
position: [0, window.innerWidth / window.innerHeight, 5]
}}
>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-10, -10, -10]} />
<Earth position={[-1.2, 0, 0]} />
</Canvas>
)
}
The component that useLoader() is called from (<Earth> in this case) needs to be wrapped in a <Suspense>, a suspense fallback cannot be specified from within the component.
Earth.js:
export const Earth = () => {
const texture = useLoader(THREE.TextureLoader, earthImg)
return (
<mesh>
<sphereBufferGeometry attach="geometry" args={[5, 32, 32]} />
<meshStandardMaterial attach="material" roughness={1} fog={false} />
</mesh>
)
}
index.js:
export default function App() {
return (
<Canvas
style={{height:'100vh',width:'100vw'}}
camera={{
position: [0, window.innerWidth / window.innerHeight, 5]
}}
>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-10, -10, -10]} />
<Suspense fallback={<h1>Loading profile...</h1>}>
<Earth position={[-1.2, 0, 0]} />
</Suspense>
</Canvas>
)
}