diff --git a/src/core/a-cubemap.js b/src/core/a-cubemap.js
index 7bdc890d36c..db9e250f894 100644
--- a/src/core/a-cubemap.js
+++ b/src/core/a-cubemap.js
@@ -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
).
* Does not listen to updates.
*/
class ACubeMap extends HTMLElement {
@@ -38,10 +38,9 @@ class ACubeMap extends HTMLElement {
/**
* Checks for exactly six elements with [src].
- * Does not check explicitly for
s in case user does not want
- * prefetching.
+ * When
s are used they will be prefetched.
*
- * @returns {Array|null} - six URLs if valid, else null.
+ * @returns {Array|null} - six URLs or
elements if valid, else null.
*/
validate () {
var elements = this.querySelectorAll('[src]');
@@ -49,7 +48,11 @@ class ACubeMap extends HTMLElement {
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;
}
diff --git a/src/core/propertyTypes.js b/src/core/propertyTypes.js
index e899ba2b1ce..5e62a9cbe34 100644
--- a/src/core/propertyTypes.js
+++ b/src/core/propertyTypes.js
@@ -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);
diff --git a/src/shaders/phong.js b/src/shaders/phong.js
index b279ec281e7..541a3ea8ab9 100644
--- a/src/shaders/phong.js
+++ b/src/shaders/phong.js
@@ -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.
*/
@@ -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) {
@@ -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);
},
/**
@@ -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);
- });
- });
- });
}
});
@@ -192,7 +132,7 @@ function getMaterialData (data, materialData) {
}
if (data.bumpMap) {
- materialData.aoMapIntensity = data.bumpMapScale;
+ materialData.bumpScale = data.bumpMapScale;
}
if (data.displacementMap) {
diff --git a/src/shaders/standard.js b/src/shaders/standard.js
index 2db6798ab3e..853f730552e 100755
--- a/src/shaders/standard.js
+++ b/src/shaders/standard.js
@@ -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.
*/
@@ -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);
},
/**
@@ -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);
- });
- });
- });
}
});
diff --git a/src/systems/material.js b/src/systems/material.js
index 23b685958c3..bf9f994c6d2 100755
--- a/src/systems/material.js
+++ b/src/systems/material.js
@@ -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).
*
diff --git a/src/utils/material.js b/src/utils/material.js
index 2e76e9ae892..df7189f8e75 100644
--- a/src/utils/material.js
+++ b/src/utils/material.js
@@ -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',
@@ -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.
*
diff --git a/src/utils/src-loader.js b/src/utils/src-loader.js
index e0e3d0fb5ed..cd705831071 100644
--- a/src/utils/src-loader.js
+++ b/src/utils/src-loader.js
@@ -25,50 +25,84 @@ function validateSrc (src, isImageCb, isVideoCb) {
}
/**
- * Validates six images as a cubemap, either as selector or comma-separated
- * URLs.
+ * Validates either six images as a cubemap or one image as an Equirectangular image.
*
- * @param {string} src - A selector or comma-separated image URLs. Image URLs
- must be wrapped by `url()`.
- * @param {string} src - A selector or comma-separated image URLs. Image URLs
+ * @param {string} src - A selector, image URL or comma-separated image URLs. Image URLS
must be wrapped by `url()`.
+ * @param {*} isCubemapCb - callback if src is a cubemap.
+ * @param {*} isEquirectCb - callback is src is a singular equirectangular image.
*/
-function validateCubemapSrc (src, cb) {
- var aCubemap;
+function validateEnvMapSrc (src, isCubemapCb, isEquirectCb) {
+ var el;
var cubemapSrcRegex = '';
var i;
var urls;
var validatedUrls = [];
- for (i = 0; i < 5; i++) {
- cubemapSrcRegex += '(url\\((?:[^\\)]+)\\),\\s*)';
- }
- cubemapSrcRegex += '(url\\((?:[^\\)]+)\\)\\s*)';
- urls = src.match(new RegExp(cubemapSrcRegex));
-
- // `src` is a comma-separated list of URLs.
- // In this case, re-use validateSrc for each side of the cube.
- function isImageCb (url) {
- validatedUrls.push(url);
- if (validatedUrls.length === 6) {
- cb(validatedUrls);
+ if (typeof src === 'string') {
+ for (i = 0; i < 5; i++) {
+ cubemapSrcRegex += '(url\\((?:[^\\)]+)\\),\\s*)';
}
- }
- if (urls) {
- for (i = 1; i < 7; i++) {
- validateSrc(parseUrl(urls[i]), isImageCb);
+ cubemapSrcRegex += '(url\\((?:[^\\)]+)\\)\\s*)';
+ urls = src.match(new RegExp(cubemapSrcRegex));
+
+ // `src` is a comma-separated list of URLs.
+ // In this case, re-use validateSrc for each side of the cube.
+ function isImageCb (url) {
+ validatedUrls.push(url);
+ if (validatedUrls.length === 6) {
+ isCubemapCb(validatedUrls);
+ }
}
- return;
+ if (urls) {
+ for (i = 1; i < 7; i++) {
+ validateSrc(parseUrl(urls[i]), isImageCb);
+ }
+ return;
+ }
+
+ // Single URL src
+ if (!src.startsWith('#')) {
+ var parsedSrc = parseUrl(src);
+ if (parsedSrc) {
+ validateSrc(parsedSrc, isEquirectCb);
+ } else {
+ validateSrc(src, isEquirectCb);
+ }
+ return;
+ }
+ }
+
+ // `src` is either an element or a query selector to an element ( or
).
+ if (src.tagName) {
+ el = src;
+ } else {
+ el = validateAndGetQuerySelector(src);
}
- // `src` is a query selector to containing six $([src])s.
- aCubemap = validateAndGetQuerySelector(src);
- if (!aCubemap) { return; }
- if (aCubemap.tagName === 'A-CUBEMAP' && aCubemap.srcs) {
- return cb(aCubemap.srcs);
+ if (!el) { return; }
+ if (el.tagName === 'A-CUBEMAP' && el.srcs) {
+ return isCubemapCb(el.srcs);
}
- // Else if aCubeMap is not a .
- warn('Selector "%s" does not point to ', src);
+ if (el.tagName === 'IMG') {
+ return isEquirectCb(el);
+ }
+ // Else if el is not a valid element, either or
.
+ warn('Selector "%s" does not point to or
', src);
+}
+
+/**
+ * Validates six images as a cubemap, either as selector or comma-separated
+ * URLs.
+ *
+ * @param {string} src - A selector or comma-separated image URLs. Image URLs
+ must be wrapped by `url()`.
+ * @param {function} cb - callback if src is a cubemap.
+ */
+function validateCubemapSrc (src, cb) {
+ return validateEnvMapSrc(src, cb, function isEquirectCb () {
+ warn('Expected cubemap but got image');
+ });
}
/**
@@ -77,7 +111,7 @@ function validateCubemapSrc (src, cb) {
* @return {string} The parsed src, if parseable.
*/
function parseUrl (src) {
- var parsedSrc = src.match(/\url\((.+)\)/);
+ var parsedSrc = src.match(/url\((.+)\)/);
if (!parsedSrc) { return; }
return parsedSrc[1];
}
@@ -153,5 +187,6 @@ function validateAndGetQuerySelector (selector) {
module.exports = {
parseUrl: parseUrl,
validateSrc: validateSrc,
- validateCubemapSrc: validateCubemapSrc
+ validateCubemapSrc: validateCubemapSrc,
+ validateEnvMapSrc: validateEnvMapSrc
};
diff --git a/tests/shaders/phong.test.js b/tests/shaders/phong.test.js
new file mode 100644
index 00000000000..69c29b5c053
--- /dev/null
+++ b/tests/shaders/phong.test.js
@@ -0,0 +1,172 @@
+/* global assert, process, setup, suite, test */
+var entityFactory = require('../helpers').entityFactory;
+var THREE = require('index').THREE;
+
+suite('phong material', function () {
+ setup(function (done) {
+ var el = this.el = entityFactory();
+ el.sceneEl.systems.material.clearTextureCache();
+ el.setAttribute('geometry', '');
+ el.setAttribute('material', {shader: 'phong'});
+ if (el.hasLoaded) { done(); }
+ el.addEventListener('loaded', function () {
+ done();
+ });
+ });
+
+ test('can unset fog', function () {
+ var el = this.el;
+ assert.ok(el.getObject3D('mesh').material.fog);
+ el.setAttribute('material', 'fog', false);
+ assert.notOk(el.getObject3D('mesh').material.fog);
+ });
+
+ test('can use ambient occlusion maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ assert.isNull(el.getObject3D('mesh').material.aoMap);
+ el.setAttribute('material', {
+ ambientOcclusionMapIntensity: 0.4,
+ ambientOcclusionMap: `url(${imageUrl})`
+ });
+ assert.equal(el.getObject3D('mesh').material.aoMapIntensity, 0.4);
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(el.getObject3D('mesh').material.aoMap, evt.detail.texture);
+ done();
+ });
+ });
+
+ test('can use normal maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ assert.isNull(el.getObject3D('mesh').material.normalMap);
+ el.setAttribute('material', {
+ normalScale: {x: 0.3, y: -0.4},
+ normalMap: `url(${imageUrl})`,
+ normalTextureRepeat: {x: 2, y: 2},
+ normalTextureOffset: {x: 0.1, y: 0.1}
+ });
+ assert.equal(el.getObject3D('mesh').material.normalScale.x, 0.3);
+ assert.equal(el.getObject3D('mesh').material.normalScale.y, -0.4);
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(el.getObject3D('mesh').material.normalMap, evt.detail.texture);
+ assert.equal(evt.detail.texture.repeat.x, 2);
+ assert.equal(evt.detail.texture.offset.x, 0.1);
+ done();
+ });
+ });
+
+ test('can use displacement maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ assert.isNull(el.getObject3D('mesh').material.displacementMap);
+ el.setAttribute('material', {
+ displacementScale: 0.3,
+ displacementBias: 0.2,
+ displacementMap: `url(${imageUrl})`,
+ displacementTextureRepeat: {x: 2, y: 2},
+ displacementTextureOffset: {x: 0.1, y: 0.1}
+ });
+ assert.equal(el.getObject3D('mesh').material.displacementScale, 0.3);
+ assert.equal(el.getObject3D('mesh').material.displacementBias, 0.2);
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(el.getObject3D('mesh').material.displacementMap, evt.detail.texture);
+ assert.equal(evt.detail.texture.repeat.x, 2);
+ assert.equal(evt.detail.texture.offset.x, 0.1);
+ done();
+ });
+ });
+
+ test('can use bump maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ assert.isNull(el.getObject3D('mesh').material.bumpMap);
+ el.setAttribute('material', {
+ bumpMapScale: 0.4,
+ bumpMap: `url(${imageUrl})`
+ });
+ assert.equal(el.getObject3D('mesh').material.bumpScale, 0.4);
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(el.getObject3D('mesh').material.bumpMap, evt.detail.texture);
+ done();
+ });
+ });
+
+ [
+ { dataName: 'normalMap', materialName: 'normalMap' },
+ { dataName: 'displacementMap', materialName: 'displacementMap' },
+ { dataName: 'ambientOcclusionMap', materialName: 'aoMap' },
+ { dataName: 'bumpMap', materialName: 'bumpMap' }
+ ].forEach(function (names) {
+ test(`can unset ${names.dataName}`, function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ assert.isNull(el.getObject3D('mesh').material[names.materialName]);
+ el.setAttribute('material', names.dataName, `url(${imageUrl})`);
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(el.getObject3D('mesh').material[names.materialName], evt.detail.texture);
+ el.setAttribute('material', names.dataName, '');
+ assert.isNull(el.getObject3D('mesh').material[names.materialName]);
+ done();
+ });
+ });
+ });
+
+ test('can use spherical env maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ el.setAttribute('material', 'sphericalEnvMap: url(' + imageUrl + ');');
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(evt.detail.texture.mapping, THREE.EquirectangularReflectionMapping);
+ assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
+ done();
+ });
+ });
+
+ test('can use cube env maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ el.setAttribute('material', 'envMap: url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + ');');
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(evt.detail.texture.mapping, THREE.CubeReflectionMapping);
+ assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
+ done();
+ });
+ });
+
+ test('can use equirectangular env maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ el.setAttribute('material', 'envMap: url(' + imageUrl + ');');
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(evt.detail.texture.mapping, THREE.EquirectangularReflectionMapping);
+ assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
+ done();
+ });
+ });
+
+ test('can use equirectangular env maps for refraction', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test2.png';
+ el.setAttribute('material', 'envMap: url(' + imageUrl + '); refract: true');
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(evt.detail.texture.mapping, THREE.EquirectangularRefractionMapping);
+ assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
+ done();
+ });
+ });
+
+ test('falls back to scene.environment for envMap', function () {
+ var el = this.el;
+ el.sceneEl.object3D.environment = new THREE.CubeTexture();
+ assert.equal(el.getObject3D('mesh').material.envMap, el.sceneEl.object3D.environment);
+ });
+
+ test('can use wireframes', function () {
+ var el = this.el;
+ assert.notOk(el.getObject3D('mesh').material.wireframe);
+ el.setAttribute('material', 'wireframe', true);
+ assert.ok(el.getObject3D('mesh').material.wireframe);
+ assert.equal(el.getObject3D('mesh').material.wireframeLinewidth, 2);
+ });
+});
diff --git a/tests/shaders/standard.test.js b/tests/shaders/standard.test.js
index 45d270e244d..bd72788729c 100644
--- a/tests/shaders/standard.test.js
+++ b/tests/shaders/standard.test.js
@@ -103,7 +103,6 @@ suite('standard material', function () {
var el = this.el;
var imageUrl = 'base/tests/assets/test.png';
el.setAttribute('material', 'sphericalEnvMap: url(' + imageUrl + ');');
- assert.ok(el.components.material.shader.isLoadingEnvMap);
el.addEventListener('materialtextureloaded', function (evt) {
assert.equal(evt.detail.texture.mapping, THREE.EquirectangularReflectionMapping);
assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
@@ -115,7 +114,6 @@ suite('standard material', function () {
var el = this.el;
var imageUrl = 'base/tests/assets/test.png';
el.setAttribute('material', 'envMap: url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + '), url(' + imageUrl + ');');
- assert.ok(el.components.material.shader.isLoadingEnvMap);
el.addEventListener('materialtextureloaded', function (evt) {
assert.equal(evt.detail.texture.mapping, THREE.CubeReflectionMapping);
assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
@@ -123,6 +121,17 @@ suite('standard material', function () {
});
});
+ test('can use equirectangular env maps', function (done) {
+ var el = this.el;
+ var imageUrl = 'base/tests/assets/test.png';
+ el.setAttribute('material', 'envMap: url(' + imageUrl + ');');
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(evt.detail.texture.mapping, THREE.EquirectangularReflectionMapping);
+ assert.equal(el.getObject3D('mesh').material.envMap, evt.detail.texture);
+ done();
+ });
+ });
+
test('can use wireframes', function () {
var el = this.el;
assert.notOk(el.getObject3D('mesh').material.wireframe);
diff --git a/tests/utils/src-loader.test.js b/tests/utils/src-loader.test.js
new file mode 100644
index 00000000000..c94b20e5d1f
--- /dev/null
+++ b/tests/utils/src-loader.test.js
@@ -0,0 +1,93 @@
+/* global assert, suite, setup, test, Image */
+var srcLoader = require('utils/src-loader');
+var entityFactory = require('../helpers').entityFactory;
+
+suite('utils.src-loader', function () {
+ suite('validateEnvMapSrc', function () {
+ setup(function (done) {
+ var el;
+ var imgAsset = document.createElement('img');
+ imgAsset.setAttribute('id', 'image');
+ imgAsset.setAttribute('src', 'base/tests/assets/test.png');
+ var imgCubemap = constructACubemap('img');
+ imgCubemap.setAttribute('id', 'cubemap-imgs');
+ var assetCubemap = constructACubemap('a-asset-item');
+ assetCubemap.setAttribute('id', 'cubemap-assets');
+
+ el = this.el = entityFactory({assets: [imgAsset, imgCubemap, assetCubemap]});
+ if (el.hasLoaded) { done(); }
+ el.addEventListener('loaded', function () {
+ done();
+ });
+ });
+
+ test('validates six urls as cubemap', function (done) {
+ const srcs = `url(base/tests/assets/test.png),
+ url(base/tests/assets/test.png),
+ url(base/tests/assets/test.png),
+ url(base/tests/assets/test.png),
+ url(base/tests/assets/test.png),
+ url(base/tests/assets/test.png)`;
+ srcLoader.validateEnvMapSrc(srcs, function isCubemapCb () {
+ done();
+ }, function isEquirectCb () {
+ assert.fail();
+ });
+ });
+
+ test('validates one url as equirectangular map', function (done) {
+ const srcs = 'url(base/tests/assets/test.png)';
+ srcLoader.validateEnvMapSrc(srcs, function isCubemapCb () {
+ assert.fail();
+ }, function isEquirectCb () {
+ done();
+ });
+ });
+
+ test('validates selector to
as equirectangular map', function (done) {
+ const srcs = '#image';
+ srcLoader.validateEnvMapSrc(srcs, function isCubemapCb () {
+ assert.fail();
+ }, function isEquirectCb () {
+ done();
+ });
+ });
+
+ test('validates selector to (with
children) as cubemap', function (done) {
+ const srcs = '#cubemap-imgs';
+ srcLoader.validateEnvMapSrc(srcs, function isCubemapCb () {
+ done();
+ }, function isEquirectCb () {
+ assert.fail();
+ });
+ });
+
+ test('validates selector to (without
children) as cubemap', function (done) {
+ const srcs = '#cubemap-assets';
+ srcLoader.validateEnvMapSrc(srcs, function isCubemapCb () {
+ done();
+ }, function isEquirectCb () {
+ assert.fail();
+ });
+ });
+
+ test('validates single non-wrapped URL as equirectangular map', function (done) {
+ const srcs = 'base/tests/assets/test.png';
+ srcLoader.validateEnvMapSrc(srcs, function isCubemapCb () {
+ assert.fail();
+ }, function isEquirectCb () {
+ done();
+ });
+ });
+ });
+});
+
+function constructACubemap (childTag) {
+ var aCubemap = document.createElement('a-cubemap');
+ for (let i = 0; i < 6; i++) {
+ var child = document.createElement(childTag);
+ child.setAttribute('src', 'base/tests/assets/test.png');
+ aCubemap.appendChild(child);
+ }
+ return aCubemap;
+}