brianzinn / react-babylonjs

React for Babylon 3D engine
https://brianzinn.github.io/react-babylonjs/
817 stars 105 forks source link

Custom Mesh from Array #95

Closed thipokch closed 3 years ago

thipokch commented 3 years ago

I am using react-babylonjs as a viewport for a modeling library and I am required to build a custom mesh from scratch (vertices, indices, and normals) and I've succeeded connect it with Babylon (without react-babylonjs and react). However, I've been pulling my hair out recreating it again inside react. The scene came up empty. Here's the relevant tutorial: BabylonJS — Custom Mesh Tutorial

without react-babylonjs and react

var customMesh = new BABYLON.Mesh("custom", scene);

var positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3];
var indices = [0, 1, 2, 3, 4, 5];    

//Empty array to contain calculated values or normals added
var normals = [];

//Calculations of normals added
BABYLON.VertexData.ComputeNormals(positions, indices, normals);

var vertexData = new BABYLON.VertexData();

vertexData.positions = positions;
vertexData.indices = indices;
vertexData.normals = normals; //Assignment of normal to vertexData added

vertexData.applyToMesh(customMesh);

with react-babylonjs and react

render() {
    return (
      <Engine canvasId="bCanvas" debug="true">
        <Scene>
          <arcRotateCamera name="arc" target={new Vector3(0, 1, 0)} minZ={0.001}
          alpha={-Math.PI / 2} beta={(0.5 + (Math.PI / 4))} radius={2} />
          <hemisphericLight name="light1" intensity={0.7} direction={Vector3.Up()}/>
          <CustomMesh/>
          <sphere name="sphere1" diameter={0.1} segments={16} position={new Vector3(0, 1, 0)}/>
         <ground name="ground1" width={6} height={6} subdivisions={2}/>
        </Scene>
      </Engine>
    )
 }

const Mesh = (props) => {

  const customMesh = new Mesh("custom");

  const positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3];
  const indices = [0, 1, 2, 3, 4, 5];
  const normals = [];

  const vertexData = new VertexData();
  VertexData.ComputeNormals(positions, indices, normals);

  vertexData.positions = positions;
  vertexData.indices = indices;
  vertexData.normals = normals; 

  vertexData.applyToMesh(customMesh);
  return (
      <mesh name="custom" fromInstance={customMesh} position={new Vector3(0,0,0)}>
  )
}

Thanks! Poom

brianzinn commented 3 years ago

Hi Poom! Thanks for the question. I think you just need to pass the scene into your Mesh constructor in your component (you can get that from a hook). I changed your component a bit to also dispose and pass name/position to make it reusable. Hope this helps:

const CustomMesh = (props) => {
  const scene = useBabylonScene();

  const [customMesh] = useState(() => {
    const meshInstance = new Mesh(props.name, scene);

    //Set arrays for positions and indices
    var positions = [-5, 2, -3, -7, -2, -3, -3, -2, -3, 5, 2, 3, 7, -2, 3, 3, -2, 3];
    var indices = [0, 1, 2, 3, 4, 5];   
    var colors = [1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0,  0, 1, 0, 1];

    //Empty array to contain calculated values
    var normals = [];

    var vertexData = new VertexData();
    VertexData.ComputeNormals(positions, indices, normals);

    //Assign positions, indices and normals to vertexData
    vertexData.positions = positions;
    vertexData.indices = indices;
    vertexData.normals = normals;
    vertexData.colors = colors;

    //Apply vertexData to custom mesh
    vertexData.applyToMesh(meshInstance);

    return meshInstance;
  })

  useEffect(() => {
    return () => {
      customMesh.dispose();
    }
  }, [])

  return (
      <mesh fromInstance={customMesh} position={props.position}>
        <standardMaterial name={`${props.name}-mat`} />
      </mesh>
  )
}

export const Testing = () => (
  <div style={{ flex: 1, display: 'flex' }}>
    <Engine antialias adaptToDeviceRatio canvasId='babylonJS' >
      <Scene>
      <arcRotateCamera
          name="camera1"
          target={Vector3.Zero()}
          alpha={Math.PI / 2}
          beta={Math.PI / 2}
          radius={20}
        />
        <hemisphericLight name='light1' intensity={0.7} direction={Vector3.Up()} />

        <CustomMesh name='custom-0' position={new Vector3(0, 0, 0)} />
        <CustomMesh name='custom-2' position={new Vector3(0, 2, 0)} />
        <CustomMesh name='custom-4' position={new Vector3(0, 4, 0)} />
      </Scene>
    </Engine>
  </div>
)

Happy Holidays! image

If you add wireframe to the material you end up with what your playground was showing:

<standardMaterial name={`${props.name}-mat`} wireframe />

image

brianzinn commented 3 years ago

@thipokch actually, I think dispose should be called automatically, so you don't need that useEffect :)

brianzinn commented 3 years ago

That dispose isn't called automatically anymore on v3, so you can keep that effect or add a new prop to the <mesh ... disposeInstanceOnUnmount />. This issue is on storybook, so if you have any questions we can use that as a starting point. Cheers.

PS: breaking changes documentation for 3.0 https://github.com/brianzinn/react-babylonjs/blob/master/docs/breaking-changes-2.x-to-3.0.md#frominstance-declared-objects-to-not-automatically-dispose-but-you-can-opt-in