threepipe
Version:
A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.
358 lines • 13.8 kB
JavaScript
/* eslint-disable */
// @ts-nocheck
// r152 jsm/MTLLoader.js
import { Color, DefaultLoadingManager, FileLoader, FrontSide, Loader, LoaderUtils, MeshPhongMaterial, RepeatWrapping, SRGBColorSpace, TextureLoader, Vector2, } from 'three';
/**
* Loads a Wavefront .mtl file specifying materials
*/
class MTLLoader2 extends Loader {
constructor(manager) {
super(manager);
}
/**
* Loads and parses a MTL asset from a URL.
*
* @param {String} url - URL to the MTL file.
* @param {Function} [onLoad] - Callback invoked with the loaded object.
* @param {Function} [onProgress] - Callback for download progress.
* @param {Function} [onError] - Callback for download errors.
*
* @link setPath setResourcePath
*
* @note In order for relative texture references to resolve correctly
* you must call setResourcePath() explicitly prior to load.
*/
load(url, onLoad, onProgress, onError) {
const scope = this;
const path = (this.path === '') ? LoaderUtils.extractUrlBase(url) : this.path;
const loader = new FileLoader(this.manager);
loader.setPath(this.path);
loader.setRequestHeader(this.requestHeader);
loader.setWithCredentials(this.withCredentials);
loader.load(url, function (text) {
try {
onLoad(scope.parse(text, path));
}
catch (e) {
if (onError) {
onError(e);
}
else {
console.error(e);
}
scope.manager.itemError(url);
}
}, onProgress, onError);
}
setMaterialOptions(value) {
this.materialOptions = value;
return this;
}
/**
* Parses a MTL file.
*
* @param {String} text - Content of MTL file
* @return {MaterialCreator}
*
* @link setPath setResourcePath
*
* @note In order for relative texture references to resolve correctly
* you must call setResourcePath() explicitly prior to parse.
*/
parse(text, path) {
const lines = text.split('\n');
let info = {};
const delimiter_pattern = /\s+/;
const materialsInfo = {};
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
line = line.trim();
if (line.length === 0 || line.charAt(0) === '#') {
// Blank line or comment ignore
continue;
}
const pos = line.indexOf(' ');
let key = (pos >= 0) ? line.substring(0, pos) : line;
key = key.toLowerCase();
let value = (pos >= 0) ? line.substring(pos + 1) : '';
value = value.trim();
if (key === 'newmtl') {
// New material
info = { name: value };
materialsInfo[value] = info;
}
else {
if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') {
const ss = value.split(delimiter_pattern, 3);
info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];
}
else {
info[key] = value;
}
}
}
const materialCreator = new MaterialCreator(this.resourcePath || path, this.materialOptions);
materialCreator.setCrossOrigin(this.crossOrigin);
materialCreator.setManager(this.manager);
materialCreator.setMaterials(materialsInfo);
return materialCreator;
}
}
/**
* Create a new MTLLoader2.MaterialCreator
* @param baseUrl - Url relative to which textures are loaded
* @param options - Set of options on how to construct the materials
* side: Which side to apply the material
* FrontSide (default), THREE.BackSide, THREE.DoubleSide
* wrap: What type of wrapping to apply for textures
* RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
* normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
* Default: false, assumed to be already normalized
* ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
* Default: false
* @constructor
*/
class MaterialCreator {
constructor(baseUrl = '', options = {}) {
this.baseUrl = baseUrl;
this.options = options;
this.materialsInfo = {};
this.materials = {};
this.materialsArray = [];
this.nameLookup = {};
this.crossOrigin = 'anonymous';
this.side = (this.options.side !== undefined) ? this.options.side : FrontSide;
this.wrap = (this.options.wrap !== undefined) ? this.options.wrap : RepeatWrapping;
}
setCrossOrigin(value) {
this.crossOrigin = value;
return this;
}
setManager(value) {
this.manager = value;
}
setMaterials(materialsInfo) {
this.materialsInfo = this.convert(materialsInfo);
this.materials = {};
this.materialsArray = [];
this.nameLookup = {};
}
convert(materialsInfo) {
if (!this.options)
return materialsInfo;
const converted = {};
for (const mn in materialsInfo) {
// Convert materials info into normalized form based on options
const mat = materialsInfo[mn];
const covmat = {};
converted[mn] = covmat;
for (const prop in mat) {
let save = true;
let value = mat[prop];
const lprop = prop.toLowerCase();
switch (lprop) {
case 'kd':
case 'ka':
case 'ks':
// Diffuse color (color under white light) using RGB values
if (this.options && this.options.normalizeRGB) {
value = [value[0] / 255, value[1] / 255, value[2] / 255];
}
if (this.options && this.options.ignoreZeroRGBs) {
if (value[0] === 0 && value[1] === 0 && value[2] === 0) {
// ignore
save = false;
}
}
break;
default:
break;
}
if (save) {
covmat[lprop] = value;
}
}
}
return converted;
}
async preload() {
for (const mn in this.materialsInfo) {
await this.create(mn);
}
}
getIndex(materialName) {
return this.nameLookup[materialName];
}
async getAsArray() {
let index = 0;
for (const mn in this.materialsInfo) {
this.materialsArray[index] = await this.create(mn);
this.nameLookup[mn] = index;
index++;
}
return this.materialsArray;
}
async create(materialName) {
if (this.materials[materialName] === undefined) {
await this.createMaterial_(materialName);
}
return this.materials[materialName];
}
async createMaterial_(materialName) {
// Create material
const scope = this;
const mat = this.materialsInfo[materialName];
const params = {
name: materialName,
side: this.side
};
function resolveURL(baseUrl, url) {
if (typeof url !== 'string' || url === '')
return '';
// Absolute URL
if (/^https?:\/\//i.test(url))
return url;
return baseUrl + url;
}
async function setMapForType(mapType, value) {
if (params[mapType])
return; // Keep the first encountered texture
const texParams = scope.getTextureParams(value, params);
return new Promise((resolve, reject) => {
let resolved = false;
let res = () => (!resolved && (resolved = true) && resolve());
const map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url), undefined, (map) => {
params[mapType] = map;
res();
}, undefined, res);
setTimeout(res, 50); // timeout.
map.repeat.copy(texParams.scale);
map.offset.copy(texParams.offset);
map.wrapS = scope.wrap;
map.wrapT = scope.wrap;
if (mapType === 'map' || mapType === 'emissiveMap') {
map.colorSpace = SRGBColorSpace;
}
});
}
/**
*
* @type {string[]}
*/
const propList = Array.from(Object.keys(mat ? mat : {}));
let hasOpacity = propList.includes('d') || propList.includes('D');
for (const prop of propList) {
const value = mat[prop];
let n;
if (value === '')
continue;
switch (prop.toLowerCase()) {
// Ns is material specular exponent
case 'kd':
// Diffuse color (color under white light) using RGB values
params.color = new Color().fromArray(value).convertSRGBToLinear();
break;
case 'ks':
// Specular color (color when light is reflected from shiny surface) using RGB values
params.specular = new Color().fromArray(value).convertSRGBToLinear();
break;
case 'ke':
// Emissive using RGB values
params.emissive = new Color().fromArray(value).convertSRGBToLinear();
break;
case 'map_kd':
// Diffuse texture map
await setMapForType('map', value);
break;
case 'map_ks':
// Specular map
await setMapForType('specularMap', value);
break;
case 'map_ke':
// Emissive map
await setMapForType('emissiveMap', value);
break;
case 'norm':
await setMapForType('normalMap', value);
break;
case 'map_bump':
case 'bump':
// Bump texture map
await setMapForType('bumpMap', value);
break;
case 'map_d':
// Alpha map
await setMapForType('alphaMap', value);
params.transparent = true;
break;
case 'ns':
// The specular exponent (defines the focus of the specular highlight)
// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
params.shininess = parseFloat(value);
break;
case 'd':
n = parseFloat(value);
if (n < 1) {
params.opacity = n;
params.transparent = true;
}
break;
case 'tr': // is this translucency?
if (hasOpacity)
break; // ignore transparency if opacity is present
n = parseFloat(value);
if (this.options && this.options.invertTrProperty)
n = 1 - n;
if (n > 0) {
params.opacity = 1 - n;
params.transparent = true;
}
break;
default:
break;
}
}
this.materials[materialName] = new MeshPhongMaterial(params);
return this.materials[materialName];
}
getTextureParams(value, matParams) {
const texParams = {
scale: new Vector2(1, 1),
offset: new Vector2(0, 0)
};
const items = value.split(/\s+/);
let pos;
pos = items.indexOf('-bm');
if (pos >= 0) {
matParams.bumpScale = parseFloat(items[pos + 1]);
items.splice(pos, 2);
}
pos = items.indexOf('-s');
if (pos >= 0) {
texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
items.splice(pos, 4); // we expect 3 parameters here!
}
pos = items.indexOf('-o');
if (pos >= 0) {
texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
items.splice(pos, 4); // we expect 3 parameters here!
}
texParams.url = items.join(' ').trim();
return texParams;
}
loadTexture(url, mapping, onLoad, onProgress, onError) {
const manager = (this.manager !== undefined) ? this.manager : DefaultLoadingManager;
let loader = manager.getHandler(url);
if (loader === null) {
loader = new TextureLoader(manager);
}
if (loader.setCrossOrigin)
loader.setCrossOrigin(this.crossOrigin);
const texture = loader.load(url, onLoad, onProgress, onError);
if (mapping !== undefined)
texture.mapping = mapping;
return texture;
}
}
export { MTLLoader2 };
//# sourceMappingURL=MTLLoader2.js.map