@@ -6,6 +6,7 @@ import { textCoreConstants } from './textCore';
66import * as constants from '../core/constants' ;
77import { UnicodeRange } from '@japont/unicode-range' ;
88import { unicodeRanges } from './unicodeRanges' ;
9+ import { Vector } from '../math/p5.Vector' ;
910
1011/*
1112 API:
@@ -542,63 +543,148 @@ export class Font {
542543 textToModel ( str , x , y , width , height , options ) {
543544 ( { width, height, options } = this . _parseArgs ( width , height , options ) ) ;
544545 const extrude = options ?. extrude || 0 ;
545- const contours = this . textToContours ( str , x , y , width , height , options ) ;
546546
547+ let contours = this . textToContours ( str , x , y , width , height , options ) ;
548+ // Step 2: build base flat geometry - single shape
547549 const geom = this . _pInst . buildGeometry ( ( ) => {
548- if ( extrude === 0 ) {
549- const prevValidateFaces = this . _pInst . _renderer . _validateFaces ;
550- this . _pInst . _renderer . _validateFaces = true ;
551- this . _pInst . beginShape ( ) ;
552- this . _pInst . normal ( 0 , 0 , 1 ) ;
553- for ( const contour of contours ) {
554- this . _pInst . beginContour ( ) ;
555- for ( const { x, y } of contour ) {
556- this . _pInst . vertex ( x , y ) ;
557- }
558- this . _pInst . endContour ( this . _pInst . CLOSE ) ;
559- }
560- this . _pInst . endShape ( ) ;
561- this . _pInst . _renderer . _validateFaces = prevValidateFaces ;
562- } else {
563- const prevValidateFaces = this . _pInst . _renderer . _validateFaces ;
564- this . _pInst . _renderer . _validateFaces = true ;
565-
566- // Draw front faces
567- for ( const side of [ 1 , - 1 ] ) {
568- this . _pInst . beginShape ( ) ;
569- for ( const contour of contours ) {
570- this . _pInst . beginContour ( ) ;
571- for ( const { x, y } of contour ) {
572- this . _pInst . vertex ( x , y , side * extrude * 0.5 ) ;
573- }
574- this . _pInst . endContour ( this . _pInst . CLOSE ) ;
575- }
576- this . _pInst . endShape ( ) ;
577- }
578- this . _pInst . _renderer . _validateFaces = prevValidateFaces ;
579-
580- // Draw sides
581- for ( const contour of contours ) {
582- this . _pInst . beginShape ( this . _pInst . QUAD_STRIP ) ;
583- for ( const v of contour ) {
584- for ( const side of [ - 1 , 1 ] ) {
585- this . _pInst . vertex ( v . x , v . y , side * extrude * 0.5 ) ;
586- }
587- }
588- this . _pInst . endShape ( ) ;
550+ const prevValidateFaces = this . _pInst . _renderer . _validateFaces ;
551+ this . _pInst . _renderer . _validateFaces = true ;
552+
553+ this . _pInst . beginShape ( ) ;
554+ for ( const contour of contours ) {
555+ this . _pInst . beginContour ( ) ;
556+ for ( const pt of contour ) {
557+ this . _pInst . vertex ( pt . x , pt . y , 0 ) ;
589558 }
559+ this . _pInst . endContour ( this . _pInst . CLOSE ) ;
590560 }
561+
562+ this . _pInst . endShape ( this . _pInst . CLOSE ) ;
563+
564+ this . _pInst . _renderer . _validateFaces = prevValidateFaces ;
591565 } ) ;
592- if ( extrude !== 0 ) {
593- geom . computeNormals ( ) ;
594- for ( const face of geom . faces ) {
595- if ( face . every ( idx => geom . vertices [ idx ] . z <= - extrude * 0.5 + 0.1 ) ) {
596- for ( const idx of face ) geom . vertexNormals [ idx ] . set ( 0 , 0 , - 1 ) ;
597- face . reverse ( ) ;
598- }
566+
567+ if ( extrude === 0 ) {
568+ return geom ;
569+ }
570+
571+ // The tessellation process creates separate vertices for each triangle,
572+ // even when they share the same position. We need to deduplicate them
573+ // to find which faces are actually connected, so we can identify the
574+ // outer edges for extrusion.
575+
576+ const vertexIndices = { } ;
577+ const vertexId = v => `${ v . x . toFixed ( 6 ) } -${ v . y . toFixed ( 6 ) } -${ v . z . toFixed ( 6 ) } ` ;
578+ const newVertices = [ ] ;
579+ const newVertexIndex = [ ] ;
580+
581+ for ( const v of geom . vertices ) {
582+ const id = vertexId ( v ) ;
583+ if ( ! ( id in vertexIndices ) ) {
584+ const index = newVertices . length ;
585+ vertexIndices [ id ] = index ;
586+ newVertices . push ( v . copy ( ) ) ;
587+ }
588+ newVertexIndex . push ( vertexIndices [ id ] ) ;
589+ }
590+
591+ // Remap faces to use deduplicated vertices
592+ const newFaces = geom . faces . map ( f => f . map ( i => newVertexIndex [ i ] ) ) ;
593+
594+ //Find outer edges (edges that appear in only one face)
595+ const seen = { } ;
596+ for ( const face of newFaces ) {
597+ for ( let off = 0 ; off < face . length ; off ++ ) {
598+ const a = face [ off ] ;
599+ const b = face [ ( off + 1 ) % face . length ] ;
600+ const id = `${ Math . min ( a , b ) } -${ Math . max ( a , b ) } ` ;
601+ if ( ! seen [ id ] ) seen [ id ] = [ ] ;
602+ seen [ id ] . push ( [ a , b ] ) ;
599603 }
600604 }
601- return geom ;
605+ const validEdges = [ ] ;
606+ for ( const key in seen ) {
607+ if ( seen [ key ] . length === 1 ) {
608+ validEdges . push ( seen [ key ] [ 0 ] ) ;
609+ }
610+ }
611+
612+ // Step 5: Create extruded geometry
613+ const extruded = this . _pInst . buildGeometry ( ( ) => { } ) ;
614+ const half = extrude * 0.5 ;
615+ extruded . vertices = [ ] ;
616+ extruded . faces = [ ] ;
617+ extruded . edges = [ ] ; // INITIALIZE EDGES ARRAY
618+
619+ // Add side face vertices (separate for each edge for flat shading)
620+ for ( const [ a , b ] of validEdges ) {
621+ const vA = newVertices [ a ] ;
622+ const vB = newVertices [ b ] ;
623+
624+ // Skip if vertices are too close (degenerate edge)
625+ // We only need to check the perimeter edge length since the other edge
626+ // is the extrude direction, which is always > 0 for extruded geometry
627+
628+ const edgeVector = new Vector ( vB . x - vA . x , vB . y - vA . y , vB . z - vA . z ) ;
629+ const extrudeVector = new Vector ( 0 , 0 , extrude ) ;
630+ const crossProduct = Vector . cross ( edgeVector , extrudeVector ) ;
631+ const dist = edgeVector . mag ( ) ;
632+ if ( crossProduct . mag ( ) < 0.0001 || dist < 0.0001 ) continue ;
633+ // Front face vertices
634+ const frontA = extruded . vertices . length ;
635+ extruded . vertices . push ( new Vector ( vA . x , vA . y , vA . z + half ) ) ;
636+ const frontB = extruded . vertices . length ;
637+ extruded . vertices . push ( new Vector ( vB . x , vB . y , vB . z + half ) ) ;
638+ const backA = extruded . vertices . length ;
639+ extruded . vertices . push ( new Vector ( vA . x , vA . y , vA . z - half ) ) ;
640+ const backB = extruded . vertices . length ;
641+ extruded . vertices . push ( new Vector ( vB . x , vB . y , vB . z - half ) ) ;
642+
643+ extruded . faces . push ( [ frontA , backA , backB ] ) ;
644+ extruded . faces . push ( [ frontA , backB , frontB ] ) ;
645+ extruded . edges . push ( [ frontA , frontB ] ) ;
646+ extruded . edges . push ( [ backA , backB ] ) ;
647+ extruded . edges . push ( [ frontA , backA ] ) ;
648+ extruded . edges . push ( [ frontB , backB ] ) ;
649+ }
650+
651+ // Add front face (with unshared vertices for flat shading)
652+ const frontVertexOffset = extruded . vertices . length ;
653+ for ( const v of newVertices ) {
654+ extruded . vertices . push ( new Vector ( v . x , v . y , v . z + half ) ) ;
655+ }
656+ for ( const face of newFaces ) {
657+ if ( face . length < 3 ) continue ;
658+ const mappedFace = face . map ( i => i + frontVertexOffset ) ;
659+ extruded . faces . push ( mappedFace ) ;
660+
661+ // ADD EDGES FOR FRONT FACE
662+ for ( let i = 0 ; i < mappedFace . length ; i ++ ) {
663+ const nextIndex = ( i + 1 ) % mappedFace . length ;
664+ extruded . edges . push ( [ mappedFace [ i ] , mappedFace [ nextIndex ] ] ) ;
665+ }
666+ }
667+
668+ // Add back face (reversed winding order)
669+ const backVertexOffset = extruded . vertices . length ;
670+ for ( const v of newVertices ) {
671+ extruded . vertices . push ( new Vector ( v . x , v . y , v . z - half ) ) ;
672+ }
673+
674+ for ( const face of newFaces ) {
675+ if ( face . length < 3 ) continue ;
676+ const mappedFace = [ ...face ] . reverse ( ) . map ( i => i + backVertexOffset ) ;
677+ extruded . faces . push ( mappedFace ) ;
678+
679+ // ADD EDGES FOR BACK FACE
680+ for ( let i = 0 ; i < mappedFace . length ; i ++ ) {
681+ const nextIndex = ( i + 1 ) % mappedFace . length ;
682+ extruded . edges . push ( [ mappedFace [ i ] , mappedFace [ nextIndex ] ] ) ;
683+ }
684+ }
685+
686+ extruded . computeNormals ( ) ;
687+ return extruded ;
602688 }
603689
604690 variations ( ) {
0 commit comments