React-three-fiber: 馃敭 v4 collecting ideas (suggestions welcome)

Created on 9 Nov 2019  路  30Comments  路  Source: pmndrs/react-three-fiber

  • [ ] switching on concurrent mode in the reconciler. this will enable time slicing and faster rendering overall. react will be able to schedule and defer updates if it detects that a 60fps framerate is threatened somehow.

  • [ ] optional import components https://github.com/react-spring/react-three-fiber/pull/233 this is more interesting for typescript users.

import { Mesh } from 'react-three-fiber/components'

<Mesh />
  • [ ] extras/helpers?

Things that usually need some boilerplate could be abstracted and collected. For instance controls, level-of-detail, etc

import { OrbitControls } from 'react-three-fiber/extras'

<OrbitControls enableDamping />
  • [ ] useCenter?
import { useCenter } from 'react-three-fiber/extras'

function useCenter(ref)
  const [center, setCenter] = useState([0, 0, 0])
  const ref = useRef()
  useEffect(() => {
    const box = new Box3().setFromObject(ref.current)
    const sphere = new Sphere()
    box.getBoundingSphere(sphere)
    setCenter([-sphere.center.x, -sphere.center.y, -sphere.center.z])
  }, [])
  return [ref, center]
}
  • [ ] something to help with simple animations other than react-spring/three?

https://twitter.com/0xca0a/status/1193148722638737408

  • [ ] new object={...} ?

currently primitive can project an object into the scene that is already there. if you have a class prototype outside of the THREE namespace you must use extent before you can use it in jsx. what about:

<new object={Xyz} args={[1,2,3]} {...props} />
// const temp = new Xyz(1,2,3)
// applyProps(temp, props)

Most helpful comment

On the of the most annoying problems i see right now is accessing siblings/parents/children, which in React isn't really wanted.

Consider:

var light = new THREE.DirectionalLight( 0xFFFFFF )
var helper = new THREE.DirectionalLightHelper( light, 5 )
scene.add( helper )

currently:

const ref = useState()
const [light, set] = useState()
useEffect(() => void set(ref.current), [])
return (
  <>
    <directionalLight ref={ref} />
    {light && <directionalLightHelper args={[light, 5]} />}
  </>
)

it sucks, it can't access the light obviously, because it's not there yet. but i'm thinking, maybe something like a hashmap:

const { objects } = useThree()
return (
  <>
    <directionalLight name="light" />
    <directionalLightHelper args={[objects.light, 5]} />
  </>
)

not sure if that works, if directionalLight really is instanciated before directionalLightHelper, but if it is, that could make re-using objects (geometries, materials) easier, as well as helpers and composers.

All 30 comments

Is https://github.com/react-spring/react-three-fiber/issues/174 doable as

// in Canvas.tsx
return (
<
  <div ref={bind} style={{ ...defaultStyles, ...style }} {...events} {...restSpread}>
    <canvas ref={canvasRef as React.MutableRefObject<HTMLCanvasElement>} style={{ display: 'block' }} />
    {canvasRef.current && (
      <IsReady {...props} size={size} gl2={gl2} canvas={canvasRef.current} setEvents={setEvents}>
        <ErrorBoundary onError={e => { throw e }}>
          {props.children}
        </ErrorBoundary>
      </IsReady>
    )}
  </div>
)

expanding on your post from https://github.com/react-spring/react-three-fiber/issues/174#issuecomment-549930382?

I'm posting it here, because propagating and unmounting the entire app for ppl without error boundary instead of just crashing three canvas is probably a breaking change.

I'm not sure if this doesn't encourage bad error handling (error boundary defined in application inside Canvas would be more user-friendly). I'd expect the errors to propagate up to a the higher React tree, but I'm not sure if they should.

switching on concurrent mode in the reconciler

image

Does it mean all of this shiny features would work? 馃ぉ

yes, that's a simple flag in createContainer in reconciler.jsx. flip it to true, that's it. but i still notice some weird race condition in my tests. as for the errorboundary, im fine with it.

It might be a bit specific to my use case but it would be nice to expose the canvas state, renderloop, and raycaster events to imperative components implemented outside of react. As far as I can tell you would only require imperative components for rapid mounting/unmounting - so I assume this to be a rare usecase.

It seems like r3f is heavily tied to react, and I've found myself rebuilding everything for my imperative components. I could share what I've build so far if you'd be interested in persuing that path - I imagine it would be layer between three and r3f that both three and r3f (or other frameworks) users could use. I understand this is a bit of an obscure direction to go in, however.

Alternatively, someway to mount r3f components outside of react would be amazing (I haven't found a way to do this myself)

It would be really interesting to me to test your Stresstest against concurrent mode. Theoretically this is what it's about, react should detect that it's about to trash the frame loop causing Jank, and prevent it with scheduled actions.

It would be impressive if it worked! Here's some sandboxes you can fork and test against, and adjust the number of objects rendered in sphereConfig.js
react mounting
raw js mounting

Another thing I use a lot is a conditional render loop. Like useUpdate() but for external state (perhaps you can do this with useUpdate?). I've tried using observers and listeners to update a component based on external state but the fastest approach seems to be to just poll the external state to see if it's changed.

So I made this useConditionalFrame hook, that takes array of dependencies and checks if they have changed before calling the function in the useFrame hook. Might be useful to bake in similar functionality?

import {useFrame} from 'react-three-fiber';
export default (useFrameFunc, dependencies) => {
    let prevDependencies = dependencies().map(d => null)
    useFrame(() => {
        if(dependencies().some((d,i) => d !== prevDependencies[i])) {
            useFrameFunc()
            prevDependencies = dependencies();
        }
    })
}

@ryanking1809 have you tried <Canvas invalidateFrameloop ... > though? it renders conditionally (only when element props change). you can also trigger single frame by calling invalidate off useThree. we use this all the time in order to save battery.

On the of the most annoying problems i see right now is accessing siblings/parents/children, which in React isn't really wanted.

Consider:

var light = new THREE.DirectionalLight( 0xFFFFFF )
var helper = new THREE.DirectionalLightHelper( light, 5 )
scene.add( helper )

currently:

const ref = useState()
const [light, set] = useState()
useEffect(() => void set(ref.current), [])
return (
  <>
    <directionalLight ref={ref} />
    {light && <directionalLightHelper args={[light, 5]} />}
  </>
)

it sucks, it can't access the light obviously, because it's not there yet. but i'm thinking, maybe something like a hashmap:

const { objects } = useThree()
return (
  <>
    <directionalLight name="light" />
    <directionalLightHelper args={[objects.light, 5]} />
  </>
)

not sure if that works, if directionalLight really is instanciated before directionalLightHelper, but if it is, that could make re-using objects (geometries, materials) easier, as well as helpers and composers.

@drcmda oh yes, I should definetly try turning off the render loop!

Yes, I definetly have a similar problem. For example passing the same material to several objects requires me to pass it down the tree (so I just create it imperatively outside of r3f). I was thinking some sort of id would help, but then you'd have to intanciate the object without mounting it right? 馃

Maybe you could have something like

<meshBasicMaterial id="blue-material" color={0x0000ff} />
...
<mesh>
    <geometry />
    <reference id="blue-material" />
</mesh>

well this is possible:

const foo = useMemo(() => new THREE. meshBasicMaterial({ color }), [color])
...
<mesh material={foo} />

i'll play with some variants. i realize i'm kind of fighting against react here, it is literally made to be reflective, it doesn't want A to rely on or know B, both should be driven by higher order state. ultimately any kind of static referencing will be wide open to race conditions with concurrent mode/suspense. not sure if it's worth it, maybe there's a better way. 馃

Yeah, sounds very tricky, and the existing approaches aren't a big inconvieience.

I would like some more documentation about all the materials and geometries. Having to parse the Three docs in your head is quite hard. There are good examples but those can be overkill.

First of all -- thank you and your contributors for this work. It's pretty freaking cool.

  • I see that three.js and r3f are working to support the VR/XR spec as it evolves. Anything in V4 that works to faciliate using r3f for VR applications would be shiny.

  • Continued support and use of ReactJS features as they emerge (E.g. Suspense) in V4 would be great. I noticed some issues with using suspense in loading models more than once - navigating away from a route that loads a model with suspense, then back, caused the model not to render although it did seem to load. Granted that's all bleeding edge and it could have been an issue with my implementation.

suspense is all working, see: https://codesandbox.io/s/react-three-fiber-suspense-zu2wo but there's a react bug currently. suspense only works flawless in experimental react. in stable react suspense won't block elements in the same boundary. made an issue for that already.

the other thing happens because your data is being disposed on unmount. you can either do:

const model = useLoader(Loader, url)
const clone = useMemo(() => model.scene.clone(true), [url])

or

const model = useLoader(Loader, url)
return (
  <mesh>
    <meshBasicMaterial dispose={false}

@drcmda about OrbitControls, I created this repo https://github.com/fibo/three-orbitcontrols that packages original OrbitControls.

Is OrbitControls already available in react-three-fiber?

Otherwise can I contribute to add it?

I would like to use react-three-fiber to rewrite my side project (which uses OrbitControls) http://g14n.info/tris3d-canvas/example/

All classes and extensions can be used, there are a couple of demos using orbit.

PS, three exposes real modules now via three/examples/jsm/...

PPS, found one example on csb https://codesandbox.io/s/twilight-feather-myhm9

PS, three exposes real modules now via three/examples/jsm/...

Ops thanks, I am going to deprecate my package

Batching Three draw calls for similar geometry (like lines) without losing the ability to split code in multiple React components.

For example it would be great to have something like :
<Rectangle> <Line/> <Line/> <Line/> <Line/> <Rectangle/>
-> With a single Three draw call of 4 line segments.

Or find React ways to batch draw calls generally...

react batches by default in concurrent mode (useState etc). but these are still four distinct lines, they will have to get created, causing four new THREE.Line() i think there's nothing i could do to speed that up.

I'd love to be able to run this in the server. Right now we use plain ThreeJS to build object in the server and export them as GLTF and PNG files. Would love to be able to use react-three-fiber to build my object, and then do a React.render and be able to get the Three object to export them.

No, pretty much the exact opposite, here is the scenario that would be nice to support:

  • I have written some react-three-fiber components that create complex scenes
  • I want to run these components on nodeJS on a server, to export these scenes as GLTF
  • (And of course I don't want to rewrite them in plain three)

Basically the same way we can do reactDom.renderToString for server-sode rendering , we could ideally do reactThreeFiber.renderToGLTF or something close! I could ditch plain threejs completely

not the libs scope, though, it would be too complex and specific. if you know the gltf spec good enough make a small tool for it, that would be really useful! i would mention it on the github front.

Just starting with R3F, so nothing to suggest yet, but for what it's worth, some of the comments in this thread should really be part of the documentation, at least until better solutions come along 馃槃

it sucks, it can't access the light obviously, because it's not there yet. but i'm thinking, maybe something like a hashmap:

const { objects } = useThree()
return (
  <>
    <directionalLight name="light" />
    <directionalLightHelper args={[objects.light, 5]} />
  </>
)

@drcmda In this specific case, I was rather expecting it would be constructed in the same way as i.e. geometry / material attributes are attached to the <mesh> object. Something like following:

<directionalLightHelper>
  <directionalLight attach="light" ... />
<directionalLightHelper/>

that depends on threejs. if that's how directionalLightHelper works, then yes you could do that. but if it relies on the light in the constructor or does something special to it in there, and unfortunately three happily mutates stuff sometimes, then it's gonna be harder.

On the light helper topic, Could we use useResource instead of the

const ref = useState()
const [light, set] = useState()
useEffect(() => void set(ref.current), [])

thing youve got there?

There's also useHelper from drei

const ref = useRef()

useHelper(ref, THREE.DirectionalLightHelper)

<directionalLight ref={ref} />

https://drei.react-spring.io/?path=/story/misc-usehelper--default-story

@drcmda closing this since we are on v5 馃崚 https://github.com/pmndrs/react-three-fiber/releases/tag/v5.0.0

Was this page helpful?
0 / 5 - 0 ratings

Related issues

francopetra picture francopetra  路  3Comments

flxsu picture flxsu  路  4Comments

talentlessguy picture talentlessguy  路  5Comments

Darkensses picture Darkensses  路  6Comments

JakeSidSmith picture JakeSidSmith  路  5Comments