Skip to content
Merged
Show file tree
Hide file tree
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
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