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