Logically speaking, the current implementation does not make much sense.
object.material for single material
object.material.materials[n] for multi material
The correct implementation (used in almost any 3D engine) is the following:
object.material[0] for single material
and
object.material[0]
object.material[1]
...
object.material[n] for multi material
object.material[0] for single material
Yes, I think, it is a good idea.
So for material sorting, you won't need extra condition:
(Now)
if(object.material instanceof THREE.MultiMaterial){
object.material.materials.forEach(function(material){
foo(material);
});
}else{
foo(object.material)
}
(Can be)
object.material.forEach(function(material){
foo(material);
});
//or
for(var i = 0; i < object.material.length; i++){
foo(object.material[i];
}
The correct implementation (used in almost any 3D engine) is the following:
Can you share links to the relevant page for those other engines?
I don't have a ton of experience with different engines, but I can speak specifically for Unreal Engine and Unity - they both utilize a simple array of materials on a mesh - with index 0 being "single", and more indices show up the more material face ids are located on the mesh.
Unreal Engine and Unity - they both utilize a simple array of materials on a mesh - with index 0 being "single"
That's right.
All the 3D engines i know use a similar implementation.
material = shader
object.shader = object.shader[0] - for single material
object.shader[0], object.shader[1], ..., object.shader[n] - for multi material
Again, in Three.js object.material.materials[n] does not make any sense.
I can't remember why we decided to use MultiMaterial. But, @mrdumb... This would break backwards compatibility.
I can't think of a way of displaying a warning/error if the user wrote something as basic as this:
mesh.material.color.setHex( 0xff0000 );
That won't work, because now material is an Array.
How about this pattern?
THREE.Mesh = function ( geometry, material ) {
THREE.Object3D.call( this );
this.type = 'Mesh';
this.geometry = geometry !== undefined ? geometry : new THREE.BufferGeometry();
if ( material instanceof THREE.Material ) {
this.materials = [ material ];
} else if ( material instanceof Array ) {
this.materials = material;
} else {
this.materials = [ new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ) ];
}
this.drawMode = THREE.TrianglesDrawMode;
this.updateMorphTargets();
Object.defineProperties( this, {
material: {
enumerable: true,
get: function() {
return this.materials[ 0 ];
},
set: function( value ) {
this.materials[ 0 ] = value;
}
}
} );
};
That could be an option. Having mesh.material and mesh.materials sounds like it can bring problems though...
We also lose MultiMaterial's .visible but I guess that's ok.
But, @mrdumb... This would break backwards compatibility.
Glad to hear that is important to you.
I can't think of a way of displaying a warning/error if the user wrote something as basic as this:
mesh.material.color.setHex( 0xff0000 );
That won't work, because now material is an Array.
As I said before, for many 3D engines:
mesh.material = mesh.material[0]
And @WestLangley 's idea is a good one.
I guess the check should then be...
if ( material instanceof THREE.MultiMaterial ) {
// Deprecated
this.materials = material.materials;
} else if ( material instanceof THREE.Material ) {
this.materials = [ material ];
} else if ( material instanceof Array ) {
this.materials = material;
} else {
this.materials = [ new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ) ];
}
And then we should also add a deprecated message to THREE.MultiMaterial's constructor...
I guess the check should then be...
It looks good to me.
I guess we can close this...
@WestLangley I think I'll implement your pattern. Otherwise, as seen in #10960, we get Array.isArray() all over the place.
Maybe that's ok though?
@gero3 what do you about "moving" to geometry.materials and avoid doing the Array.isArray()?
@mrdoob I can be happy with either my proposal above or your current implementation.
@WestLangley I think I'll try your approach tonight and see how it feels.
Ok, so I gave a it a go... Unfortunately, this is the code that we have for rendering multi materials:
https://github.com/mrdoob/three.js/blob/dev/src/renderers/WebGLRenderer.js#L1365-L1378
With that code, geometry groups mandates how materials are used. Which means that, if we switch to materials only, BoxGeometry could no longer be rendered with a single material because we generate groups for each side by default.
This is how webgl_animation_cloth ends up:

Yep. Well, what you have implemented is acceptable. The only downsides to it that I can see are:
isArray() tests have proliferated.@gero3 what do you about "moving" to geometry.materials and avoid doing the Array.isArray()?
I don't mind.
mesh.material is singular
could you not run Object.defineProperty to add materialS as well, with [0] pointing to single material, etc