@@ -2,9 +2,6 @@ var registerSystem = require('../core/system').registerSystem;
22var THREE = require ( '../lib/three' ) ;
33var utils = require ( '../utils/' ) ;
44
5- var EVENTS = {
6- TEXTURE_LOADED : 'material-texture-loaded'
7- } ;
85var debug = utils . debug ;
96var error = debug ( 'components:texture:error' ) ;
107var TextureLoader = new THREE . TextureLoader ( ) ;
@@ -30,58 +27,69 @@ module.exports.System = registerSystem('material', {
3027 } ,
3128
3229 /**
33- * High-level function for loading image textures. Meat of logic is in `loadImageTexture` .
30+ * Determine whether `src` is a image or video. Then try to load the asset, then call back .
3431 *
35- * @param {Element } el - Entity, used to emit event.
36- * @param {object } material - three.js material, bound by the A-Frame shader.
37- * @param {object } data - Shader data, bound by the A-Frame shader.
38- * @param {Element|string } src - Texture source, bound by `src-loader` utils.
32+ * @param {string } src - Texture URL.
33+ * @param {string } data - Relevant texture data used for caching.
34+ * @param {function } cb - Callback to pass texture to.
3935 */
40- loadImage : function ( el , material , data , src ) {
41- var repeat = data . repeat || '1 1' ;
42- var srcString = src ;
43- var textureCache = this . textureCache ;
36+ loadTexture : function ( src , data , cb ) {
37+ var self = this ;
38+ utils . srcLoader . validateSrc ( src , loadImageCb , loadVideoCb ) ;
39+ function loadImageCb ( src ) { self . loadImage ( src , data , cb ) ; }
40+ function loadVideoCb ( src ) { self . loadVideo ( src , data , cb ) ; }
41+ } ,
4442
45- if ( typeof src !== 'string' ) { srcString = src . getAttribute ( 'src' ) ; }
43+ /**
44+ * High-level function for loading image textures (THREE.Texture).
45+ *
46+ * @param {Element|string } src - Texture source.
47+ * @param {object } data - Texture data.
48+ * @param {function } cb - Callback to pass texture to.
49+ */
50+ loadImage : function ( src , data , cb ) {
51+ var hash = this . hash ( data ) ;
52+ var handleImageTextureLoaded = cb ;
53+ var textureCache = this . textureCache ;
4654
47- // Another material is already loading this texture . Wait on promise.
48- if ( textureCache [ src ] && textureCache [ src ] [ repeat ] ) {
49- textureCache [ src ] [ repeat ] . then ( handleImageTextureLoaded ) ;
55+ // Texture already being loaded or already loaded . Wait on promise.
56+ if ( textureCache [ hash ] ) {
57+ textureCache [ hash ] . then ( handleImageTextureLoaded ) ;
5058 return ;
5159 }
5260
53- // Material instance is first to try to load this texture. Load it.
54- textureCache [ srcString ] = textureCache [ srcString ] || { } ;
55- textureCache [ srcString ] [ repeat ] = textureCache [ srcString ] [ repeat ] || { } ;
56- textureCache [ srcString ] [ repeat ] = loadImageTexture ( material , src , repeat ) ;
57- textureCache [ srcString ] [ repeat ] . then ( handleImageTextureLoaded ) ;
58-
59- function handleImageTextureLoaded ( texture ) {
60- utils . material . updateMaterialTexture ( material , texture ) ;
61- el . emit ( EVENTS . TEXTURE_LOADED , { src : src , texture : texture } ) ;
62- }
61+ // Texture not yet being loaded. Start loading it.
62+ textureCache [ hash ] = loadImageTexture ( src , data ) ;
63+ textureCache [ hash ] . then ( handleImageTextureLoaded ) ;
6364 } ,
6465
65- /**
66- * Load video texture.
67- * Note that creating a video texture is more synchronous than creating an image texture.
66+ /**
67+ * Load video texture (THREE.VideoTexture).
68+ * Which is just an image texture that RAFs + needsUpdate.
69+ * Note that creating a video texture is synchronous unlike loading an image texture.
70+ * Made asynchronous to be consistent with image textures.
6871 *
69- * @param {Element } el - Entity, used to emit event.
70- * @param {object } material - three.js material.
71- * @param data {object} - Shader data, bound by the A-Frame shader.
72- * @param src {Element|string} - Texture source, bound by `src-loader` utils.
72+ * @param {Element|string } src - Texture source.
73+ * @param {object } data - Texture data.
74+ * @param {function } cb - Callback to pass texture to.
7375 */
74- loadVideo : function ( el , material , data , src ) {
76+ loadVideo : function ( src , data , cb ) {
7577 var hash ;
7678 var texture ;
7779 var textureCache = this . textureCache ;
7880 var videoEl ;
7981 var videoTextureResult ;
8082
83+ function handleVideoTextureLoaded ( result ) {
84+ result . texture . needsUpdate = true ;
85+ cb ( result . texture , result . videoEl ) ;
86+ }
87+
88+ // Video element provided.
8189 if ( typeof src !== 'string' ) {
8290 // Check cache before creating texture.
8391 videoEl = src ;
84- hash = calculateVideoCacheHash ( videoEl ) ;
92+ hash = this . hashVideo ( data , videoEl ) ;
8593 if ( textureCache [ hash ] ) {
8694 textureCache [ hash ] . then ( handleVideoTextureLoaded ) ;
8795 return ;
@@ -90,11 +98,11 @@ module.exports.System = registerSystem('material', {
9098 fixVideoAttributes ( videoEl ) ;
9199 }
92100
93- // Use video element to create texture.
94- videoEl = videoEl || createVideoEl ( material , src , data . width , data . height ) ;
101+ // Only URL provided. Use video element to create texture.
102+ videoEl = videoEl || createVideoEl ( src , data . width , data . height ) ;
95103
96104 // Generated video element already cached. Use that.
97- hash = calculateVideoCacheHash ( videoEl ) ;
105+ hash = this . hashVideo ( data , videoEl ) ;
98106 if ( textureCache [ hash ] ) {
99107 textureCache [ hash ] . then ( handleVideoTextureLoaded ) ;
100108 return ;
@@ -103,28 +111,20 @@ module.exports.System = registerSystem('material', {
103111 // Create new video texture.
104112 texture = new THREE . VideoTexture ( videoEl ) ;
105113 texture . minFilter = THREE . LinearFilter ;
114+ setTextureProperties ( texture , data ) ;
106115
107116 // Cache as promise to be consistent with image texture caching.
108- videoTextureResult = {
109- texture : texture ,
110- videoEl : videoEl
111- } ;
117+ videoTextureResult = { texture : texture , videoEl : videoEl } ;
112118 textureCache [ hash ] = Promise . resolve ( videoTextureResult ) ;
113119 handleVideoTextureLoaded ( videoTextureResult ) ;
120+ } ,
114121
115- function handleVideoTextureLoaded ( res ) {
116- texture = res . texture ;
117- videoEl = res . videoEl ;
118- utils . material . updateMaterialTexture ( material , texture ) ;
119- el . emit ( EVENTS . TEXTURE_LOADED , { element : videoEl , src : src } ) ;
120- videoEl . addEventListener ( 'loadeddata' , function ( ) {
121- el . emit ( 'material-video-loadeddata' , { element : videoEl , src : src } ) ;
122- } ) ;
123- videoEl . addEventListener ( 'ended' , function ( ) {
124- // Works for non-looping videos only.
125- el . emit ( 'material-video-ended' , { element : videoEl , src : src } ) ;
126- } ) ;
127- }
122+ hash : function ( data ) {
123+ return JSON . stringify ( data ) ;
124+ } ,
125+
126+ hashVideo : function ( data , videoEl ) {
127+ return calculateVideoCacheHash ( data , videoEl ) ;
128128 } ,
129129
130130 /**
@@ -161,10 +161,11 @@ module.exports.System = registerSystem('material', {
161161 * If the video element has an ID, use that.
162162 * Else build a hash that looks like `src:myvideo.mp4;height:200;width:400;`.
163163 *
164+ * @param data {object} - Texture data such as repeat.
164165 * @param videoEl {Element} - Video element.
165166 * @returns {string }
166167 */
167- function calculateVideoCacheHash ( videoEl ) {
168+ function calculateVideoCacheHash ( data , videoEl ) {
168169 var i ;
169170 var id = videoEl . getAttribute ( 'id' ) ;
170171 var hash ;
@@ -174,7 +175,7 @@ function calculateVideoCacheHash (videoEl) {
174175
175176 // Calculate hash using sorted video attributes.
176177 hash = '' ;
177- videoAttributes = { } ;
178+ videoAttributes = data || { } ;
178179 for ( i = 0 ; i < videoEl . attributes . length ; i ++ ) {
179180 videoAttributes [ videoEl . attributes [ i ] . name ] = videoEl . attributes [ i ] . value ;
180181 }
@@ -186,84 +187,84 @@ function calculateVideoCacheHash (videoEl) {
186187}
187188
188189/**
189- * Set image texture on material as `map` .
190+ * Load image texture.
190191 *
191192 * @private
192- * @param {object } el - Entity element.
193- * @param {object } material - three.js material.
194193 * @param {string|object } src - An <img> element or url to an image file.
195- * @param {string } repeat - X and Y value for size of texture repeating (in UV units) .
194+ * @param {object } data - Data to set texture properties like `repeat` .
196195 * @returns {Promise } Resolves once texture is loaded.
197196 */
198- function loadImageTexture ( material , src , repeat ) {
197+ function loadImageTexture ( src , data ) {
199198 return new Promise ( doLoadImageTexture ) ;
200199
201200 function doLoadImageTexture ( resolve , reject ) {
202201 var isEl = typeof src !== 'string' ;
203202
203+ function resolveTexture ( texture ) {
204+ setTextureProperties ( texture , data ) ;
205+ texture . needsUpdate = true ;
206+ resolve ( texture ) ;
207+ }
208+
204209 // Create texture from an element.
205210 if ( isEl ) {
206- createTexture ( src ) ;
211+ resolveTexture ( new THREE . Texture ( src ) ) ;
207212 return ;
208213 }
209214
210215 // Load texture from src string. THREE will create underlying element.
211216 // Use THREE.TextureLoader (src, onLoad, onProgress, onError) to load texture.
212217 TextureLoader . load (
213218 src ,
214- createTexture ,
219+ resolveTexture ,
215220 function ( ) { /* no-op */ } ,
216221 function ( xhr ) {
217222 error ( '`$s` could not be fetched (Error code: %s; Response: %s)' , xhr . status ,
218223 xhr . statusText ) ;
219224 }
220225 ) ;
226+ }
227+ }
221228
222- /**
223- * Texture loaded. Set it.
224- */
225- function createTexture ( texture ) {
226- var repeatXY ;
227- if ( ! ( texture instanceof THREE . Texture ) ) { texture = new THREE . Texture ( texture ) ; }
228-
229- // Handle UV repeat.
230- repeatXY = repeat . split ( ' ' ) ;
231- if ( repeatXY . length === 2 ) {
232- texture . wrapS = THREE . RepeatWrapping ;
233- texture . wrapT = THREE . RepeatWrapping ;
234- texture . repeat . set ( parseInt ( repeatXY [ 0 ] , 10 ) , parseInt ( repeatXY [ 1 ] , 10 ) ) ;
235- }
229+ /**
230+ * Set texture properties such as repeat.
231+ *
232+ * @param {object } data - With keys like `repeat`.
233+ */
234+ function setTextureProperties ( texture , data ) {
235+ // Handle UV repeat.
236+ var repeat = data . repeat || '1 1' ;
237+ var repeatXY = repeat . split ( ' ' ) ;
236238
237- resolve ( texture ) ;
238- }
239- }
239+ // Don't bother setting repeat if it is 1/1. Power-of-two is required to repeat.
240+ if ( repeat === '1 1' || repeatXY . length !== 2 ) { return ; }
241+
242+ texture . wrapS = THREE . RepeatWrapping ;
243+ texture . wrapT = THREE . RepeatWrapping ;
244+ texture . repeat . set ( parseInt ( repeatXY [ 0 ] , 10 ) , parseInt ( repeatXY [ 1 ] , 10 ) ) ;
240245}
241246
242247/**
243248 * Create video element to be used as a texture.
244249 *
245- * @param {object } material - three.js material.
246250 * @param {string } src - Url to a video file.
247251 * @param {number } width - Width of the video.
248252 * @param {number } height - Height of the video.
249253 * @returns {Element } Video element.
250254 */
251- function createVideoEl ( material , src , width , height ) {
252- var el = material . videoEl || document . createElement ( 'video' ) ;
253- el . width = width ;
254- el . height = height ;
255- if ( el !== this . videoEl ) {
256- el . setAttribute ( 'webkit-playsinline' , '' ) ; // To support inline videos in iOS webviews.
257- el . autoplay = true ;
258- el . loop = true ;
259- el . crossOrigin = true ;
260- el . addEventListener ( 'error' , function ( ) {
261- warn ( '`$s` is not a valid video' , src ) ;
262- } , true ) ;
263- material . videoEl = el ;
264- }
265- el . src = src ;
266- return el ;
255+ function createVideoEl ( src , width , height ) {
256+ var videoEl = document . createElement ( 'video' ) ;
257+ videoEl . width = width ;
258+ videoEl . height = height ;
259+ videoEl . setAttribute ( 'webkit-playsinline' , '' ) ; // Support inline videos for iOS webviews.
260+ videoEl . autoplay = true ;
261+ videoEl . loop = true ;
262+ videoEl . crossOrigin = true ;
263+ videoEl . addEventListener ( 'error' , function ( ) {
264+ warn ( '`$s` is not a valid video' , src ) ;
265+ } , true ) ;
266+ videoEl . src = src ;
267+ return videoEl ;
267268}
268269
269270/**
@@ -280,10 +281,8 @@ function createVideoEl (material, src, width, height) {
280281 * @returns {Element } Video element with the correct properties updated.
281282 */
282283function fixVideoAttributes ( videoEl ) {
284+ videoEl . autoplay = videoEl . getAttribute ( 'autoplay' ) !== 'false' ;
283285 videoEl . controls = videoEl . getAttribute ( 'controls' ) !== 'false' ;
284- if ( videoEl . getAttribute ( 'autoplay' ) === 'false' ) {
285- videoEl . removeAttribute ( 'autoplay' ) ;
286- }
287286 if ( videoEl . getAttribute ( 'loop' ) === 'false' ) {
288287 videoEl . removeAttribute ( 'loop' ) ;
289288 }
0 commit comments