* @private * Parse OBJ lines into model. For reference, this is what a simple model of a * square might look like: * * v -0.5 -0.5 0.5 * v -0.5 -0.5 -0.5 * v -0.5 0.5 -0.5 * v -0.5 0.5 0.5 * * f 4 3 2 1
(model, lines, materials = {})
| 538 | * f 4 3 2 1 |
| 539 | */ |
| 540 | function parseObj(model, lines, materials = {}) { |
| 541 | // OBJ allows a face to specify an index for a vertex (in the above example), |
| 542 | // but it also allows you to specify a custom combination of vertex, UV |
| 543 | // coordinate, and vertex normal. So, "3/4/3" would mean, "use vertex 3 with |
| 544 | // UV coordinate 4 and vertex normal 3". In WebGL, every vertex with different |
| 545 | // parameters must be a different vertex, so loadedVerts is used to |
| 546 | // temporarily store the parsed vertices, normals, etc., and indexedVerts is |
| 547 | // used to map a specific combination (keyed on, for example, the string |
| 548 | // "3/4/3"), to the actual index of the newly created vertex in the final |
| 549 | // object. |
| 550 | const loadedVerts = { |
| 551 | v: [], |
| 552 | vt: [], |
| 553 | vn: [] |
| 554 | }; |
| 555 | |
| 556 | |
| 557 | // Map from source index → Map of material → destination index |
| 558 | const usedVerts = {}; // Track colored vertices |
| 559 | let currentMaterial = null; |
| 560 | let hasColoredVertices = false; |
| 561 | let hasColorlessVertices = false; |
| 562 | for (let line = 0; line < lines.length; ++line) { |
| 563 | // Each line is a separate object (vertex, face, vertex normal, etc) |
| 564 | // For each line, split it into tokens on whitespace. The first token |
| 565 | // describes the type. |
| 566 | const tokens = lines[line].trim().split(/\b\s+/); |
| 567 | |
| 568 | if (tokens.length > 0) { |
| 569 | if (tokens[0] === 'usemtl') { |
| 570 | // Switch to a new material |
| 571 | currentMaterial = tokens[1]; |
| 572 | } else if (tokens[0] === 'v' || tokens[0] === 'vn') { |
| 573 | // Check if this line describes a vertex or vertex normal. |
| 574 | // It will have three numeric parameters. |
| 575 | const vertex = new Vector( |
| 576 | parseFloat(tokens[1]), |
| 577 | parseFloat(tokens[2]), |
| 578 | parseFloat(tokens[3]) |
| 579 | ); |
| 580 | loadedVerts[tokens[0]].push(vertex); |
| 581 | } else if (tokens[0] === 'vt') { |
| 582 | // Check if this line describes a texture coordinate. |
| 583 | // It will have two numeric parameters U and V (W is omitted). |
| 584 | // Because of WebGL texture coordinates rendering behaviour, the V |
| 585 | // coordinate is inversed. |
| 586 | const texVertex = [parseFloat(tokens[1]), 1 - parseFloat(tokens[2])]; |
| 587 | loadedVerts[tokens[0]].push(texVertex); |
| 588 | } else if (tokens[0] === 'f') { |
| 589 | // Check if this line describes a face. |
| 590 | // OBJ faces can have more than three points. Triangulate points. |
| 591 | for (let tri = 3; tri < tokens.length; ++tri) { |
| 592 | const face = []; |
| 593 | const vertexTokens = [1, tri - 1, tri]; |
| 594 | |
| 595 | for (let tokenInd = 0; tokenInd < vertexTokens.length; ++tokenInd) { |
| 596 | // Now, convert the given token into an index |
| 597 | const vertString = tokens[vertexTokens[tokenInd]]; |