* * Converts text into a 3D model that can be rendered in WebGL mode. * * This method transforms flat text into extruded 3D geometry, allowing * for dynamic effects like depth, warping, and custom shading. * * It works by taking the outlines (contours) of each character in the *
(str, x, y, width, height, options)
| 516 | * } |
| 517 | */ |
| 518 | textToModel(str, x, y, width, height, options) { |
| 519 | ({ width, height, options } = this._parseArgs(width, height, options)); |
| 520 | const extrude = options?.extrude || 0; |
| 521 | |
| 522 | let contours = this.textToContours(str, x, y, width, height, options); |
| 523 | if (!contours || contours.length === 0) { |
| 524 | return new p5.Geometry(); |
| 525 | } |
| 526 | |
| 527 | // Step 2: build base flat geometry - single shape |
| 528 | const geom = this._pInst.buildGeometry(() => { |
| 529 | const prevValidateFaces = this._pInst._renderer._validateFaces; |
| 530 | this._pInst._renderer._validateFaces = true; |
| 531 | |
| 532 | this._pInst.beginShape(); |
| 533 | for (const contour of contours) { |
| 534 | this._pInst.beginContour(); |
| 535 | for (const pt of contour) { |
| 536 | this._pInst.vertex(pt.x, pt.y, 0); |
| 537 | } |
| 538 | this._pInst.endContour(this._pInst.CLOSE); |
| 539 | } |
| 540 | |
| 541 | this._pInst.endShape(this._pInst.CLOSE); |
| 542 | |
| 543 | this._pInst._renderer._validateFaces = prevValidateFaces; |
| 544 | }); |
| 545 | |
| 546 | if (extrude === 0) { |
| 547 | return geom; |
| 548 | } |
| 549 | |
| 550 | // The tessellation process creates separate vertices for each triangle, |
| 551 | // even when they share the same position. We need to deduplicate them |
| 552 | // to find which faces are actually connected, so we can identify the |
| 553 | // outer edges for extrusion. |
| 554 | |
| 555 | const vertexIndices = {}; |
| 556 | const vertexId = v => `${v.x.toFixed(6)}-${v.y.toFixed(6)}-${v.z.toFixed(6)}`; |
| 557 | const newVertices = []; |
| 558 | const newVertexIndex = []; |
| 559 | |
| 560 | for (const v of geom.vertices) { |
| 561 | const id = vertexId(v); |
| 562 | if (!(id in vertexIndices)) { |
| 563 | const index = newVertices.length; |
| 564 | vertexIndices[id] = index; |
| 565 | newVertices.push(v.copy()); |
| 566 | } |
| 567 | newVertexIndex.push(vertexIndices[id]); |
| 568 | } |
| 569 | |
| 570 | // Remap faces to use deduplicated vertices |
| 571 | const newFaces = geom.faces.map(f => f.map(i => newVertexIndex[i])); |
| 572 | |
| 573 | //Find outer edges (edges that appear in only one face) |
| 574 | const seen = {}; |
| 575 | for (const face of newFaces) { |
no test coverage detected