diff --git a/docs/components/material.md b/docs/components/material.md
index 82133b13df1..e3d8a1749f3 100644
--- a/docs/components/material.md
+++ b/docs/components/material.md
@@ -46,8 +46,8 @@ The material component has only a few base properties, but more properties will
| Event Name | Description |
|-------------------------|--------------------------------------------------------------------------------------------|
-| material-texture-loaded | Texture loaded onto material. Or when the first frame is playing for video textures. |
-| material-video-ended | For video textures, emitted when the video has reached its end (may not work with `loop`). |
+| materialtextureloaded | Texture loaded onto material. Or when the first frame is playing for video textures. |
+| materialvideoended | For video textures, emitted when the video has reached its end (may not work with `loop`). |
## Textures
diff --git a/package.json b/package.json
index 83ca4c04d27..a72d7031a19 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"style-attr": "^1.0.2",
"three": "^0.75.0",
"tween.js": "^15.0.0",
- "webvr-polyfill": "borismus/webvr-polyfill#3f47796"
+ "webvr-polyfill": "borismus/webvr-polyfill#57ba04e"
},
"devDependencies": {
"browserify": "^11.0.1",
diff --git a/src/components/scene/keyboard-shortcuts.js b/src/components/scene/keyboard-shortcuts.js
index b1ebf06f72c..1255ff5a918 100644
--- a/src/components/scene/keyboard-shortcuts.js
+++ b/src/components/scene/keyboard-shortcuts.js
@@ -16,13 +16,26 @@ module.exports.Component = registerComponent('keyboard-shortcuts', {
this.listener = window.addEventListener('keyup', function (event) {
if (!shouldCaptureKeyEvent(event)) { return; }
- if (self.enterVREnabled && event.keyCode === 70) { // f.
- scene.enterVR();
+
+ if (self.enterVREnabled) {
+ if (event.keyCode === 70) { // f.
+ scene.enterVR();
+ return;
+ }
+
+ if (navigator.getVRDisplays && event.keyCode === 27) { // Escape.
+ // The new WebVR API doesn't use Fullscreen, so let's
+ // still exit VR when the Escape key is pressed. This also works
+ // fine in older builds using the old WebVR API.
+ scene.exitVR();
+ return;
+ }
}
+
if (self.resetSensorEnabled && event.keyCode === 90) { // z.
controls.resetSensor();
}
- }, false);
+ });
},
update: function (oldData) {
diff --git a/src/components/scene/vr-mode-ui.js b/src/components/scene/vr-mode-ui.js
index c7edc52011a..43dab004482 100644
--- a/src/components/scene/vr-mode-ui.js
+++ b/src/components/scene/vr-mode-ui.js
@@ -120,8 +120,8 @@ function createEnterVR (enterVRHandler, isMobile) {
var compatModal;
var compatModalLink;
var compatModalText;
- // window.hasNonPolyfillWebVRSupport is set in src/index.js.
- var hasWebVR = isMobile || window.hasNonPolyfillWebVRSupport;
+ // window.hasNativeWebVRSupport is set in src/index.js.
+ var hasWebVR = isMobile || window.hasNativeWebVRSupport;
var orientation;
var vrButton;
var wrapper;
@@ -138,6 +138,7 @@ function createEnterVR (enterVRHandler, isMobile) {
compatModalLink.innerHTML = 'Learn more.';
vrButton = document.createElement('button');
vrButton.className = ENTER_VR_BTN_CLASS;
+ vrButton.title = 'Enter VR';
// Insert elements.
wrapper.appendChild(vrButton);
diff --git a/src/core/scene/a-scene.js b/src/core/scene/a-scene.js
index 645c72ef3b5..05864b6f12a 100644
--- a/src/core/scene/a-scene.js
+++ b/src/core/scene/a-scene.js
@@ -72,13 +72,15 @@ var AScene = module.exports = registerElement('a-scene', {
attachedCallback: {
value: function () {
+ var resize = this.resize.bind(this);
+ var exitVR = this.exitVR.bind(this);
initFullscreen(this);
initMetaTags(this);
initWakelock(this);
-
- window.addEventListener('load', this.resize.bind(this));
- window.addEventListener('resize', this.resize.bind(this), false);
- this.addEventListener('fullscreen-exit', this.exitVR.bind(this));
+ window.addEventListener('load', resize);
+ window.addEventListener('resize', resize);
+ window.addEventListener('beforeunload', exitVR);
+ this.addEventListener('fullscreen-exit', exitVR);
this.play();
},
writable: window.debug
@@ -128,22 +130,27 @@ var AScene = module.exports = registerElement('a-scene', {
*/
enterVR: {
value: function (event) {
- this.setStereoRenderer();
- if (isMobile) {
- setFullscreen(this.canvas);
- } else {
- this.stereoRenderer.setFullScreen(true);
- }
- this.addState('vr-mode');
- this.emit('enter-vr', event);
+ var self = this;
+ self.setStereoRenderer();
+ self.stereoRenderer.requestPresent()
+ .then(function () {
+ self.addState('vr-mode');
+ self.emit('enter-vr', event);
+ })
+ .catch(console.error.bind(console));
}
},
exitVR: {
value: function () {
- this.setMonoRenderer();
- this.removeState('vr-mode');
- this.emit('exit-vr', { target: this });
+ var self = this;
+ self.stereoRenderer.exitPresent()
+ .then(function () {
+ self.setMonoRenderer();
+ self.removeState('vr-mode');
+ self.emit('exit-vr', {target: self});
+ })
+ .catch(console.error.bind(console));
}
},
@@ -180,7 +187,7 @@ var AScene = module.exports = registerElement('a-scene', {
camera.updateProjectionMatrix();
// Notify renderer of size change.
- this.renderer.setSize(size.width, size.height, true);
+ this.renderer.setSize(size.width, size.height);
},
writable: window.debug
},
@@ -216,7 +223,7 @@ var AScene = module.exports = registerElement('a-scene', {
antialias: antialias,
alpha: true
});
- renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.setPixelRatio(Math.floor(window.devicePixelRatio));
renderer.sortObjects = false;
AScene.renderer = renderer;
this.stereoRenderer = new THREE.VREffect(renderer);
@@ -340,19 +347,3 @@ function getCanvasSize (canvas) {
width: canvas.offsetWidth
};
}
-
-/**
- * Manually handles fullscreen for non-VR mobile where the renderer' VR
- * display is not polyfilled.
- *
- * Desktop just works so use the renderer.setFullScreen in that case.
- */
-function setFullscreen (canvas) {
- if (canvas.requestFullscreen) {
- canvas.requestFullscreen();
- } else if (canvas.mozRequestFullScreen) {
- canvas.mozRequestFullScreen();
- } else if (canvas.webkitRequestFullscreen) {
- canvas.webkitRequestFullscreen();
- }
-}
diff --git a/src/geometries/icosahedron.js b/src/geometries/icosahedron.js
new file mode 100644
index 00000000000..fd8b449a612
--- /dev/null
+++ b/src/geometries/icosahedron.js
@@ -0,0 +1,13 @@
+var registerGeometry = require('../core/geometry').registerGeometry;
+var THREE = require('../lib/three');
+
+registerGeometry('icosahedron', {
+ schema: {
+ detail: {default: 0, min: 0, max: 1},
+ radius: {default: 1, min: 0}
+ },
+
+ init: function (data) {
+ this.geometry = new THREE.IcosahedronGeometry(data.radius, data.detail);
+ }
+});
diff --git a/src/geometries/index.js b/src/geometries/index.js
index bee12af030e..f9611592981 100644
--- a/src/geometries/index.js
+++ b/src/geometries/index.js
@@ -2,6 +2,7 @@ require('./box.js');
require('./circle.js');
require('./cone.js');
require('./cylinder.js');
+require('./icosahedron.js');
require('./plane.js');
require('./ring.js');
require('./sphere.js');
diff --git a/src/index.js b/src/index.js
index fe1e053988d..39adeeddd9e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -30,11 +30,12 @@ require('./systems/index'); // Register standard systems.
var ANode = require('./core/a-node');
var AEntity = require('./core/a-entity'); // Depends on ANode and core components.
-// WebVR polyfill configuration.
-window.hasNonPolyfillWebVRSupport = !!navigator.getVRDevices || !!navigator.getVRDisplays;
+window.hasNativeWebVRSupport = !!navigator.getVRDevices || !!navigator.getVRDisplays;
window.WebVRConfig = window.WebVRConfig || {
TOUCH_PANNER_DISABLED: true,
- MOUSE_KEYBOARD_CONTROLS_DISABLED: true
+ MOUSE_KEYBOARD_CONTROLS_DISABLED: true,
+ ENABLE_DEPRECATED_API: true,
+ BUFFER_SCALE: 0.5
};
require('webvr-polyfill');
diff --git a/src/lib/three.js b/src/lib/three.js
index 3c652462d16..c732bb0f1ce 100644
--- a/src/lib/three.js
+++ b/src/lib/three.js
@@ -22,7 +22,7 @@ if (THREE.Cache) {
require('../../node_modules/three/examples/js/loaders/OBJLoader'); // THREE.OBJLoader
require('../../node_modules/three/examples/js/loaders/MTLLoader'); // THREE.MTLLoader
require('../../node_modules/three/examples/js/loaders/ColladaLoader'); // THREE.ColladaLoader
-require('../../node_modules/three/examples/js/controls/VRControls'); // THREE.VRControls
-require('../../node_modules/three/examples/js/effects/VREffect'); // THREE.VREffect
+require('../../vendor/VRControls'); // THREE.VRControls
+require('../../vendor/VREffect'); // THREE.VREffect
module.exports = THREE;
diff --git a/src/systems/material.js b/src/systems/material.js
index f18d91da8b4..7f19121211f 100755
--- a/src/systems/material.js
+++ b/src/systems/material.js
@@ -2,9 +2,6 @@ var registerSystem = require('../core/system').registerSystem;
var THREE = require('../lib/three');
var utils = require('../utils/');
-var EVENTS = {
- TEXTURE_LOADED: 'material-texture-loaded'
-};
var debug = utils.debug;
var error = debug('components:texture:error');
var TextureLoader = new THREE.TextureLoader();
@@ -30,58 +27,69 @@ module.exports.System = registerSystem('material', {
},
/**
- * High-level function for loading image textures. Meat of logic is in `loadImageTexture`.
+ * Determine whether `src` is a image or video. Then try to load the asset, then call back.
*
- * @param {Element} el - Entity, used to emit event.
- * @param {object} material - three.js material, bound by the A-Frame shader.
- * @param {object} data - Shader data, bound by the A-Frame shader.
- * @param {Element|string} src - Texture source, bound by `src-loader` utils.
+ * @param {string} src - Texture URL.
+ * @param {string} data - Relevant texture data used for caching.
+ * @param {function} cb - Callback to pass texture to.
*/
- loadImage: function (el, material, data, src) {
- var repeat = data.repeat || '1 1';
- var srcString = src;
- var textureCache = this.textureCache;
+ loadTexture: function (src, data, cb) {
+ var self = this;
+ utils.srcLoader.validateSrc(src, loadImageCb, loadVideoCb);
+ function loadImageCb (src) { self.loadImage(src, data, cb); }
+ function loadVideoCb (src) { self.loadVideo(src, data, cb); }
+ },
- if (typeof src !== 'string') { srcString = src.getAttribute('src'); }
+ /**
+ * High-level function for loading image textures (THREE.Texture).
+ *
+ * @param {Element|string} src - Texture source.
+ * @param {object} data - Texture data.
+ * @param {function} cb - Callback to pass texture to.
+ */
+ loadImage: function (src, data, cb) {
+ var hash = this.hash(data);
+ var handleImageTextureLoaded = cb;
+ var textureCache = this.textureCache;
- // Another material is already loading this texture. Wait on promise.
- if (textureCache[src] && textureCache[src][repeat]) {
- textureCache[src][repeat].then(handleImageTextureLoaded);
+ // Texture already being loaded or already loaded. Wait on promise.
+ if (textureCache[hash]) {
+ textureCache[hash].then(handleImageTextureLoaded);
return;
}
- // Material instance is first to try to load this texture. Load it.
- textureCache[srcString] = textureCache[srcString] || {};
- textureCache[srcString][repeat] = textureCache[srcString][repeat] || {};
- textureCache[srcString][repeat] = loadImageTexture(material, src, repeat);
- textureCache[srcString][repeat].then(handleImageTextureLoaded);
-
- function handleImageTextureLoaded (texture) {
- utils.material.updateMaterialTexture(material, texture);
- el.emit(EVENTS.TEXTURE_LOADED, { src: src, texture: texture });
- }
+ // Texture not yet being loaded. Start loading it.
+ textureCache[hash] = loadImageTexture(src, data);
+ textureCache[hash].then(handleImageTextureLoaded);
},
- /**
- * Load video texture.
- * Note that creating a video texture is more synchronous than creating an image texture.
+ /**
+ * Load video texture (THREE.VideoTexture).
+ * Which is just an image texture that RAFs + needsUpdate.
+ * Note that creating a video texture is synchronous unlike loading an image texture.
+ * Made asynchronous to be consistent with image textures.
*
- * @param {Element} el - Entity, used to emit event.
- * @param {object} material - three.js material.
- * @param data {object} - Shader data, bound by the A-Frame shader.
- * @param src {Element|string} - Texture source, bound by `src-loader` utils.
+ * @param {Element|string} src - Texture source.
+ * @param {object} data - Texture data.
+ * @param {function} cb - Callback to pass texture to.
*/
- loadVideo: function (el, material, data, src) {
+ loadVideo: function (src, data, cb) {
var hash;
var texture;
var textureCache = this.textureCache;
var videoEl;
var videoTextureResult;
+ function handleVideoTextureLoaded (result) {
+ result.texture.needsUpdate = true;
+ cb(result.texture, result.videoEl);
+ }
+
+ // Video element provided.
if (typeof src !== 'string') {
// Check cache before creating texture.
videoEl = src;
- hash = calculateVideoCacheHash(videoEl);
+ hash = this.hashVideo(data, videoEl);
if (textureCache[hash]) {
textureCache[hash].then(handleVideoTextureLoaded);
return;
@@ -90,11 +98,11 @@ module.exports.System = registerSystem('material', {
fixVideoAttributes(videoEl);
}
- // Use video element to create texture.
- videoEl = videoEl || createVideoEl(material, src, data.width, data.height);
+ // Only URL provided. Use video element to create texture.
+ videoEl = videoEl || createVideoEl(src, data.width, data.height);
// Generated video element already cached. Use that.
- hash = calculateVideoCacheHash(videoEl);
+ hash = this.hashVideo(data, videoEl);
if (textureCache[hash]) {
textureCache[hash].then(handleVideoTextureLoaded);
return;
@@ -103,28 +111,20 @@ module.exports.System = registerSystem('material', {
// Create new video texture.
texture = new THREE.VideoTexture(videoEl);
texture.minFilter = THREE.LinearFilter;
+ setTextureProperties(texture, data);
// Cache as promise to be consistent with image texture caching.
- videoTextureResult = {
- texture: texture,
- videoEl: videoEl
- };
+ videoTextureResult = {texture: texture, videoEl: videoEl};
textureCache[hash] = Promise.resolve(videoTextureResult);
handleVideoTextureLoaded(videoTextureResult);
+ },
- function handleVideoTextureLoaded (res) {
- texture = res.texture;
- videoEl = res.videoEl;
- utils.material.updateMaterialTexture(material, texture);
- el.emit(EVENTS.TEXTURE_LOADED, { element: videoEl, src: src });
- videoEl.addEventListener('loadeddata', function () {
- el.emit('material-video-loadeddata', { element: videoEl, src: src });
- });
- videoEl.addEventListener('ended', function () {
- // Works for non-looping videos only.
- el.emit('material-video-ended', { element: videoEl, src: src });
- });
- }
+ hash: function (data) {
+ return JSON.stringify(data);
+ },
+
+ hashVideo: function (data, videoEl) {
+ return calculateVideoCacheHash(data, videoEl);
},
/**
@@ -161,10 +161,11 @@ module.exports.System = registerSystem('material', {
* If the video element has an ID, use that.
* Else build a hash that looks like `src:myvideo.mp4;height:200;width:400;`.
*
+ * @param data {object} - Texture data such as repeat.
* @param videoEl {Element} - Video element.
* @returns {string}
*/
-function calculateVideoCacheHash (videoEl) {
+function calculateVideoCacheHash (data, videoEl) {
var i;
var id = videoEl.getAttribute('id');
var hash;
@@ -174,7 +175,7 @@ function calculateVideoCacheHash (videoEl) {
// Calculate hash using sorted video attributes.
hash = '';
- videoAttributes = {};
+ videoAttributes = data || {};
for (i = 0; i < videoEl.attributes.length; i++) {
videoAttributes[videoEl.attributes[i].name] = videoEl.attributes[i].value;
}
@@ -186,24 +187,28 @@ function calculateVideoCacheHash (videoEl) {
}
/**
- * Set image texture on material as `map`.
+ * Load image texture.
*
* @private
- * @param {object} el - Entity element.
- * @param {object} material - three.js material.
* @param {string|object} src - An element or url to an image file.
- * @param {string} repeat - X and Y value for size of texture repeating (in UV units).
+ * @param {object} data - Data to set texture properties like `repeat`.
* @returns {Promise} Resolves once texture is loaded.
*/
-function loadImageTexture (material, src, repeat) {
+function loadImageTexture (src, data) {
return new Promise(doLoadImageTexture);
function doLoadImageTexture (resolve, reject) {
var isEl = typeof src !== 'string';
+ function resolveTexture (texture) {
+ setTextureProperties(texture, data);
+ texture.needsUpdate = true;
+ resolve(texture);
+ }
+
// Create texture from an element.
if (isEl) {
- createTexture(src);
+ resolveTexture(new THREE.Texture(src));
return;
}
@@ -211,59 +216,55 @@ function loadImageTexture (material, src, repeat) {
// Use THREE.TextureLoader (src, onLoad, onProgress, onError) to load texture.
TextureLoader.load(
src,
- createTexture,
+ resolveTexture,
function () { /* no-op */ },
function (xhr) {
error('`$s` could not be fetched (Error code: %s; Response: %s)', xhr.status,
xhr.statusText);
}
);
+ }
+}
- /**
- * Texture loaded. Set it.
- */
- function createTexture (texture) {
- var repeatXY;
- if (!(texture instanceof THREE.Texture)) { texture = new THREE.Texture(texture); }
-
- // Handle UV repeat.
- repeatXY = repeat.split(' ');
- if (repeatXY.length === 2) {
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- texture.repeat.set(parseInt(repeatXY[0], 10), parseInt(repeatXY[1], 10));
- }
+/**
+ * Set texture properties such as repeat.
+ *
+ * @param {object} data - With keys like `repeat`.
+ */
+function setTextureProperties (texture, data) {
+ // Handle UV repeat.
+ var repeat = data.repeat || '1 1';
+ var repeatXY = repeat.split(' ');
- resolve(texture);
- }
- }
+ // Don't bother setting repeat if it is 1/1. Power-of-two is required to repeat.
+ if (repeat === '1 1' || repeatXY.length !== 2) { return; }
+
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(parseInt(repeatXY[0], 10), parseInt(repeatXY[1], 10));
}
/**
* Create video element to be used as a texture.
*
- * @param {object} material - three.js material.
* @param {string} src - Url to a video file.
* @param {number} width - Width of the video.
* @param {number} height - Height of the video.
* @returns {Element} Video element.
*/
-function createVideoEl (material, src, width, height) {
- var el = material.videoEl || document.createElement('video');
- el.width = width;
- el.height = height;
- if (el !== this.videoEl) {
- el.setAttribute('webkit-playsinline', ''); // To support inline videos in iOS webviews.
- el.autoplay = true;
- el.loop = true;
- el.crossOrigin = true;
- el.addEventListener('error', function () {
- warn('`$s` is not a valid video', src);
- }, true);
- material.videoEl = el;
- }
- el.src = src;
- return el;
+function createVideoEl (src, width, height) {
+ var videoEl = document.createElement('video');
+ videoEl.width = width;
+ videoEl.height = height;
+ videoEl.setAttribute('webkit-playsinline', ''); // Support inline videos for iOS webviews.
+ videoEl.autoplay = true;
+ videoEl.loop = true;
+ videoEl.crossOrigin = true;
+ videoEl.addEventListener('error', function () {
+ warn('`$s` is not a valid video', src);
+ }, true);
+ videoEl.src = src;
+ return videoEl;
}
/**
@@ -280,10 +281,8 @@ function createVideoEl (material, src, width, height) {
* @returns {Element} Video element with the correct properties updated.
*/
function fixVideoAttributes (videoEl) {
+ videoEl.autoplay = videoEl.getAttribute('autoplay') !== 'false';
videoEl.controls = videoEl.getAttribute('controls') !== 'false';
- if (videoEl.getAttribute('autoplay') === 'false') {
- videoEl.removeAttribute('autoplay');
- }
if (videoEl.getAttribute('loop') === 'false') {
videoEl.removeAttribute('loop');
}
diff --git a/src/utils/material.js b/src/utils/material.js
index bbc2df0fd96..d5ffeaf2344 100644
--- a/src/utils/material.js
+++ b/src/utils/material.js
@@ -1,47 +1,52 @@
-var srcLoader = require('./src-loader');
-
/**
- * Update shader instance's `material.map` given `data.src`.
+ * Update `material.map` given `data.src`. For standard and flat shaders.
*
* @param {object} shader - A-Frame shader instance.
* @param {object} data
*/
module.exports.updateMap = function (shader, data) {
var el = shader.el;
- var src = data.src;
var material = shader.material;
- var materialSystem = el.sceneEl.systems.material;
+ var src = data.src;
if (src) {
- if (src === shader.mapSrc) { return; }
+ if (src === shader.textureSrc) { return; }
// Texture added or changed.
- shader.mapSrc = src;
- srcLoader.validateSrc(
- src,
- function loadImageCb (src) { materialSystem.loadImage(el, material, data, src); },
- function loadVideoCb (src) { materialSystem.loadVideo(el, material, data, src); }
- );
+ shader.textureSrc = src;
+ el.sceneEl.systems.material.loadTexture(src, {src: src, repeat: data.repeat}, setMap);
return;
}
// Texture removed.
- updateMaterialTexture(material, null);
+ if (!material.map) { return; }
+ setMap(null);
+
+ function setMap (texture) {
+ material.map = texture;
+ material.needsUpdate = true;
+ handleTextureEvents(el, texture);
+ }
};
/**
- * Set material texture and update if necessary.
+ * Emit event on entities on texture-related events.
*
- * @param {object} material
- * @param {object} texture
+ * @param {Element} el - Entity.
+ * @param {object} texture - three.js Texture.
*/
-function updateMaterialTexture (material, texture) {
- var oldMap = material.map;
- if (texture) { texture.needsUpdate = true; }
- material.map = texture;
+function handleTextureEvents (el, texture) {
+ if (!texture) { return; }
- // Only need to update three.js material if presence or not of texture has changed.
- if (oldMap === null && material.map || material.map === null && oldMap) {
- material.needsUpdate = true;
- }
+ el.emit('materialtextureloaded', {src: texture.image, texture: texture});
+
+ // Video events.
+ if (texture.image.tagName !== 'VIDEO') { return; }
+ texture.image.addEventListener('loadeddata', function emitVideoTextureLoadedDataAll () {
+ el.emit('materialvideoloadeddata', {src: texture.image, texture: texture});
+ });
+ texture.image.addEventListener('ended', function emitVideoTextureEndedAll () {
+ // Works for non-looping videos only.
+ el.emit('materialvideoended', {src: texture.image, texture: texture});
+ });
}
-module.exports.updateMaterialTexture = updateMaterialTexture;
+module.exports.handleTextureEvents = handleTextureEvents;
diff --git a/tests/components/geometry.test.js b/tests/components/geometry.test.js
index 4bef6c91e5f..613859df5ef 100644
--- a/tests/components/geometry.test.js
+++ b/tests/components/geometry.test.js
@@ -173,6 +173,18 @@ suite('standard geometries', function () {
assert.equal(geometry.parameters.thetaLength, degToRad(350));
});
+ test('icosahedron', function () {
+ var el = this.el;
+ var geometry;
+ el.setAttribute('geometry', {
+ buffer: false, primitive: 'icosahedron', detail: 0, radius: 5});
+
+ geometry = el.getObject3D('mesh').geometry;
+ assert.equal(geometry.type, 'IcosahedronGeometry');
+ assert.equal(geometry.parameters.radius, 5);
+ assert.equal(geometry.parameters.detail, 0);
+ });
+
test('plane', function () {
var el = this.el;
var geometry;
diff --git a/tests/components/material.test.js b/tests/components/material.test.js
index ccd8bebb39f..6a9fa1d6725 100644
--- a/tests/components/material.test.js
+++ b/tests/components/material.test.js
@@ -1,5 +1,6 @@
/* global assert, process, setup, suite, test, AFRAME */
var entityFactory = require('../helpers').entityFactory;
+var shaders = require('core/shader').shaders;
var THREE = require('index').THREE;
suite('material', function () {
@@ -25,6 +26,13 @@ suite('material', function () {
assert.shallowDeepEqual(el.getObject3D('mesh').material.side, THREE.DoubleSide);
});
+ test('updates material shader', function () {
+ var el = this.el;
+ assert.equal(el.getObject3D('mesh').material.type, 'MeshBasicMaterial');
+ el.setAttribute('material', 'shader', 'standard');
+ assert.equal(el.getObject3D('mesh').material.type, 'MeshStandardMaterial');
+ });
+
test('disposes material when changing to new material', function () {
var el = this.el;
var material = el.getObject3D('mesh').material;
@@ -63,15 +71,24 @@ suite('material', function () {
var el = this.el;
var imageUrl = 'base/tests/assets/test.png';
el.setAttribute('material', 'src: url(' + imageUrl + ')');
- el.addEventListener('material-texture-loaded', function (evt) {
- assert.equal(evt.detail.src, imageUrl);
+ el.addEventListener('materialtextureloaded', function (evt) {
+ assert.equal(evt.detail.texture.image.getAttribute('src'), imageUrl);
done();
});
});
+
+ test('sets material to MeshShaderMaterial for custom shaders', function () {
+ var el = this.el;
+ delete shaders.test;
+ AFRAME.registerShader('test', {});
+ assert.equal(el.getObject3D('mesh').material.type, 'MeshBasicMaterial');
+ el.setAttribute('material', 'shader', 'test');
+ assert.equal(el.getObject3D('mesh').material.type, 'ShaderMaterial');
+ });
});
suite('updateSchema', function () {
- test('updates schema', function () {
+ test('updates schema for flat shader', function () {
var el = this.el;
el.components.material.updateSchema({shader: 'flat'});
assert.ok(el.components.material.schema.color);
@@ -84,39 +101,21 @@ suite('material', function () {
assert.notOk(el.components.material.schema.roughness);
assert.notOk(el.components.material.schema.envMap);
});
- });
-
- suite('updateShader', function () {
- test('updates material shader', function () {
- var el = this.el;
- assert.equal(el.getObject3D('mesh').material.type, 'MeshBasicMaterial');
- el.components.material.updateShader('standard');
- assert.equal(el.getObject3D('mesh').material.type, 'MeshStandardMaterial');
- });
- test('sets material to MeshShaderMaterial for custom shaders', function () {
+ test('updates schema for custom shader', function () {
var el = this.el;
+ delete shaders.test;
AFRAME.registerShader('test', {
schema: {
- 'luminance': { default: 1 }
- },
-
- vertexShader: [
- 'varying vec3 vWorldPosition;',
- 'void main() {',
- 'vec4 worldPosition = modelMatrix * vec4( position, 1.0 );',
- 'vWorldPosition = worldPosition.xyz;',
- 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
- '}'
- ].join('\n'),
-
- fragmentShader: [
- 'void main() { gl_FragColor = vec4(1.0,0.0,1.0,1.0); }'
- ].join('\n')
+ color: {type: 'color'},
+ luminance: {default: 1}
+ }
});
- assert.equal(el.getObject3D('mesh').material.type, 'MeshBasicMaterial');
- el.components.material.updateShader('test');
- assert.equal(el.getObject3D('mesh').material.type, 'ShaderMaterial');
+ el.setAttribute('material', 'shader', 'test');
+ assert.ok(el.components.material.schema.opacity);
+ assert.ok(el.components.material.schema.color);
+ assert.ok(el.components.material.schema.luminance);
+ assert.notOk(el.components.material.schema.src);
});
});
diff --git a/tests/systems/material.test.js b/tests/systems/material.test.js
index b3bb4968ee6..72e9a8790ac 100644
--- a/tests/systems/material.test.js
+++ b/tests/systems/material.test.js
@@ -1,11 +1,17 @@
/* global assert, process, setup, suite, test */
var entityFactory = require('../helpers').entityFactory;
-var THREE = require('index').THREE;
+
+var IMAGE1 = 'base/tests/assets/test.png';
+var IMAGE2 = 'base/tests/assets/test2.png';
+var VIDEO1 = 'base/tests/assets/test.mp4';
+var VIDEO2 = 'base/tests/assets/test2.mp4';
suite('material system', function () {
setup(function (done) {
+ var self = this;
var el = this.el = entityFactory();
el.addEventListener('loaded', function () {
+ self.system = el.sceneEl.systems.material;
done();
});
});
@@ -38,97 +44,182 @@ suite('material system', function () {
suite('texture caching', function () {
setup(function () {
- this.el.sceneEl.systems.material.clearTextureCache();
+ this.system.clearTextureCache();
});
- test('does not cache different image textures', function (done) {
- var el = this.el;
- var imageUrl = 'base/tests/assets/test.png';
- var imageUrl2 = 'base/tests/assets/test2.png';
- var textureSpy = this.sinon.spy(THREE, 'Texture');
- assert.equal(textureSpy.callCount, 0);
-
- el.setAttribute('material', 'src: url(' + imageUrl + ')');
-
- el.addEventListener('material-texture-loaded', function (evt) {
- var el2;
- assert.equal(textureSpy.callCount, 1);
-
- el2 = document.createElement('a-entity');
- el2.setAttribute('material', 'src: url(' + imageUrl2 + ')');
- el.sceneEl.appendChild(el2);
- el2.addEventListener('material-texture-loaded', function () {
- // Two textures created.
- assert.equal(textureSpy.callCount, 2);
+ suite('loadImage', function () {
+ test('loads image texture', function (done) {
+ var system = this.system;
+ var src = IMAGE1;
+ var data = {src: IMAGE1};
+ var hash = system.hash(data);
+
+ system.loadImage(src, data, function (texture) {
+ assert.equal(texture.image.getAttribute('src'), src);
+ system.textureCache[hash].then(function (texture2) {
+ assert.equal(texture, texture2);
+ done();
+ });
+ });
+ });
+
+ test('loads image given an element', function (done) {
+ var img = document.createElement('img');
+ var system = this.system;
+ var data = {src: IMAGE1};
+ var hash = system.hash(data);
+
+ img.setAttribute('src', IMAGE1);
+ system.loadImage(img, data, function (texture) {
+ assert.equal(texture.image, img);
+ system.textureCache[hash].then(function (texture2) {
+ assert.equal(texture, texture2);
+ done();
+ });
+ });
+ });
+
+ test('caches identical image textures', function (done) {
+ var system = this.system;
+ var src = IMAGE1;
+ var data = {src: src};
+ var hash = system.hash(data);
+
+ Promise.all([
+ new Promise(function (resolve) { system.loadImage(src, data, resolve); }),
+ new Promise(function (resolve) { system.loadImage(src, data, resolve); })
+ ]).then(function (results) {
+ assert.equal(results[0], results[1]);
+ assert.equal(results[0].image.getAttribute('src'), src);
+ assert.ok(system.textureCache[hash]);
+ assert.equal(Object.keys(system.textureCache).length, 1);
done();
});
});
- });
- test('can cache image textures', function (done) {
- var el = this.el;
- var imageUrl = 'base/tests/assets/test.png';
- var textureSpy = this.sinon.spy(THREE, 'Texture');
- assert.equal(textureSpy.callCount, 0);
-
- el.setAttribute('material', 'src: url(' + imageUrl + ')');
-
- el.addEventListener('material-texture-loaded', function () {
- var el2;
- assert.equal(textureSpy.callCount, 1);
-
- el2 = document.createElement('a-entity');
- el2.setAttribute('material', 'src: url(' + imageUrl + ')');
- el.sceneEl.appendChild(el2);
- el2.addEventListener('material-texture-loaded', function () {
- // Only one texture created.
- assert.equal(textureSpy.callCount, 1);
+ test('caches different textures for different images', function (done) {
+ var system = this.system;
+ var src1 = IMAGE1;
+ var src2 = IMAGE2;
+ var data1 = {src: src1};
+ var data2 = {src: src2};
+
+ Promise.all([
+ new Promise(function (resolve) { system.loadImage(src1, data1, resolve); }),
+ new Promise(function (resolve) { system.loadImage(src2, data2, resolve); })
+ ]).then(function (results) {
+ assert.equal(results[0].image.getAttribute('src'), src1);
+ assert.equal(results[1].image.getAttribute('src'), src2);
+ assert.notEqual(results[0].uuid, results[1].uuid);
done();
});
});
- });
- test('does not cache different video textures', function (done) {
- var el = this.el;
- var videoUrl = 'base/tests/assets/test.mp4';
- var videoUrl2 = 'base/tests/assets/test2.mp4';
- var textureSpy = this.sinon.spy(THREE, 'VideoTexture');
- assert.equal(textureSpy.callCount, 0);
-
- el.setAttribute('material', 'src: url(' + videoUrl + ')');
-
- el.addEventListener('material-texture-loaded', function (evt) {
- var el2;
- assert.equal(textureSpy.callCount, 1);
-
- el2 = document.createElement('a-entity');
- el2.setAttribute('material', 'src: url(' + videoUrl2 + ')');
- el.sceneEl.appendChild(el2);
- el2.addEventListener('material-texture-loaded', function () {
- // Two textures created.
- assert.equal(textureSpy.callCount, 2);
+ test('caches different textures for different repeat', function (done) {
+ var system = this.system;
+ var src = IMAGE1;
+ var data1 = {src: src};
+ var data2 = {src: src, repeat: '5 5'};
+ var hash1 = system.hash(data1);
+ var hash2 = system.hash(data2);
+
+ Promise.all([
+ new Promise(function (resolve) { system.loadImage(src, data1, resolve); }),
+ new Promise(function (resolve) { system.loadImage(src, data2, resolve); })
+ ]).then(function (results) {
+ assert.notEqual(results[0].uuid, results[1].uuid);
+ assert.shallowDeepEqual(results[0].repeat, {x: 1, y: 1});
+ assert.shallowDeepEqual(results[1].repeat, {x: 5, y: 5});
+ assert.equal(Object.keys(system.textureCache).length, 2);
+ assert.ok(system.textureCache[hash1]);
+ assert.ok(system.textureCache[hash2]);
done();
});
});
});
- test('can cache video textures', function (done) {
- var el = this.el;
- var videoUrl = 'base/tests/assets/test.mp4';
- var textureSpy = this.sinon.spy(THREE, 'VideoTexture');
- assert.equal(textureSpy.callCount, 0);
+ suite('loadVideo', function () {
+ test('loads video texture', function (done) {
+ var system = this.system;
+ var src = VIDEO1;
+ var data = {src: VIDEO1};
+
+ system.loadVideo(src, data, function (texture) {
+ var hash = Object.keys(system.textureCache)[0];
+ assert.equal(texture.image.getAttribute('src'), src);
+ system.textureCache[hash].then(function (result) {
+ assert.equal(texture, result.texture);
+ assert.equal(texture.image, result.videoEl);
+ done();
+ });
+ });
+ });
+
+ test('loads image given a