@@ -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,129 +543,145 @@ 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- // Step 1: generate glyph contours
546+
546547 let contours = this . textToContours ( str , x , y , width , height , options ) ;
547548 if ( ! Array . isArray ( contours [ 0 ] [ 0 ] ) ) {
548549 contours = [ contours ] ;
549550 }
550551
551- // Step 2: build base flat geometry
552552 const geom = this . _pInst . buildGeometry ( ( ) => {
553553 const prevValidateFaces = this . _pInst . _renderer . _validateFaces ;
554554 this . _pInst . _renderer . _validateFaces = true ;
555+ this . _pInst . push ( ) ;
556+ this . _pInst . stroke ( 0 ) ;
555557
556558 contours . forEach ( glyphContours => {
557559 this . _pInst . beginShape ( ) ;
558- const outer = glyphContours [ 0 ] ;
559- outer . forEach ( ( { x, y } ) => this . _pInst . vertex ( x , y , 0 ) ) ;
560-
561- for ( let i = 1 ; i < glyphContours . length ; i ++ ) {
560+ for ( const contour of glyphContours ) {
562561 this . _pInst . beginContour ( ) ;
563- glyphContours [ i ] . forEach ( ( { x, y } ) => this . _pInst . vertex ( x , y , 0 ) ) ;
562+ contour . forEach ( ( { x, y } ) => this . _pInst . vertex ( x , y , 0 ) ) ;
564563 this . _pInst . endContour ( this . _pInst . CLOSE ) ;
565564 }
566-
567565 this . _pInst . endShape ( this . _pInst . CLOSE ) ;
568566 } ) ;
569-
567+ this . _pInst . pop ( ) ;
570568 this . _pInst . _renderer . _validateFaces = prevValidateFaces ;
571569 } ) ;
572570
573571 if ( extrude === 0 ) {
574- console . log ( 'No extrusion' ) ;
575572 return geom ;
576573 }
577574
578- // Step 3: Create extruded geometry with UNSHARED vertices for flat shading
575+ const vertexIndices = { } ;
576+ const vertexId = v => `${ v . x . toFixed ( 6 ) } -${ v . y . toFixed ( 6 ) } -${ v . z . toFixed ( 6 ) } ` ;
577+ const newVertices = [ ] ;
578+ const newVertexIndex = [ ] ;
579+
580+ for ( const v of geom . vertices ) {
581+ const id = vertexId ( v ) ;
582+ if ( ! ( id in vertexIndices ) ) {
583+ const index = newVertices . length ;
584+ vertexIndices [ id ] = index ;
585+ newVertices . push ( v . copy ( ) ) ;
586+ }
587+ newVertexIndex . push ( vertexIndices [ id ] ) ;
588+ }
589+
590+ // Remap faces to use deduplicated vertices
591+ const newFaces = geom . faces . map ( f => f . map ( i => newVertexIndex [ i ] ) ) ;
592+
593+ //Find outer edges (edges that appear in only one face)
594+ const seen = { } ;
595+ for ( const face of newFaces ) {
596+ for ( let off = 0 ; off < face . length ; off ++ ) {
597+ const a = face [ off ] ;
598+ const b = face [ ( off + 1 ) % face . length ] ;
599+ const id = `${ Math . min ( a , b ) } -${ Math . max ( a , b ) } ` ;
600+ if ( ! seen [ id ] ) seen [ id ] = [ ] ;
601+ seen [ id ] . push ( [ a , b ] ) ;
602+ }
603+ }
604+ const validEdges = [ ] ;
605+ for ( const key in seen ) {
606+ if ( seen [ key ] . length === 1 ) {
607+ validEdges . push ( seen [ key ] [ 0 ] ) ;
608+ }
609+ }
610+
611+ console . log ( `Found ${ validEdges . length } outer edges from ${ Object . keys ( seen ) . length } total edges` ) ;
612+
613+ // Step 5: Create extruded geometry
579614 const extruded = this . _pInst . buildGeometry ( ( ) => { } ) ;
580615 const half = extrude * 0.5 ;
581-
582616 extruded . vertices = [ ] ;
583- extruded . vertexNormals = [ ] ;
584617 extruded . faces = [ ] ;
618+ extruded . edges = [ ] ; // INITIALIZE EDGES ARRAY
585619
586- let vertexIndex = 0 ;
587- const Vector = this . _pInst . constructor . Vector ;
588- // Helper to add a triangle with flat normal
589- const addTriangle = ( v0 , v1 , v2 ) => {
590- const edge1 = Vector . sub ( v1 , v0 ) ;
591- const edge2 = Vector . sub ( v2 , v0 ) ;
592- const normal = Vector . cross ( edge1 , edge2 ) ;
593- if ( normal . magSq ( ) > 0.0001 ) {
594- normal . normalize ( ) ;
595- } else {
596- normal . set ( 0 , 0 , 1 ) ;
597- }
598-
599- // Add vertices (unshared - each triangle gets its own copies)
600- extruded . vertices . push ( v0 . copy ( ) , v1 . copy ( ) , v2 . copy ( ) ) ;
601- extruded . vertexNormals . push ( normal . copy ( ) , normal . copy ( ) , normal . copy ( ) ) ;
602- extruded . faces . push ( [ vertexIndex , vertexIndex + 1 , vertexIndex + 2 ] ) ;
603- vertexIndex += 3 ;
604- } ;
605-
606- for ( const face of geom . faces ) {
607- if ( face . length < 3 ) continue ;
608- const v0 = geom . vertices [ face [ 0 ] ] ;
609- for ( let i = 1 ; i < face . length - 1 ; i ++ ) {
610- const v1 = geom . vertices [ face [ i ] ] ;
611- const v2 = geom . vertices [ face [ i + 1 ] ] ;
612- addTriangle (
613- new Vector ( v0 . x , v0 . y , v0 . z + half ) ,
614- new Vector ( v1 . x , v1 . y , v1 . z + half ) ,
615- new Vector ( v2 . x , v2 . y , v2 . z + half )
616- ) ;
617- }
620+ // Add side face vertices (separate for each edge for flat shading)
621+ for ( const [ a , b ] of validEdges ) {
622+ const vA = newVertices [ a ] ;
623+ const vB = newVertices [ b ] ;
624+ // Skip if vertices are too close (degenerate edge)
625+ const dist = Math . sqrt (
626+ Math . pow ( vB . x - vA . x , 2 ) +
627+ Math . pow ( vB . y - vA . y , 2 ) +
628+ Math . pow ( vB . z - vA . z , 2 )
629+ ) ;
630+ if ( dist < 0.0001 ) continue ;
631+ // Front face vertices
632+ const frontA = extruded . vertices . length ;
633+ extruded . vertices . push ( new Vector ( vA . x , vA . y , vA . z + half ) ) ;
634+ const frontB = extruded . vertices . length ;
635+ extruded . vertices . push ( new Vector ( vB . x , vB . y , vB . z + half ) ) ;
636+ const backA = extruded . vertices . length ;
637+ extruded . vertices . push ( new Vector ( vA . x , vA . y , vA . z - half ) ) ;
638+ const backB = extruded . vertices . length ;
639+ extruded . vertices . push ( new Vector ( vB . x , vB . y , vB . z - half ) ) ;
640+
641+ extruded . faces . push ( [ frontA , backA , backB ] ) ;
642+ extruded . faces . push ( [ frontA , backB , frontB ] ) ;
643+ extruded . edges . push ( [ frontA , frontB ] ) ;
644+ extruded . edges . push ( [ backA , backB ] ) ;
645+ extruded . edges . push ( [ frontA , backA ] ) ;
646+ extruded . edges . push ( [ frontB , backB ] ) ;
618647 }
619648
620- for ( const face of geom . faces ) {
621- if ( face . length < 3 ) continue ;
622- const v0 = geom . vertices [ face [ 0 ] ] ;
623- for ( let i = 1 ; i < face . length - 1 ; i ++ ) {
624- const v1 = geom . vertices [ face [ i ] ] ;
625- const v2 = geom . vertices [ face [ i + 1 ] ] ;
626- addTriangle (
627- new Vector ( v0 . x , v0 . y , v0 . z - half ) ,
628- new Vector ( v2 . x , v2 . y , v2 . z - half ) ,
629- new Vector ( v1 . x , v1 . y , v1 . z - half )
630- ) ;
631- }
649+ // Add front face (with unshared vertices for flat shading)
650+ const frontVertexOffset = extruded . vertices . length ;
651+ for ( const v of newVertices ) {
652+ extruded . vertices . push ( new Vector ( v . x , v . y , v . z + half ) ) ;
632653 }
654+ for ( const face of newFaces ) {
655+ if ( face . length < 3 ) continue ;
656+ const mappedFace = face . map ( i => i + frontVertexOffset ) ;
657+ extruded . faces . push ( mappedFace ) ;
633658
634- // Side faces from edges
635- let edges = geom . edges ;
636- if ( ! edges || ! Array . isArray ( edges ) ) {
637- edges = [ ] ;
638- const edgeSet = new Set ( ) ;
639- for ( const face of geom . faces ) {
640- for ( let i = 0 ; i < face . length ; i ++ ) {
641- const a = face [ i ] ;
642- const b = face [ ( i + 1 ) % face . length ] ;
643- if ( a === b ) continue ;
644- const key = a < b ? `${ a } ,${ b } ` : `${ b } ,${ a } ` ;
645- if ( ! edgeSet . has ( key ) ) {
646- edgeSet . add ( key ) ;
647- edges . push ( [ a , b ] ) ;
648- }
649- }
659+ // ADD EDGES FOR FRONT FACE
660+ for ( let i = 0 ; i < mappedFace . length ; i ++ ) {
661+ const nextIndex = ( i + 1 ) % mappedFace . length ;
662+ extruded . edges . push ( [ mappedFace [ i ] , mappedFace [ nextIndex ] ] ) ;
650663 }
651664 }
652665
653- const validEdges = edges . filter ( ( [ a , b ] ) => a !== b ) ;
654-
655- for ( const [ a , b ] of validEdges ) {
656- const v0 = geom . vertices [ a ] ;
657- const v1 = geom . vertices [ b ] ;
666+ // Add back face (reversed winding order)
667+ const backVertexOffset = extruded . vertices . length ;
668+ for ( const v of newVertices ) {
669+ extruded . vertices . push ( new Vector ( v . x , v . y , v . z - half ) ) ;
670+ }
658671
659- const vFront0 = new Vector ( v0 . x , v0 . y , v0 . z + half ) ;
660- const vFront1 = new Vector ( v1 . x , v1 . y , v1 . z + half ) ;
661- const vBack0 = new Vector ( v0 . x , v0 . y , v0 . z - half ) ;
662- const vBack1 = new Vector ( v1 . x , v1 . y , v1 . z - half ) ;
672+ for ( const face of newFaces ) {
673+ if ( face . length < 3 ) continue ;
674+ const mappedFace = [ ... face ] . reverse ( ) . map ( i => i + backVertexOffset ) ;
675+ extruded . faces . push ( mappedFace ) ;
663676
664- // Two triangles forming the side quad
665- addTriangle ( vFront0 , vBack0 , vBack1 ) ;
666- addTriangle ( vFront0 , vBack1 , vFront1 ) ;
677+ // ADD EDGES FOR BACK FACE
678+ for ( let i = 0 ; i < mappedFace . length ; i ++ ) {
679+ const nextIndex = ( i + 1 ) % mappedFace . length ;
680+ extruded . edges . push ( [ mappedFace [ i ] , mappedFace [ nextIndex ] ] ) ;
681+ }
667682 }
683+
684+ extruded . computeNormals ( ) ;
668685 return extruded ;
669686 }
670687
0 commit comments