playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
222 lines (221 loc) • 7.15 kB
JavaScript
import { http } from "../../platform/net/http.js";
import { standardMaterialCubemapParameters, standardMaterialTextureParameters } from "../../scene/materials/standard-material-parameters.js";
import { AssetReference } from "../asset/asset-reference.js";
import { JsonStandardMaterialParser } from "../parsers/material/json-standard-material.js";
import { ResourceHandler } from "./handler.js";
import { getBuiltInTexture } from "../../platform/graphics/built-in-textures.js";
const PLACEHOLDER_MAP = {
aoMap: "white",
aoDetailMap: "white",
diffuseMap: "gray",
diffuseDetailMap: "gray",
specularMap: "gray",
specularityFactorMap: "white",
metalnessMap: "black",
glossMap: "gray",
sheenMap: "black",
sheenGlossMap: "gray",
clearCoatMap: "black",
clearCoatGlossMap: "gray",
clearCoatNormalMap: "normal",
refractionMap: "white",
emissiveMap: "gray",
normalMap: "normal",
normalDetailMap: "normal",
heightMap: "gray",
opacityMap: "gray",
sphereMap: "gray",
lightMap: "white",
thicknessMap: "black",
iridescenceMap: "black",
iridescenceThicknessMap: "black",
envAtlas: "black",
anisotropyMap: "black"
};
class MaterialHandler extends ResourceHandler {
constructor(app) {
super(app, "material");
this._assets = app.assets;
this._device = app.graphicsDevice;
this._parser = new JsonStandardMaterialParser();
}
load(url, callback) {
if (typeof url === "string") {
url = {
load: url,
original: url
};
}
http.get(url.load, {
retry: this.maxRetries > 0,
maxRetries: this.maxRetries
}, (err, response) => {
if (!err) {
if (callback) {
response._engine = true;
callback(null, response);
}
} else {
if (callback) {
callback(`Error loading material: ${url.original} [${err}]`);
}
}
});
}
open(url, data) {
const material = this._parser.parse(data);
if (data._engine) {
material._data = data;
delete data._engine;
}
return material;
}
patch(asset, assets) {
if (asset.resource._data) {
asset._data = asset.resource._data;
delete asset.resource._data;
}
asset.data.name = asset.name;
asset.resource.name = asset.name;
this._bindAndAssignAssets(asset, assets);
asset.off("unload", this._onAssetUnload, this);
asset.on("unload", this._onAssetUnload, this);
}
_onAssetUnload(asset) {
delete asset.data.parameters;
delete asset.data.chunks;
delete asset.data.name;
}
_assignTexture(parameterName, materialAsset, texture) {
materialAsset.resource[parameterName] = texture;
}
// returns the correct placeholder texture for the texture parameter
_getPlaceholderTexture(parameterName) {
const placeholder = PLACEHOLDER_MAP[parameterName];
return getBuiltInTexture(this._device, placeholder);
}
// assign a placeholder texture while waiting for one to load
_assignPlaceholderTexture(parameterName, materialAsset) {
materialAsset.resource[parameterName] = this._getPlaceholderTexture(parameterName);
}
_onTextureLoad(parameterName, materialAsset, textureAsset) {
this._assignTexture(parameterName, materialAsset, textureAsset.resource);
materialAsset.resource.update();
}
_onTextureAdd(parameterName, materialAsset, textureAsset) {
this._assets.load(textureAsset);
}
_onTextureRemoveOrUnload(parameterName, materialAsset, textureAsset) {
const material = materialAsset.resource;
if (material) {
if (materialAsset.resource[parameterName] === textureAsset.resource) {
this._assignPlaceholderTexture(parameterName, materialAsset);
material.update();
}
}
}
_assignCubemap(parameterName, materialAsset, textures) {
materialAsset.resource[parameterName] = textures[0];
if (parameterName === "cubeMap") {
const prefiltered = textures.slice(1);
if (prefiltered.every((t) => t)) {
materialAsset.resource.prefilteredCubemaps = prefiltered;
} else if (prefiltered[0]) {
materialAsset.resource.envAtlas = prefiltered[0];
}
}
}
_onCubemapLoad(parameterName, materialAsset, cubemapAsset) {
this._assignCubemap(parameterName, materialAsset, cubemapAsset.resources);
this._parser.initialize(materialAsset.resource, materialAsset.data);
}
_onCubemapAdd(parameterName, materialAsset, cubemapAsset) {
this._assets.load(cubemapAsset);
}
_onCubemapRemoveOrUnload(parameterName, materialAsset, cubemapAsset) {
const material = materialAsset.resource;
if (materialAsset.data.prefilteredCubeMap128 === cubemapAsset.resources[1]) {
this._assignCubemap(parameterName, materialAsset, [null, null, null, null, null, null, null]);
material.update();
}
}
_bindAndAssignAssets(materialAsset, assets) {
const data = this._parser.migrate(materialAsset.data);
const material = materialAsset.resource;
const pathMapping = data.mappingFormat === "path";
const TEXTURES = standardMaterialTextureParameters;
let i, name, assetReference;
for (i = 0; i < TEXTURES.length; i++) {
name = TEXTURES[i];
assetReference = material._assetReferences[name];
const dataAssetId = data[name];
const materialTexture = material[name];
const isPlaceHolderTexture = materialTexture === this._getPlaceholderTexture(name);
const dataValidated = data.validated;
if (dataAssetId && (!materialTexture || !dataValidated || isPlaceHolderTexture)) {
if (!assetReference) {
assetReference = new AssetReference(name, materialAsset, assets, {
load: this._onTextureLoad,
add: this._onTextureAdd,
remove: this._onTextureRemoveOrUnload,
unload: this._onTextureRemoveOrUnload
}, this);
material._assetReferences[name] = assetReference;
}
if (pathMapping) {
assetReference.url = materialAsset.getAbsoluteUrl(dataAssetId);
} else {
assetReference.id = dataAssetId;
}
if (assetReference.asset) {
if (assetReference.asset.resource) {
this._assignTexture(name, materialAsset, assetReference.asset.resource);
} else {
this._assignPlaceholderTexture(name, materialAsset);
}
assets.load(assetReference.asset);
}
} else {
if (assetReference) {
if (pathMapping) {
assetReference.url = null;
} else {
assetReference.id = null;
}
} else {
}
}
}
const CUBEMAPS = standardMaterialCubemapParameters;
for (i = 0; i < CUBEMAPS.length; i++) {
name = CUBEMAPS[i];
assetReference = material._assetReferences[name];
if (data[name] && !materialAsset.data.prefilteredCubeMap128) {
if (!assetReference) {
assetReference = new AssetReference(name, materialAsset, assets, {
load: this._onCubemapLoad,
add: this._onCubemapAdd,
remove: this._onCubemapRemoveOrUnload,
unload: this._onCubemapRemoveOrUnload
}, this);
material._assetReferences[name] = assetReference;
}
if (pathMapping) {
assetReference.url = data[name];
} else {
assetReference.id = data[name];
}
if (assetReference.asset) {
if (assetReference.asset.loaded) {
this._assignCubemap(name, materialAsset, assetReference.asset.resources);
}
assets.load(assetReference.asset);
}
}
}
this._parser.initialize(material, data);
}
}
export {
MaterialHandler
};