Skip to content

Commit e0dddcd

Browse files
authored
Move envMap loading into material utils and system (#5454)
Co-authored-by: Noeri Huisman <[email protected]>
1 parent 59176dc commit e0dddcd

File tree

10 files changed

+470
-171
lines changed

10 files changed

+470
-171
lines changed

src/core/a-cubemap.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var debug = require('../utils/debug');
44
var warn = debug('core:cubemap:warn');
55

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

3939
/**
4040
* Checks for exactly six elements with [src].
41-
* Does not check explicitly for <img>s in case user does not want
42-
* prefetching.
41+
* When <img>s are used they will be prefetched.
4342
*
44-
* @returns {Array|null} - six URLs if valid, else null.
43+
* @returns {Array|null} - six URLs or <img> elements if valid, else null.
4544
*/
4645
validate () {
4746
var elements = this.querySelectorAll('[src]');
4847
var i;
4948
var srcs = [];
5049
if (elements.length === 6) {
5150
for (i = 0; i < elements.length; i++) {
52-
srcs.push(elements[i].getAttribute('src'));
51+
if (elements[i].tagName === 'IMG') {
52+
srcs.push(elements[i]);
53+
} else {
54+
srcs.push(elements[i].getAttribute('src'));
55+
}
5356
}
5457
return srcs;
5558
}

src/core/propertyTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var warn = debug('core:propertyTypes:warn');
66

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

1111
// Built-in property types.
1212
registerPropertyType('audio', '', assetParse);

src/shaders/phong.js

Lines changed: 12 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ var registerShader = require('../core/shader').registerShader;
22
var THREE = require('../lib/three');
33
var utils = require('../utils/');
44

5-
var CubeLoader = new THREE.CubeTextureLoader();
6-
var texturePromises = {};
7-
85
/**
96
* Phong shader using THREE.MeshPhongMaterial.
107
*/
@@ -55,6 +52,16 @@ module.exports.Shader = registerShader('phong', {
5552
this.materialData = { color: new THREE.Color(), specular: new THREE.Color(), emissive: new THREE.Color() };
5653
getMaterialData(data, this.materialData);
5754
this.material = new THREE.MeshPhongMaterial(this.materialData);
55+
var sceneEl = this.el.sceneEl;
56+
// Fallback to scene environment when no envMap is defined (matching behaviour of standard material)
57+
Object.defineProperty(this.material, 'envMap', {
58+
get: function () {
59+
return this._envMap || sceneEl.object3D.environment;
60+
},
61+
set: function (value) {
62+
this._envMap = value;
63+
}
64+
});
5865
},
5966

6067
update: function (data) {
@@ -64,7 +71,7 @@ module.exports.Shader = registerShader('phong', {
6471
utils.material.updateDistortionMap('displacement', this, data);
6572
utils.material.updateDistortionMap('ambientOcclusion', this, data);
6673
utils.material.updateDistortionMap('bump', this, data);
67-
this.updateEnvMap(data);
74+
utils.material.updateEnvMap(this, data);
6875
},
6976

7077
/**
@@ -78,73 +85,6 @@ module.exports.Shader = registerShader('phong', {
7885
for (key in this.materialData) {
7986
this.material[key] = this.materialData[key];
8087
}
81-
},
82-
83-
/**
84-
* Handle environment cubemap. Textures are cached in texturePromises.
85-
*/
86-
updateEnvMap: function (data) {
87-
var self = this;
88-
var material = this.material;
89-
var envMap = data.envMap;
90-
var sphericalEnvMap = data.sphericalEnvMap;
91-
var refract = data.refract;
92-
var sceneEl = this.el.sceneEl;
93-
94-
// No envMap defined or already loading.
95-
if ((!envMap && !sphericalEnvMap) || this.isLoadingEnvMap) {
96-
Object.defineProperty(material, 'envMap', {
97-
get: function () {
98-
return sceneEl.object3D.environment;
99-
},
100-
set: function (value) {
101-
delete this.envMap;
102-
this.envMap = value;
103-
}
104-
});
105-
material.needsUpdate = true;
106-
return;
107-
}
108-
this.isLoadingEnvMap = true;
109-
delete material.envMap;
110-
111-
// if a spherical env map is defined then use it.
112-
if (sphericalEnvMap) {
113-
this.el.sceneEl.systems.material.loadTexture(sphericalEnvMap, { src: sphericalEnvMap }, function textureLoaded (texture) {
114-
self.isLoadingEnvMap = false;
115-
texture.mapping = refract ? THREE.EquirectangularRefractionMapping : THREE.EquirectangularReflectionMapping;
116-
117-
material.envMap = texture;
118-
utils.material.handleTextureEvents(self.el, texture);
119-
material.needsUpdate = true;
120-
});
121-
return;
122-
}
123-
124-
// Another material is already loading this texture. Wait on promise.
125-
if (texturePromises[envMap]) {
126-
texturePromises[envMap].then(function (cube) {
127-
self.isLoadingEnvMap = false;
128-
material.envMap = cube;
129-
utils.material.handleTextureEvents(self.el, cube);
130-
material.needsUpdate = true;
131-
});
132-
return;
133-
}
134-
135-
// Material is first to load this texture. Load and resolve texture.
136-
texturePromises[envMap] = new Promise(function (resolve) {
137-
utils.srcLoader.validateCubemapSrc(envMap, function loadEnvMap (urls) {
138-
CubeLoader.load(urls, function (cube) {
139-
// Texture loaded.
140-
self.isLoadingEnvMap = false;
141-
material.envMap = cube;
142-
cube.mapping = refract ? THREE.CubeRefractionMapping : THREE.CubeReflectionMapping;
143-
utils.material.handleTextureEvents(self.el, cube);
144-
resolve(cube);
145-
});
146-
});
147-
});
14888
}
14989
});
15090

@@ -192,7 +132,7 @@ function getMaterialData (data, materialData) {
192132
}
193133

194134
if (data.bumpMap) {
195-
materialData.aoMapIntensity = data.bumpMapScale;
135+
materialData.bumpScale = data.bumpMapScale;
196136
}
197137

198138
if (data.displacementMap) {

src/shaders/standard.js

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ var registerShader = require('../core/shader').registerShader;
22
var THREE = require('../lib/three');
33
var utils = require('../utils/');
44

5-
var CubeLoader = new THREE.CubeTextureLoader();
6-
var texturePromises = {};
7-
85
/**
96
* Standard (physically-based) shader using THREE.MeshStandardMaterial.
107
*/
@@ -74,7 +71,7 @@ module.exports.Shader = registerShader('standard', {
7471
utils.material.updateDistortionMap('ambientOcclusion', this, data);
7572
utils.material.updateDistortionMap('metalness', this, data);
7673
utils.material.updateDistortionMap('roughness', this, data);
77-
this.updateEnvMap(data);
74+
utils.material.updateEnvMap(this, data);
7875
},
7976

8077
/**
@@ -90,60 +87,6 @@ module.exports.Shader = registerShader('standard', {
9087
for (key in this.materialData) {
9188
material[key] = this.materialData[key];
9289
}
93-
},
94-
95-
/**
96-
* Handle environment cubemap. Textures are cached in texturePromises.
97-
*/
98-
updateEnvMap: function (data) {
99-
var self = this;
100-
var material = this.material;
101-
var envMap = data.envMap;
102-
var sphericalEnvMap = data.sphericalEnvMap;
103-
104-
// No envMap defined or already loading.
105-
if ((!envMap && !sphericalEnvMap) || this.isLoadingEnvMap) {
106-
material.envMap = null;
107-
material.needsUpdate = true;
108-
return;
109-
}
110-
this.isLoadingEnvMap = true;
111-
112-
// if a spherical env map is defined then use it.
113-
if (sphericalEnvMap) {
114-
this.el.sceneEl.systems.material.loadTexture(sphericalEnvMap, {src: sphericalEnvMap}, function textureLoaded (texture) {
115-
self.isLoadingEnvMap = false;
116-
texture.mapping = THREE.EquirectangularReflectionMapping;
117-
material.envMap = texture;
118-
utils.material.handleTextureEvents(self.el, texture);
119-
material.needsUpdate = true;
120-
});
121-
return;
122-
}
123-
124-
// Another material is already loading this texture. Wait on promise.
125-
if (texturePromises[envMap]) {
126-
texturePromises[envMap].then(function (cube) {
127-
self.isLoadingEnvMap = false;
128-
material.envMap = cube;
129-
utils.material.handleTextureEvents(self.el, cube);
130-
material.needsUpdate = true;
131-
});
132-
return;
133-
}
134-
135-
// Material is first to load this texture. Load and resolve texture.
136-
texturePromises[envMap] = new Promise(function (resolve) {
137-
utils.srcLoader.validateCubemapSrc(envMap, function loadEnvMap (urls) {
138-
CubeLoader.load(urls, function (cube) {
139-
// Texture loaded.
140-
self.isLoadingEnvMap = false;
141-
material.envMap = cube;
142-
utils.material.handleTextureEvents(self.el, cube);
143-
resolve(cube);
144-
});
145-
});
146-
});
14790
}
14891
});
14992

src/systems/material.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,39 @@ module.exports.System = registerSystem('material', {
6969
function loadVideoCb (src) { self.loadVideo(src, data, cb); }
7070
},
7171

72+
/**
73+
* Load the six individual sides and construct a cube texture, then call back.
74+
*
75+
* @param {Array} srcs - Array of six texture URLs or elements.
76+
* @param {function} cb - Callback to pass cube texture to.
77+
*/
78+
loadCubeMapTexture: function (srcs, cb) {
79+
var self = this;
80+
var loaded = 0;
81+
var cube = new THREE.CubeTexture();
82+
cube.colorSpace = THREE.SRGBColorSpace;
83+
84+
function loadSide (index) {
85+
self.loadTexture(srcs[index], {src: srcs[index]}, function (texture) {
86+
cube.images[index] = texture.image;
87+
loaded++;
88+
if (loaded === 6) {
89+
cube.needsUpdate = true;
90+
cb(cube);
91+
}
92+
});
93+
}
94+
95+
if (srcs.length !== 6) {
96+
warn('Cube map texture requires exactly 6 sources, got only %s sources', srcs.length);
97+
return;
98+
}
99+
100+
for (var i = 0; i < srcs.length; i++) {
101+
loadSide(i);
102+
}
103+
},
104+
72105
/**
73106
* High-level function for loading image textures (THREE.Texture).
74107
*

src/utils/material.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
var THREE = require('../lib/three');
2+
var srcLoader = require('./src-loader');
3+
var debug = require('./debug');
4+
var warn = debug('utils:material:warn');
25

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

154+
// Cache env map results as promises
155+
var envMapPromises = {};
156+
157+
/**
158+
* Updates the material's environment map providing reflections or refractions.
159+
*
160+
* @param {object} shader - A-Frame shader instance
161+
* @param {object} data
162+
*/
163+
module.exports.updateEnvMap = function (shader, data) {
164+
var material = shader.material;
165+
var el = shader.el;
166+
var materialName = 'envMap';
167+
var src = data.envMap;
168+
var sphericalEnvMap = data.sphericalEnvMap;
169+
var refract = data.refract;
170+
171+
if (sphericalEnvMap) {
172+
src = sphericalEnvMap;
173+
warn('`sphericalEnvMap` property is deprecated, using spherical map as equirectangular map instead. ' +
174+
'Use `envMap` property with a CubeMap or Equirectangular image instead.');
175+
}
176+
177+
if (!shader.materialSrcs) { shader.materialSrcs = {}; }
178+
179+
// EnvMap has been removed
180+
if (!src) {
181+
// Forget the prior material src.
182+
delete shader.materialSrcs[materialName];
183+
material.envMap = null;
184+
material.needsUpdate = true;
185+
return;
186+
}
187+
188+
// Remember the new src for this env map.
189+
shader.materialSrcs[materialName] = src;
190+
191+
// Env map is already loading. Wait on promise.
192+
if (envMapPromises[src]) {
193+
envMapPromises[src].then(checkSetMap);
194+
return;
195+
}
196+
197+
// First time loading this env map.
198+
envMapPromises[src] = new Promise(function (resolve) {
199+
srcLoader.validateEnvMapSrc(src, function loadCubeMap (srcs) {
200+
el.sceneEl.systems.material.loadCubeMapTexture(srcs, function (texture) {
201+
texture.mapping = refract ? THREE.CubeRefractionMapping : THREE.CubeReflectionMapping;
202+
checkSetMap(texture);
203+
resolve(texture);
204+
});
205+
}, function loadEquirectMap (src) {
206+
el.sceneEl.systems.material.loadTexture(src, {src: src}, function (texture) {
207+
texture.mapping = refract ? THREE.EquirectangularRefractionMapping : THREE.EquirectangularReflectionMapping;
208+
checkSetMap(texture);
209+
resolve(texture);
210+
});
211+
});
212+
});
213+
214+
function checkSetMap (texture) {
215+
if (shader.materialSrcs[materialName] !== src) { return; }
216+
material.envMap = texture;
217+
material.needsUpdate = true;
218+
handleTextureEvents(el, texture);
219+
}
220+
};
221+
151222
/**
152223
* Emit event on entities on texture-related events.
153224
*

0 commit comments

Comments
 (0)