Cannot export Animations with React three Fiber - three.js

"#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

Related

React Three Fiber | Lines are not casting shadows

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, unable to get mouse position for html content

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 />
</>
)
}

How to get OrbitControls ref

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} />;
};

RingBufferGeometry thetaLength animation with GSAP and react-three-fiber

I would like to animate my ringBufferGeometry like in example below, but I would like to use GSAP to animate this ringBufferGeometry for certain amount of time and I would like then to stop it when the thetaLength will equal to some value. Something like in example below but without the use of Math.sin().
three js RingBufferGeometry, update thetaLength
This is what I want to achieve(link below), but I want stop to animate when it reaches the full triangle form.
https://jsfiddle.net/02oyunbj/1/
My code
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
function Container(props) {
return <div style={{ position: 'absolute', inset: 0 }} {...props} />
}
ReactDOM.render(
<Container>
<App />
</Container>,
document.getElementById('root')
)
import { useRef, useEffect } from 'react'
import { Canvas } from '#react-three/fiber'
import { OrbitControls } from '#react-three/drei'
import * as THREE from 'three'
import gsap from 'gsap'
export default function App() {
return (
<Canvas camera={{ position: [0, 0, 15] }}>
<color attach="background" args={['black']} />
<OrbitControls />
<spotLight position={[15, 15, 15]} angle={0.3} color={'white'} />
<TriangleRing />
</Canvas>
)
}
const TriangleRing = () => {
const triangleRingRef = useRef()
const parametersObj = {
innerRadius: 4,
outerRadius: 8,
thetaSegments: 1,
phiSegments: 30,
thetaStart: 1.55,
thetaLength: 0,
}
const parameters = [
parametersObj.innerRadius,
parametersObj.outerRadius,
parametersObj.thetaSegments,
parametersObj.phiSegments,
parametersObj.thetaStart,
parametersObj.thetaLength,
]
useEffect(() => {
console.log(triangleRingRef.current)
const triangleGeometry = triangleRingRef.current.geometry
gsap.set(triangleGeometry, {
parameters: { ...parametersObj, thetaLength: 0 },
})
const tl = gsap.timeline({ defaults: { ease: 'power3.inOut' } })
tl.to(triangleGeometry, {
parameters: { ...parametersObj, thetaLength: 6.3 },
duration: 4,
})
})
return (
<mesh ref={triangleRingRef}>
<ringBufferGeometry args={parameters} name="TriangleRing" />
<meshToonMaterial side={THREE.DoubleSide} color={0xffffff} />
</mesh>
)
}
Link to sandbox:
https://codesandbox.io/s/affectionate-lichterman-edpfr?file=/src/App.jsx:0-1517
By default GSAP works in terms of seconds, not velocity or anything like that. So you'll need to calculate how long the tween(s) should run (by using the difference in value and the rate at which you want it to move) to get it to result in the end value that you need.
Without a minimal, complete, and verifiable example it's hard for us to help more at this point.

Objects don't overlap when rotating

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>
);
}

Resources