Summary
Right now useTexture only takes a path. Any texture setting you need (colorSpace, repeat, wrapS, flipY, anisotropy...) has to be applied by hand after the texture loads, with a watch.
We would like to hear from the community before changing the API. Should useTexture (and useTextures) accept an options object?
How it works today
import { useTexture } from '@tresjs/cientos'
import { SRGBColorSpace, RepeatWrapping } from 'three'
const { state: grassDiffuseMap } = useTexture('/textures/grass/splat.jpg')
watch(grassDiffuseMap, (tex) => {
if (!tex) { return }
tex.colorSpace = SRGBColorSpace
tex.wrapS = tex.wrapT = RepeatWrapping
tex.repeat.set(4, 4)
}, { immediate: true })
This works, but there are a few sharp edges:
-
The if (tex) guard doesn't do what it looks like it does. useTexture passes initialValue: new Texture(), so state holds a truthy placeholder Texture from the very first tick. The immediate run applies your settings to a throwaway object, and then they get applied again when the real texture arrives.
-
Reloading resets everything. When the path is a ref and it changes, useLoader re-executes and returns a new Texture. A watch catches that. onMounted, whenever, or a one-shot watch without immediate do not. It is an easy trap to fall into.
-
There is a window where the wrong texture is live. The texture reaches the material through reactivity before the watcher callback runs. In practice you can get a frame with the wrong color space or wrapping. Applying settings inside the load callback removes that window entirely.
-
It is a lot of ceremony for "this texture is sRGB".
Proposal: an options object
const { state: grassDiffuseMap } = useTexture('/textures/grass/splat.jpg', {
colorSpace: SRGBColorSpace,
wrapS: RepeatWrapping,
wrapT: RepeatWrapping,
repeat: [4, 4],
anisotropy: 8,
manager, // currently not supported at all, see below
onLoad: (tex) => { /* escape hatch for anything the object can't express */ },
})
Settings would be applied inside the loader callback, before the texture ever becomes reactive state. Reloads reapply them automatically.
Design questions we'd like input on
1. Flat partial-texture object, or a transform callback?
Option A, a Partial<Texture>-ish object (as above). Reads like template props, discoverable through autocomplete, declarative.
Option B, just a callback:
useTexture('/textures/grass/splat.jpg', {
onLoad: (tex) => {
tex.colorSpace = SRGBColorSpace
tex.repeat.set(4, 4)
},
})
Simpler to implement and zero API surface to maintain, but less declarative and doesn't read like the rest of Tres.
Our current leaning is both: the object for the common 90%, the callback as an escape hatch.
2. Should repeat: [4, 4] work?
repeat, offset and center are Vector2, so a plain Object.assign would silently do nothing useful with an array. To make it behave like a template prop (:repeat="[4, 4]") we need the same coercion patchProp does in core (packages/core/src/core/nodeOps.ts). That logic currently lives inside the nodeOps closure and isn't exported, so this means either extracting it into a reusable util in core, or writing a smaller local applier in cientos.
Is array-to-vector coercion worth that, or would you rather write repeat: new Vector2(4, 4) and keep the implementation dumb?
3. Should this also cover useTextures?
useTextures has the same gap. Per-texture options, or one shared options object for the whole batch, or both?
4. Anything else that belongs in there?
manager is an obvious one. useTexture doesn't accept a LoadingManager today, even though the <UseTexture /> component declares a manager prop and never passes it along. Whatever we do here should fix that.
Prior art
Both react-three-fiber's useTexture and drei accept a callback for exactly this reason. Curious whether people prefer that shape or something more declarative.
If you use useTexture in a real project, we'd love to know which settings you find yourself applying after load most often. That tells us whether the flat object is worth it or the callback is enough.
Summary
Right now
useTextureonly takes a path. Any texture setting you need (colorSpace,repeat,wrapS,flipY,anisotropy...) has to be applied by hand after the texture loads, with awatch.We would like to hear from the community before changing the API. Should
useTexture(anduseTextures) accept an options object?How it works today
This works, but there are a few sharp edges:
The
if (tex)guard doesn't do what it looks like it does.useTexturepassesinitialValue: new Texture(), sostateholds a truthy placeholderTexturefrom the very first tick. Theimmediaterun applies your settings to a throwaway object, and then they get applied again when the real texture arrives.Reloading resets everything. When the path is a ref and it changes,
useLoaderre-executes and returns a newTexture. Awatchcatches that.onMounted,whenever, or a one-shotwatchwithoutimmediatedo not. It is an easy trap to fall into.There is a window where the wrong texture is live. The texture reaches the material through reactivity before the watcher callback runs. In practice you can get a frame with the wrong color space or wrapping. Applying settings inside the load callback removes that window entirely.
It is a lot of ceremony for "this texture is sRGB".
Proposal: an options object
Settings would be applied inside the loader callback, before the texture ever becomes reactive state. Reloads reapply them automatically.
Design questions we'd like input on
1. Flat partial-texture object, or a transform callback?
Option A, a
Partial<Texture>-ish object (as above). Reads like template props, discoverable through autocomplete, declarative.Option B, just a callback:
Simpler to implement and zero API surface to maintain, but less declarative and doesn't read like the rest of Tres.
Our current leaning is both: the object for the common 90%, the callback as an escape hatch.
2. Should
repeat: [4, 4]work?repeat,offsetandcenterareVector2, so a plainObject.assignwould silently do nothing useful with an array. To make it behave like a template prop (:repeat="[4, 4]") we need the same coercionpatchPropdoes in core (packages/core/src/core/nodeOps.ts). That logic currently lives inside thenodeOpsclosure and isn't exported, so this means either extracting it into a reusable util in core, or writing a smaller local applier in cientos.Is array-to-vector coercion worth that, or would you rather write
repeat: new Vector2(4, 4)and keep the implementation dumb?3. Should this also cover
useTextures?useTextureshas the same gap. Per-texture options, or one shared options object for the whole batch, or both?4. Anything else that belongs in there?
manageris an obvious one.useTexturedoesn't accept aLoadingManagertoday, even though the<UseTexture />component declares amanagerprop and never passes it along. Whatever we do here should fix that.Prior art
Both react-three-fiber's
useTextureand drei accept a callback for exactly this reason. Curious whether people prefer that shape or something more declarative.If you use
useTexturein a real project, we'd love to know which settings you find yourself applying after load most often. That tells us whether the flat object is worth it or the callback is enough.