I'm trying to reproduce this animation (see below) with react-three-fiber. I'm still very new to this package and to three-js.
http://makesportmakebook.com/livres/.
I've been able to do create book shapes with meshLambertMaterial, as such:
function Book(props) {
const mesh = useRef();
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01
})
const bookCover = useLoader(TextureLoader, bookCoverImg)
const bookSpine = useLoader(TextureLoader, bookSpineImg)
const bookBack = useLoader(TextureLoader, bookBackImg)
const bookPages = useLoader(TextureLoader, bookPagesImg)
const bookPagesTexture = useLoader(TextureLoader, bookPagesTextureImg)
const bookPagesTopBottomTexture = useLoader(TextureLoader, bookPagesTopBottomTextureImg)
return (
<mesh
position={props.position}
ref={mesh}>
<boxBufferGeometry attach="geometry" args={
[
7, 10, 1.2, 4, 4, 1
]
} />
<meshLambertMaterial color={"0xffffff"} map={bookCover} />
<meshLambertMaterial map={bookSpine} />
<meshLambertMaterial map={bookBack} />
<meshLambertMaterial map={bookPages} />
<meshLambertMaterial map={texture_5} />
<meshLambertMaterial map={texture_6} />
</mesh>
)
};
Here's a code sandbox of my code so far: https://codesandbox.io/s/cocky-fast-61ndj
My question is: how can I avoid the overlap of these you can see here (below) but still keep the same parallel position we can see in the first example?
There are two ways that you could handle this. The first is to move the camera instead of the books. The second is that instead of moving the books separately, move them as a group. I suspect that this second method is what you want to do.
This was quick and dirty, but it works.
Create a function to house the two books. Put the two books in a group and rotate the group.
import React, { useRef, Suspense } from "react";
import { Canvas, useFrame, extend, useLoader } from "react-three-fiber";
import { OrbitControls, StandardEffects, draco } from "drei";
import { TextureLoader } from "three/src/loaders/TextureLoader.js";
extend({ OrbitControls });
function Bookshelf(props) {
const mesh = useRef();
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01;
});
return (
<group ref={mesh} position={[0, 0, 0]}>
<Suspense fallback={null}><Book position={[3, 0, 3]} /></Suspense>
<Suspense fallback={null}><Book position={[-3, 0, 0]} /></Suspense>
</group>
)
}
function Book(props) {
const bookCover = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732427/HEAD_PUBLISHING/book-cover.jpg"
);
const bookSpine = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732420/HEAD_PUBLISHING/book-back.jpg"
);
const bookBack = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732421/HEAD_PUBLISHING/book-side.jpg"
);
const bookPages = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732421/HEAD_PUBLISHING/book-side.jpg"
);
const texture_5 = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732427/HEAD_PUBLISHING/book-cover.jpg"
);
const texture_6 = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732421/HEAD_PUBLISHING/book-spine.jpg"
);
return (
<mesh position={props.position} >
<boxBufferGeometry attach="geometry" args={[7, 10, 1.2, 4, 4, 1]} />
<meshLambertMaterial color={"0xffffff"} map={bookCover} />
<meshLambertMaterial map={bookSpine} />
<meshLambertMaterial map={bookBack} />
<meshLambertMaterial map={bookPages} />
<meshLambertMaterial map={texture_5} />
<meshLambertMaterial map={texture_6} />
</mesh>
);
}
export default function App() {
let styling = {
width: "100vw",
height: "100vh",
position: "relative"
};
return (
<div style={{ position: "relative" }}>
<Canvas camera={{ position: [0, 0, 20] }} style={styling}>
<ambientLight intensity={0.3} />
<directionalLight intensity={1} />
<Suspense fallback={null}>
<Bookshelf>
</Bookshelf>
</Suspense>
<OrbitControls enableZoom={false} />
</Canvas>
</div>
);
}
Related
I recently started digging into ThreeJS, followed some tutorials and now I have a problem with my line / bufferGeometry not casting any shadows. Shadows are casted, as long as I use boxBufferGeometry inside a mesh. But if I use lines, nothing happens
import React from 'react'
import { Canvas } from '#react-three/fiber'
import Curve from './components/Curve';
import Controls from './components/Controls';
import Plane from './components/Plane';
import * as THREE from 'three';
const App = () => {
return (
<div className="App">
<Canvas shadows colorManagement camera={{position: [5,5,5]}} onCreated={({gl}) => {
gl.shadowMap.enabled = true;
gl.shadowMap.type = THREE.PCFShadowMap;
gl.setPixelRatio(window.devicePixelRatio || 2);
}}>
<spotLight
position={[0,70,0]}
color="white"
intensity={1.5}
castShadow
shadowMapHeight={1024}
shadowMapWidth={1024}
shadowCameraNear={200}
shadowCameraFar={2000}
shadowBias={ - 0.00022}
angle={0.01}
/>
<ambientLight color="#f0f0f0" />
<Controls />
<Curve />
<Plane />
<gridHelper args={[30, 100, 'white', 'white']} position={[0,-3.99,0]} receiveShadow />
</Canvas>
</div>
);
}
export default App;
import React, { useLayoutEffect, useRef } from 'react';
import * as THREE from 'three';
const Curve = () => {
const curve = new THREE.CubicBezierCurve3
(
new THREE.Vector3( -10, 0, 0 ),
new THREE.Vector3( -5, 15, 0 ),
new THREE.Vector3( 20, 15, 0 ),
new THREE.Vector3( 10, 0, 0 )
);
const points = curve.getPoints( 50 );
const ref = useRef()
useLayoutEffect(() => {void ref.current.setFromPoints(points); }, [points])
return (
<line>
<bufferGeometry ref={ref} />
<lineBasicMaterial color={0xd2452b} />
</line>
);
};
export default Curve;
import React from 'react';
const Plane = () =>
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0,-4,0]} receiveShadow>
<planeGeometry
attach="geometry"
args={[1024,1024]}
/>
<shadowMaterial attach="material" color="#000000" opacity="1" />
</mesh>
export default Plane;
Rendering this component (Box) instead of curve, would cast a shadow.
import React, { useState } from 'react';
import { useSpring, a } from '#react-spring/three';
const Box = () => {
const [hovered, setHovered] = useState(false);
const [active, setActive] = useState(false);
const props = useSpring({
scale: active ? 2 : 1,
color: hovered ? 'red' : 'gray',
});
return (
<a.mesh
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
onClick={() => setActive(!active)}
scale={props.scale}
castShadow
>
<boxBufferGeometry
attach="geometry"
args={[1,1,1]}
/>
<a.meshPhysicalMaterial attach="material" color={props.color} />
</a.mesh>
);
};
export default Box;
The only difference (I'm still a newbie), is that I am using lines instead of mesh. Mesh accepts attributes like "receiveShadow and castShadow", lines doesn't.
How do I manage to let my lines cast shadows, too?
Thanks a lot in advance
"#react-three/fiber": "^8.2.0",
"three": "^0.142.0",
Hello I am trying to export a model containing animations. My model uses the useFrame hook and manipulates the ref like so
const TopicBadge = forwardRef((props, ref) => {
let {playlistBody, hasBadge} = useSelector(s => s.playlist)
return (
<Canvas>
<ShapeModel title={playlistBody.title} ref={ref} />
</Canvas>
);
})
export const ShapeModel = React.forwardRef(({title}, ref ) => {
const meshTurnRef = useRef()
useFrame(() => {
meshTurnRef.current.rotation.y += 0.01
})
return (
<group ref={ref}>
<Html fullscreen>
<div className='text-right w-1/3 ml-16 mt-8'>
<h1 className='text-sm text-plum-green'>{title}</h1>
</div>
</Html>
<mesh matrixAutoUpdate={true} position={[0, 0, 2.4]} ref={meshTurnRef}>
<torusBufferGeometry args={[1, .48, 16, 32]} attach="geometry" />
<meshStandardMaterial roughness={1} fog={true} color={"#049ef4"} attach="material" />
</mesh>
<directionalLight position={[1, 1, 0]} />
<directionalLight position={[0, 0, 3]} />
</group>
)
})
I then export the model with the GLTFExporter in a parent component
const exporter = new GLTFExporter()
let saveRef = useRef()
const onSavePlaylist = async () => {
try {
exporter.parse( saveRef.current, async function ( gltf ) {
// saveRef.current does not have animations
}
}
I see the saveRef.current does not have the animations.
Is there documentation or a recommended way to save animations for importing into a model later? Seems all documentation shows this as the recommended way so I think this might be a bug
I'm trying to to get current mouse position on the canvas when html contents are clicked. With useFrame, I can successfully print out mouse position on any parts of canvas except when mouse is on html contents (it just shows the last mouse position before it enters html part and just stops there).
Are there any ways I can get mouse position when it hovers on html content?
Here are my codes:
#app.js
import React, {useRef, useState } from 'react';
import {Canvas, useFrame, useThree } from '#react-three/fiber';
import { Stars, Html } from '#react-three/drei'
function Sphere(props){
const mesh = useRef(null);
const { viewport, mouse } = useThree()
useFrame(({ mouse }) => {
const x_viewport = (mouse.x * viewport.height) / 2
console.log(x_viewport)
});
return(
<mesh
ref={mesh}
<sphereGeometry args={[1, 16, 16]} />
<meshStandardMaterial />
</mesh>
);
};
function Blocks (props) {
const {handlePost } = props;
let rows = [];
for (let i = 0; i < 30; i++) {
rows.push(<div onClick={handlePost} key={i} ></div>);
}
return (
<>
<div> { rows } </div>
</>
)
}
export default function Meshes() {
const [ hasRender, setRender ] = useState(false);
const handlePost = (e) => {
setRender(true);
}
return (
<>
<Canvas
orthographic
dpr={[1, 1.5]}
mode="concurrent"
camera={{ zoom: 10, position: [0, 0, 500] }}
style={{ width: c_width, color: "black" }}
>
<color attach="background" args={["black"]} />
<Html >
<Blocks handlePost={handlePost} />
</Html>
<ambientLight intensity={0.1} />
<directionalLight position={[0, 1, 1]} />
<Sphere days={days}/>
</Canvas>
{ hasRender && ( <PostItems /> )}
</>
);
}
function App() {
return (
<>
<Meshes />
</>
)
}
I am trying to access OrbitControls ref as follow:
const controlsRef = useRef<any>();
return (
<>
<Canvas
camera={{
position: [0, 0, 100],
up: [0, 0, 1],
}}
>
<OrbitControls makeDefault ref={controlsRef} />
<ambientLight />
<FakeSphere position={[0, 0, 0]} color="red" />
<FakeSphere position={[100, 100, 0]} color="blue" />
<Nav controls={controlsRef.current} />
</Canvas>
</>
);
Code:
https://codesandbox.io/s/cool-flower-f5qr0?file=/src/App.tsx:949-962
The ref is undefined. Any idea please?
Found a hint hehe (https://github.com/pmndrs/drei/issues/718). Cant get ref from outside Canvas.
For now, add a wrapper over OrbitControls to capture the ref. Not sure if any better solution or not.
const MyControls = (props: any) => {
const { setControls } = props;
const ref = useRef<any>();
useEffect(() => {
if (!ref.current) return;
setControls(ref.current);
}, ref.current);
return <OrbitControls makeDefault ref={ref} />;
};
I will implement an effect that I can move camera around an object.
Error example is this and I need to fix it. The difference between this and the latter is that this can update camera position to a 3D object in a model. This example eventually can move camera around a 3D object after updating camera position. But if I follow general use case, i.e. the latter, it can't function correctly. I wonder what's difference between this and the latter.
function Button(props) {
const vec = new THREE.Vector3(0, 3, 5);
const { x, y, z } = props.position;
if (x && y && z) {
vec.set(x, y, z);
}
useFrame((state) => {
const step = 0.1;
state.camera.position.lerp(vec, step);
state.camera.lookAt(0, 0, 0);
state.camera.updateProjectionMatrix();
});
return null;
}
const ModelCanvas = (props) => {
return (
<div className="center">
<Canvas
shadowMap
style={{ background: "#ffffff" }}
camera={{ position: [0, 3, 10], fov: 100 }}
gl={{ antialias: false, logarithmicDepthBuffer: true }}
id="my-canvas"
>
<ambientLight intensity={0.5} />
<pointLight position={[0, 60, -100]} intensity={20} />
<pointLight position={[-50, 0, -50]} intensity={2} />
<spotLight
castShadow
intensity={8}
angle={Math.PI / 10}
position={[10, 10, 10]}
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
<Suspense fallback={null}>
<Model url={"/compressed.glb"} />
</Suspense>
{/* <PointerLockControls /> */}
<Button position={props.value} />
<PointerLockControls />
</Canvas>
</div>
);
};
And this example functions correctly.
const ModelCanvas = (props) => {
return (
<div className="center">
<Canvas
shadowMap
style={{ background: "#ffffff" }}
camera={{ position: [0, 3, 10], fov: 100 }}
gl={{ antialias: false, logarithmicDepthBuffer: true }}
id="my-canvas"
>
<ambientLight intensity={0.5} />
<pointLight position={[0, 60, -100]} intensity={20} />
<pointLight position={[-50, 0, -50]} intensity={2} />
<spotLight
castShadow
intensity={8}
angle={Math.PI / 10}
position={[10, 10, 10]}
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
<Suspense fallback={null}>
<Model url={"/compressed.glb"} />
</Suspense>
{/* <PointerLockControls /> */}
{/* <Button position={props.value} /> */}
<PointerLockControls />
</Canvas>
</div>
);
};