/* * GDevelop JS Platform * Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved. * This project is released under the MIT License. */ namespace gdjs { const logger = new gdjs.Logger('Model3DManager'); const resourceKinds: Array = ['model3D']; /** * Load GLB files (using `Three.js`), using the "model3D" resources * registered in the game resources. * @category Resources > 3D Models */ export class Model3DManager implements gdjs.ResourceManager { /** * Map associating a resource name to the loaded Three.js model. */ private _loadedThreeModels = new gdjs.ResourceCache(); private _downloadedArrayBuffers = new gdjs.ResourceCache(); _resourceLoader: gdjs.ResourceLoader; _loader: THREE_ADDONS.GLTFLoader | null = null; _dracoLoader: THREE_ADDONS.DRACOLoader | null = null; //@ts-ignore Can only be null if THREE is not loaded. _invalidModel: THREE_ADDONS.GLTF; /** * @param resourceLoader The resources loader of the game. */ constructor(resourceLoader: gdjs.ResourceLoader) { this._resourceLoader = resourceLoader; if (typeof THREE !== 'undefined') { this._loader = new THREE_ADDONS.GLTFLoader(); this._dracoLoader = new THREE_ADDONS.DRACOLoader(); this._dracoLoader.setDecoderPath('./pixi-renderers/draco/gltf/'); this._loader.setDRACOLoader(this._dracoLoader); /** * The invalid model is a box with magenta (#ff00ff) faces, to be * easily spotted if rendered on screen. */ const group = new THREE.Group(); group.add( new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: '#ff00ff' }) ) ); this._invalidModel = { scene: group, animations: [], cameras: [], scenes: [], asset: {}, userData: {}, //@ts-ignore parser: null, }; } } getResourceKinds(): ResourceKind[] { return resourceKinds; } async processResource(resourceName: string): Promise { const resource = this._resourceLoader.getResource(resourceName); if (!resource) { logger.warn( 'Unable to find texture for resource "' + resourceName + '".' ); return; } const loader = this._loader; if (!loader) { return; } const data = this._downloadedArrayBuffers.get(resource); if (!data) { return; } this._downloadedArrayBuffers.delete(resource); try { const gltf: THREE_ADDONS.GLTF = await loader.parseAsync(data, ''); this._loadedThreeModels.set(resource, gltf); } catch (error) { logger.error( "Can't fetch the 3D model file " + resource.file + ', error: ' + error ); } } async loadResource(resourceName: string): Promise { const resource = this._resourceLoader.getResource(resourceName); if (!resource) { logger.warn( 'Unable to find texture for resource "' + resourceName + '".' ); return; } const loader = this._loader; if (!loader) { return; } if (this._loadedThreeModels.get(resource)) { return; } const url = this._resourceLoader.getFullUrl(resource.file); try { const response = await fetch(url, { credentials: this._resourceLoader.checkIfCredentialsRequired(url) ? 'include' : 'omit', }); if (!response.ok) { throw new Error('Network response was not ok'); } const data = await response.arrayBuffer(); this._downloadedArrayBuffers.set(resource, data); } catch (error) { logger.error( "Can't fetch the 3D model file " + resource.file + ', error: ' + error ); throw error; } } /** * Return a 3D model. * * Caller should not modify the object but clone it. * * @param resourceName The name of the json resource. * @returns a 3D model if it exists. */ getModel(resourceName: string): THREE_ADDONS.GLTF { return ( this._loadedThreeModels.getFromName(resourceName) || this._invalidModel ); } /** * To be called when the game is disposed. * Clear the models, resources loaded and destroy 3D models loaders in this manager. */ dispose(): void { this._loadedThreeModels.clear(); this._downloadedArrayBuffers.clear(); this._loader = null; this._dracoLoader = null; if (this._invalidModel) { this._invalidModel.cameras = []; this._invalidModel.animations = []; this._invalidModel.scenes = []; this._invalidModel.userData = {}; this._invalidModel.asset = {}; this._invalidModel.scene.clear(); } } unloadResource(resourceData: ResourceData): void { const loadedThreeModel = this._loadedThreeModels.getFromName( resourceData.name ); if (loadedThreeModel) { loadedThreeModel.scene.clear(); this._loadedThreeModels.delete(resourceData); } const downloadedArrayBuffer = this._downloadedArrayBuffers.getFromName( resourceData.name ); if (downloadedArrayBuffer) { this._downloadedArrayBuffers.delete(resourceData); } } } }