How to create multiMaterial in React and BabylonJs

My intention is to map a box so that the faces receive different materials, I also want them not to stretch when resizing the box.
I’m starting with Babylon and React, but I would like to continue with this declarative programming approach.

I found this example, but I have a lot of difficulty making it into declarative programming with react.

https://playground.babylonjs.com/#T40FK

Example code

import { Color4, FresnelParameters, Vector3 } from "@babylonjs/core";
import imagem from './core/assets/images/Toronto_490x360.jpg';


type Props = {
    largura: number;
    altura: number;
    profundidade: number;
}

const Caixa = ({ largura, altura, profundidade }: Props) => {

    return (
        <box name="box1" width={80} height={100} depth={30} scaling={new Vector3(largura / 80, altura / 100, profundidade / 30)} position={new Vector3(0, altura / 2, 0)}
             topBaseAt={2} bottomBaseAt={3} wrap={true} onCreated={box => box.enableEdgesRendering()} 
                                  edgesWidth={30} edgesColor={new Color4(0, 0, 0, 1)}>
            <standardMaterial name='material1' specularPower={16}
                reflectionFresnelParameters={FresnelParameters.Parse({
                    isEnabled: true,
                    leftColor: [1, 1, 1],
                    rightColor: [0, 0, 0],
                    bias: 0.1,
                    power: 1
                })}>
                <texture url={imagem} assignTo="diffuseTexture" hasAlpha={true}
                    uOffset={0.5} vOffset={0.5} />
            </standardMaterial>
        </box >
    );
}

export default Caixa;

Maybe @brianzinn can help?

1 Like

Hi @JRobsonGomes . good question. If you want to make a reusable component then your best bet would be to make a Caixa like you have done. When you make a <standardMaterial ../> as a child of the box, it is automatically assigned to the material. The first problem is that the Material will walk down the component hierarchy looking for a Mesh! If there are no meshes further down the hierarchy then you can do this (just typing not tested):

const Caixa = ({ largura, altura, profundidade }: Props) => {
   const side1 = useRef<StandardMaterial | null>(null);
   ...
   const side6 = useRef<StandardMaterial | null>(null);
   const box = useRef<Mesh | null>(null);
   const multi = useCallback<MultiMaterial>((multimat: MultiMaterial) => {
      // I think you should have all refs here for all materials and can create sub-materials etc.
      if (side1.current === null) {
         // I think it will always be set here, but you can see by testing.
      }

      multimat.subMaterials.push(side1);
      ....
      
      new SubMesh(0, 0, verticesCount, 0, 900, box.current);
    }, []);

   return (
      <>
        <standardMaterial ref={side1} .../>
        ...
        <standardMaterial ref={side6} .../>
        <box name='box1' ref={box} ...>
          <multiMaterial ref={multi}  .../>
        </box>
      </>
   )
}

If you are want this material to not attach further down your component graph then I think I will need to add a flag to support that:
<standardMaterial ... doNotAttach />

1 Like

@brianzinn first thanks for your help, you could provide a more concrete example, i tried to adapt your code to mine and react complains about errors that for me are complex.
I left an example playground, see that there is a MyMesh component that I tried to make, if you want you can use it.
Thanks

I adapted what I had sent you before with the playground from your original question;

import {
  Mesh,
  MultiMaterial,
  StandardMaterial,
  SubMesh,
  Color3,
  Vector3
} from "@babylonjs/core";
import { useRef, useEffect } from "react";
//import imagem from "./core/assets/images/Toronto_490x360.jpg";

type Props = {
  largura: number;
  altura: number;
  profundidade: number;
};

// adapted from https://playground.babylonjs.com/#T40FK
const MyMesh = ({ largura, altura, profundidade }: Props) => {
  const material0 = useRef<StandardMaterial | null>(null);
  const material1 = useRef<StandardMaterial | null>(null);
  const material2 = useRef<StandardMaterial | null>(null);
  const material3 = useRef<StandardMaterial | null>(null);
  const material4 = useRef<StandardMaterial | null>(null);
  const material5 = useRef<StandardMaterial | null>(null);

  const box = useRef<Mesh | null>(null);
  const multi = useRef<MultiMaterial | null>(null);

  useEffect(() => {
    if (multi.current === null || box.current === null) {
      return;
    }

    multi.current!.subMaterials.push(material0.current);
    multi.current!.subMaterials.push(material1.current);
    multi.current!.subMaterials.push(material2.current);
    multi.current!.subMaterials.push(material3.current);
    multi.current!.subMaterials.push(material4.current);
    multi.current!.subMaterials.push(material5.current);
    //apply material
    box.current!.subMeshes = [];
    var verticesCount = box.current!.getTotalVertices();
    box.current!.subMeshes.push(
      new SubMesh(0, 0, verticesCount, 0, 6, box.current!)
    );
    box.current!.subMeshes.push(
      new SubMesh(1, 1, verticesCount, 6, 6, box.current!)
    );
    box.current!.subMeshes.push(
      new SubMesh(2, 2, verticesCount, 12, 6, box.current!)
    );
    box.current!.subMeshes.push(
      new SubMesh(3, 3, verticesCount, 18, 6, box.current!)
    );
    box.current!.subMeshes.push(
      new SubMesh(4, 4, verticesCount, 24, 6, box.current!)
    );
    box.current!.subMeshes.push(
      new SubMesh(5, 5, verticesCount, 30, 6, box.current!)
    );
    // already one box.material=multi;
  }, [box, multi]);

  return (
    <>
      <standardMaterial
        name="material0"
        ref={material0}
        diffuseColor={new Color3(0.75, 0, 0)}
      />
      <standardMaterial
        name="material1"
        ref={material1}
        diffuseColor={new Color3(0, 0, 0.75)}
      />
      <standardMaterial
        name="material2"
        ref={material2}
        diffuseColor={new Color3(0, 0.75, 0.75)}
      />
      <standardMaterial
        name="material3"
        ref={material3}
        diffuseColor={new Color3(0, 0, 0.75)}
      />
      <standardMaterial
        name="material4"
        ref={material4}
        diffuseColor={new Color3(0, 0.75, 0)}
      />
      <standardMaterial
        name="material5"
        ref={material5}
        diffuseColor={new Color3(1, 1, 0)}
      />

      <box
        name="boxMulti"
        ref={box}
        width={80}
        height={100}
        depth={30}
        scaling={new Vector3(largura / 80, altura / 100, profundidade / 30)}
        position={new Vector3(0, altura / 2, 0)}
      >
        <multiMaterial name="nuggetman" ref={multi} />
      </box>
    </>
  );
};

export default MyMesh;

It’s a bit tedious declarative like that, but it is a re-usable component. You can assign textures to those standard materials declaratively. I did quite a bit of box textures per side that for a game I made - I had adapted code from another project ( SvenFrankson/planet-builder-web: BabylonJS version of PlanetBuilder algorithms (github.com) using UV mapping of textures. I ended up using different boxes than the MeshBuilder, but I suspect you are doing something more involved and that is just a sample. Cheers.

1 Like

This solves my problem, so I can scale the materials independently without stretching images.
I still need some time to study the code well, but as I understand it, here is a way to access a component variable and call its methods like javascript vanilla.
With this approach it is also possible to exclude faces from a mesh for example?
I still have a lot to do, I want to create furniture with babylonjs.

Once again thank you very much @brianzinn

1 Like

I would need to know how you intend to exclude faces. backfaceCulling is supported declaratively.

1 Like

Yes, I actually have a lot of questions. :sweat_smile:
And that question will be one of them perhaps, but I think it is better to leave it for a new topic.