@@ -52,6 +52,12 @@ export class TryOnFace {
5252 this . size = { x : 1 , y : 1 , z : 1 } ;
5353 this . faceMesh = null ;
5454 this . camera = null ;
55+ this . debugEnabled = params . debug !== undefined ? params . debug : false ;
56+ this . debugCanvas = document . getElementById ( 'debugCanvas' ) ;
57+ this . debugCanvas . width = this . width ;
58+ this . debugCanvas . height = this . height ;
59+ this . debugCtx = this . debugCanvas . getContext ( '2d' ) ;
60+ this . lastLandmarks = null ;
5561 this . init3D ( ) ;
5662 }
5763
@@ -99,11 +105,18 @@ export class TryOnFace {
99105 this . changeStatus ( 'STATUS_SEARCH' ) ;
100106 this . size . x = 0 ;
101107 this . size . y = 0 ;
108+ this . lastLandmarks = null ;
102109 this . render ( ) ;
110+ if ( this . debugEnabled && this . debugCtx ) {
111+ // Clear debug canvas when no face detected
112+ this . debugCtx . clearRect ( 0 , 0 , this . width , this . height ) ;
113+ }
103114 return ;
104115 }
105116 this . changeStatus ( 'STATUS_FOUND' ) ;
106117 const landmarks = results . multiFaceLandmarks [ 0 ] ;
118+ this . lastLandmarks = landmarks ;
119+
107120 // Use MediaPipe landmark indices for left/right ear, eyes, nose, etc.
108121 // See: https://github.com/tensorflow/tfjs-models/blob/master/face-landmarks-detection/mesh_map.jpg
109122 // Example indices:
@@ -163,8 +176,9 @@ export class TryOnFace {
163176 this . size . z = this . size . x * CONFIG . GLASSES . DEPTH_TO_WIDTH_RATIO ;
164177 const absYaw = Math . min ( Math . abs ( yawAngleRad ) , maxRotationRad ) / maxRotationRad ;
165178 const depthDampen = 1 - ( absYaw * 0.6 ) ;
166- this . position . z = - ( this . size . z / 2 ) * depthDampen ;
179+ this . position . z = - ( this . size . z ) * depthDampen ;
167180 this . render ( ) ;
181+ this . drawDebugLandmarks ( ) ;
168182 }
169183
170184 loop ( ) {
@@ -300,6 +314,7 @@ export class TryOnFace {
300314 this . textures = await this . loadTextures ( textureLoader , renderer , sources ) ;
301315 const materials = this . createMaterials ( this . textures ) ;
302316 const { scene, camera, cube } = this . createScene ( renderer , materials ) ;
317+ this . renderer = renderer ;
303318 this . render = ( ) => {
304319 cube . position . x = this . position . x ;
305320 cube . position . y = this . position . y ;
@@ -324,4 +339,146 @@ export class TryOnFace {
324339 size : { ...this . size }
325340 } ;
326341 }
342+
343+ enableDebug ( ) {
344+ this . debugEnabled = true ;
345+ }
346+
347+ disableDebug ( ) {
348+ this . debugEnabled = false ;
349+ if ( this . debugCtx ) {
350+ this . debugCtx . clearRect ( 0 , 0 , this . width , this . height ) ;
351+ }
352+ }
353+
354+ drawDebugLandmarks ( ) {
355+ if ( ! this . debugEnabled || ! this . lastLandmarks || ! this . debugCtx ) {
356+ return ;
357+ }
358+
359+ const ctx = this . debugCtx ;
360+ const landmarks = this . lastLandmarks ;
361+
362+ // Clear previous debug drawings
363+ ctx . clearRect ( 0 , 0 , this . width , this . height ) ;
364+
365+ // Key landmark indices for MediaPipe Face Mesh
366+ const keyPoints = {
367+ LEFT_EAR : 234 ,
368+ RIGHT_EAR : 454 ,
369+ LEFT_EYE : 33 ,
370+ RIGHT_EYE : 263 ,
371+ NOSE_TIP : 1 ,
372+ NOSE_BRIDGE : 168 ,
373+ LEFT_EYE_OUTER : 133 ,
374+ RIGHT_EYE_OUTER : 362 ,
375+ LEFT_EYE_INNER : 33 ,
376+ RIGHT_EYE_INNER : 263 ,
377+ CHIN : 152 ,
378+ FOREHEAD : 10 ,
379+ LEFT_CHEEK : 234 ,
380+ RIGHT_CHEEK : 454
381+ } ;
382+
383+ // Draw all landmarks as small dots (optional, can be overwhelming)
384+ ctx . fillStyle = 'rgba(0, 255, 0, 0.3)' ;
385+ landmarks . forEach ( ( landmark , idx ) => {
386+ const x = landmark . x * this . width ;
387+ const y = landmark . y * this . height ;
388+ ctx . beginPath ( ) ;
389+ ctx . arc ( x , y , 1 , 0 , 2 * Math . PI ) ;
390+ ctx . fill ( ) ;
391+ } ) ;
392+
393+ // Draw key landmarks with labels
394+ Object . entries ( keyPoints ) . forEach ( ( [ label , idx ] ) => {
395+ const landmark = landmarks [ idx ] ;
396+ if ( ! landmark ) return ;
397+
398+ const x = landmark . x * this . width ;
399+ const y = landmark . y * this . height ;
400+
401+ // Draw a larger circle for key points
402+ ctx . fillStyle = 'rgba(255, 0, 0, 0.8)' ;
403+ ctx . beginPath ( ) ;
404+ ctx . arc ( x , y , 4 , 0 , 2 * Math . PI ) ;
405+ ctx . fill ( ) ;
406+
407+ // Draw label
408+ ctx . fillStyle = 'rgba(255, 255, 0, 0.9)' ;
409+ ctx . font = '10px monospace' ;
410+ ctx . fillText ( label , x + 6 , y + 4 ) ;
411+ } ) ;
412+
413+ // Draw face bounding box
414+ const leftEar = landmarks [ keyPoints . LEFT_EAR ] ;
415+ const rightEar = landmarks [ keyPoints . RIGHT_EAR ] ;
416+ const forehead = landmarks [ keyPoints . FOREHEAD ] ;
417+ const chin = landmarks [ keyPoints . CHIN ] ;
418+
419+ if ( leftEar && rightEar && forehead && chin ) {
420+ const left = leftEar . x * this . width ;
421+ const right = rightEar . x * this . width ;
422+ const top = forehead . y * this . height ;
423+ const bottom = chin . y * this . height ;
424+
425+ ctx . strokeStyle = 'rgba(0, 255, 255, 0.8)' ;
426+ ctx . lineWidth = 2 ;
427+ ctx . strokeRect ( left , top , right - left , bottom - top ) ;
428+
429+ // Draw face width line
430+ const earY = ( leftEar . y + rightEar . y ) / 2 * this . height ;
431+ ctx . strokeStyle = 'rgba(255, 0, 255, 0.8)' ;
432+ ctx . lineWidth = 2 ;
433+ ctx . beginPath ( ) ;
434+ ctx . moveTo ( left , earY ) ;
435+ ctx . lineTo ( right , earY ) ;
436+ ctx . stroke ( ) ;
437+
438+ // Draw face width measurement
439+ const faceWidth = Math . abs ( right - left ) ;
440+ ctx . fillStyle = 'rgba(255, 255, 255, 0.9)' ;
441+ ctx . font = '12px monospace' ;
442+ ctx . fillText ( `Face Width: ${ faceWidth . toFixed ( 1 ) } px` , left , earY - 10 ) ;
443+ }
444+
445+ // Draw eye line
446+ const leftEye = landmarks [ keyPoints . LEFT_EYE ] ;
447+ const rightEye = landmarks [ keyPoints . RIGHT_EYE ] ;
448+ if ( leftEye && rightEye ) {
449+ const lx = leftEye . x * this . width ;
450+ const ly = leftEye . y * this . height ;
451+ const rx = rightEye . x * this . width ;
452+ const ry = rightEye . y * this . height ;
453+
454+ ctx . strokeStyle = 'rgba(0, 255, 255, 0.8)' ;
455+ ctx . lineWidth = 2 ;
456+ ctx . beginPath ( ) ;
457+ ctx . moveTo ( lx , ly ) ;
458+ ctx . lineTo ( rx , ry ) ;
459+ ctx . stroke ( ) ;
460+
461+ const eyeDistance = Math . sqrt ( ( rx - lx ) ** 2 + ( ry - ly ) ** 2 ) ;
462+ ctx . fillStyle = 'rgba(255, 255, 255, 0.9)' ;
463+ ctx . font = '12px monospace' ;
464+ ctx . fillText ( `Eye Distance: ${ eyeDistance . toFixed ( 1 ) } px` , ( lx + rx ) / 2 , ( ly + ry ) / 2 - 10 ) ;
465+ }
466+
467+ // Draw nose bridge to tip line
468+ const noseBridge = landmarks [ keyPoints . NOSE_BRIDGE ] ;
469+ const noseTip = landmarks [ keyPoints . NOSE_TIP ] ;
470+ if ( noseBridge && noseTip ) {
471+ const bx = noseBridge . x * this . width ;
472+ const by = noseBridge . y * this . height ;
473+ const tx = noseTip . x * this . width ;
474+ const ty = noseTip . y * this . height ;
475+
476+ ctx . strokeStyle = 'rgba(255, 128, 0, 0.8)' ;
477+ ctx . lineWidth = 2 ;
478+ ctx . beginPath ( ) ;
479+ ctx . moveTo ( bx , by ) ;
480+ ctx . lineTo ( tx , ty ) ;
481+ ctx . stroke ( ) ;
482+ }
483+ }
327484}
0 commit comments