From 5e557e583d8df3f14821878ebebb75e27b38a927 Mon Sep 17 00:00:00 2001 From: VANSH3104 Date: Mon, 15 Sep 2025 16:38:16 +0530 Subject: [PATCH 1/3] Fix textToModel face normals for extruded text --- src/type/p5.Font.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type/p5.Font.js b/src/type/p5.Font.js index 83c30fee82..5201a4770c 100644 --- a/src/type/p5.Font.js +++ b/src/type/p5.Font.js @@ -591,7 +591,7 @@ export class Font { if (extrude !== 0) { geom.computeNormals(); for (const face of geom.faces) { - if (face.every(idx => geom.vertices[idx].z <= -extrude * 0.5 + 0.1)) { + if (face.every(idx => geom.vertices[idx].z <= -extrude * 0.5 + 0.1 || geom.vertices[idx].z >= extrude * 0.5 - 0.1)) { for (const idx of face) geom.vertexNormals[idx].set(0, 0, -1); face.reverse(); } From 3e567bf894bda0d4b0e0806cb431936fc730ee0e Mon Sep 17 00:00:00 2001 From: VANSH3104 Date: Mon, 29 Sep 2025 14:51:56 +0530 Subject: [PATCH 2/3] Fix textToModel extrusion and degenerate face issues --- src/type/p5.Font.js | 157 +++++++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 45 deletions(-) diff --git a/src/type/p5.Font.js b/src/type/p5.Font.js index 7d669db11a..1475697b49 100644 --- a/src/type/p5.Font.js +++ b/src/type/p5.Font.js @@ -541,63 +541,130 @@ export class Font { textToModel(str, x, y, width, height, options) { ({ width, height, options } = this._parseArgs(width, height, options)); const extrude = options?.extrude || 0; - const contours = this.textToContours(str, x, y, width, height, options); + // Step 1: generate glyph contours + let contours = this.textToContours(str, x, y, width, height, options); + if (!Array.isArray(contours[0][0])) { + contours = [contours]; + } + // Step 2: build base flat geometry const geom = this._pInst.buildGeometry(() => { - if (extrude === 0) { - const prevValidateFaces = this._pInst._renderer._validateFaces; - this._pInst._renderer._validateFaces = true; + const prevValidateFaces = this._pInst._renderer._validateFaces; + this._pInst._renderer._validateFaces = true; + + contours.forEach(glyphContours => { this._pInst.beginShape(); - this._pInst.normal(0, 0, 1); - for (const contour of contours) { + const outer = glyphContours[0]; + outer.forEach(({ x, y }) => this._pInst.vertex(x, y, 0)); + + for (let i = 1; i < glyphContours.length; i++) { this._pInst.beginContour(); - for (const { x, y } of contour) { - this._pInst.vertex(x, y); - } + glyphContours[i].forEach(({ x, y }) => this._pInst.vertex(x, y, 0)); this._pInst.endContour(this._pInst.CLOSE); } - this._pInst.endShape(); - this._pInst._renderer._validateFaces = prevValidateFaces; + + this._pInst.endShape(this._pInst.CLOSE); + }); + + this._pInst._renderer._validateFaces = prevValidateFaces; + }); + + if (extrude === 0) { + console.log('No extrusion'); + return geom; + } + + // Step 3: Create extruded geometry with UNSHARED vertices for flat shading + const extruded = this._pInst.buildGeometry(() => {}); + const half = extrude * 0.5; + + extruded.vertices = []; + extruded.vertexNormals = []; + extruded.faces = []; + + let vertexIndex = 0; + + // Helper to add a triangle with flat normal + const addTriangle = (v0, v1, v2) => { + const edge1 = p5.Vector.sub(v1, v0); + const edge2 = p5.Vector.sub(v2, v0); + const normal = p5.Vector.cross(edge1, edge2); + if (normal.magSq() > 0.0001) { + normal.normalize(); } else { - const prevValidateFaces = this._pInst._renderer._validateFaces; - this._pInst._renderer._validateFaces = true; - - // Draw front faces - for (const side of [1, -1]) { - this._pInst.beginShape(); - for (const contour of contours) { - this._pInst.beginContour(); - for (const { x, y } of contour) { - this._pInst.vertex(x, y, side * extrude * 0.5); - } - this._pInst.endContour(this._pInst.CLOSE); - } - this._pInst.endShape(); - } - this._pInst._renderer._validateFaces = prevValidateFaces; - - // Draw sides - for (const contour of contours) { - this._pInst.beginShape(this._pInst.QUAD_STRIP); - for (const v of contour) { - for (const side of [-1, 1]) { - this._pInst.vertex(v.x, v.y, side * extrude * 0.5); - } - } - this._pInst.endShape(); - } + normal.set(0, 0, 1); } - }); - if (extrude !== 0) { - geom.computeNormals(); + + // Add vertices (unshared - each triangle gets its own copies) + extruded.vertices.push(v0.copy(), v1.copy(), v2.copy()); + extruded.vertexNormals.push(normal.copy(), normal.copy(), normal.copy()); + extruded.faces.push([vertexIndex, vertexIndex + 1, vertexIndex + 2]); + vertexIndex += 3; + }; + + for (const face of geom.faces) { + if (face.length < 3) continue; + const v0 = geom.vertices[face[0]]; + for (let i = 1; i < face.length - 1; i++) { + const v1 = geom.vertices[face[i]]; + const v2 = geom.vertices[face[i + 1]]; + addTriangle( + new p5.Vector(v0.x, v0.y, v0.z + half), + new p5.Vector(v1.x, v1.y, v1.z + half), + new p5.Vector(v2.x, v2.y, v2.z + half) + ); + } + } + + for (const face of geom.faces) { + if (face.length < 3) continue; + const v0 = geom.vertices[face[0]]; + for (let i = 1; i < face.length - 1; i++) { + const v1 = geom.vertices[face[i]]; + const v2 = geom.vertices[face[i + 1]]; + addTriangle( + new p5.Vector(v0.x, v0.y, v0.z - half), + new p5.Vector(v2.x, v2.y, v2.z - half), + new p5.Vector(v1.x, v1.y, v1.z - half) + ); + } + } + + // Side faces from edges + let edges = geom.edges; + if (!edges || !Array.isArray(edges)) { + edges = []; + const edgeSet = new Set(); for (const face of geom.faces) { - if (face.every(idx => geom.vertices[idx].z <= -extrude * 0.5 + 0.1 || geom.vertices[idx].z >= extrude * 0.5 - 0.1)) { - for (const idx of face) geom.vertexNormals[idx].set(0, 0, -1); - face.reverse(); + for (let i = 0; i < face.length; i++) { + const a = face[i]; + const b = face[(i + 1) % face.length]; + if (a === b) continue; + const key = a < b ? `${a},${b}` : `${b},${a}`; + if (!edgeSet.has(key)) { + edgeSet.add(key); + edges.push([a, b]); + } } } } - return geom; + + const validEdges = edges.filter(([a, b]) => a !== b); + + for (const [a, b] of validEdges) { + const v0 = geom.vertices[a]; + const v1 = geom.vertices[b]; + + const vFront0 = new p5.Vector(v0.x, v0.y, v0.z + half); + const vFront1 = new p5.Vector(v1.x, v1.y, v1.z + half); + const vBack0 = new p5.Vector(v0.x, v0.y, v0.z - half); + const vBack1 = new p5.Vector(v1.x, v1.y, v1.z - half); + + // Two triangles forming the side quad + addTriangle(vFront0, vFront1, vBack1); + addTriangle(vFront0, vBack1, vBack0); + } + return extruded; } variations() { From 4c91d6e075d0a2f71ac1d8e407d24beb0e0dd4f4 Mon Sep 17 00:00:00 2001 From: VANSH3104 Date: Mon, 29 Sep 2025 15:21:50 +0530 Subject: [PATCH 3/3] Fix textToModel extrusion and degenerate face issues --- src/type/p5.Font.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/type/p5.Font.js b/src/type/p5.Font.js index 1475697b49..0921f7f968 100644 --- a/src/type/p5.Font.js +++ b/src/type/p5.Font.js @@ -583,12 +583,12 @@ export class Font { extruded.faces = []; let vertexIndex = 0; - + const Vector = this._pInst.constructor.Vector; // Helper to add a triangle with flat normal const addTriangle = (v0, v1, v2) => { - const edge1 = p5.Vector.sub(v1, v0); - const edge2 = p5.Vector.sub(v2, v0); - const normal = p5.Vector.cross(edge1, edge2); + const edge1 = Vector.sub(v1, v0); + const edge2 = Vector.sub(v2, v0); + const normal = Vector.cross(edge1, edge2); if (normal.magSq() > 0.0001) { normal.normalize(); } else { @@ -609,9 +609,9 @@ export class Font { const v1 = geom.vertices[face[i]]; const v2 = geom.vertices[face[i + 1]]; addTriangle( - new p5.Vector(v0.x, v0.y, v0.z + half), - new p5.Vector(v1.x, v1.y, v1.z + half), - new p5.Vector(v2.x, v2.y, v2.z + half) + new Vector(v0.x, v0.y, v0.z + half), + new Vector(v1.x, v1.y, v1.z + half), + new Vector(v2.x, v2.y, v2.z + half) ); } } @@ -623,9 +623,9 @@ export class Font { const v1 = geom.vertices[face[i]]; const v2 = geom.vertices[face[i + 1]]; addTriangle( - new p5.Vector(v0.x, v0.y, v0.z - half), - new p5.Vector(v2.x, v2.y, v2.z - half), - new p5.Vector(v1.x, v1.y, v1.z - half) + new Vector(v0.x, v0.y, v0.z - half), + new Vector(v2.x, v2.y, v2.z - half), + new Vector(v1.x, v1.y, v1.z - half) ); } } @@ -655,14 +655,14 @@ export class Font { const v0 = geom.vertices[a]; const v1 = geom.vertices[b]; - const vFront0 = new p5.Vector(v0.x, v0.y, v0.z + half); - const vFront1 = new p5.Vector(v1.x, v1.y, v1.z + half); - const vBack0 = new p5.Vector(v0.x, v0.y, v0.z - half); - const vBack1 = new p5.Vector(v1.x, v1.y, v1.z - half); + const vFront0 = new Vector(v0.x, v0.y, v0.z + half); + const vFront1 = new Vector(v1.x, v1.y, v1.z + half); + const vBack0 = new Vector(v0.x, v0.y, v0.z - half); + const vBack1 = new Vector(v1.x, v1.y, v1.z - half); // Two triangles forming the side quad - addTriangle(vFront0, vFront1, vBack1); - addTriangle(vFront0, vBack1, vBack0); + addTriangle(vFront0, vBack0, vBack1); + addTriangle(vFront0, vBack1, vFront1); } return extruded; }