Skip to content

Commit

Permalink
Move envMap loading into material utils and system (#5454)
Browse files Browse the repository at this point in the history
Co-authored-by: Noeri Huisman <[email protected]>
  • Loading branch information
mrxz and mrxz authored Feb 9, 2024
1 parent 59176dc commit e0dddcd
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 171 deletions.
13 changes: 8 additions & 5 deletions src/core/a-cubemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var debug = require('../utils/debug');
var warn = debug('core:cubemap:warn');

/**
* Cubemap element that handles validation and exposes list of URLs.
* Cubemap element that handles validation and exposes list of six image sources (URL or <img>).
* Does not listen to updates.
*/
class ACubeMap extends HTMLElement {
Expand Down Expand Up @@ -38,18 +38,21 @@ class ACubeMap extends HTMLElement {

/**
* Checks for exactly six elements with [src].
* Does not check explicitly for <img>s in case user does not want
* prefetching.
* When <img>s are used they will be prefetched.
*
* @returns {Array|null} - six URLs if valid, else null.
* @returns {Array|null} - six URLs or <img> elements if valid, else null.
*/
validate () {
var elements = this.querySelectorAll('[src]');
var i;
var srcs = [];
if (elements.length === 6) {
for (i = 0; i < elements.length; i++) {
srcs.push(elements[i].getAttribute('src'));
if (elements[i].tagName === 'IMG') {
srcs.push(elements[i]);
} else {
srcs.push(elements[i].getAttribute('src'));
}
}
return srcs;
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/propertyTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var warn = debug('core:propertyTypes:warn');

var propertyTypes = module.exports.propertyTypes = {};
var nonCharRegex = /[,> .[\]:]/;
var urlRegex = /\url\((.+)\)/;
var urlRegex = /url\((.+)\)/;

// Built-in property types.
registerPropertyType('audio', '', assetParse);
Expand Down
84 changes: 12 additions & 72 deletions src/shaders/phong.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ var registerShader = require('../core/shader').registerShader;
var THREE = require('../lib/three');
var utils = require('../utils/');

var CubeLoader = new THREE.CubeTextureLoader();
var texturePromises = {};

/**
* Phong shader using THREE.MeshPhongMaterial.
*/
Expand Down Expand Up @@ -55,6 +52,16 @@ module.exports.Shader = registerShader('phong', {
this.materialData = { color: new THREE.Color(), specular: new THREE.Color(), emissive: new THREE.Color() };
getMaterialData(data, this.materialData);
this.material = new THREE.MeshPhongMaterial(this.materialData);
var sceneEl = this.el.sceneEl;
// Fallback to scene environment when no envMap is defined (matching behaviour of standard material)
Object.defineProperty(this.material, 'envMap', {
get: function () {
return this._envMap || sceneEl.object3D.environment;
},
set: function (value) {
this._envMap = value;
}
});
},

update: function (data) {
Expand All @@ -64,7 +71,7 @@ module.exports.Shader = registerShader('phong', {
utils.material.updateDistortionMap('displacement', this, data);
utils.material.updateDistortionMap('ambientOcclusion', this, data);
utils.material.updateDistortionMap('bump', this, data);
this.updateEnvMap(data);
utils.material.updateEnvMap(this, data);
},

/**
Expand All @@ -78,73 +85,6 @@ module.exports.Shader = registerShader('phong', {
for (key in this.materialData) {
this.material[key] = this.materialData[key];
}
},

/**
* Handle environment cubemap. Textures are cached in texturePromises.
*/
updateEnvMap: function (data) {
var self = this;
var material = this.material;
var envMap = data.envMap;
var sphericalEnvMap = data.sphericalEnvMap;
var refract = data.refract;
var sceneEl = this.el.sceneEl;

// No envMap defined or already loading.
if ((!envMap && !sphericalEnvMap) || this.isLoadingEnvMap) {
Object.defineProperty(material, 'envMap', {
get: function () {
return sceneEl.object3D.environment;
},
set: function (value) {
delete this.envMap;
this.envMap = value;
}
});
material.needsUpdate = true;
return;
}
this.isLoadingEnvMap = true;
delete material.envMap;

// if a spherical env map is defined then use it.
if (sphericalEnvMap) {
this.el.sceneEl.systems.material.loadTexture(sphericalEnvMap, { src: sphericalEnvMap }, function textureLoaded (texture) {
self.isLoadingEnvMap = false;
texture.mapping = refract ? THREE.EquirectangularRefractionMapping : THREE.EquirectangularReflectionMapping;

material.envMap = texture;
utils.material.handleTextureEvents(self.el, texture);
material.needsUpdate = true;
});
return;
}

// Another material is already loading this texture. Wait on promise.
if (texturePromises[envMap]) {
texturePromises[envMap].then(function (cube) {
self.isLoadingEnvMap = false;
material.envMap = cube;
utils.material.handleTextureEvents(self.el, cube);
material.needsUpdate = true;
});
return;
}

// Material is first to load this texture. Load and resolve texture.
texturePromises[envMap] = new Promise(function (resolve) {
utils.srcLoader.validateCubemapSrc(envMap, function loadEnvMap (urls) {
CubeLoader.load(urls, function (cube) {
// Texture loaded.
self.isLoadingEnvMap = false;
material.envMap = cube;
cube.mapping = refract ? THREE.CubeRefractionMapping : THREE.CubeReflectionMapping;
utils.material.handleTextureEvents(self.el, cube);
resolve(cube);
});
});
});
}
});

Expand Down Expand Up @@ -192,7 +132,7 @@ function getMaterialData (data, materialData) {
}

if (data.bumpMap) {
materialData.aoMapIntensity = data.bumpMapScale;
materialData.bumpScale = data.bumpMapScale;
}

if (data.displacementMap) {
Expand Down
59 changes: 1 addition & 58 deletions src/shaders/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ var registerShader = require('../core/shader').registerShader;
var THREE = require('../lib/three');
var utils = require('../utils/');

var CubeLoader = new THREE.CubeTextureLoader();
var texturePromises = {};

/**
* Standard (physically-based) shader using THREE.MeshStandardMaterial.
*/
Expand Down Expand Up @@ -74,7 +71,7 @@ module.exports.Shader = registerShader('standard', {
utils.material.updateDistortionMap('ambientOcclusion', this, data);
utils.material.updateDistortionMap('metalness', this, data);
utils.material.updateDistortionMap('roughness', this, data);
this.updateEnvMap(data);
utils.material.updateEnvMap(this, data);
},

/**
Expand All @@ -90,60 +87,6 @@ module.exports.Shader = registerShader('standard', {
for (key in this.materialData) {
material[key] = this.materialData[key];
}
},

/**
* Handle environment cubemap. Textures are cached in texturePromises.
*/
updateEnvMap: function (data) {
var self = this;
var material = this.material;
var envMap = data.envMap;
var sphericalEnvMap = data.sphericalEnvMap;

// No envMap defined or already loading.
if ((!envMap && !sphericalEnvMap) || this.isLoadingEnvMap) {
material.envMap = null;
material.needsUpdate = true;
return;
}
this.isLoadingEnvMap = true;

// if a spherical env map is defined then use it.
if (sphericalEnvMap) {
this.el.sceneEl.systems.material.loadTexture(sphericalEnvMap, {src: sphericalEnvMap}, function textureLoaded (texture) {
self.isLoadingEnvMap = false;
texture.mapping = THREE.EquirectangularReflectionMapping;
material.envMap = texture;
utils.material.handleTextureEvents(self.el, texture);
material.needsUpdate = true;
});
return;
}

// Another material is already loading this texture. Wait on promise.
if (texturePromises[envMap]) {
texturePromises[envMap].then(function (cube) {
self.isLoadingEnvMap = false;
material.envMap = cube;
utils.material.handleTextureEvents(self.el, cube);
material.needsUpdate = true;
});
return;
}

// Material is first to load this texture. Load and resolve texture.
texturePromises[envMap] = new Promise(function (resolve) {
utils.srcLoader.validateCubemapSrc(envMap, function loadEnvMap (urls) {
CubeLoader.load(urls, function (cube) {
// Texture loaded.
self.isLoadingEnvMap = false;
material.envMap = cube;
utils.material.handleTextureEvents(self.el, cube);
resolve(cube);
});
});
});
}
});

Expand Down
33 changes: 33 additions & 0 deletions src/systems/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,39 @@ module.exports.System = registerSystem('material', {
function loadVideoCb (src) { self.loadVideo(src, data, cb); }
},

/**
* Load the six individual sides and construct a cube texture, then call back.
*
* @param {Array} srcs - Array of six texture URLs or elements.
* @param {function} cb - Callback to pass cube texture to.
*/
loadCubeMapTexture: function (srcs, cb) {
var self = this;
var loaded = 0;
var cube = new THREE.CubeTexture();
cube.colorSpace = THREE.SRGBColorSpace;

function loadSide (index) {
self.loadTexture(srcs[index], {src: srcs[index]}, function (texture) {
cube.images[index] = texture.image;
loaded++;
if (loaded === 6) {
cube.needsUpdate = true;
cb(cube);
}
});
}

if (srcs.length !== 6) {
warn('Cube map texture requires exactly 6 sources, got only %s sources', srcs.length);
return;
}

for (var i = 0; i < srcs.length; i++) {
loadSide(i);
}
},

/**
* High-level function for loading image textures (THREE.Texture).
*
Expand Down
71 changes: 71 additions & 0 deletions src/utils/material.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
var THREE = require('../lib/three');
var srcLoader = require('./src-loader');
var debug = require('./debug');
var warn = debug('utils:material:warn');

var COLOR_MAPS = new Set([
'emissiveMap',
Expand Down Expand Up @@ -148,6 +151,74 @@ module.exports.updateDistortionMap = function (longType, shader, data) {
return module.exports.updateMapMaterialFromData(shortType + 'Map', 'src', shader, info);
};

// Cache env map results as promises
var envMapPromises = {};

/**
* Updates the material's environment map providing reflections or refractions.
*
* @param {object} shader - A-Frame shader instance
* @param {object} data
*/
module.exports.updateEnvMap = function (shader, data) {
var material = shader.material;
var el = shader.el;
var materialName = 'envMap';
var src = data.envMap;
var sphericalEnvMap = data.sphericalEnvMap;
var refract = data.refract;

if (sphericalEnvMap) {
src = sphericalEnvMap;
warn('`sphericalEnvMap` property is deprecated, using spherical map as equirectangular map instead. ' +
'Use `envMap` property with a CubeMap or Equirectangular image instead.');
}

if (!shader.materialSrcs) { shader.materialSrcs = {}; }

// EnvMap has been removed
if (!src) {
// Forget the prior material src.
delete shader.materialSrcs[materialName];
material.envMap = null;
material.needsUpdate = true;
return;
}

// Remember the new src for this env map.
shader.materialSrcs[materialName] = src;

// Env map is already loading. Wait on promise.
if (envMapPromises[src]) {
envMapPromises[src].then(checkSetMap);
return;
}

// First time loading this env map.
envMapPromises[src] = new Promise(function (resolve) {
srcLoader.validateEnvMapSrc(src, function loadCubeMap (srcs) {
el.sceneEl.systems.material.loadCubeMapTexture(srcs, function (texture) {
texture.mapping = refract ? THREE.CubeRefractionMapping : THREE.CubeReflectionMapping;
checkSetMap(texture);
resolve(texture);
});
}, function loadEquirectMap (src) {
el.sceneEl.systems.material.loadTexture(src, {src: src}, function (texture) {
texture.mapping = refract ? THREE.EquirectangularRefractionMapping : THREE.EquirectangularReflectionMapping;
checkSetMap(texture);
resolve(texture);
});
});
});

function checkSetMap (texture) {
if (shader.materialSrcs[materialName] !== src) { return; }
material.envMap = texture;
material.needsUpdate = true;
handleTextureEvents(el, texture);
}
};

/**
* Emit event on entities on texture-related events.
*
Expand Down
Loading

0 comments on commit e0dddcd

Please sign in to comment.