Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi-env-map #265

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions src/misc/multi-env-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* @param {Array<THREE.Material>|THREE.Material} material
* @return {Array<THREE.Material>}
*/
function ensureMaterialArray (material) {
if (!material) {
return [];
} else if (Array.isArray(material)) {
return material;
} else if (material.materials) {
return material.materials;
} else {
return [material];
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm only aware of two possibilities here, and would expect this to be enough:

return Array.isArray( material ) ? material : [ material ];

Do you know of situations where a mesh might not have a material, or where material.materials might come from?


/**
* @param {THREE.Object3D} mesh
* @param {Array<string>} materialNames
* @param {THREE.Texture} envMap
* @param {number} reflectivity [description]
*/
function applyEnvMap (mesh, materialNames, envMap, reflectivity) {
if (!mesh) return;

materialNames = materialNames || [];

mesh.traverse((node) => {

if (!node.isMesh) return;

const meshMaterials = ensureMaterialArray(node.material);

meshMaterials.forEach((material) => {

if (material && !('envMap' in material)) return;
if (materialNames.length && materialNames.indexOf(material.name) === -1) return;

material.envMap = envMap;
material.reflectivity = reflectivity;
material.needsUpdate = true;

});

});
}

/**
* Specifies an envMap on an entity, without replacing any existing material
* properties.
*/
AFRAME.registerComponent('multi-env-map', {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's just name this env-map, and then we can remove the other component. :)

multiple: true,

schema: {
path: {default: ''},
extension: {default: 'jpg', oneOf: ['jpg', 'png']},
type: {default: 'cube', oneOf: ['cube', 'equirectangular']},
fileName: {default: 'equirectangular'},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't mind, let's condense all of this into one property:

src: {default: []}

... where the cube type would require six (complete) URLs in set order, and the equirectangular type would require exactly one:

<a-entity env-map="src: env/posx.png, env/negx.png, env/posy.png, ..." />

I think that will be simpler overall.

mapping: {default: 'reflection', oneOf: ['reflection', 'refraction']},
format: {default: 'RGBFormat', oneOf: ['RGBFormat', 'RGBAFormat']},
enableBackground: {default: false},
reflectivity: {default: 1, min: 0, max: 1},
materials: {default: []}
},

init: function () {
const data = this.data;

if (data.type == 'cube') {
this.texture = new THREE.CubeTextureLoader().load([
data.path + 'posx.' + data.extension, data.path + 'negx.' + data.extension,
data.path + 'posy.' + data.extension, data.path + 'negy.' + data.extension,
data.path + 'posz.' + data.extension, data.path + 'negz.' + data.extension
]);
}
else if (data.type == 'equirectangular') {
this.texture = new THREE.TextureLoader().load( data.path + data.fileName + '.' + data.extension );
this.texture.magFilter = THREE.LinearFilter;
this.texture.minFilter = THREE.LinearMipMapLinearFilter;
if (data.mapping == 'reflection') {
this.texture.mapping = THREE.EquirectangularReflectionMapping;
}
else if (data.mapping == 'refraction') {
this.texture.mapping = THREE.EquirectangularRefractionMapping;
}
}
this.texture.format = THREE[data.format];

this.object3dsetHandler = () => {
const mesh = this.el.getObject3D('mesh');
const data = this.data;
applyEnvMap(mesh, data.materials, this.texture, data.reflectivity);
};
this.el.addEventListener('object3dset', this.object3dsetHandler);
},

update: function (oldData) {
const data = this.data;
const mesh = this.el.getObject3D('mesh');

let addedMaterialNames = [];
let removedMaterialNames = [];

if (data.materials.length) {
if (oldData.materials) {
addedMaterialNames = data.materials.filter((name) => !oldData.materials.includes(name));
removedMaterialNames = oldData.materials.filter((name) => !data.materials.includes(name));
} else {
addedMaterialNames = data.materials;
}
}
if (addedMaterialNames.length) {
applyEnvMap(mesh, addedMaterialNames, this.texture, data.reflectivity);
}
if (removedMaterialNames.length) {
applyEnvMap(mesh, removedMaterialNames, null, 1);
}

if (oldData.materials && data.reflectivity !== oldData.reflectivity) {
const maintainedMaterialNames = data.materials
.filter((name) => oldData.materials.includes(name));
if (maintainedMaterialNames.length) {
applyEnvMap(mesh, maintainedMaterialNames, this.texture, data.reflectivity);
}
}

if (this.data.enableBackground && !oldData.enableBackground) {
this.setBackground(this.texture);
} else if (!this.data.enableBackground && oldData.enableBackground) {
this.setBackground(null);
}
},

remove: function () {
this.el.removeEventListener('object3dset', this.object3dsetHandler);
const mesh = this.el.getObject3D('mesh');
const data = this.data;

applyEnvMap(mesh, data.materials, null, 1);
if (data.enableBackground) this.setBackground(null);
},

setBackground: function (texture) {
this.el.sceneEl.object3D.background = texture;
}
});