Search code examples
three.jsaframe

How do I set different materials to different groups of indexed faces in a customized mesh in A-Frame?


I'm trying to register a custom geometry in A-Frame using THREE.BufferGeometry using two or more indexed groups of faces, because many vertices will be shared, and I want to change the mesh AFTER creation, WITHOUT having to move multiple points and worry about holes appearing in the model. I want several groups of faces, and I want to assign a different material to EACH GROUP.

I know it might be easier to make nested groups of entities with different materials, but I am trying to save CPU and GPU time, as well as memory.

ALL the old examples I could find online used THREE.Geometry, NOT THREE.BufferGeometry. None are working with the current versions, as THREE.Geometry has been depreciated.

I have made eight or nine variations based on the new formatting info I found in the online docs. I even tried 20+ new versions of the code with the "help" of ChatGPT.

NONE of it's code worked either.

enter image description here

AFRAME.registerGeometry('my-custom-geometry', {
  init: function () {
// Define the vertices of the geometry
const vertices = [
  // First set of five shared points
  -1, 1, 0,
  -1, -1, 0,
  0, -1, 0,
  1, -1, 0,
  1, 1, 0,

  // Second set of five shared points
  -1, 1, 1,
  -1, -1, 1,
  0, -1, 1,
  1, -1, 1,
  1, 1, 1,
];

// Define the indices of the faces
const indices = [
  // Group 1
  0, 1, 2,
  2, 3, 4,
  0, 2, 4,

  // Group 2
  5, 6, 7,
  7, 8, 9,
  5, 7, 9,
];

  // Create a buffer geometry object
  const geometry = new THREE.BufferGeometry();

// Set the attributes of the buffer geometry
const positionAttribute = new THREE.Float32BufferAttribute(vertices, 3);
geometry.setAttribute('position', positionAttribute);

const indexAttribute = new THREE.Uint16BufferAttribute(indices, 1);
geometry.setIndex(indexAttribute);

// Define the groups of faces
geometry.addGroup(0, 3, 0); // Group 1, material 1
geometry.addGroup(3, 3, 1); // Group 1, material 2
geometry.addGroup(6, 3, 2); // Group 1, material 3
geometry.addGroup(9, 3, 3); // Group 2, material 4
geometry.addGroup(12, 3, 4); // Group 2, material 5
geometry.addGroup(15, 3, 5); // Group 2, material 6

// Register the geometry with A-Frame
this.geometry = geometry;
  }
});

// Define the materials for the groups of faces
const materials = [
  new THREE.MeshBasicMaterial({ color: 'red' }),
  new THREE.MeshBasicMaterial({ color: 'green' }),
  new THREE.MeshBasicMaterial({ color: 'blue' }),
  new THREE.MeshBasicMaterial({ color: 'yellow' }),
  new THREE.MeshBasicMaterial({ color: 'purple' }),
  new THREE.MeshBasicMaterial({ color: 'orange' }),
];

// Create a mesh object with the registered geometry and materials
const mesh = new THREE.Mesh(
  AFRAME.scenes[0].systems.geometry.getGeometry('my-custom-geometry'),materials);

// Add the mesh object to the scene
AFRAME.scenes[0].object3D.add(mesh);

I ALSO tried the following:

AFRAME.registerGeometry('custom-geometry', {
  schema: {},
  init: function () {
var geometry = new THREE.BufferGeometry();

// Define vertices and indices
var vertices = [
  // Define vertices here
];
var indices = [
  // Define indices here
];

// Set vertex and index data
geometry.setIndex(indices);
geometry.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));

// Create two groups of faces
geometry.addGroup(0, 3, 0);  // Group 1: 3 faces, starting at index 0
geometry.addGroup(3, 3, 1);  // Group 2: 3 faces, starting at index 3

// Define materials for each group
var material1 = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var material2 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

// Create mesh and apply materials to groups
var mesh = new THREE.Mesh(geometry, [material1, material2]);
mesh.geometry.groups.forEach(function (group) {
  mesh.geometry.addGroup(group.start, group.count, group.materialIndex);
});

// Add mesh to A-Frame entity
this.geometry = mesh.geometry;
  }
});

Solution

  • The first code piece creates the groups correctly. If you want your mesh to work properly with lights, you need to:

    • compute the normals (otherwise it will be pitch black),
    • assign a PBR material like THREE.MeshStandardMaterial instead of the THREE.MeshBasicMaterial.
    • set casting and/or shadow receiving via mesh.castShadow = mesh.receiveShadow = true;
    • work out through plane-casting-shadows issues (wink, wink)

    Once you've done it - it should be working:

    // define colors for the groups
    const colors = ["red", "green", "blue", "yellow", "purple", "orange"];
    
    // create the mesh and append it to the scene when its ready
    const scene = document.querySelector("a-scene")
    scene.addEventListener("loaded", () => {
      const mesh = new THREE.Mesh(
        scene.systems.geometry.getOrCreateGeometry({
          "primitive": 'my-custom-geometry'
        }),
        colors.map(color => new THREE.MeshStandardMaterial({
          color,
          side: THREE.DoubleSide
        }))
      );
      mesh.position.set(0, 1, -4);
      mesh.rotation.set(0, Math.PI / 4, 0);
      mesh.castShadow = mesh.receiveShadow = true;
      scene.object3D.add(mesh);
    });
    
    // "fix" self - shadowing
    const dlight = document.querySelector("#dlight");
    dlight.addEventListener("loaded", () => {
      dlight.object3D.children[0].shadow.bias = -0.0001;
    })
    <script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
    <script>
      AFRAME.registerGeometry('my-custom-geometry', {
        init: function() {
          // Define the vertices of the geometry
          const vertices = [
            // First set of five shared points
            -1, 1, 0, -1, -1, 0,
            0, -1, 0,
            1, -1, 0,
            1, 1, 0,
    
            // Second set of five shared points
            -1, 1, 1, -1, -1, 1,
            0, -1, 1,
            1, -1, 1,
            1, 1, 1,
          ];
    
          // Define the indices of the faces
          const indices = [
            // Group 1
            0, 1, 2,
            2, 3, 4,
            0, 2, 4,
    
            // Group 2
            5, 6, 7,
            7, 8, 9,
            5, 7, 9,
          ];
    
          // Create a buffer geometry object
          const geometry = new THREE.BufferGeometry();
    
          // Set the attributes of the buffer geometry
          const positionAttribute = new THREE.Float32BufferAttribute(vertices, 3);
          geometry.setAttribute('position', positionAttribute);
    
          const indexAttribute = new THREE.Uint16BufferAttribute(indices, 1);
          geometry.setIndex(indexAttribute);
    
          // Define the groups of faces
          geometry.addGroup(0, 3, 0); // Group 1, material 1
          geometry.addGroup(3, 3, 1); // Group 1, material 2
          geometry.addGroup(6, 3, 2); // Group 1, material 3
          geometry.addGroup(9, 3, 3); // Group 2, material 4
          geometry.addGroup(12, 3, 4); // Group 2, material 5
          geometry.addGroup(15, 3, 5); // Group 2, material 6
          geometry.computeVertexNormals(); // compute normals
    
          // Register the geometry with A-Frame
          this.geometry = geometry;
        }
      });
    </script>
    <a-scene renderer="colorManagement: true">
      <a-entity position="-2 1.5 -2" animation="property: position; to: 2 1.5 -2; dir: alternate; loop: true; dur: 1500; easing: linear">
        <a-sphere radius="0.25" color="yellow" position="0 0 0">></a-sphere>
        <a-light type="point" cast-shadow="true" distance="3"></a-light>
      </a-entity>
      <a-entity id="dlight" light="type:directional; castShadow:true; intensity: 0.2" position="1 1 1"></a-entity>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow="cast: true; receive: true"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>