Files
GDevelop/GDJS/Runtime/Model3DManager.ts

181 lines
5.3 KiB
TypeScript

/*
* 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<ResourceKind> = ['model3D'];
/**
* Load GLB files (using `Three.js`), using the "model3D" resources
* registered in the game resources.
*/
export class Model3DManager implements gdjs.ResourceManager {
/**
* Map associating a resource name to the loaded Three.js model.
*/
private _loadedThreeModels = new gdjs.ResourceCache<THREE_ADDONS.GLTF>();
private _downloadedArrayBuffers = new gdjs.ResourceCache<ArrayBuffer>();
_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<void> {
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<void> {
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
);
}
}
/**
* 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.get(resourceData);
if (loadedThreeModel) {
loadedThreeModel.scene.clear();
this._loadedThreeModels.delete(resourceData);
}
const downloadedArrayBuffer =
this._downloadedArrayBuffers.get(resourceData);
if (downloadedArrayBuffer) {
this._downloadedArrayBuffers.delete(resourceData);
}
}
}
}