• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / loaders.gl / 20352515932

18 Dec 2025 09:56PM UTC coverage: 35.115% (-28.4%) from 63.485%
20352515932

push

github

web-flow
feat(loader-utils): Export is-type helpers (#3258)

1188 of 1998 branches covered (59.46%)

Branch coverage included in aggregate %.

147 of 211 new or added lines in 13 files covered. (69.67%)

30011 existing lines in 424 files now uncovered.

37457 of 108056 relevant lines covered (34.66%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.57
/modules/obj/src/lib/parse-mtl.ts
1
// loaders.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4
// Forked from THREE.js under MIT license
1✔
5
// https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/MTLLoader.js
1✔
6

1✔
7
// import type {DiffuseMaterial} from '@loaders.gl/schema';
1✔
8

1✔
9
export type MTLMaterial = {
1✔
10
  name: string;
1✔
11
  ambientColor?: [number, number, number];
1✔
12
  diffuseColor?: [number, number, number];
1✔
13
  specularColor?: [number, number, number];
1✔
14
  emissiveColor?: [number, number, number];
1✔
15
  // specular?: number;
1✔
16
  shininess?: number;
1✔
17
  refraction?: number;
1✔
18
  illumination?: number;
1✔
19
  diffuseTextureUrl?: string;
1✔
20
  emissiveTextureUrl?: string;
1✔
21
  specularTextureUrl?: string;
1✔
22
};
1✔
23

1✔
24
const DELIMITER_PATTERN = /\s+/;
1✔
25

1✔
26
/**
1✔
27
 * Set of options on how to construct materials
1✔
28
 * @param normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 (Default: false, assumed to be already normalized)
1✔
29
 * @param ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's Default: false
1✔
30
 * @param baseUrl - Url relative to which textures are loaded
1✔
31
 */
1✔
32
export type ParseMTLOptions = {
1✔
33
  normalizeRGB?: boolean;
1✔
34
  ignoreZeroRGBs?: boolean;
1✔
35
  baseUrl?: string;
1✔
36
};
1✔
37

1✔
38
/**
1✔
39
 * Parses a MTL file.
1✔
40
 * Parses a Wavefront .mtl file specifying materials
1✔
41
 * http://paulbourke.net/dataformats/mtl/
1✔
42
 * https://www.loc.gov/preservation/digital/formats/fdd/fdd000508.shtml
1✔
43
 *
1✔
44
 * @param  text - Content of MTL file
1✔
45
 */
1✔
46
// eslint-disable-next-line complexity
1✔
47
export function parseMTL(text: string, options?: ParseMTLOptions): MTLMaterial[] {
1✔
UNCOV
48
  // const materialsInfo: Record<string, MTLMaterial> = {};
×
UNCOV
49
  const materials: MTLMaterial[] = [];
×
UNCOV
50

×
UNCOV
51
  let currentMaterial: MTLMaterial = {name: 'placeholder'};
×
UNCOV
52

×
UNCOV
53
  const lines = text.split('\n');
×
UNCOV
54
  for (let line of lines) {
×
UNCOV
55
    line = line.trim();
×
UNCOV
56

×
UNCOV
57
    if (line.length === 0 || line.charAt(0) === '#') {
×
UNCOV
58
      // Blank line or comment ignore
×
UNCOV
59
      continue; // eslint-disable-line no-continue
×
UNCOV
60
    }
×
UNCOV
61

×
UNCOV
62
    const pos = line.indexOf(' ');
×
UNCOV
63

×
UNCOV
64
    let key = pos >= 0 ? line.substring(0, pos) : line;
×
UNCOV
65
    key = key.toLowerCase();
×
UNCOV
66

×
UNCOV
67
    let value = pos >= 0 ? line.substring(pos + 1) : '';
×
UNCOV
68
    value = value.trim();
×
UNCOV
69

×
UNCOV
70
    switch (key) {
×
UNCOV
71
      case 'newmtl':
×
UNCOV
72
        // New material
×
UNCOV
73
        currentMaterial = {name: value};
×
UNCOV
74
        // insert into map
×
UNCOV
75
        materials.push(currentMaterial);
×
UNCOV
76
        break;
×
UNCOV
77

×
UNCOV
78
      case 'ka': // Ka
×
UNCOV
79
        currentMaterial.ambientColor = parseColor(value);
×
UNCOV
80
        break;
×
UNCOV
81

×
UNCOV
82
      case 'kd':
×
UNCOV
83
        // Kd: Diffuse color (color under white light) using RGB values
×
UNCOV
84
        currentMaterial.diffuseColor = parseColor(value);
×
UNCOV
85
        break;
×
UNCOV
86
      case 'map_kd':
×
UNCOV
87
        // Diffuse texture map
×
UNCOV
88
        currentMaterial.diffuseTextureUrl = value;
×
UNCOV
89
        //         setMapForType('map', value);
×
UNCOV
90
        break;
×
UNCOV
91

×
UNCOV
92
      case 'ks':
×
UNCOV
93
        // Specular color (color when light is reflected from shiny surface) using RGB values
×
UNCOV
94
        currentMaterial.specularColor = parseColor(value);
×
UNCOV
95
        break;
×
UNCOV
96
      case 'map_ks':
×
97
        // Specular map
×
98
        currentMaterial.specularTextureUrl = value;
×
99
        // setMapForType('specularMap', value);
×
100
        break;
×
UNCOV
101

×
UNCOV
102
      case 'ke':
×
UNCOV
103
        // Emissive using RGB values
×
UNCOV
104
        currentMaterial.emissiveColor = parseColor(value);
×
UNCOV
105
        break;
×
UNCOV
106
      case 'map_ke':
×
107
        // Emissive map
×
108
        currentMaterial.emissiveTextureUrl = value;
×
109
        // setMapForType('emissiveMap', value);
×
110
        break;
×
UNCOV
111

×
UNCOV
112
      case 'ns':
×
UNCOV
113
        // Ns is material specular exponent (defines the focus of the specular highlight)
×
UNCOV
114
        // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
×
UNCOV
115
        currentMaterial.shininess = parseFloat(value);
×
UNCOV
116
        break;
×
UNCOV
117
      case 'map_ns':
×
UNCOV
118
        // Ns is material specular exponent
×
UNCOV
119
        // TODO?
×
UNCOV
120
        // currentMaterial.shininessMap = parseFloat(value);
×
UNCOV
121
        break;
×
UNCOV
122
      case 'ni':
×
UNCOV
123
        currentMaterial.refraction = parseFloat(value);
×
UNCOV
124
        break;
×
UNCOV
125
      case 'illum':
×
UNCOV
126
        currentMaterial.illumination = parseFloat(value);
×
UNCOV
127
        break;
×
UNCOV
128

×
UNCOV
129
      default:
×
UNCOV
130
        // log unknown message?
×
UNCOV
131
        break;
×
UNCOV
132

×
UNCOV
133
      /*
×
UNCOV
134
      case 'norm':
×
UNCOV
135
        setMapForType('normalMap', value);
×
UNCOV
136
        break;
×
UNCOV
137

×
UNCOV
138
      case 'map_bump':
×
UNCOV
139
      case 'bump':
×
UNCOV
140
        // Bump texture map
×
UNCOV
141
        setMapForType('bumpMap', value);
×
UNCOV
142
        break;
×
UNCOV
143

×
UNCOV
144
      case 'd':
×
UNCOV
145
        n = parseFloat(value);
×
UNCOV
146
        if (n < 1) {
×
UNCOV
147
          params.opacity = n;
×
UNCOV
148
          params.transparent = true;
×
UNCOV
149
        }
×
UNCOV
150
        break;
×
UNCOV
151

×
UNCOV
152
      case 'map_d':
×
UNCOV
153
        // Alpha map
×
UNCOV
154
        setMapForType('alphaMap', value);
×
UNCOV
155
        params.transparent = true;
×
UNCOV
156
        break;
×
UNCOV
157

×
UNCOV
158
      case 'tr':
×
UNCOV
159
        n = parseFloat(value);
×
UNCOV
160
        if (this.options && this.options.invertTrProperty) n = 1 - n;
×
UNCOV
161
        if (n > 0) {
×
UNCOV
162
          params.opacity = 1 - n;
×
UNCOV
163
          params.transparent = true;
×
UNCOV
164
        }
×
UNCOV
165
      */
×
UNCOV
166
    }
×
UNCOV
167
  }
×
UNCOV
168

×
UNCOV
169
  return materials;
×
UNCOV
170
}
×
171

1✔
UNCOV
172
function parseColor(value: string, options?: ParseMTLOptions): [number, number, number] {
×
UNCOV
173
  const rgb = value.split(DELIMITER_PATTERN, 3);
×
UNCOV
174
  const color: [number, number, number] = [
×
UNCOV
175
    parseFloat(rgb[0]),
×
UNCOV
176
    parseFloat(rgb[1]),
×
UNCOV
177
    parseFloat(rgb[2])
×
UNCOV
178
  ];
×
UNCOV
179
  // TODO auto detect big values?
×
UNCOV
180
  // if (this.options && this.options.normalizeRGB) {
×
UNCOV
181
  //   value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
×
UNCOV
182
  // }
×
UNCOV
183

×
UNCOV
184
  // if (this.options && this.options.ignoreZeroRGBs) {
×
UNCOV
185
  //   if (value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0) {
×
UNCOV
186
  //     // ignore
×
UNCOV
187
  //     save = false;
×
UNCOV
188
  //   }
×
UNCOV
189
  // }
×
UNCOV
190
  return color;
×
UNCOV
191
}
×
192

1✔
193
/* TODO parse url options
1✔
194
function parseTexture(value, matParams) {
1✔
195
  const texParams = {
1✔
196
    scale: new Vector2(1, 1),
1✔
197
    offset: new Vector2(0, 0)
1✔
198
  };
1✔
199

1✔
200
  const items = value.split(/\s+/);
1✔
201
  let pos;
1✔
202

1✔
203
  pos = items.indexOf('-bm');
1✔
204
  if (pos >= 0) {
1✔
205
    matParams.bumpScale = parseFloat(items[ pos + 1 ]);
1✔
206
    items.splice(pos, 2);
1✔
207
  }
1✔
208

1✔
209
  pos = items.indexOf('-s');
1✔
210
  if (pos >= 0) {
1✔
211
    texParams.scale.set(parseFloat(items[ pos + 1 ]), parseFloat(items[ pos + 2 ]));
1✔
212
    items.splice(pos, 4); // we expect 3 parameters here!
1✔
213

1✔
214
  }
1✔
215

1✔
216
  pos = items.indexOf('-o');
1✔
217

1✔
218
  if (pos >= 0) {
1✔
219
    texParams.offset.set(parseFloat(items[ pos + 1 ]), parseFloat(items[ pos + 2 ]));
1✔
220
    items.splice(pos, 4); // we expect 3 parameters here!
1✔
221
  }
1✔
222

1✔
223
  texParams.url = items.join(' ').trim();
1✔
224
  return texParams;
1✔
225
}
1✔
226

1✔
227
 *function resolveURL(baseUrl, url) {
1✔
228
 * baseUrl?: string;
1✔
229
    // Absolute URL
1✔
230
    if (/^https?:\/\//i.test(url)) return url;
1✔
231
    return baseUrl + url;
1✔
232
  }
1✔
233

1✔
234
  function setMapForType(mapType, value) {
1✔
235
    if (params[ mapType ]) return; // Keep the first encountered texture
1✔
236

1✔
237
    const texParams = scope.getTextureParams(value, params);
1✔
238
    const map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url));
1✔
239

1✔
240
    map.repeat.copy(texParams.scale);
1✔
241
    map.offset.copy(texParams.offset);
1✔
242

1✔
243
    map.wrapS = scope.wrap;
1✔
244
    map.wrapT = scope.wrap;
1✔
245

1✔
246
    params[ mapType ] = map;
1✔
247
  }
1✔
248
*/
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc