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