Is this a good approach?

Hi there,

While building a configurator i came across different problems, one of them is having global textures. I use the same image for multiple objects in my scene, but with different repetition values. At first I thought I could manage with about 5-6 textures. So I created 6 different folders with 6 times the same image. This allowed me to use the image with different repeat values in the objects. Now i managed to create this Component:

import * as THREE from "three";
import { useTürenStore } from "../Stores/useTürenStore";
import { useLayoutEffect } from "react";
import { useTexture } from "@react-three/drei";

const concatHexValue = (value) => {
  if (value.startsWith("#m")) {
    return `#${value.slice(2)}`;
  } else {
    return value;
  }
};

const concatQuer = (value) => {
  if (value.includes(" quer")) {
    return value.replace(" quer", "");
  } else {
    return value;
  }
};

export const GlasleisteOberfläche = ({ OFL_TB, scale, part }) => {
  const AFLG = useTürenStore((state) => state.AFLG);

  const isColor = OFL_TB.startsWith("#");
  const isMatt = OFL_TB.startsWith("#m");
  const roughness = isColor ? (isMatt ? 0.7 : 0.5) : 0.9;
  const color = isColor ? concatHexValue(OFL_TB) : "white";
  const texture = isColor ? "CPL Brillantweiß" : concatQuer(OFL_TB);

  const textureGlasL = useTexture({
    map: `./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`,
    map1: `./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`,
    map2: `./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`,
    map3: `./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`,
    map4: `./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`,
  });

  if (part === "horiz") {
    textureGlasL.map.flipY = false;
    textureGlasL.map.wrapS = textureGlasL.map.wrapT = THREE.RepeatWrapping;
    textureGlasL.map.repeat.set(3, scale * 3);
  } else if (part === "vert") {
    textureGlasL.map1.flipY = false;
    textureGlasL.map1.wrapS = textureGlasL.map1.wrapT = THREE.RepeatWrapping;
    textureGlasL.map1.repeat.set(3, scale * 3);
    textureGlasL.map1.offset.x = -0.1;
  } else if (part === "corn") {
    textureGlasL.map2.flipY = false;
    textureGlasL.map2.wrapS = textureGlasL.map2.wrapT = THREE.RepeatWrapping;
    textureGlasL.map2.repeat.set(3, 3);
  } else if (part === "Drive") {
    textureGlasL.map3.flipY = false;
    textureGlasL.map3.wrapS = textureGlasL.map3.wrapT = THREE.RepeatWrapping;
    textureGlasL.map3.repeat.set(3, scale);
  } else if (part === "Falz") {
    textureGlasL.map4.flipY = false;
    textureGlasL.map4.wrapS = textureGlasL.map4.wrapT = THREE.RepeatWrapping;
    textureGlasL.map4.repeat.set(3, scale);
  } else {
    null;
  }

  useLayoutEffect(() => {
    textureGlasL.map.needsUpdate = true;
    textureGlasL.map1.needsUpdate = true;
    textureGlasL.map2.needsUpdate = true;
    textureGlasL.map3.needsUpdate = true;
    textureGlasL.map4.needsUpdate = true;
  }, [part]);

  return (
    <>
      <meshStandardMaterial
        envMapIntensity={0.1}
        map={
          part === "horiz"
            ? textureGlasL.map
            : part === "vert"
            ? textureGlasL.map1
            : part === "corn"
            ? textureGlasL.map2
            : part === "Drive"
            ? textureGlasL.map3
            : part === "Falz"
            ? textureGlasL.map4
            : null
        }
        color={color}
        roughness={roughness}
        toneMapped={false}
      />
    </>
  );
};

Basically i use one of my folders to load the image as a map. When i split the iage into different maps, they do not use the same image but a new reference to that image. With that reference i can set different values for repetition based on which part of the scene it is. I return a MSM with the right map based on the part. 
Is it okay to use this approach? am I getting problems in the future, if there are alot more of the parts?

Greetings Tom

i think this has been a longstanding request for threejs, to scale/offset textures at the shader level, which would make things a little more re-usable. but i believe as it is currently it’s a texture option. as for your 6 folders, you can just clone the texture, it wouldn’t make sense to have 6 fetch requests.

function Foo({ AFLG }) {
  const texture = useTexture(`./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`)
  const cloneTexture = useMemo(() => texture.clone(), [texture])
  ...

too many textures is generally bad and will quickly get you memory faults, depending on how many you need of course.

Thanks for the advice!! The other part with changing the map in the material is okay? And if i want to deffer the Value i would do it like that?

import * as THREE from "three";
import { useTürenStore } from "../Stores/useTürenStore";
import { useDeferredValue, useLayoutEffect, useMemo } from "react";
import { useTexture } from "@react-three/drei";

export const GlasleisteOberfläche = ({ OFL_TB, scale, part }) => {
  const AFLG = useTürenStore((state) => state.AFLG);

  const texture = OFL_TB

  const deferred = useDeferredValue(
    `./textures/glasLeisteTexture/${AFLG}/${texture}.jpg`
  );
  const map = useTexture(deferred);

  const map1 = useMemo(() => map.clone(), [map]);
  const map2 = useMemo(() => map.clone(), [map]);
  const map3 = useMemo(() => map.clone(), [map]);
  const map4 = useMemo(() => map.clone(), [map]);

  if (part === "horiz") {
    map.flipY = false;
    map.wrapS = map.wrapT = THREE.RepeatWrapping;
    map.repeat.set(3, scale * 3);
  } else if (part === "vert") {
    map1.flipY = false;
    map1.wrapS = map1.wrapT = THREE.RepeatWrapping;
    map1.repeat.set(3, scale * 3);
    map1.offset.x = -0.1;
  } else if (part === "corn") {
    map2.flipY = false;
    map2.wrapS = map2.wrapT = THREE.RepeatWrapping;
    map2.repeat.set(3, 3);
  } else if (part === "Drive") {
    map3.flipY = false;
    map3.wrapS = map3.wrapT = THREE.RepeatWrapping;
    map3.repeat.set(3, scale);
  } else if (part === "Falz") {
    map4.flipY = false;
    map4.wrapS = map4.wrapT = THREE.RepeatWrapping;
    map4.repeat.set(3, scale);
  } else {
    null;
  }

  useLayoutEffect(() => {
    map.needsUpdate = true;
    map1.needsUpdate = true;
    map2.needsUpdate = true;
    map3.needsUpdate = true;
    map4.needsUpdate = true;
  }, [part]);

  return (
    <>
      <meshStandardMaterial
        envMapIntensity={0.1}
        map={
          part === "horiz"
            ? map
            : part === "vert"
            ? map1
            : part === "corn"
            ? map2
            : part === "Drive"
            ? map3
            : part === "Falz"
            ? map4
            : null
        }
        color={color}
        roughness={roughness}
        toneMapped={false}
      />
    </>
  );
};

you don’t need all that code imo. why so many memoizations. Each < GlasleisteMaterial /> will have its own texture, shouldn’t that be enough? i think you should generally strive for self containment, it’s often easier to observe things from that perspective.

export function GlasleisteMaterial({ OFL_TB, scale, part, ...props }) {
  const AFLG = useTürenStore(state => state.AFLG)
  const deferred = useDeferredValue(`./textures/glasLeisteTexture/${AFLG}/${OFL_TB}.jpg`)
  const texture = useTexture(deferred)
  const map = useMemo(() => texture.clone(), [texture])
  map.flipY = false
  map.wrapS = map.wrapT = THREE.RepeatWrapping
  if (part === 'horiz') map.repeat.set(3, scale * 3)
  else if (part === 'vert') map.repeat.set(3, scale * 3), (map.offset.x = -0.1)
  else if (part === 'corn') map.repeat.set(3, 3)
  else if (part === 'Drive' || part === 'Falz') map.repeat.set(3, scale)
  return <meshStandardMaterial map={map} {...props} />
}

<mesh>
  <GlasleisteMaterial
    OFL_TB={...}
    scale={...}
    part={...}
    envMapIntensity={0.1}
    color={color}
    roughness={roughness} />
  ...
1 Like

Works like a charm, thanks for the answer!! Have a great day :wave:t3: