/** * Storage System takes care loading and linking components and dependencies * @param apiSystem GameLib.API.System * @param apiUrl * @param token * @param apiUploadUrl * @param onImageLoaded * @param onImageProgress * @param onImageError * @constructor */ GameLib.System.Storage = function( apiSystem, apiUrl, token, apiUploadUrl, onImageLoaded, onImageProgress, onImageError ) { GameLib.System.call( this, apiSystem ); if (GameLib.Utils.UndefinedOrNull(apiUrl)) { console.warn('Need an API URL for a storage system'); apiUrl = ''; } this.apiUrl = apiUrl; if (GameLib.Utils.UndefinedOrNull(token)) { token = null; } this.token = token; if (GameLib.Utils.UndefinedOrNull(apiUploadUrl)) { console.warn('Need an API Upload URL for a storage system'); apiUploadUrl = ''; } this.apiUploadUrl = apiUploadUrl; if (GameLib.Utils.UndefinedOrNull(onImageLoaded)) { onImageLoaded = null; } this.onImageLoaded = onImageLoaded; if (GameLib.Utils.UndefinedOrNull(onImageProgress)) { onImageProgress = null; } this.onImageProgress = onImageProgress; if (GameLib.Utils.UndefinedOrNull(onImageError)) { onImageError = null; } this.onImageError = onImageError; this.loginSubscription = null; this.saveSubscription = null; this.loadSubscription = null; this.loadImageSubscription = null; this.meshInstanceCreatedSubscription = null; this.lightInstanceCreatedSubscription = null; this.sceneInstanceCreatedSubscription = null; this.imageInstanceCreatedSubscription = null; this.textureInstanceCreatedSubscription = null; this.materialInstanceCreatedSubscription = null; }; GameLib.System.Storage.prototype = Object.create(GameLib.System.prototype); GameLib.System.Storage.prototype.constructor = GameLib.System.Storage; GameLib.System.Storage.prototype.start = function() { this.loginSubscription = this.subscribe( GameLib.Event.LOGGED_IN, function(data) { this.token = data.token; } ); this.saveSubscription = this.subscribe( GameLib.Event.SAVE_COMPONENT, this.save ); this.loadSubscription = this.subscribe( GameLib.Event.LOAD_COMPONENT, this.load ); this.loadImageSubscription = this.subscribe( GameLib.Event.LOAD_IMAGE, this.loadImage ); this.sceneInstanceCreatedSubscription = this.subscribe( GameLib.Event.SCENE_INSTANCE_CREATED, this.sceneInstanceCreated ); this.meshInstanceCreatedSubscription = this.subscribe( GameLib.Event.MESH_INSTANCE_CREATED, this.meshInstanceCreated ); this.lightInstanceCreatedSubscription = this.subscribe( GameLib.Event.LIGHT_INSTANCE_CREATED, this.lightInstanceCreated ); this.imageInstanceCreatedSubscription = this.subscribe( GameLib.Event.IMAGE_INSTANCE_CREATED, this.imageInstanceCreated ); this.textureInstanceCreatedSubscription = this.subscribe( GameLib.Event.TEXTURE_INSTANCE_CREATED, this.textureInstanceCreated ); this.materialInstanceCreatedSubscription = this.subscribe( GameLib.Event.MATERIAL_INSTANCE_CREATED, this.materialInstanceCreated ); }; /** * 'Saves' data to baseURL */ GameLib.System.Storage.prototype.save = function(data) { if (typeof XMLHttpRequest === 'undefined') { console.log('Implement server side save here'); return; } var xhr = new XMLHttpRequest(); xhr.open( 'POST', this.apiUrl + '/component/create' ); xhr.setRequestHeader("Accept", "application/json"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function () { if (this.readyState === 4) { try { var response = JSON.parse(this.responseText) } catch (error) { GameLib.Event.Emit( GameLib.Event.SAVE_COMPONENT_ERROR, { message: this.responseText } ) } if (response.result === 'success') { GameLib.Event.Emit( GameLib.Event.COMPONENT_SAVED, { message: response.message || 'Successfully saved the component' } ) } else { GameLib.Event.Emit( GameLib.Event.SAVE_COMPONENT_ERROR, { message: response.message || 'The server responded but failed to save the component' } ) } } }; xhr.send(JSON.stringify({ component : data, session : this.token })); }; /** * 'Loads' data from a url */ GameLib.System.Storage.prototype.load = function(data) { if (typeof XMLHttpRequest === 'undefined') { console.log('Implement server side load here'); return; } var xhr = new XMLHttpRequest(); xhr.open( 'GET', data.url ); xhr.onreadystatechange = function (xhr) { return function () { if (xhr.readyState === 4) { try { var object = JSON.parse(xhr.responseText); } catch (error) { this.publish( GameLib.Event.LOAD_COMPONENT_ERROR, { error : error } ); return; } if (object.result !== 'success') { this.publish( GameLib.Event.LOAD_COMPONENT_ERROR, { error : object } ); return; } this.publish( GameLib.Event.COMPONENT_LOADED, { response : object, includeDependencies : data.includeDependencies } ) } } }(xhr).bind(this); xhr.send(); }; GameLib.System.Storage.prototype.loadImage = function(data) { console.log('loading image : ' + data.image.name); var onLoaded = this.onImageLoaded; var onProgress = this.onImageProgress;; var onError = this.onImageError; var image = data.image; var url = this.apiUploadUrl + image.path + '?ts=' + Date.now(); var preflight = new XMLHttpRequest(); preflight.withCredentials = true; preflight.open( 'OPTIONS', url ); preflight.setRequestHeader('Content-Type', 'application/json'); preflight.onload = function() { var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open('GET', url); xhr.setRequestHeader('Content-Type', image.contentType); xhr.responseType = 'blob'; xhr.onload = function() { try { if (this.response.type !== 'application/json') { var url = window.URL.createObjectURL(this.response); } else { if (onError) { onError(image, {message:'Image not found'}); return; } } } catch (error) { if (onError) { onError(image, {message:'Image not found'}); return; } } var img = document.createElement('img'); img.onload = function() { window.URL.revokeObjectURL(url); image.instance = img; image.publish( GameLib.Event.IMAGE_INSTANCE_CREATED, { image: image } ); if (onLoaded) { onLoaded(image); } }; img.src = url; }; xhr.onprogress = function(progressEvent) { var progress = 0; if (progressEvent.total !== 0) { progress = Number(progressEvent.loaded / progressEvent.total); progress *= 100; } if (onProgress) { onProgress(image, progress); } image.size = progressEvent.total; }; xhr.onerror = function(error) { console.warn('image load failed for image ' + image.name); if (onError) { onError(image, error) } }; xhr.send(); }; preflight.onerror = function(error) { console.warn('image pre-flight request failed for image ' + image.name); if (onError) { onError(image, error); } }; preflight.send(); }; GameLib.System.Storage.prototype.meshInstanceCreated = function(data) { var scenes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Scene]); scenes.map(function(scene){ if (data.mesh.parentScene === scene) { scene.addObject(data.mesh); } }); }; GameLib.System.Storage.prototype.lightInstanceCreated = function(data) { var scenes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Scene]); scenes.map(function(scene){ if (data.light.parentScene === scene) { scene.addObject(data.light); } }); }; GameLib.System.Storage.prototype.sceneInstanceCreated = function(data) { var scene = data.scene; scene.images.map( function(image){ GameLib.Event.Emit( GameLib.Event.LOAD_IMAGE, { onLoaded : function(image) { if (this.onImageLoaded) { this.onImageLoaded(image); } }, onProgress : function(image, progress) { if (this.onImageProgress) { this.onImageProgress(image, progress); } }, onError : function(image, error) { if (this.onImageError) { this.onImageError(image, error); } }, image : image } ); }.bind(this) ); /** * Add all meshes and lights */ var object = GameLib.EntityManager.Instance.queryComponents([ GameLib.D3.Mesh, GameLib.D3.Light ]); object.map(function(object){ if ( object.parentScene === scene ) { scene.addObject(object); } }); }; GameLib.System.Storage.prototype.imageInstanceCreated = function(data) { var textures = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Texture]); textures.map( function(texture) { /** * Only work with images that belong to this texture */ if ( texture.image === data.image ) { /** * Update instance, if already an instance */ if (texture.instance) { texture.updateInstance(); GameLib.Event.Emit( GameLib.Event.TEXTURE_INSTANCE_UPDATED, { texture : texture } ) } else { /** * Create a new instance */ texture.instance = texture.createInstance(); GameLib.Event.Emit( GameLib.Event.TEXTURE_INSTANCE_CREATED, { texture : texture } ) } } } ); }; GameLib.System.Storage.prototype.textureInstanceCreated = function(data) { var materials = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Material]); materials.map( function(material) { if (!material.instance) { console.log('No material instance for ' + material.name); return; } var modified = false; /** * We also need to check if the image of the texture is assigned - * if not we should disable the map */ if (material.alphaMap === data.texture) { if (data.texture.image) { material.instance.alphaMap = data.texture.instance; } else { material.instance.alphaMap = null; } modified = true; } if (material.aoMap === data.texture) { if (data.texture.image) { material.instance.aoMap = data.texture.instance; } else { material.instance.aoMap = null; } modified = true; } if (material.bumpMap === data.texture) { if (data.texture.image) { material.instance.bumpMap = data.texture.instance; } else { material.instance.bumpMap = null; } modified = true; } if (material.diffuseMap === data.texture) { if (data.texture.image) { material.instance.map = data.texture.instance; } else { material.instance.map = null; } modified = true; } if (material.displacementMap === data.texture) { if (data.texture.image) { material.instance.displacementMap = data.texture.instance; } else { material.instance.displacementMap = null; } modified = true; } if (material.emissiveMap === data.texture) { if (data.texture.image) { material.instance.emissiveMap = data.texture.instance; } else { material.instance.emissiveMap = null; } modified = true; } if (material.environmentMap === data.texture) { if (data.texture.image) { material.instance.envMap = data.texture.instance; } else { material.instance.envMap = null; } modified = true; } if (material.lightMap === data.texture) { if (data.texture.image) { material.instance.lightMap = data.texture.instance; } else { material.instance.lightMap = null; } modified = true; } if (material.metalnessMap === data.texture) { if (data.texture.image) { material.instance.metalnessMap = data.texture.instance; } else { material.instance.metalnessMap = null; } modified = true; } if (material.normalMap === data.texture) { if (data.texture.image) { material.instance.normalMap = data.texture.instance; } else { material.instance.normalMap = null; } modified = true; } if (material.roughnessMap === data.texture) { if (data.texture.image) { material.instance.roughnessMap = data.texture.instance; } else { material.instance.roughnessMap = null; } modified = true; } if (material.specularMap === data.texture) { if (data.texture.image) { material.instance.specularMap = data.texture.instance; } else { material.instance.specularMap = null; } modified = true; } if (modified) { material.updateInstance(); GameLib.Event.Emit( GameLib.Event.MATERIAL_INSTANCE_UPDATED, { material : material } ) } } ); }; GameLib.System.Storage.prototype.materialInstanceCreated = function(data) { var meshes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Mesh]); meshes.map(function(mesh){ if (!mesh.instance) { return; } /** * Only work with materials assigned to us */ if (mesh.materials[0] !== data.material) { return; } if (mesh.instance.material === data.material.instance) { //mesh.instance.geometry.uvsNeedUpdate = true; return; } if (mesh.materials[0] === data.material) { if (mesh.instance.material !== data.material.instance) { mesh.instance.material = data.material.instance; } //mesh.instance.geometry.uvsNeedUpdate = true; } }); }; GameLib.System.Storage.prototype.stop = function() { this.loginSubscription.remove(); this.loadSubscription.remove(); this.saveSubscription.remove(); this.loadImageSubscription.remove(); this.meshInstanceCreatedSubscription.remove(); this.lightInstanceCreatedSubscription.remove(); this.sceneInstanceCreatedSubscription.remove(); this.imageInstanceCreatedSubscription.remove(); this.textureInstanceCreatedSubscription.remove(); this.materialInstanceCreatedSubscription.remove(); };