var geometries = require('../core/geometry').geometries; var registerSystem = require('../core/system').registerSystem; var THREE = require('../lib/three'); /** * System for geometry component. * Handle geometry caching. * * @member {object} cache - Mapping of stringified component data to THREE.Geometry objects. * @member {object} cacheCount - Keep track of number of entities using a geometry to * know whether to dispose on removal. */ module.exports.System = registerSystem('geometry', { init: function () { this.cache = {}; this.cacheCount = {}; }, /** * Reset cache. Mainly for testing. */ clearCache: function () { this.cache = {}; this.cacheCount = {}; }, /** * Attempt to retrieve from cache. * * @returns {Object|null} A geometry if it exists, else null. */ getOrCreateGeometry: function (data) { var cache = this.cache; var cachedGeometry; var hash; // Skip all caching logic. if (data.skipCache) { return createGeometry(data); } // Try to retrieve from cache first. hash = this.hash(data); cachedGeometry = cache[hash]; incrementCacheCount(this.cacheCount, hash); if (cachedGeometry) { return cachedGeometry; } // Create geometry. cachedGeometry = createGeometry(data); // Cache and return geometry. cache[hash] = cachedGeometry; return cachedGeometry; }, /** * Let system know that an entity is no longer using a geometry. */ unuseGeometry: function (data) { var cache = this.cache; var cacheCount = this.cacheCount; var geometry; var hash; if (data.skipCache) { return; } hash = this.hash(data); if (!cache[hash]) { return; } decrementCacheCount(cacheCount, hash); // Another entity is still using this geometry. No need to do anything. if (cacheCount[hash] > 0) { return; } // No more entities are using this geometry. Dispose. geometry = cache[hash]; geometry.dispose(); delete cache[hash]; delete cacheCount[hash]; }, /** * Use JSON.stringify to turn component data into hash. * Should be deterministic within a single browser engine. * If not, then look into json-stable-stringify. */ hash: function (data) { return JSON.stringify(data); } }); /** * Create geometry using component data. * * @param {object} data - Component data. * @returns {object} Geometry. */ function createGeometry (data) { var geometryType = data.primitive; var GeometryClass = geometries[geometryType] && geometries[geometryType].Geometry; var geometryInstance = new GeometryClass(); if (!GeometryClass) { throw new Error('Unknown geometry `' + geometryType + '`'); } geometryInstance.init(data); return toBufferGeometry(geometryInstance.geometry, data.buffer); } /** * Decreate count of entity using a geometry. */ function decrementCacheCount (cacheCount, hash) { cacheCount[hash]--; } /** * Increase count of entity using a geometry. */ function incrementCacheCount (cacheCount, hash) { cacheCount[hash] = cacheCount[hash] === undefined ? 1 : cacheCount[hash] + 1; } /** * Transform geometry to BufferGeometry if `doBuffer`. * * @param {object} geometry * @param {boolean} doBuffer * @returns {object} Geometry. */ function toBufferGeometry (geometry, doBuffer) { var bufferGeometry; if (!doBuffer) { return geometry; } bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry); bufferGeometry.metadata = {type: geometry.type, parameters: geometry.parameters || {}}; geometry.dispose(); // Dispose no longer needed non-buffer geometry. return bufferGeometry; }