BabylonJS update images for textures dynamically

Hey guys, I am using BabylonJS with React and I am experimenting with a simple cuboid whose textures I am creating using images. Issue is that whenever the image is updated, the texture doesnt update and still uses the old image. I have tried using useState/useEffect hooks to update the state but still on re-renders this issue persists. I dont really know how to force babylon to use the texture with the new image. Is there something I am missing

tagging @brianzinn :sweat_smile:

Did you try to use updateURL() for your texture?

updateURL(url: string, buffer?: Nullable<string | ArrayBufferView | Blob | ArrayBuffer | HTMLImageElement | ImageBitmap>, onLoad?: (() => void), forcedExtension?: string): void

API - Texture | Babylon.js Documentation

yea I did saw that in the documentation but not really sure how to use that in react-babylonjs, the library is using a tag to load in the textures, I tried using a ref for it to update the URL but its not really working.

so I figured it out, I picked it up from how the docs were doing it for dynamic fire textures and just used it. looks hacky to me but not sure how to do it better. what I did was create a separate component called Cuboid and updated the texture there and in my main App.tsx, I just called it and passed in the url

const Cuboid = ({ url }: { url: string }) => {
    const scene = useScene()
    const textureRef = useCallback((node) => {
        if (node != null) {
            const texture = new Texture(url = url, scene)
            const material = node
            material.diffuseTexture = texture
        }
    }, [url])

    return (
        <box name="test-box" size={2}>
            <standardMaterial ref={textureRef} name="test" specularColor={Color3.Black()} />
        </box>
    )
}

App.tsx

<Engine antialias adaptToDeviceRatio canvasId="babylonJS">
          <Scene>
            <arcRotateCamera
              name="camera1"
              target={Vector3.Zero()}
              alpha={Math.PI / 2}
              beta={Math.PI / 4}
              radius={8}
            />
            <hemisphericLight
              name="light1"
              intensity={0.7}
              direction={Vector3.Up()}
            />
            <Cuboid url={url} />
          </Scene>
        </Engine>

Thanks @labris now I just need to fix typescript screaming at me in the usecallback func

It’s because you are changing a constructor argument. I’ll post a playground later today

Glad you got it working. One thing with useCallback is you are not managing the lifetime of the texture - you are memoizing a callback. Maybe you want useLayoutEffect and to trigger a state change for the re-render. With useEffect you can return a callback when the dependency array changes and that is useful for disposing the Texture, if you want to take care of creating Textures imperatively then comes the responsibility of cleaning that up. react-babylonjs does that for you unless you provide the escape hatch that you want to manage the lifetime yourself (ie: if you are passing as fromInstance, etc.).

  <texture disposeOnUnmount={false} ... />

Here is an example with key - that forces a dispose and re-creation at reconciler level:
codesandbox-react-tsx (forked) - CodeSandbox

Using keys on textures has the downside of the loading lag, but you could use pre-loaded resources like here as well (this is the example I “copied” for above example):
https://brianzinn.github.io/react-babylonjs/examples/textures/image-textures

There is an ongoing discussion in the react-babylonjs repo to introduce a single parameter with constructor arguments that would handle this transparently - just going to require a deep equality check and some changes to how renderer operates. Currently only instance properties are monitored (not constructor properties).
Texture | Babylon.js Documentation (babylonjs.com)

HTH

1 Like

yea, that does make sense, if I need to use the escape hatch then I need to clean it up too. I tried fixing that typescript error but it seems even the docs example is suffering from the same thing, also how would you have gone ahead with switching textures on the fly by changing the url? It doesnt seem like there’s a declarative way of using updateURL to do this. In the example you linked you just used a simple state variable but that didnt work in my case. I’ll try out the useLayoutEffect hook. Thanks

P.S. the example you linked above wont work in my case because the urls themselves are generated on the fly and they depend on the user’s actions, so there’s no way of actually compiling a list of assets and just switching them up

The example I gave you would work with dynamically generate URLs.

For the TypeScript error I think you need to hard cast the “node” or otherwise to use typings in your ref declaration. ie:

const Shapes: FC<{}> = () => {
  const textureUrl = useRef(ALT_TEXTURE_URL)
  const textureRef = useRef<Texture | null>(null)
  //                        ^^^^^^^^
  const onClick = () => {
    if (textureRef.current) {
      textureUrl.current =
        textureUrl.current === ALT_TEXTURE_URL
          ? GRASS_TEXTURE_URL
          : ALT_TEXTURE_URL
      textureRef.current.updateURL(textureUrl.current)
    }
  }

  return (
    <>
       {/* other JSX removed for brevity */}
       <standardMaterial name="...">
          <texture ref={textureRef} url={textureUrl.current} />
        </standardMaterial>
    </>
  )
}