Source: resources/model.js

Object.assign(pc, function () {
    /**
     * @constructor
     * @name pc.ModelHandler
     * @classdesc Resource Handler for creating pc.Model resources
     * @description {@link pc.ResourceHandler} use to load 3D model resources
     * @param {pc.GraphicsDevice} device The graphics device that will be rendering
     * @param {pc.StandardMaterial} defaultMaterial The shared default material that is used in any place that a material is not specified
     */
    var ModelHandler = function (device, defaultMaterial) {
        this._device = device;
        this._parsers = [];
        this._defaultMaterial = defaultMaterial;
        this.retryRequests = false;

        this.addParser(new pc.JsonModelParser(this._device), function (url, data) {
            return (pc.path.getExtension(url) === '.json');
        });
    };

    Object.assign(ModelHandler.prototype, {
        /**
         * @function
         * @name pc.ModelHandler#load
         * @description Fetch model data from a remote url
         * @param {String} url The URL of the model data.
         * @param {Function} callback Callback function called when the load completes. The
         * callback is of the form fn(err, response), where err is a String error message in
         * the case where the load fails, and repsponse is the model data that has been
         * successfully loaded.
         */
        load: function (url, callback) {
            if (typeof url === 'string') {
                url = {
                    load: url,
                    original: url
                };
            }

            // we need to specify JSON for blob URLs
            var options = {
                retry: this.retryRequests
            };

            if (url.load.startsWith('blob:')) {
                options.responseType = pc.Http.ResponseType.JSON;
            }

            pc.http.get(url.load, options, function (err, response) {
                if (!callback)
                    return;

                if (!err) {
                    callback(null, response);
                } else {
                    callback(pc.string.format("Error loading model: {0} [{1}]", url.original, err));
                }
            });
        },

        /**
         * @function
         * @name pc.ModelHandler#open
         * @description Process data in deserialized format into a pc.Model object.
         * @param {String} url The URL of the model data.
         * @param {Object} data The data from model file deserialized into a JavaScript Object.
         * @returns {pc.Model} The loaded model.
         */
        open: function (url, data) {
            for (var i = 0; i < this._parsers.length; i++) {
                var p = this._parsers[i];

                if (p.decider(url, data)) {
                    return p.parser.parse(data);
                }
            }
            logWARNING(pc.string.format("No model parser found for: {0}", url));
            return null;
        },

        patch: function (asset, assets) {
            if (!asset.resource)
                return;

            var data = asset.data;

            var self = this;
            asset.resource.meshInstances.forEach(function (meshInstance, i) {
                if (data.mapping) {
                    var handleMaterial = function (asset) {
                        if (asset.resource) {
                            meshInstance.material = asset.resource;
                        } else {
                            asset.once('load', handleMaterial);
                            assets.load(asset);
                        }

                        asset.once('remove', function (asset) {
                            if (meshInstance.material === asset.resource) {
                                meshInstance.material = self._defaultMaterial;
                            }
                        });
                    };

                    if (!data.mapping[i]) {
                        meshInstance.material = self._defaultMaterial;
                        return;
                    }

                    var id = data.mapping[i].material;
                    var url = data.mapping[i].path;
                    var material;

                    if (id !== undefined) { // id mapping
                        if (!id) {
                            meshInstance.material = self._defaultMaterial;
                        } else {
                            material = assets.get(id);
                            if (material) {
                                handleMaterial(material);
                            } else {
                                assets.once('add:' + id, handleMaterial);
                            }
                        }
                    } else if (url) {
                        // url mapping
                        var fileUrl = asset.getFileUrl();
                        var dirUrl = pc.path.getDirectory(fileUrl);
                        var path = pc.path.join(dirUrl, data.mapping[i].path);
                        material = assets.getByUrl(path);

                        if (material) {
                            handleMaterial(material);
                        } else {
                            assets.once('add:url:' + path, handleMaterial);
                        }
                    }
                }
            });
        },

        /**
         * @function
         * @name pc.ModelHandler#addParser
         * @description Add a parser that converts raw data into a {@link pc.Model}
         * Default parser is for JSON models
         * @param {Object} parser See JsonModelParser for example
         * @param {Function} 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 pc.Model}.
         * The first parser to return true is used.
         */
        addParser: function (parser, decider) {
            this._parsers.push({
                parser: parser,
                decider: decider
            });
        }
    });

    return {
        ModelHandler: ModelHandler
    };
}());