From dd148226c9d24430ebd53e263dcea2874fa179fe Mon Sep 17 00:00:00 2001 From: "Theunis J. Botha" Date: Wed, 1 Feb 2017 16:09:34 +0100 Subject: [PATCH] getting therer -- deep linking objects --- src/game-lib-a-api-component.js | 2 +- src/game-lib-a-component-a.js | 125 +++++++++++++++++++++- src/game-lib-d3-api-image-factory.js | 55 ++++++++++ src/game-lib-d3-api-scene.js | 3 +- src/game-lib-d3-helper.js | 4 +- src/game-lib-d3-image-factory.js | 151 +++++++++++++++++++-------- src/game-lib-d3-material.js | 14 +-- src/game-lib-d3-mesh.js | 10 +- src/game-lib-d3-scene.js | 53 +++++----- src/game-lib-d3-texture.js | 17 ++- 10 files changed, 347 insertions(+), 87 deletions(-) create mode 100644 src/game-lib-d3-api-image-factory.js diff --git a/src/game-lib-a-api-component.js b/src/game-lib-a-api-component.js index 91146ec..848fd5f 100644 --- a/src/game-lib-a-api-component.js +++ b/src/game-lib-a-api-component.js @@ -23,7 +23,7 @@ GameLib.API.Component = function( loaded = false; } this.loaded = loaded; - + if (GameLib.Utils.UndefinedOrNull(parentEntity)) { parentEntity = null; } diff --git a/src/game-lib-a-component-a.js b/src/game-lib-a-component-a.js index 4901df0..cf523cb 100644 --- a/src/game-lib-a-component-a.js +++ b/src/game-lib-a-component-a.js @@ -22,6 +22,10 @@ GameLib.Component = function( this.idToObject = {}; + this.parentObjects = []; + + this.build = true; + this.linkedObjects.parentEntity = GameLib.Entity; }; @@ -55,6 +59,7 @@ GameLib.Component.COMPONENT_SKELETON = 0x19; GameLib.Component.COMPONENT_TEXTURE = 0x1a; GameLib.Component.COMPONENT_ENTITY_MANAGER = 0x1b; GameLib.Component.COMPONENT_DOM_ELEMENT = 0x1c; +GameLib.Component.COMPONENT_IMAGE_FACTORY = 0x1d; /** * Components are linked at runtime - for storing, we just store the ID @@ -66,12 +71,19 @@ GameLib.Component.prototype.toApiComponent = function() { GameLib.Component.prototype.buildIdToObject = function() { + if (!this.build) { + return; + } + + this.build = false; + this.idToObject = {}; for (var property in this.linkedObjects) { if ( this.linkedObjects.hasOwnProperty(property) && - this.hasOwnProperty(property) + this.hasOwnProperty(property) && + this[property] ) { if (this.linkedObjects[property] instanceof Array) { this.idToObject = GameLib.Utils.LoadIdsFromArrayToIdObject(this[property], this.idToObject); @@ -82,4 +94,115 @@ GameLib.Component.prototype.buildIdToObject = function() { } this.idToObject[this.id] = this; + + this.build = true; }; + +/** + * Some components need to be loaded first, and are referenced by ID string from other objects. + * These components should implement this 'linkObjects' function - this will be implementation specific. + * Example: Scene has meshes which have materials which have textures. + * However, we don't store the texture inside the material inside the mesh, since they are re-usable among meshes. + * Instead, we store them on the scene object, reference them by string id from the mesh, and after scene has loaded, + * we link the objects + */ +GameLib.Component.prototype.linkObjects = function(idToObject) { + + if (this.loaded) { + return; + } + + this.loaded = true; + + for (var property in this.linkedObjects) { + if ( + this.linkedObjects.hasOwnProperty(property) && + this.hasOwnProperty(property) && + this[property] + ) { + + if (this.linkedObjects[property] instanceof Array) { + if (this[property] instanceof Array) { + + this[property] = this[property].map( + function(p) { + if (p instanceof Object) { + + p.parentObjects.push(this); + + /** + * This object is already an object, does not need to be linked + */ + if (p.linkObjects) { + p.linkObjects(idToObject); + } + + return p; + } else if (typeof p == 'string') { + + if (!idToObject[p]) { + console.warn('Could not locate the object to be linked in array - fix this'); + throw new Error('Could not locate the object to be linked in array - fix this'); + } + + idToObject[p].parentObjects.push(this); + + /** + * Perform deep-linking + */ + if (idToObject[p].linkObjects) { + idToObject[p].linkObjects(idToObject); + } + + return idToObject[p]; + } else { + console.warn('Unhandled type : ', p); + throw new Error('Unhandled type : ', p); + } + + }.bind(this) + ) + } else { + console.warn('Incompatible Link Type - should be instance of array'); + throw new Error('Incompatible Link Type - should be instance of array'); + } + } else { + + if (this[property] instanceof Object) { + + this[property].parentObjects.push(this); + + /** + * This object is already an object + */ + if (this[property].linkObjects) { + this[property].linkObjects(idToObject); + } + + return this[property]; + + } else if (typeof this[property] == 'string') { + + if (!idToObject[this[property]]) { + console.warn('Could not locate the object to be linked - fix this'); + throw new Error('Could not locate the object to be linked - fix this'); + } + + this[property] = idToObject[this[property]]; + + this[property].parentObjects.push(this); + + /** + * Perform deep-linking + */ + if (this[property].linkObjects) { + this[property].linkObjects(idToObject); + } + } else { + console.warn('Unhandled property type - fix this : ' + typeof this[property]); + throw new Error('Unhandled property type - fix this : ' + typeof this[property]); + } + } + } + } +}; \ No newline at end of file diff --git a/src/game-lib-d3-api-image-factory.js b/src/game-lib-d3-api-image-factory.js new file mode 100644 index 0000000..7fb0b23 --- /dev/null +++ b/src/game-lib-d3-api-image-factory.js @@ -0,0 +1,55 @@ +/** + * Raw ImageFactory API object - should always correspond with the ImageFactory Schema + * @param id + * @param name + * @param baseUrl String + * @param parentEntity + * @constructor + */ +GameLib.D3.API.ImageFactory = function( + id, + name, + baseUrl, + parentEntity +) { + GameLib.Component.call( + this, + GameLib.Component.COMPONENT_IMAGE_FACTORY, + null, + null, + parentEntity + ); + + if (GameLib.Utils.UndefinedOrNull(id)) { + id = GameLib.Utils.RandomId(); + } + this.id = id; + + if (GameLib.Utils.UndefinedOrNull(name)) { + name = 'ImageFactory (' + this.id + ')'; + } + this.name = name; + + if (GameLib.Utils.UndefinedOrNull(baseUrl)) { + baseUrl = ''; + console.warn('No baseURL defined for image factory'); + } + this.baseUrl = baseUrl; +}; + +GameLib.D3.API.ImageFactory.prototype = Object.create(GameLib.Component.prototype); +GameLib.D3.API.ImageFactory.prototype.constructor = GameLib.D3.API.ImageFactory; + +/** + * Returns an API ImageFactory from an Object ImageFactory + * @param objectImageFactory + * @constructor + */ +GameLib.D3.API.ImageFactory.FromObjectImageFactory = function(objectImageFactory) { + return new GameLib.D3.API.ImageFactory( + objectImageFactory.id, + objectImageFactory.name, + objectImageFactory.baseUrl, + objectImageFactory.parentEntity + ); +}; diff --git a/src/game-lib-d3-api-scene.js b/src/game-lib-d3-api-scene.js index d52d655..4ea27fd 100644 --- a/src/game-lib-d3-api-scene.js +++ b/src/game-lib-d3-api-scene.js @@ -33,8 +33,7 @@ GameLib.D3.API.Scene = function( 'meshes' : [GameLib.D3.Mesh], 'lights' : [GameLib.D3.Light], 'textures' : [GameLib.D3.Texture], - 'materials' : [GameLib.D3.Material], - 'imageFactory' : GameLib.D3.ImageFactory + 'materials' : [GameLib.D3.Material] }, false, parentEntity diff --git a/src/game-lib-d3-helper.js b/src/game-lib-d3-helper.js index 3357d8f..d8c5d18 100644 --- a/src/game-lib-d3-helper.js +++ b/src/game-lib-d3-helper.js @@ -41,7 +41,7 @@ GameLib.D3.Helper = function Helper( object instanceof GameLib.D3.Mesh && object.meshType != GameLib.D3.Mesh.TYPE_CURVE ) { - helperType = GameLib.D3.Helper.HELPER_TYPE_WIREFRAME; + helperType = GameLib.D3.Helper.HELPER_TYPE_EDGES; } if (object instanceof GameLib.D3.Light) { @@ -92,7 +92,7 @@ GameLib.D3.Helper.prototype.createInstance = function(update) { } if (this.helperType == GameLib.D3.Helper.HELPER_TYPE_EDGES) { - instance = new THREE.WireframeHelper(this.object.instance, 0x007700); + instance = new THREE.EdgesHelper(this.object.instance, 0x007700); } if (this.helperType == GameLib.D3.Helper.HELPER_TYPE_DIRECTIONAL_LIGHT) { diff --git a/src/game-lib-d3-image-factory.js b/src/game-lib-d3-image-factory.js index 90bf0cb..913b042 100644 --- a/src/game-lib-d3-image-factory.js +++ b/src/game-lib-d3-image-factory.js @@ -1,71 +1,136 @@ /** * The image factory takes care that we only make requests for Image URLs which we have not already started downloading * @param graphics GameLib.D3.Graphics - * @param baseUrl + * @param apiImageFactory GameLib.D3.API.ImageFactory + * @param progressCallback * @returns {Function} * @constructor */ GameLib.D3.ImageFactory = function ( graphics, - baseUrl + apiImageFactory, + progressCallback ) { - graphics.isNotThreeThrow(); + this.graphics = graphics; - var promiseList = {}; - - return function(imagePath, progressCallback) { - - if (!imagePath) { - console.log('Attempted to download bad URL : ' + imagePath); - throw new Error('Bad URL : ' + imagePath); - } - - if (promiseList[imagePath]) { - return promiseList[imagePath]; - } - - var defer = Q.defer(); - - promiseList[imagePath] = defer.promise; - - GameLib.D3.ImageFactory.LoadImage(graphics, baseUrl + imagePath, defer, progressCallback); - - return promiseList[imagePath]; + if (GameLib.Utils.UndefinedOrNull(apiImageFactory)) { + apiImageFactory = {}; } + + if (GameLib.Utils.UndefinedOrNull(progressCallback)) { + progressCallback = null; + } + this.progressCallback = progressCallback; + + GameLib.D3.API.ImageFactory.call( + this, + apiImageFactory.id, + apiImageFactory.name, + apiImageFactory.baseUrl, + apiImageFactory.parentEntity + ); + + this.buildIdToObject(); + + this.promiseList = {}; + + this.instance = this.createInstance(); +}; + +GameLib.D3.ImageFactory.prototype = Object.create(GameLib.D3.API.ImageFactory.prototype); +GameLib.D3.ImageFactory.prototype.constructor = GameLib.D3.ImageFactory; + +GameLib.D3.ImageFactory.prototype.createInstance = function(update) { + + var instance = null; + + if (update) { + instance = this.instance; + } else { + instance = new THREE.ImageLoader(); + } + + instance.crossOrigin = ''; + + return instance; }; /** - * Loads an image and resolves the defered promise once it succeeded (or failed) - * @param graphics - * @param url - * @param defer - * @param progressCallback + * Update instance + */ +GameLib.D3.ImageFactory.prototype.updateInstance = function() { + this.instance = this.createInstance(true); +}; + + +/** + * Loads an image, either returns the image immediately if already loaded, or a promise to load the image + * @param imagePath + * @returns {*} * @constructor */ -GameLib.D3.ImageFactory.LoadImage = function ( - graphics, - url, - defer, - progressCallback +GameLib.D3.ImageFactory.prototype.loadImage = function( + imagePath ) { - var loader = new graphics.instance.ImageLoader(); + imagePath = imagePath.replace(new RegExp('\/*'), '/'); - loader.crossOrigin = ''; + if (!imagePath) { + console.log('Attempted to download bad URL : ' + imagePath); + throw new Error('Bad URL : ' + imagePath); + } - loader.load( - url + '?ts=' + Date.now(), + if (this.promiseList[imagePath]) { + return this.promiseList[imagePath]; + } + + var defer = Q.defer(); + + this.promiseList[imagePath] = defer.promise; + + this.instance.load( + this.baseUrl + imagePath + '?ts=' + Date.now(), function (image) { defer.resolve(image); - }, + }.bind(this), function onProgress(xhr) { - if (progressCallback) { - progressCallback((xhr.loaded / xhr.total * 100)); + if (this.progressCallback) { + this.progressCallback((xhr.loaded / xhr.total * 100)); } - }, + }.bind(this), function onError() { - defer.reject('Failed to download image : ' + url); - } + defer.reject('Failed to download image : ' + this.baseUrl + imagePath); + }.bind(this) + ); + + return this.promiseList[imagePath]; +}; + +/** + * Converts a GameLib.D3.ImageFactory to a GameLib.D3.API.ImageFactory + * @returns {GameLib.D3.API.ImageFactory} + */ +GameLib.D3.ImageFactory.prototype.toApiImageFactory = function() { + return new GameLib.D3.API.ImageFactory( + this.id, + this.name, + this.baseUrl, + GameLib.Utils.IdOrNull(this.parentEntity) ); }; + +/** + * Returns a new GameLib.D3.ImageFactory from a GameLib.D3.API.ImageFactory + * @param graphics GameLib.D3.Graphics + * @param objectImageFactory GameLib.D3.API.ImageFactory + * @returns {GameLib.D3.ImageFactory} + */ +GameLib.D3.ImageFactory.FromObjectImageFactory = function(graphics, objectImageFactory) { + + return new GameLib.D3.ImageFactory( + graphics, + GameLib.D3.API.ImageFactory.FromObjectImageFactory(objectImageFactory) + ); + +}; \ No newline at end of file diff --git a/src/game-lib-d3-material.js b/src/game-lib-d3-material.js index 26991d5..1bea64b 100644 --- a/src/game-lib-d3-material.js +++ b/src/game-lib-d3-material.js @@ -101,20 +101,20 @@ GameLib.D3.Material = function Material( this.specular = new GameLib.Color( graphics, - this, - this.specular + this.specular, + this ); this.color = new GameLib.Color( graphics, - this, - this.color + this.color, + this ); this.emissive = new GameLib.Color( graphics, - this, - this.emissive + this.emissive, + this ); if (this.alphaMap) { @@ -273,6 +273,8 @@ GameLib.D3.Material = function Material( } } + this.needsUpdate = false; + this.buildIdToObject(); this.instance = this.createInstance(); diff --git a/src/game-lib-d3-mesh.js b/src/game-lib-d3-mesh.js index 5efc726..a6bf2e2 100644 --- a/src/game-lib-d3-mesh.js +++ b/src/game-lib-d3-mesh.js @@ -296,11 +296,11 @@ GameLib.D3.Mesh.prototype.createInstance = function(update) { } if (this.meshType == GameLib.D3.Mesh.TYPE_NORMAL) { - instance = new THREE.Mesh(instanceGeometry, this.materials[0].instance); + instance = new THREE.Mesh(instanceGeometry); } if (this.meshType == GameLib.D3.Mesh.TYPE_CURVE) { - instance = new THREE.Points(instanceGeometry, this.materials[0].instance); + instance = new THREE.Points(instanceGeometry); } if (this.meshType == GameLib.D3.Mesh.TYPE_SKINNED) { @@ -333,7 +333,7 @@ GameLib.D3.Mesh.prototype.createInstance = function(update) { ); } - instance = new THREE.SkinnedMesh(instanceGeometry, this.materials[0].instance); + instance = new THREE.SkinnedMesh(instanceGeometry); instance.add(this.skeleton.rootBoneInstance); @@ -402,6 +402,10 @@ GameLib.D3.Mesh.prototype.createInstance = function(update) { instance.rotateZ(this.localRotation.z); } + if (this.materials.length == 1 && this.materials[0].instance) { + instance.material = this.materials[0].instance; + } + instance.renderOrder = this.renderOrder; return instance; diff --git a/src/game-lib-d3-scene.js b/src/game-lib-d3-scene.js index 58192f0..c21d4bc 100644 --- a/src/game-lib-d3-scene.js +++ b/src/game-lib-d3-scene.js @@ -54,8 +54,8 @@ GameLib.D3.Scene = function ( return new GameLib.D3.Mesh( this.graphics, apiMesh, - this.computeNormals, - this.imageFactory + this.imageFactory, + this.computeNormals ); } else { @@ -106,11 +106,15 @@ GameLib.D3.Scene = function ( function(apiTexture) { if (apiTexture instanceof GameLib.D3.API.Texture) { - return new GameLib.D3.Texture( + var texture = new GameLib.D3.Texture( this.graphics, apiTexture, this.imageFactory ); + + this.idToObject[texture.id] = texture; + + return texture; } else { console.warn('apiTexture not an instance of API.Texture'); throw new Error('apiTexture not an instance of API.Texture'); @@ -123,20 +127,39 @@ GameLib.D3.Scene = function ( function(apiMaterial) { if (apiMaterial instanceof GameLib.D3.API.Material) { - return new GameLib.D3.Material( + + var material = new GameLib.D3.Material( this.graphics, apiMaterial, this.imageFactory ); + + this.idToObject[material.id] = material; + + return material; + } else { console.warn('apiMaterial not an instance of API.Material'); throw new Error('apiMaterial not an instance of API.Material'); } - + }.bind(this) ); - this.linkObjects(); + this.idToObject[this.id] = this; + + this.linkObjects(this.idToObject); + + this.meshes.map( + function(mesh) { + mesh.updateInstance(); + mesh.materials.map( + function(material) { + material.updateInstance(); + } + ) + } + ); this.buildIdToObject(); @@ -146,24 +169,6 @@ GameLib.D3.Scene = function ( GameLib.D3.Scene.prototype = Object.create(GameLib.D3.API.Scene.prototype); GameLib.D3.Scene.prototype.constructor = GameLib.D3.Scene; -GameLib.D3.Scene.prototype.linkObjects = function() { - this.meshes.map( - function(mesh) { - this.materials = mesh.materials.map( - function(material) { - return this.idToObject[material]; - } - ); - - mesh.materials.map( - function(material) { - material.diffuseMap = this.idToObject[material.diffuseMap]; - } - ) - } - ) -}; - /** * Creates an instance scene * @returns {THREE.Scene} diff --git a/src/game-lib-d3-texture.js b/src/game-lib-d3-texture.js index 120ac3b..21cedea 100644 --- a/src/game-lib-d3-texture.js +++ b/src/game-lib-d3-texture.js @@ -77,14 +77,22 @@ GameLib.D3.Texture.prototype.constructor = GameLib.D3.Texture; */ GameLib.D3.Texture.prototype.loadTexture = function() { - this.imageData = this.imageFactory(this.imagePath); + this.imageData = this.imageFactory.loadImage(this.imagePath); this.imageData.then( function (imageInstance){ this.imageInstance = imageInstance; this.instance = this.createInstance(); + this.parentObjects.map( + function(parentObject){ + if (parentObject instanceof GameLib.D3.Material && parentObject.updateInstance) { + parentObject.updateInstance(); + } + } + ) }.bind(this), - function onRejected() { + function onRejected(message) { + console.warn(message); } ); }; @@ -173,7 +181,8 @@ GameLib.D3.Texture.TEXTURE_TYPE_SPECULAR = 'specular'; /** * Creates an instance of our texture object - * @returns {GameLib.D3.Texture|THREE.SoftwareRenderer.Texture|THREE.Texture|*|Texture} + * @param update + * @returns {*} */ GameLib.D3.Texture.prototype.createInstance = function(update) { @@ -213,8 +222,6 @@ GameLib.D3.Texture.prototype.createInstance = function(update) { instance.premultiplyAlpha = this.premultiplyAlpha; instance.textureType = this.textureType; - instance.needsUpdate = true; - return instance; };