playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
141 lines (140 loc) • 4.32 kB
JavaScript
import { path } from "../../core/path.js";
import { http, Http } from "../../platform/net/http.js";
import { getDefaultMaterial } from "../../scene/materials/default-material.js";
import { GlbModelParser } from "../parsers/glb-model.js";
import { JsonModelParser } from "../parsers/json-model.js";
import { ResourceHandler } from "./handler.js";
class ModelHandler extends ResourceHandler {
/**
* Create a new ModelHandler instance.
*
* @param {AppBase} app - The running {@link AppBase}.
* @ignore
*/
constructor(app) {
super(app, "model");
this._parsers = [];
this.device = app.graphicsDevice;
this.assets = app.assets;
this.defaultMaterial = getDefaultMaterial(this.device);
this.addParser(new JsonModelParser(this), (url, data) => {
return path.getExtension(url) === ".json";
});
this.addParser(new GlbModelParser(this), (url, data) => {
return path.getExtension(url) === ".glb";
});
}
load(url, callback, asset) {
if (typeof url === "string") {
url = {
load: url,
original: url
};
}
const options = {
retry: this.maxRetries > 0,
maxRetries: this.maxRetries
};
if (url.load.startsWith("blob:") || url.load.startsWith("data:")) {
if (path.getExtension(url.original).toLowerCase() === ".glb") {
options.responseType = Http.ResponseType.ARRAY_BUFFER;
} else {
options.responseType = Http.ResponseType.JSON;
}
}
http.get(url.load, options, (err, response) => {
if (!callback) {
return;
}
if (!err) {
for (let i = 0; i < this._parsers.length; i++) {
const p = this._parsers[i];
if (p.decider(url.original, response)) {
p.parser.parse(response, (err2, parseResult) => {
if (err2) {
callback(err2);
} else {
callback(null, parseResult);
}
}, asset);
return;
}
}
callback("No parsers found");
} else {
callback(`Error loading model: ${url.original} [${err}]`);
}
});
}
open(url, data) {
return data;
}
patch(asset, assets) {
if (!asset.resource) {
return;
}
const data = asset.data;
const self = this;
asset.resource.meshInstances.forEach((meshInstance, i) => {
if (data.mapping) {
const handleMaterial = function(asset2) {
if (asset2.resource) {
meshInstance.material = asset2.resource;
} else {
asset2.once("load", handleMaterial);
assets.load(asset2);
}
asset2.once("remove", (asset3) => {
if (meshInstance.material === asset3.resource) {
meshInstance.material = self.defaultMaterial;
}
});
};
if (!data.mapping[i]) {
meshInstance.material = self.defaultMaterial;
return;
}
const id = data.mapping[i].material;
const url = data.mapping[i].path;
let material;
if (id !== void 0) {
if (!id) {
meshInstance.material = self.defaultMaterial;
} else {
material = assets.get(id);
if (material) {
handleMaterial(material);
} else {
assets.once(`add:${id}`, handleMaterial);
}
}
} else if (url) {
const path2 = asset.getAbsoluteUrl(data.mapping[i].path);
material = assets.getByUrl(path2);
if (material) {
handleMaterial(material);
} else {
assets.once(`add:url:${path2}`, handleMaterial);
}
}
}
});
}
/**
* Add a parser that converts raw data into a {@link Model}. Default parser is for JSON models.
*
* @param {object} parser - See JsonModelParser for example.
* @param {AddParserCallback} decider - Function that decides on which parser to use. Function
* should take (url, data) arguments and return true if this parser should be used to parse the
* data into a {@link Model}. The first parser to return true is used.
*/
addParser(parser, decider) {
this._parsers.push({
parser,
decider
});
}
}
export {
ModelHandler
};