Search code examples
aframe

Is there a way I can add multiple material components to an entity in a-frame?


I am trying to write a component for a-frame that adds different materials to 'sides' of a model. So, one geometry with the groups set up in a way that allows for different materials with different properties on different sides.

I would like for the user of the component to be able to manipulate all the properties of each material to the extent that they can a single material (i.e. has all the default properties available to change declaratively, able to tweak in the inspector etc.)

Given that I would like for all the materials to behave just as the default one does, rather than reinvent the wheel by rewriting all the logic for a material component (e.g. figuring out if the 'src' is an image or video for textures and behaving accordingly etc.) I wondered if I could just add multiple instances of the default material component as part of my component.

Understandably, this seems to not be possible in the way I would expect to be able to add multiple components in a-frame (by appending an ID with double underscore to the component name.

For example, if I write this as part of my init function;

AFRAME.registerComponent('multimaterial', {

    init: function(){

        this.el.setAttribute('material__one', '')
        this.el.setAttribute('material__two', '')

    },

}) 

I get the following error

Uncaught (in promise) Error: Trying to initialize multiple components of type `material`. There can only be one component of this type per entity.

I think I can understand why this would have been defined far an out of the box geometry/material combination but is there a way that I could set the material component to `multiple: true' and then use it in my component?

Or do I need to branch that bit of the code? If so, could anyone explain to me the most straightforward way of doing that given the use case described above, it seems that the material component is defined in different parts of the code (by that I mean there is a core component material which pulls in a separate shader etc.

Any advice is much appreciated.


Solution

  • The material component just sets up a THREE.MeshBasicMaterial on a given geometry.

    If you want more, you need to dig into THREE.js - You can make an array of materials, and apply it to your geometry in a custom component:

    var materials = [new THREE.MeshBasicMaterial({
        color: 0xFF0000
    }), new THREE.MeshBasicMaterial({
        color: 0x00FF00
    })]
    
    this.el.getObject3D('mesh').material = materials;
    

    Check it out in action:

    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
    <script>
      AFRAME.registerComponent("foo", {
        init: function() {
          const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xaa0000, 0x00aa00, 0x0000aa]; // colors array
          const materials = colors.map(color => new THREE.MeshBasicMaterial({color: color})) // create an array of materials with different colors
          const mesh = this.el.getObject3D("mesh"); // grab the mesh reference
          const old_material = mesh.material; // grab the actual material - we need to get rid of it
          mesh.material = materials; // use the materials array as the new material
          old_material.dispose(); // get rid of the old material
        }
      })
    
    </script>
    <a-scene>
      <a-box position="-1 0.5 -3" foo></a-box>
    </a-scene>


    If you want to check what kind of source are you dealing with, src/utils/src-loader.js has a neat function which should help you in checking if the source is a video or an image:

    validateSrc(src, loadImageCallback, loadVideoCallback)