How to set the parent in a React component in Babylonjs

I would like to create a React component where I can pass the parent through parameter, as an example.

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

const COMPRIEMTO = 2.75;
const LARGURA = 1.83;
const ESPESSURA = 0.015;
/* var widthAux = 0;
var heightAux = 0;
var depthAux = 0; */

type Props = {
    largura: number;
    comprimento: number;
    espessura: number;
    posX?: number;
    posY?: number;
    posZ?: number;
    rotX?: number;
    rotY?: number;
    rotZ?: number;
    pai?: Mesh;
};

// adapted from https://playground.babylonjs.com/#T40FK
const Chapa = ({ largura, comprimento, espessura, posX, posY, posZ, rotX=1, rotY=1, rotZ=1, pai }: 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);

    const alterararCriacao = (box: Mesh) => {
        //Movendo o pivot para o canto 
        const vectorsBox = box.getBoundingInfo().boundingBox.vectorsWorld;
        box.setPivotMatrix(Matrix.Translation(Number(vectorsBox[1].x - (vectorsBox[0].x)) / 2,
            Number(vectorsBox[1].y - (vectorsBox[0].y)) / 2,
            -Number(vectorsBox[1].z - (vectorsBox[0].z)) / 2), false);

        //Rederiza arestas
        box.enableEdgesRendering();
    }

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

        multi.current!.subMaterials.push(material0.current);
        multi.current!.subMaterials.push(material0.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}>
                <texture url={imagem} assignTo="diffuseTexture" hasAlpha={true} uScale={largura / LARGURA} vScale={comprimento / COMPRIEMTO} />
            </standardMaterial>
            {/* <standardMaterial
          name="material1"
          ref={material0}
          diffuseColor={new Color3(0, 0, 0.75)}
        /> */}
            <standardMaterial name="material2" ref={material2}>
                <texture url={imagem} assignTo="diffuseTexture" hasAlpha={true} uScale={espessura / LARGURA} vScale={comprimento / COMPRIEMTO} />
            </standardMaterial>
            <standardMaterial name="material3" ref={material3}>
                <texture url={imagem} assignTo="diffuseTexture" hasAlpha={true} uScale={espessura / LARGURA} vScale={comprimento / COMPRIEMTO} />
            </standardMaterial>
            <standardMaterial name="material4" ref={material4}>
                <texture url={imagem} assignTo="diffuseTexture" hasAlpha={true} uScale={espessura / LARGURA} vScale={largura / COMPRIEMTO} />
            </standardMaterial>
            <standardMaterial name="material5" ref={material5}>
                <texture url={imagem} assignTo="diffuseTexture" hasAlpha={true} uScale={espessura / LARGURA} vScale={largura / COMPRIEMTO} />
            </standardMaterial>

            <box
                name="caixa" ref={box} width={LARGURA} height={COMPRIEMTO} depth={ESPESSURA}
                scaling={new Vector3(largura / LARGURA, comprimento / COMPRIEMTO, espessura / ESPESSURA)}
                position={new Vector3(posX, posY, posZ)}
                rotation={new Vector3(Math.PI / rotX, Math.PI / rotY, Math.PI / rotZ)}
                wrap={true} topBaseAt={2} bottomBaseAt={3} edgesWidth={0.3} edgesColor={new Color4(0, 0, 0, 1)}
                onCreated={box => alterararCriacao(box)} parent={pai}
            >
                <multiMaterial name="multiMat" ref={multi} />
            </box>
        </>
    );
};

export default Chapa;

This is the component that I want to pass the father on the parameter.

import { Mesh, Matrix, Vector3, Color4 } from "@babylonjs/core";
import { useEffect, useRef } from "react";
import Chapa from "./Chapa";

const LARGURA = 0.8;
const ALTURA = 1.5;
const PROFUNDIDADE = 0.30;

type Props = {
largura: number;
altura: number;
profundidade: number;
posX?: number;
posY?: number;
posZ?: number;
}

const Corpo = ({ largura, altura, profundidade, posX = 0, posY = 0, posZ = 0 }: Props) => {

const box = useRef<Mesh>();

const alterararCriacao = (box: Mesh) => {
    //Movendo o pivot para o canto 
    const vectorsBox = box.getBoundingInfo().boundingBox.vectorsWorld;
    box.setPivotMatrix(Matrix.Translation(Number(vectorsBox[1].x - (vectorsBox[0].x)) / 2,
        Number(vectorsBox[1].y - (vectorsBox[0].y)) / 2,
        -Number(vectorsBox[1].z - (vectorsBox[0].z)) / 2), false);

    //Rederiza arestas
    box.enableEdgesRendering();
}

useEffect(() => {
    if (box.current === null) {
        return;
    }
    
}, [box]);

return (
    <>
        <box
            name="caixa" ref={box} width={LARGURA} height={ALTURA} depth={PROFUNDIDADE}
            scaling={new Vector3(largura / LARGURA, altura / ALTURA, profundidade / PROFUNDIDADE)}
            position={new Vector3(posX, posY, posZ)} edgesWidth={0.3} edgesColor={new Color4(0, 0, 0, 1)}
            rotation={new Vector3(0, 0, 0)}
            onCreated={box => alterararCriacao(box)} visibility={0.1} isVisible={true}
        >
        </box>
        <Chapa largura={profundidade} comprimento={altura} espessura={0.015} posX={0} posY={0} posZ={0} rotY={-2} pai={box.current} />{/* Lateral esq */}
    </>
);
};

export default Corpo;

This is the component that receives the child, he has a box that will be passed as a father in the parameter of the son “Chapa”.

@brianzinn realized that you are the owner of this piece, so I am marking you.
Thanks again.

hi @JRobsonGomes I think you just need to have the parent component have children in it’s JSX. It’s a standard pattern in React, but you can also use multiple of these “slots”, which is how many layouts work.

Here is a babylon example where you declare the parent and then pass the children:

const ParentBox = (props) => (
  <box size={2} position={props.position}>
    {props.children}
  </box>
);

export const Example = () => (
  <div>
    <Engine ...>
      <Scene>
        <freeCamera ... />
        <hemisphericLight ... />
        <ParentBox position={new Vector3(-2, 0, 0)}>
          <standardMaterial diffuseColor={Color3.Red()} specularColor={Color3.Black()} />
        </ParentBox>
      </Scene>
    </Engine>
  </div>
)

Hi @brianzinn
I don’t know if I understood, but doing according to the React standard, the child Component is not positioned in the pivot of the parent component. I changed the transformation center of the father and son to the lower left corner, I expected the son to position himself there.
See my playground

How could I get the son to go to the father’s modified transformation center?

hi @JRobsonGomes - I don’t have good computer access until next month. Try asking in the forum without React and a playground and you’ll probably get an answer right away. You might be able to do a PreTransformMatrix, but I can’t visualize why it’s not working.

Hi @brianzinn.
I understand…
Maybe I should give up on implementing Babylon and React in my project, it has been very difficult for me as a beginner to convert everything to React, given that there are many things already ready in javascript vanilla that could be used without much work.
I appreciate your effort so far.
Thanks.

@Deltakosh do you know anyone who could help me in this matter? Thank you very much in advance.

hi @JRobsonGomes - just back now from dropping off the face of the Earth. I think that learning React and Babylon at the same time is a really tall order. I had already made a couple of games with babylon 2.5 and built a trading platform in React before combining the two. The easiest learning path is most certainly to build in vanilla js/ts. The benefits of React are having a state management tie into DOM (like you are doing with sliders) and especially on declarative GUI.

When you start getting into (pre)transformations that is already difficult without React. That is why I was suggesting to ask a general non-React question to get a better answer from the community. You may be able to, for example, solve the issue better with a parent TransformNode. The ordering of transforms/rotates comes into play and can cause confusion (at least for me - look at my 3D progress bar playgrounds!). I would definitely suggest using the Inspector’s Scene Explorer to make debugging and experimenting a bit more fun. I didn’t have a proper way to test what you had made before - Hope you got it figured out.

1 Like

Hi @brianzinn,
Unfortunately I couldn’t solve it.
I chose to use React and Babylon, precisely because of the ease of integrating the controls. As my intention was to make furniture that is basically nested boxes, I didn’t think it would be that difficult. But I was mistaken.
I’ll try it with javascript vanilla, but I won’t have the same facility to create controls as in React.
Anyway thank you.

1 Like

You might be able to solve with parenting and positioning (which is then local to parent). If you use setParent() then it will take current position. I would recommend making a vanilla playground to illustrate the underlying problem you are trying to solve for and hopefully there is an easier solution. The community can be really helpful and can help easier with that as a starting point. If you can avoid pivot matrices would make it easier :slight_smile:

There are some helpful visualization tools like Gizmo and Scene Explorer.

Hi. I am not sure if I understood what is going on here exactly, but as I understand you come from vanilla background and want to use React. Now, I am react beginner myself and there might be better ways to do this, but I just built an entire app using Babylonjs and React, and I am really satisfied with the mix of power they both give you.

Wouldn’t be easer to just call a function when scene is created, and what you get from onMountScene is scene reference which you can then use in normal vanilla functions As you can see, I am even using assetsManager there to get and control all of my models. Having “scene” reference basically gives you everything you need, meaning you can catch your models by whichever method you want and parent them with whichever method you would usually do that.

Yes, this is what I’m trying to do on my playground.
<Chapa largura={profundidade} comprimento={altura} espessura={0.015} posX={0} posY={0} posZ={0} rotY={-2} pai={box.current} />
And in the “Chapa” component it has the property setParent () property that takes {box.current} as a parameter.

But he is not setting his father, i know that when that happens the son takes the father’s position, but it is not happening, the son seems to float and does not accompany the father.

@nogalo thanks for the example, I used something similar to change the position of the pivot on my playground.
onCreated={box => alterararCriacao(box)}
But I was unable to do setParent (parent) in the function onCreated = {(box) => alterarCriacao (box)}

@JRobsonGomes pai is undefined in the render, so will not set parent - at least not till a re-render is triggered and your playground doesn’t.

Here is a useful snippet to include an Inspector that shows parent/child relationships in Scene Explorer Inspector.tsx:

import React, { FC } from 'react';
import '@babylonjs/inspector';
import { useScene } from 'react-babylonjs';

type InspectorProps = {
  show: boolean
}

const Inspector: FC<InspectorProps> = ({show}) => {
  const scene = useScene();
  if (show) {
    scene!.debugLayer.show();
  } else {
    scene!.debugLayer.hide();
  }
  return null;
}

export default Inspector;

Use it like this:

import Inspector from "./Inspector";
// anywhere *inside* <Scene>
<Inspector show />

What you will see is that even if you remove your parent={pai} and setParent={pai} in Chapa.tsx that it is parenting from the meshes being nested. The lifecycle of the mesh adds is after the initial properties - I’d have to debug to see for sure. I wasn’t saying that you weren’t parenting also, I was suggesting that there may be a solution without pivot matrix depending on what you are trying to accomplish.