@incodetech/core 2.1.0-rc.2 → 2.1.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3dAvatarProvider-ChciZmjE.esm.js +28214 -0
- package/dist/{BrowserStorageProvider-D5xc7Lqo.esm.js → BrowserStorageProvider-CrrN540a.esm.js} +1 -25
- package/dist/BrowserVisibilityObserverProvider-rim9Jrz-.esm.js +25 -0
- package/dist/Face2DProvider-BeLlYBbB.esm.js +824 -0
- package/dist/FaceDetectionProvider-B78455D4.d.ts +119 -0
- package/dist/IVisibilityObserverCapability-BbWjgpiF.d.ts +12 -0
- package/dist/IWasmUtilCapability-d2XvzaCk.d.ts +145 -0
- package/dist/IpifyProvider-BRW14_uo.esm.js +51 -0
- package/dist/{Manager-B2OEDYqf.d.ts → Manager-CJ3G2zqi.d.ts} +1 -4
- package/dist/{OpenViduRecordingProvider-DF7A4ZNJ.esm.js → OpenViduRecordingProvider-B_0GKkzc.esm.js} +1 -1
- package/dist/PrivacyLensProvider-hDzaDzZ6.esm.js +339 -0
- package/dist/StateMachine-C_AY4cs7.d.ts +2 -0
- package/dist/{StreamCanvasCapture-Dr5n_iK1.esm.js → StreamCanvasCapture-Bz17q4FP.esm.js} +1 -1
- package/dist/StreamCanvasCapture-DS1TOoiH.d.ts +42 -0
- package/dist/{WasmUtilProvider-CIu373BZ.esm.js → WasmUtilProvider-ADG54T6M.esm.js} +16 -1
- package/dist/{addressSearch-BH96a3fq.esm.js → addressSearch-DJyNTg5S.esm.js} +4 -4
- package/dist/{ae-signature-CDEN8y-w.esm.js → ae-signature-DlElqFrZ.esm.js} +1 -1
- package/dist/ae-signature.d.ts +4 -3
- package/dist/ae-signature.esm.js +10 -9
- package/dist/antifraud.d.ts +4 -3
- package/dist/antifraud.esm.js +7 -6
- package/dist/{antifraudManager-DZifA5zX.esm.js → antifraudManager-BKdQoMFf.esm.js} +4 -3
- package/dist/{antifraudManager-B7WQTIdu.d.ts → antifraudManager-DpIvbrZP.d.ts} +2 -2
- package/dist/{antifraudStateMachine-DlKrK2hL.esm.js → antifraudStateMachine-C0NGnP9W.esm.js} +3 -3
- package/dist/{api-DUZwyz0o.esm.js → api-DsbUi7TO.esm.js} +21 -2
- package/dist/authentication.d.ts +19 -12
- package/dist/authentication.esm.js +33 -27
- package/dist/{authenticationManager-Dve7mGLe.d.ts → authenticationManager-CdPbNS2H.d.ts} +3 -3
- package/dist/{authenticationManager-BM4LzyV7.esm.js → authenticationManager-Dtl1vcET.esm.js} +6 -6
- package/dist/{authenticationStateMachine-Chx3ySdT.esm.js → authenticationStateMachine-D-hOlg8p.esm.js} +8 -8
- package/dist/avatar.d.ts +3 -0
- package/dist/avatar.esm.js +3 -0
- package/dist/{backCameraStream-iftkf3rd.esm.js → backCameraStream-ZyqrFyjO.esm.js} +3 -3
- package/dist/{browser-DlFgHpoC-7tbRR1aR.esm.js → browser-DlFgHpoC-qLKs75Qp.esm.js} +1 -1
- package/dist/{browserSimulation-CGjpdNA1.esm.js → browserSimulation-TsSoavQv.esm.js} +153 -1216
- package/dist/camera-DUkgmYMo.esm.js +79 -0
- package/dist/camera.d.ts +7 -1
- package/dist/camera.esm.js +4 -4
- package/dist/{StreamCanvasCapture-Ctgyx-oD.d.ts → canvas-CJUmoXSD.d.ts} +1 -41
- package/dist/certificate-issuance.d.ts +31 -30
- package/dist/certificate-issuance.esm.js +8 -7
- package/dist/{certificateIssuanceStateMachine-CLNXtXaC.esm.js → certificateIssuanceStateMachine-C07m7qtP.esm.js} +4 -4
- package/dist/clientLifecycle-Ct7h9uxx.esm.js +534 -0
- package/dist/consent.d.ts +4 -3
- package/dist/consent.esm.js +7 -6
- package/dist/{consentManager-psDCeJ0E.esm.js → consentManager-BtnPZUW4.esm.js} +4 -3
- package/dist/{consentManager-BqUGU_YD.d.ts → consentManager-DufVo9D9.d.ts} +52 -52
- package/dist/{consentStateMachine-DRsFFSHE.esm.js → consentStateMachine-DO2JqoZW.esm.js} +3 -3
- package/dist/cpf-ocr.d.ts +4 -3
- package/dist/cpf-ocr.esm.js +7 -6
- package/dist/{cpfOcrManager-BxcgxvgO.esm.js → cpfOcrManager-C4rLgKsu.esm.js} +6 -5
- package/dist/{cpfOcrManager-B2u9hMzd.d.ts → cpfOcrManager-DqZZ94Gt.d.ts} +19 -19
- package/dist/createFaceAvatar-B8pFpy6o.d.ts +401 -0
- package/dist/createFaceAvatar-BOHDKQRq.esm.js +46 -0
- package/dist/cross-document-data-match.d.ts +2 -1
- package/dist/cross-document-data-match.esm.js +6 -5
- package/dist/curp-validation.d.ts +10 -7
- package/dist/curp-validation.esm.js +7 -6
- package/dist/{curpValidationManager-BgLDw0Ub.esm.js → curpValidationManager-BafobPXC.esm.js} +4 -3
- package/dist/{curpValidationManager-Bt6CVD8b.d.ts → curpValidationManager-Co6LZAHT.d.ts} +3 -3
- package/dist/{curpValidationStateMachine-BK_wJ48C.esm.js → curpValidationStateMachine-DXTPK6vI.esm.js} +3 -3
- package/dist/custom-fields.d.ts +3 -2
- package/dist/custom-fields.esm.js +6 -5
- package/dist/custom-watchlist.d.ts +2 -1
- package/dist/custom-watchlist.esm.js +6 -5
- package/dist/{customWatchlistStateMachine-Bi4P59HM.esm.js → customWatchlistStateMachine-B4G3Chkx.esm.js} +3 -3
- package/dist/{deepsightLoader-BMmvk0X6.esm.js → deepsightLoader-DHSVD776.esm.js} +14 -13
- package/dist/{deepsightService-CEZrdiW0.esm.js → deepsightService-CDahSjGP.esm.js} +9 -38
- package/dist/deepsightService-CMB9XjES.d.ts +69 -0
- package/dist/device.d.ts +9 -1
- package/dist/device.esm.js +5 -5
- package/dist/document-capture.d.ts +175 -174
- package/dist/document-capture.esm.js +14 -13
- package/dist/document-upload.d.ts +48 -47
- package/dist/document-upload.esm.js +9 -8
- package/dist/{documentCaptureStateMachine-MpwpJO7t.esm.js → documentCaptureStateMachine-DSOX2epf.esm.js} +7 -7
- package/dist/dynamic-forms.d.ts +4 -192
- package/dist/dynamic-forms.esm.js +8 -160
- package/dist/dynamicFormsManager-BOefZbyF.esm.js +168 -0
- package/dist/dynamicFormsManager-CUYLOj9g.d.ts +239 -0
- package/dist/{dynamicFormsStateMachine-Do84A4uU.esm.js → dynamicFormsStateMachine-BXHcaocI.esm.js} +4 -4
- package/dist/ekyb.d.ts +9 -6
- package/dist/ekyb.esm.js +20 -17
- package/dist/{ekybStateMachine-nsuPOw3f.esm.js → ekybStateMachine-CCSeqjCt.esm.js} +5 -5
- package/dist/ekyc.d.ts +4 -3
- package/dist/ekyc.esm.js +9 -8
- package/dist/{ekycStateMachine-DcynX0rj.esm.js → ekycStateMachine-DkLVTOyA.esm.js} +5 -5
- package/dist/electronic-signature.d.ts +4 -3
- package/dist/electronic-signature.esm.js +9 -8
- package/dist/{electronicSignatureManager-B63aAuI5.esm.js → electronicSignatureManager-DjzUzqZj.esm.js} +50 -87
- package/dist/email.d.ts +5 -4
- package/dist/email.esm.js +9 -8
- package/dist/{emailManager-DxHjAe5u.esm.js → emailManager-B_XzEsXH.esm.js} +5 -4
- package/dist/{emailManager-C6D2u4mQ.d.ts → emailManager-fHASH6uo.d.ts} +3 -3
- package/dist/{emailStateMachine-758f34gg.esm.js → emailStateMachine-fqJ42b19.esm.js} +5 -5
- package/dist/{endpoints-BM2_j_IE.esm.js → endpoints-BzMJFAPV.esm.js} +13 -0
- package/dist/{events-DN-u67gb.esm.js → events-CqGY1yMq.esm.js} +2 -188
- package/dist/events.d.ts +2 -0
- package/dist/events.esm.js +3 -2
- package/dist/extensibility.d.ts +37 -26
- package/dist/extensibility.esm.js +74 -59
- package/dist/{externalFetch-crimxkN9.esm.js → externalFetch-CyhBfD-n.esm.js} +1 -1
- package/dist/face-match.d.ts +2 -2
- package/dist/face-match.esm.js +6 -5
- package/dist/{faceCaptureManagerFactory-BNXZchWX.esm.js → faceCaptureManagerFactory-2brKTzMb.esm.js} +25 -16
- package/dist/faceCaptureManagerFactory-CGKbf7zt.d.ts +400 -0
- package/dist/{faceCaptureSetup-Dqb09D24.esm.js → faceCaptureSetup-CDUVU1nl.esm.js} +97 -13
- package/dist/{faceMatchStateMachine-FikRaUZH.esm.js → faceMatchStateMachine-DUeUjEPx.esm.js} +3 -3
- package/dist/field-comparison.d.ts +4 -3
- package/dist/field-comparison.esm.js +6 -5
- package/dist/{fieldComparisonManager-Dic0DVgl.esm.js → fieldComparisonManager-B9W0a9A5.esm.js} +5 -4
- package/dist/{fieldComparisonManager-BXc6-DJv.d.ts → fieldComparisonManager-DdLqDeB2.d.ts} +2 -2
- package/dist/fiscal-qr.d.ts +5 -3
- package/dist/fiscal-qr.esm.js +13 -12
- package/dist/flow-events.d.ts +8 -6
- package/dist/flow.d.ts +21 -13
- package/dist/flow.esm.js +28 -19
- package/dist/{flowCompletionService-CdGDKv0z.d.ts → flowCompletionService-BIqSTZcd.d.ts} +2 -1
- package/dist/{flowCompletionService-D_CsiDZM.esm.js → flowCompletionService-qEqh_g14.esm.js} +3 -3
- package/dist/{flowServices-ar5DXv71.esm.js → flowServices-C4piblgx.esm.js} +24 -9
- package/dist/geolocation.d.ts +4 -127
- package/dist/geolocation.esm.js +8 -115
- package/dist/geolocationManager-BP2Do9O-.esm.js +123 -0
- package/dist/geolocationManager-Cs50eRlC.d.ts +156 -0
- package/dist/{geolocationStateMachine-BWjrMGS_.esm.js → geolocationStateMachine-BYfOOOG0.esm.js} +4 -4
- package/dist/{getDeviceClass-rXKlXywy.esm.js → getDeviceClass-DGGzzfqm.esm.js} +1 -1
- package/dist/getDeviceData-TrKM8NPh.esm.js +101 -0
- package/dist/government-validation.d.ts +4 -3
- package/dist/government-validation.esm.js +8 -7
- package/dist/{governmentValidationStateMachine-BoF7U1of.esm.js → governmentValidationStateMachine-CwoPPO8b.esm.js} +5 -5
- package/dist/home.d.ts +18 -15
- package/dist/home.esm.js +4 -3
- package/dist/http.esm.js +2 -2
- package/dist/id-ocr.d.ts +4 -625
- package/dist/id-ocr.esm.js +8 -84
- package/dist/id-verification.d.ts +28 -27
- package/dist/id-verification.esm.js +6 -5
- package/dist/id.d.ts +16 -11
- package/dist/id.esm.js +39 -32
- package/dist/idCaptureDefaults-DNJON8Xo.esm.js +188 -0
- package/dist/{idCaptureManager-BvGUSN2e.d.ts → idCaptureManager-BVdvfJys.d.ts} +40 -10
- package/dist/{idCaptureManager-Bm6Wm6Qv.esm.js → idCaptureManager-Bo9DCQSU.esm.js} +14 -11
- package/dist/{idCaptureStateMachine-D1YhOj6n.esm.js → idCaptureStateMachine-BeDidMLU.esm.js} +395 -382
- package/dist/idOcrManager-BkRDrpZu.esm.js +92 -0
- package/dist/idOcrManager-CJzrmM91.d.ts +641 -0
- package/dist/{idOcrStateMachine-YMCDGplq.esm.js → idOcrStateMachine-DRZc4FBl.esm.js} +5 -5
- package/dist/{idVerificationStateMachine-Bo1IYuZH.esm.js → idVerificationStateMachine-oqiERKpg.esm.js} +3 -3
- package/dist/identity-reuse.d.ts +4 -3
- package/dist/identity-reuse.esm.js +7 -6
- package/dist/{identityReuseManager-D0knIrOy.esm.js → identityReuseManager-Bu62LToE.esm.js} +4 -3
- package/dist/{identityReuseManager-DcWSvC-A.d.ts → identityReuseManager-TWKbXuvY.d.ts} +33 -33
- package/dist/{identityReuseStateMachine-CSQfZOqD.esm.js → identityReuseStateMachine-BmONeF6B.esm.js} +3 -3
- package/dist/{index-BgxYUYuD.d.ts → index-DtUlD-bz.d.ts} +2 -2
- package/dist/{index-DV134v62.d.ts → index-Icld-cKW.d.ts} +175 -295
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +16 -13
- package/dist/{invokeOnCaptureCallback-Dzemudhk.esm.js → invokeOnCaptureCallback-v_PV_IJz.esm.js} +1 -1
- package/dist/{lib-7ZQ9BdKH.esm.js → lib-B050WTth.esm.js} +1 -1
- package/dist/mandatory-consent.d.ts +10 -7
- package/dist/mandatory-consent.esm.js +7 -6
- package/dist/{mandatoryConsentManager-Tzl5Qd35.d.ts → mandatoryConsentManager-Bmdjo14S.d.ts} +50 -50
- package/dist/{mandatoryConsentManager-Lon46nPQ.esm.js → mandatoryConsentManager-CQ3jirlZ.esm.js} +4 -3
- package/dist/{mandatoryConsentStateMachine-D_dZ7IF6.esm.js → mandatoryConsentStateMachine-DkJJLHqC.esm.js} +3 -3
- package/dist/mediaRecorderOptions-C3NAkVgK.esm.js +42 -0
- package/dist/mediapipeShared-CRAKVElv.esm.js +67 -0
- package/dist/{recordingService-CxMJP4tQ.esm.js → onDeviceFaceResults-DVBggeq8.esm.js} +71 -389
- package/dist/{openviduLazy-oAXXI9w_.esm.js → openviduLazy-BTOIauGP.esm.js} +2 -2
- package/dist/openviduLazy-Dov8oKfL.esm.js +3 -0
- package/dist/{permissionGuards-D-Z-lgNj.esm.js → permissionGuards-BR2oN573.esm.js} +1 -1
- package/dist/{permissionServices-CPDboRoE.esm.js → permissionServices-BJhGL13R.esm.js} +7 -4
- package/dist/personhood.d.ts +534 -0
- package/dist/personhood.esm.js +1494 -0
- package/dist/phone.d.ts +5 -4
- package/dist/phone.esm.js +9 -8
- package/dist/{phoneManager-TSq1Ief6.d.ts → phoneManager-BOTcjXgs.d.ts} +3 -3
- package/dist/{phoneManager-Blt4Oc2P.esm.js → phoneManager-BWlxqbyD.esm.js} +5 -4
- package/dist/{phoneStateMachine-W3c6Z8sj.esm.js → phoneStateMachine-B_V9nODs.esm.js} +5 -5
- package/dist/{qe-signature-CQLK6LKE.esm.js → qe-signature-BhFnI9pT.esm.js} +1 -1
- package/dist/qe-signature.d.ts +4 -3
- package/dist/qe-signature.esm.js +10 -9
- package/dist/recordingService-m_1xCthr.esm.js +347 -0
- package/dist/redirect-to-mobile.d.ts +4 -3
- package/dist/redirect-to-mobile.esm.js +9 -8
- package/dist/{redirectToMobileManager-DCvpKdkD.esm.js → redirectToMobileManager-BaAo9rgi.esm.js} +5 -4
- package/dist/{redirectToMobileManager-yOHAmwg-.d.ts → redirectToMobileManager-Br0KZK1G.d.ts} +2 -2
- package/dist/{redirectToMobileStateMachine-DC9z4Vfu.esm.js → redirectToMobileStateMachine-DoAnf2ql.esm.js} +10 -12
- package/dist/{runChildModule-C9G0wfVC.esm.js → runChildModule-0nQfjVVo.esm.js} +3 -3
- package/dist/selfie.d.ts +19 -12
- package/dist/selfie.esm.js +34 -28
- package/dist/{selfieManager-BRL6c_Oc.esm.js → selfieManager-D_ZFERsE.esm.js} +6 -6
- package/dist/{selfieManager-imY2dAcH.d.ts → selfieManager-R0Jw5sjb.d.ts} +3 -3
- package/dist/{selfieStateMachine-D2Q0uwDK.esm.js → selfieStateMachine-CIgeeiN9.esm.js} +4 -4
- package/dist/{session-Be_IA1e0.esm.js → session-9jIfKFCc.esm.js} +3 -3
- package/dist/session.d.ts +7 -5
- package/dist/session.esm.js +16 -14
- package/dist/{sessionInitializer-CaiGA8GP.esm.js → sessionInitializer-C_TYIgyk.esm.js} +11 -107
- package/dist/{setup-BJZAkajt.d.ts → setup-CpRnXxn8.d.ts} +16 -3
- package/dist/setup-DHc6OaHm.esm.js +562 -0
- package/dist/signature.d.ts +3 -2
- package/dist/signature.esm.js +6 -5
- package/dist/{signatureStateMachine-DAxXmMrk.esm.js → signatureStateMachine-DtoiPzys.esm.js} +3 -3
- package/dist/{stats-DntyIiGr.esm.js → stats-CYcScXcO.esm.js} +2 -2
- package/dist/stats.esm.js +3 -3
- package/dist/thumbmarkAdapter-fHBUDlAP.esm.js +1194 -0
- package/dist/{tri-CtxWthxz.esm.js → tri-BG1iJsr4.esm.js} +12 -2
- package/dist/tri.d.ts +1 -1
- package/dist/{tri.esm-CrWAulDi.esm.js → tri.esm-DUC01NgO.esm.js} +27 -23
- package/dist/tri.esm.js +2 -2
- package/dist/trust-graph.d.ts +4 -3
- package/dist/trust-graph.esm.js +4 -3
- package/dist/{types-DpTWgfKY.d.ts → types-BklZ_Wne.d.ts} +1 -1
- package/dist/{types-Dv-9WPJX.d.ts → types-BsYXnC5s.d.ts} +39 -3
- package/dist/types-CxW2e0Ar.d.ts +132 -0
- package/dist/{types-Z9wv19v2.d.ts → types-DgGlgyZT.d.ts} +1 -1
- package/dist/types-ElOJAGaM.esm.js +189 -0
- package/dist/video-C2WIj6TS.esm.js +36 -0
- package/dist/video-selfie.d.ts +351 -0
- package/dist/video-selfie.esm.js +1509 -0
- package/dist/videoSelfieSteps-7M5vxKn-.esm.js +58 -0
- package/dist/{warmup--6HA3yUR.d.ts → warmup-BJZPQIiX.d.ts} +5 -1
- package/dist/wasm.d.ts +4 -4
- package/dist/wasm.esm.js +17 -14
- package/dist/watchlist-for-business.d.ts +2 -1
- package/dist/watchlist-for-business.esm.js +6 -5
- package/dist/watchlist.d.ts +2 -1
- package/dist/watchlist.esm.js +6 -5
- package/dist/{watchlistForBusinessStateMachine-DtefJb-D.esm.js → watchlistForBusinessStateMachine-DvEunDxs.esm.js} +3 -3
- package/dist/{watchlistStateMachine-B0aIS2_n.esm.js → watchlistStateMachine-AHXVf-TB.esm.js} +3 -3
- package/dist/workflow.d.ts +230 -98
- package/dist/workflow.esm.js +123 -79
- package/dist/{xstate.esm-Dkm1jy5u.esm.js → xstate.esm-Bomq1a0z.esm.js} +41 -1
- package/package.json +16 -1
- package/dist/IpifyProvider-oZf7Ke1O.esm.js +0 -167
- package/dist/camera-x4LjGTjZ.esm.js +0 -22
- package/dist/deepsightService-ByNKY5Jv.d.ts +0 -276
- package/dist/faceCaptureManagerFactory-CL-7T_vH.d.ts +0 -731
- package/dist/getBrowser-Dv8LZyHE.esm.js +0 -8
- package/dist/openviduLazy-B_s6jnEk.esm.js +0 -3
- package/dist/setup-C7tintPc.esm.js +0 -1064
- /package/dist/{Actor-fIPLXfoi.d.ts → Actor-CGEelYtQ.d.ts} +0 -0
- /package/dist/{BrowserTimerProvider-BVgutW_b.esm.js → BrowserTimerProvider-CHloo0eQ.esm.js} +0 -0
- /package/dist/{ITimerCapability-BvH0pDwp.esm.js → ITimerCapability-pltuG1E8.esm.js} +0 -0
- /package/dist/{MotionSensorProvider-C9fPAJdS.esm.js → MotionSensorProvider-DCbId4De.esm.js} +0 -0
- /package/dist/{apiError-D-GTvFrH.esm.js → apiError-DNS2OCEX.esm.js} +0 -0
- /package/dist/{camera-biGPWlmx.d.ts → camera-CNVUo-bC.d.ts} +0 -0
- /package/dist/{canvas-7uX2izHb.esm.js → canvas-_I95hkPg.esm.js} +0 -0
- /package/dist/{chunk-BK1xu56N.esm.js → chunk-CIKcdghV.esm.js} +0 -0
- /package/dist/{cpf-xBg-DFNn.esm.js → cpf-BKaho0j_.esm.js} +0 -0
- /package/dist/{dateUtils-8dCcuArE.esm.js → dateUtils-7-tQYRNN.esm.js} +0 -0
- /package/dist/{getDeviceClass-C0zrM97a.esm.js → getDeviceClass-DavNdcxh.esm.js} +0 -0
- /package/dist/{http-CfVTgMfU.esm.js → http-BkiUT7EB.esm.js} +0 -0
- /package/dist/{index-52_qi4bd.d.ts → index-XTtxaahe.d.ts} +0 -0
- /package/dist/{otp-D87RTwrx.esm.js → otp-Bfnzf7I3.esm.js} +0 -0
- /package/dist/{otp-BHJnjEEc.d.ts → otp-M4iy57XY.d.ts} +0 -0
- /package/dist/{platform-BfoqeM61.esm.js → platform-uIVQUnJP.esm.js} +0 -0
- /package/dist/{types-BdVOWGGW.esm.js → types-CMcxlSmH.esm.js} +0 -0
- /package/dist/{types-DYZja-k9.d.ts → types-D_IceWxT.d.ts} +0 -0
- /package/dist/{types-F0IXbaCB.d.ts → types-Duy2rDxF.d.ts} +0 -0
- /package/dist/{urlUtils-8RNHyW3x.esm.js → urlUtils-BZqFU9rR.esm.js} +0 -0
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { t as createHiddenVideoElement } from "./video-C2WIj6TS.esm.js";
|
|
2
|
+
import { i as dtAlpha, n as asRvfc, r as createFaceLandmarker, t as MAX_EMA_DT_MS } from "./mediapipeShared-CRAKVElv.esm.js";
|
|
3
|
+
|
|
4
|
+
//#region ../infra/src/avatar/2d-default-avatar/assets.ts
|
|
5
|
+
/**
|
|
6
|
+
* 2D avatar MediaPipe asset resolution — **self-contained** (no shared
|
|
7
|
+
* `avatarDefaults`; owns its asset config). No VRM here — 2D satin face.
|
|
8
|
+
* See docs/features/FACE_AVATAR.md → Isolation and ownership.
|
|
9
|
+
*
|
|
10
|
+
* Prebuilt tasks-vision ESM + wasm + the face-landmarker model. Public CDNs for
|
|
11
|
+
* development; production should self-host.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_MEDIAPIPE_MODULE_URL = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.35";
|
|
14
|
+
const DEFAULT_FACE_LANDMARKER_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task";
|
|
15
|
+
function valid(value) {
|
|
16
|
+
return typeof value === "string" && value.trim() !== "";
|
|
17
|
+
}
|
|
18
|
+
function resolveAvatar2DAssets(overrides) {
|
|
19
|
+
const moduleUrl = valid(overrides?.mediapipeModulePath) ? overrides.mediapipeModulePath : DEFAULT_MEDIAPIPE_MODULE_URL;
|
|
20
|
+
return {
|
|
21
|
+
mediapipeModulePath: moduleUrl,
|
|
22
|
+
mediapipeWasmPath: valid(overrides?.mediapipeWasmPath) ? overrides.mediapipeWasmPath : `${moduleUrl}/wasm`,
|
|
23
|
+
faceLandmarkerModelPath: valid(overrides?.faceLandmarkerModelPath) ? overrides.faceLandmarkerModelPath : DEFAULT_FACE_LANDMARKER_MODEL_URL
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region ../infra/src/avatar/renderers/SatinSmileyRenderer.ts
|
|
29
|
+
const SPRITE = 512;
|
|
30
|
+
const HEAD_CX = 256;
|
|
31
|
+
const HEAD_CY = 256;
|
|
32
|
+
const HEAD_R = 150;
|
|
33
|
+
const HALO_R = HEAD_R + 28;
|
|
34
|
+
const EYE_Y = 218;
|
|
35
|
+
const EYE_DX = 56;
|
|
36
|
+
const EYE_RX = 12;
|
|
37
|
+
const NOSE_Y0 = 258;
|
|
38
|
+
const NOSE_Y1 = 292;
|
|
39
|
+
const MOUTH_Y = 330;
|
|
40
|
+
const MOUTH_HALF = 44;
|
|
41
|
+
const YAW_PARALLAX = 90;
|
|
42
|
+
const PITCH_PARALLAX = 72;
|
|
43
|
+
const REST_SMILE = .35;
|
|
44
|
+
const PALETTES = {
|
|
45
|
+
detecting: {
|
|
46
|
+
stops: [
|
|
47
|
+
"#f4f9ff",
|
|
48
|
+
"#dbeafe",
|
|
49
|
+
"#cbe0fb",
|
|
50
|
+
"#aacdf3"
|
|
51
|
+
],
|
|
52
|
+
occlusion: "#6f9fd8",
|
|
53
|
+
contact: "#3a5f8f",
|
|
54
|
+
feature: "#007aff",
|
|
55
|
+
halo: "#2f86f0"
|
|
56
|
+
},
|
|
57
|
+
success: {
|
|
58
|
+
stops: [
|
|
59
|
+
"#f0fdf4",
|
|
60
|
+
"#dcfce7",
|
|
61
|
+
"#bbf7d0",
|
|
62
|
+
"#86c79a"
|
|
63
|
+
],
|
|
64
|
+
occlusion: "#3f7a52",
|
|
65
|
+
contact: "#1f5132",
|
|
66
|
+
feature: "#15803d",
|
|
67
|
+
halo: "#22c55e"
|
|
68
|
+
},
|
|
69
|
+
error: {
|
|
70
|
+
stops: [
|
|
71
|
+
"#fff1f1",
|
|
72
|
+
"#fee2e2",
|
|
73
|
+
"#fecaca",
|
|
74
|
+
"#e89a9a"
|
|
75
|
+
],
|
|
76
|
+
occlusion: "#8c4a4a",
|
|
77
|
+
contact: "#5f1e1e",
|
|
78
|
+
feature: "#b91c1c",
|
|
79
|
+
halo: "#dc2626"
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const clamp$1 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
83
|
+
function hexToRgb(hex) {
|
|
84
|
+
const h = hex.replace("#", "");
|
|
85
|
+
return [
|
|
86
|
+
parseInt(h.slice(0, 2), 16),
|
|
87
|
+
parseInt(h.slice(2, 4), 16),
|
|
88
|
+
parseInt(h.slice(4, 6), 16)
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function rgb(c) {
|
|
92
|
+
return `rgb(${c[0] | 0}, ${c[1] | 0}, ${c[2] | 0})`;
|
|
93
|
+
}
|
|
94
|
+
function rgba(c, a) {
|
|
95
|
+
return `rgba(${c[0] | 0}, ${c[1] | 0}, ${c[2] | 0}, ${a})`;
|
|
96
|
+
}
|
|
97
|
+
function toRgbPalette(p) {
|
|
98
|
+
return {
|
|
99
|
+
stops: [
|
|
100
|
+
hexToRgb(p.stops[0]),
|
|
101
|
+
hexToRgb(p.stops[1]),
|
|
102
|
+
hexToRgb(p.stops[2]),
|
|
103
|
+
hexToRgb(p.stops[3])
|
|
104
|
+
],
|
|
105
|
+
occlusion: hexToRgb(p.occlusion),
|
|
106
|
+
contact: hexToRgb(p.contact),
|
|
107
|
+
feature: hexToRgb(p.feature),
|
|
108
|
+
halo: hexToRgb(p.halo)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const RGB_PALETTES = {
|
|
112
|
+
detecting: toRgbPalette(PALETTES.detecting),
|
|
113
|
+
success: toRgbPalette(PALETTES.success),
|
|
114
|
+
error: toRgbPalette(PALETTES.error)
|
|
115
|
+
};
|
|
116
|
+
function clonePalette(p) {
|
|
117
|
+
return {
|
|
118
|
+
stops: [
|
|
119
|
+
[...p.stops[0]],
|
|
120
|
+
[...p.stops[1]],
|
|
121
|
+
[...p.stops[2]],
|
|
122
|
+
[...p.stops[3]]
|
|
123
|
+
],
|
|
124
|
+
occlusion: [...p.occlusion],
|
|
125
|
+
contact: [...p.contact],
|
|
126
|
+
feature: [...p.feature],
|
|
127
|
+
halo: [...p.halo]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function easeRgb(cur, target, t) {
|
|
131
|
+
cur[0] += (target[0] - cur[0]) * t;
|
|
132
|
+
cur[1] += (target[1] - cur[1]) * t;
|
|
133
|
+
cur[2] += (target[2] - cur[2]) * t;
|
|
134
|
+
}
|
|
135
|
+
/** Ease every channel of `cur` toward `target` in place (smooth state crossfade). */
|
|
136
|
+
function easePaletteToward(cur, target, t) {
|
|
137
|
+
for (let i = 0; i < 4; i += 1) easeRgb(cur.stops[i], target.stops[i], t);
|
|
138
|
+
easeRgb(cur.occlusion, target.occlusion, t);
|
|
139
|
+
easeRgb(cur.contact, target.contact, t);
|
|
140
|
+
easeRgb(cur.feature, target.feature, t);
|
|
141
|
+
easeRgb(cur.halo, target.halo, t);
|
|
142
|
+
}
|
|
143
|
+
function resolveOverride() {
|
|
144
|
+
const v = globalThis.__incodeAvatarState;
|
|
145
|
+
return v === "detecting" || v === "success" || v === "error" ? v : null;
|
|
146
|
+
}
|
|
147
|
+
var SatinSmileyRenderer = class {
|
|
148
|
+
constructor(size = 512) {
|
|
149
|
+
this.ctx = null;
|
|
150
|
+
this.presence = 0;
|
|
151
|
+
this.faceState = "detecting";
|
|
152
|
+
this.eased = clonePalette(RGB_PALETTES.detecting);
|
|
153
|
+
this.size = size;
|
|
154
|
+
this.canvas = document.createElement("canvas");
|
|
155
|
+
this.canvas.width = size;
|
|
156
|
+
this.canvas.height = size;
|
|
157
|
+
}
|
|
158
|
+
init() {
|
|
159
|
+
this.ctx = this.canvas.getContext("2d");
|
|
160
|
+
}
|
|
161
|
+
getCanvas() {
|
|
162
|
+
return this.canvas;
|
|
163
|
+
}
|
|
164
|
+
/** Set the verdict state (detecting/success/error); colours ease toward it. */
|
|
165
|
+
setState(state) {
|
|
166
|
+
this.faceState = state;
|
|
167
|
+
}
|
|
168
|
+
dispose() {
|
|
169
|
+
this.ctx = null;
|
|
170
|
+
}
|
|
171
|
+
render(frame) {
|
|
172
|
+
const ctx = this.ctx;
|
|
173
|
+
if (!ctx) return;
|
|
174
|
+
const s = this.size;
|
|
175
|
+
ctx.clearRect(0, 0, s, s);
|
|
176
|
+
const state = resolveOverride() ?? this.faceState;
|
|
177
|
+
easePaletteToward(this.eased, RGB_PALETTES[state], .08);
|
|
178
|
+
const target = frame.tracked ? 1 : 0;
|
|
179
|
+
this.presence += (target - this.presence) * .12;
|
|
180
|
+
const faceAlpha = this.presence;
|
|
181
|
+
const k = s / SPRITE;
|
|
182
|
+
const slideX = frame.rootOffset.x * s * .03;
|
|
183
|
+
const slideY = frame.rootOffset.y * s * .03;
|
|
184
|
+
const scale = 1 + clamp$1(frame.depth, -.6, .8) * .35;
|
|
185
|
+
const yaw = clamp$1(frame.headEuler.y, -.9, .9);
|
|
186
|
+
const pitch = clamp$1(frame.headEuler.x, -.9, .9);
|
|
187
|
+
this.drawHalo(ctx, s, k, scale, slideX, slideY);
|
|
188
|
+
ctx.save();
|
|
189
|
+
ctx.translate(s / 2 + slideX, s / 2 + slideY);
|
|
190
|
+
ctx.rotate(frame.headEuler.z);
|
|
191
|
+
ctx.scale(scale * k, scale * k);
|
|
192
|
+
ctx.translate(-SPRITE / 2, -SPRITE / 2);
|
|
193
|
+
if (faceAlpha > .02) {
|
|
194
|
+
ctx.globalAlpha = faceAlpha;
|
|
195
|
+
this.drawBall(ctx);
|
|
196
|
+
const featX = yaw * YAW_PARALLAX;
|
|
197
|
+
const featY = pitch * PITCH_PARALLAX;
|
|
198
|
+
ctx.save();
|
|
199
|
+
ctx.translate(HEAD_CX, HEAD_CY);
|
|
200
|
+
ctx.scale(1 - Math.abs(yaw) * .24, 1 - Math.abs(pitch) * .12);
|
|
201
|
+
ctx.translate(-HEAD_CX, -HEAD_CY);
|
|
202
|
+
ctx.translate(featX, featY);
|
|
203
|
+
const feat = rgb(this.eased.feature);
|
|
204
|
+
let blinkL = clamp$1(frame.blink.left, 0, 1);
|
|
205
|
+
let blinkR = clamp$1(frame.blink.right, 0, 1);
|
|
206
|
+
if (state === "success") {
|
|
207
|
+
blinkL = 0;
|
|
208
|
+
blinkR = .85;
|
|
209
|
+
}
|
|
210
|
+
this.drawEye(ctx, HEAD_CX - EYE_DX, EYE_Y, blinkL, feat);
|
|
211
|
+
this.drawEye(ctx, HEAD_CX + EYE_DX, EYE_Y, blinkR, feat);
|
|
212
|
+
this.drawNose(ctx, feat);
|
|
213
|
+
let mouth = Math.max(REST_SMILE, clamp$1(frame.smile ?? 0, 0, 1));
|
|
214
|
+
if (state === "success") mouth = 1;
|
|
215
|
+
else if (state === "error") mouth = -.55;
|
|
216
|
+
this.drawMouth(ctx, mouth, feat);
|
|
217
|
+
ctx.restore();
|
|
218
|
+
ctx.globalAlpha = 1;
|
|
219
|
+
}
|
|
220
|
+
ctx.restore();
|
|
221
|
+
}
|
|
222
|
+
/** Dashed halo ring around the head — the "we've got you" frame (state-colored). */
|
|
223
|
+
drawHalo(ctx, s, k, scale, slideX, slideY) {
|
|
224
|
+
ctx.save();
|
|
225
|
+
ctx.translate(s / 2 + slideX, s / 2 + slideY);
|
|
226
|
+
ctx.scale(scale, scale);
|
|
227
|
+
ctx.globalAlpha = .9;
|
|
228
|
+
ctx.strokeStyle = rgb(this.eased.halo);
|
|
229
|
+
ctx.lineWidth = 4 * k;
|
|
230
|
+
ctx.lineCap = "round";
|
|
231
|
+
ctx.setLineDash([15 * k, 12 * k]);
|
|
232
|
+
ctx.beginPath();
|
|
233
|
+
ctx.arc(0, 0, HALO_R * k, 0, Math.PI * 2);
|
|
234
|
+
ctx.stroke();
|
|
235
|
+
ctx.setLineDash([]);
|
|
236
|
+
ctx.restore();
|
|
237
|
+
}
|
|
238
|
+
/** Soft 3D-shaded ball via runtime gradients (so state colours can ease). */
|
|
239
|
+
drawBall(ctx) {
|
|
240
|
+
const p = this.eased;
|
|
241
|
+
const contact = ctx.createRadialGradient(256, 424, 0, 256, 424, 122);
|
|
242
|
+
contact.addColorStop(0, rgba(p.contact, .28));
|
|
243
|
+
contact.addColorStop(1, rgba(p.contact, 0));
|
|
244
|
+
ctx.fillStyle = contact;
|
|
245
|
+
ctx.beginPath();
|
|
246
|
+
ctx.ellipse(256, 424, 122, 26, 0, 0, Math.PI * 2);
|
|
247
|
+
ctx.fill();
|
|
248
|
+
const body = ctx.createRadialGradient(HEAD_CX - HEAD_R * .28, HEAD_CY - HEAD_R * .34, HEAD_R * .08, HEAD_CX, HEAD_CY, HEAD_R * 1.05);
|
|
249
|
+
body.addColorStop(0, rgb(p.stops[0]));
|
|
250
|
+
body.addColorStop(.45, rgb(p.stops[1]));
|
|
251
|
+
body.addColorStop(.8, rgb(p.stops[2]));
|
|
252
|
+
body.addColorStop(1, rgb(p.stops[3]));
|
|
253
|
+
ctx.fillStyle = body;
|
|
254
|
+
ctx.beginPath();
|
|
255
|
+
ctx.arc(HEAD_CX, HEAD_CY, HEAD_R, 0, Math.PI * 2);
|
|
256
|
+
ctx.fill();
|
|
257
|
+
const occl = ctx.createRadialGradient(HEAD_CX, HEAD_CY, HEAD_R * .68, HEAD_CX, HEAD_CY, HEAD_R);
|
|
258
|
+
occl.addColorStop(0, rgba(p.occlusion, 0));
|
|
259
|
+
occl.addColorStop(1, rgba(p.occlusion, .22));
|
|
260
|
+
ctx.fillStyle = occl;
|
|
261
|
+
ctx.beginPath();
|
|
262
|
+
ctx.arc(HEAD_CX, HEAD_CY, HEAD_R, 0, Math.PI * 2);
|
|
263
|
+
ctx.fill();
|
|
264
|
+
const spec = ctx.createRadialGradient(200, 190, 0, 200, 190, 64);
|
|
265
|
+
spec.addColorStop(0, "rgba(255, 255, 255, 0.85)");
|
|
266
|
+
spec.addColorStop(1, "rgba(255, 255, 255, 0)");
|
|
267
|
+
ctx.fillStyle = spec;
|
|
268
|
+
ctx.beginPath();
|
|
269
|
+
ctx.ellipse(200, 190, 64, 48, 0, 0, Math.PI * 2);
|
|
270
|
+
ctx.fill();
|
|
271
|
+
}
|
|
272
|
+
/** One eye: a near-round dot (open) that flattens to a line as blink → 1. */
|
|
273
|
+
drawEye(ctx, cx, cy, blink, color) {
|
|
274
|
+
const ry = 2.5 + (1 - blink) * 14;
|
|
275
|
+
ctx.fillStyle = color;
|
|
276
|
+
ctx.beginPath();
|
|
277
|
+
ctx.ellipse(cx, cy, EYE_RX, ry, 0, 0, Math.PI * 2);
|
|
278
|
+
ctx.fill();
|
|
279
|
+
}
|
|
280
|
+
/** Nose: a short, rounded vertical line under the eyes (personhood cue). */
|
|
281
|
+
drawNose(ctx, color) {
|
|
282
|
+
ctx.strokeStyle = color;
|
|
283
|
+
ctx.lineWidth = 8;
|
|
284
|
+
ctx.lineCap = "round";
|
|
285
|
+
ctx.beginPath();
|
|
286
|
+
ctx.moveTo(HEAD_CX, NOSE_Y0);
|
|
287
|
+
ctx.lineTo(HEAD_CX, NOSE_Y1);
|
|
288
|
+
ctx.stroke();
|
|
289
|
+
}
|
|
290
|
+
/** Mouth: a rounded line — flat at rest, bowing up with smile, down on frown. */
|
|
291
|
+
drawMouth(ctx, smile, color) {
|
|
292
|
+
const cy = MOUTH_Y;
|
|
293
|
+
const curve = 2 + smile * 32;
|
|
294
|
+
const lift = smile * 10;
|
|
295
|
+
ctx.strokeStyle = color;
|
|
296
|
+
ctx.lineWidth = 11;
|
|
297
|
+
ctx.lineCap = "round";
|
|
298
|
+
ctx.beginPath();
|
|
299
|
+
ctx.moveTo(HEAD_CX - MOUTH_HALF, cy - lift);
|
|
300
|
+
ctx.quadraticCurveTo(HEAD_CX, cy + curve, HEAD_CX + MOUTH_HALF, cy - lift);
|
|
301
|
+
ctx.stroke();
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region ../infra/src/avatar/2d-default-avatar/pose.ts
|
|
307
|
+
const AVATAR2D_NEUTRAL_POSE = {
|
|
308
|
+
tracked: false,
|
|
309
|
+
headEuler: {
|
|
310
|
+
x: 0,
|
|
311
|
+
y: 0,
|
|
312
|
+
z: 0
|
|
313
|
+
},
|
|
314
|
+
rootOffset: {
|
|
315
|
+
x: 0,
|
|
316
|
+
y: 0
|
|
317
|
+
},
|
|
318
|
+
depth: 0,
|
|
319
|
+
blink: {
|
|
320
|
+
left: 0,
|
|
321
|
+
right: 0
|
|
322
|
+
},
|
|
323
|
+
mouthOpen: 0,
|
|
324
|
+
smile: 0
|
|
325
|
+
};
|
|
326
|
+
const LM_NOSE_TIP = 4;
|
|
327
|
+
const LM_EYE_L = 33;
|
|
328
|
+
const LM_EYE_L_INNER = 133;
|
|
329
|
+
const LM_EYE_R = 263;
|
|
330
|
+
const LM_EYE_R_INNER = 362;
|
|
331
|
+
const LM_FOREHEAD = 10;
|
|
332
|
+
const LM_CHIN = 152;
|
|
333
|
+
const AVATAR2D_POSE_CONFIG = {
|
|
334
|
+
yawGain: 2.2,
|
|
335
|
+
pitchUpGain: 3.4,
|
|
336
|
+
pitchDownGain: 2.6,
|
|
337
|
+
rollGain: 1,
|
|
338
|
+
rotMax: .9,
|
|
339
|
+
lateralGain: 1.6,
|
|
340
|
+
lateralMax: 1,
|
|
341
|
+
depthGain: 2,
|
|
342
|
+
depthMax: .8,
|
|
343
|
+
signYaw: 1,
|
|
344
|
+
signPitch: 1,
|
|
345
|
+
signRoll: 1,
|
|
346
|
+
signX: 1,
|
|
347
|
+
signY: 1
|
|
348
|
+
};
|
|
349
|
+
function clamp(v, lo, hi) {
|
|
350
|
+
return Math.min(hi, Math.max(lo, v));
|
|
351
|
+
}
|
|
352
|
+
/** Soft deadzone (continuous past the threshold): kills idle micro-jitter. (§3b.) */
|
|
353
|
+
function deadzone(v, dz) {
|
|
354
|
+
return Math.abs(v) <= dz ? 0 : v - Math.sign(v) * dz;
|
|
355
|
+
}
|
|
356
|
+
const AVATAR2D_DEADZONE = {
|
|
357
|
+
rot: .005,
|
|
358
|
+
roll: .006,
|
|
359
|
+
pos: .03,
|
|
360
|
+
depth: .1
|
|
361
|
+
};
|
|
362
|
+
const MEDIAN_WINDOW = 7;
|
|
363
|
+
/**
|
|
364
|
+
* Below this face-size ratio the detector emits sporadic single-frame outlier
|
|
365
|
+
* spikes ("the avatar jumps") that the deadzone (idle-only) and the EMA (delays,
|
|
366
|
+
* doesn't reject) both miss; run the {@link Median} first in that regime. At/above
|
|
367
|
+
* it, the face is near and exp smoothing alone is enough. (FACE_AVATAR.md → Pose pipeline.)
|
|
368
|
+
*/
|
|
369
|
+
const MEDIAN_FACE_RATIO = .35;
|
|
370
|
+
/**
|
|
371
|
+
* Single-axis rolling-window median for outlier rejection. Returns the latest
|
|
372
|
+
* sample until the window fills (no start-up lag spike), then the middle of the
|
|
373
|
+
* sorted window. The buffer stays in insertion order so `shift()` evicts the
|
|
374
|
+
* OLDEST sample, not the smallest. Ported from the incode-personhood-widget
|
|
375
|
+
* `MedianFilter` — **copied, not shared** (POC isolation; renderer is the only
|
|
376
|
+
* shared surface). Use three independent instances (one per axis).
|
|
377
|
+
*/
|
|
378
|
+
var Median = class {
|
|
379
|
+
constructor() {
|
|
380
|
+
this.buf = [];
|
|
381
|
+
}
|
|
382
|
+
reset() {
|
|
383
|
+
this.buf = [];
|
|
384
|
+
}
|
|
385
|
+
filter(x) {
|
|
386
|
+
this.buf.push(x);
|
|
387
|
+
if (this.buf.length > MEDIAN_WINDOW) this.buf.shift();
|
|
388
|
+
if (this.buf.length < MEDIAN_WINDOW) return x;
|
|
389
|
+
const sorted = [...this.buf].sort((a, b) => a - b);
|
|
390
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Caps on the auto-calibrated neutral so calibration cancels small device/framing
|
|
395
|
+
* bias but NOT the user's real starting orientation. Yaw/roll only (straight ≈ 0
|
|
396
|
+
* for both); pitch + position are left unbounded (pitch has a person-specific
|
|
397
|
+
* baseline; position is genuine framing) — bounding those risks an "always looks
|
|
398
|
+
* down/off-centre" artifact. Tune on device.
|
|
399
|
+
*/
|
|
400
|
+
const AVATAR2D_NEUTRAL_BIAS = {
|
|
401
|
+
yaw: .06,
|
|
402
|
+
roll: .05
|
|
403
|
+
};
|
|
404
|
+
/**
|
|
405
|
+
* Raw pose from landmarks, in **pixel space** (multiply normalized coords by the
|
|
406
|
+
* input width/height) so distances/angles aren't skewed on non-square frames.
|
|
407
|
+
* Returns null on a missing or non-finite frame (NaN guard).
|
|
408
|
+
*/
|
|
409
|
+
function computeRawPose(result, width, height) {
|
|
410
|
+
const lm = result.faceLandmarks?.[0];
|
|
411
|
+
if (!lm) return null;
|
|
412
|
+
const nose = lm[LM_NOSE_TIP];
|
|
413
|
+
const eyeLo = lm[LM_EYE_L];
|
|
414
|
+
const eyeLi = lm[LM_EYE_L_INNER];
|
|
415
|
+
const eyeRo = lm[LM_EYE_R];
|
|
416
|
+
const eyeRi = lm[LM_EYE_R_INNER];
|
|
417
|
+
const forehead = lm[LM_FOREHEAD];
|
|
418
|
+
const chin = lm[LM_CHIN];
|
|
419
|
+
if (!nose || !eyeLo || !eyeLi || !eyeRo || !eyeRi || !forehead || !chin) return null;
|
|
420
|
+
const nx = nose.x * width;
|
|
421
|
+
const ny = nose.y * height;
|
|
422
|
+
const lx = (eyeLo.x + eyeLi.x) / 2 * width;
|
|
423
|
+
const ly = (eyeLo.y + eyeLi.y) / 2 * height;
|
|
424
|
+
const rx = (eyeRo.x + eyeRi.x) / 2 * width;
|
|
425
|
+
const ry = (eyeRo.y + eyeRi.y) / 2 * height;
|
|
426
|
+
const fy = forehead.y * height;
|
|
427
|
+
const cy = chin.y * height;
|
|
428
|
+
const eyeMidX = (lx + rx) / 2;
|
|
429
|
+
const eyeMidY = (ly + ry) / 2;
|
|
430
|
+
const faceMidY = (fy + cy) / 2;
|
|
431
|
+
const eyeDist = Math.hypot(rx - lx, ry - ly) || 1e-4;
|
|
432
|
+
const faceH = Math.abs(cy - fy) || 1e-4;
|
|
433
|
+
const yaw = (nx - eyeMidX) / eyeDist;
|
|
434
|
+
const pitch = (ny - faceMidY) / faceH;
|
|
435
|
+
const roll = Math.atan2(ry - ly, rx - lx);
|
|
436
|
+
if (!Number.isFinite(yaw) || !Number.isFinite(pitch) || !Number.isFinite(roll) || !Number.isFinite(eyeMidX) || !Number.isFinite(eyeDist)) return null;
|
|
437
|
+
let faceSizeRatio;
|
|
438
|
+
if (width > 0 && height > 0) {
|
|
439
|
+
let minX = Infinity;
|
|
440
|
+
let minY = Infinity;
|
|
441
|
+
let maxX = -Infinity;
|
|
442
|
+
let maxY = -Infinity;
|
|
443
|
+
for (let i = 0; i < lm.length; i += 1) {
|
|
444
|
+
const p = lm[i];
|
|
445
|
+
if (p.x < minX) minX = p.x;
|
|
446
|
+
if (p.x > maxX) maxX = p.x;
|
|
447
|
+
if (p.y < minY) minY = p.y;
|
|
448
|
+
if (p.y > maxY) maxY = p.y;
|
|
449
|
+
}
|
|
450
|
+
const bw = (maxX - minX) * width;
|
|
451
|
+
const bh = (maxY - minY) * height;
|
|
452
|
+
if (bw > 0 && bh > 0) faceSizeRatio = Math.sqrt(bw * bh) / Math.min(width, height);
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
yaw,
|
|
456
|
+
pitch,
|
|
457
|
+
roll,
|
|
458
|
+
eyeMidX,
|
|
459
|
+
eyeMidY,
|
|
460
|
+
eyeDist,
|
|
461
|
+
faceH,
|
|
462
|
+
faceSizeRatio
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/** Map a raw pose (relative to neutral) into head euler + offset + depth. */
|
|
466
|
+
function mapToPose(raw, neutral, cfg) {
|
|
467
|
+
let dRoll = raw.roll - neutral.roll;
|
|
468
|
+
dRoll = Math.atan2(Math.sin(dRoll), Math.cos(dRoll));
|
|
469
|
+
const dist = neutral.dist || 1e-4;
|
|
470
|
+
const dPitch = deadzone(raw.pitch - neutral.pitch, AVATAR2D_DEADZONE.rot);
|
|
471
|
+
const pitchGain = dPitch < 0 ? cfg.pitchUpGain : cfg.pitchDownGain;
|
|
472
|
+
const dYaw = deadzone(raw.yaw - neutral.yaw, AVATAR2D_DEADZONE.rot);
|
|
473
|
+
const dRollDz = deadzone(dRoll, AVATAR2D_DEADZONE.roll);
|
|
474
|
+
const relX = deadzone((raw.eyeMidX - neutral.cx) / dist, AVATAR2D_DEADZONE.pos);
|
|
475
|
+
const relY = deadzone((raw.eyeMidY - neutral.cy) / dist, AVATAR2D_DEADZONE.pos);
|
|
476
|
+
const faceHN = neutral.faceH || 1e-4;
|
|
477
|
+
const relZ = deadzone((raw.faceH - neutral.faceH) / faceHN, AVATAR2D_DEADZONE.depth);
|
|
478
|
+
return {
|
|
479
|
+
headEuler: {
|
|
480
|
+
x: clamp(dPitch * pitchGain * cfg.signPitch, -cfg.rotMax, cfg.rotMax),
|
|
481
|
+
y: clamp(dYaw * cfg.yawGain * cfg.signYaw, -cfg.rotMax, cfg.rotMax),
|
|
482
|
+
z: clamp(dRollDz * cfg.rollGain * cfg.signRoll, -cfg.rotMax, cfg.rotMax)
|
|
483
|
+
},
|
|
484
|
+
rootOffset: {
|
|
485
|
+
x: clamp(relX * cfg.lateralGain * cfg.signX, -cfg.lateralMax, cfg.lateralMax),
|
|
486
|
+
y: clamp(relY * cfg.lateralGain * cfg.signY, -cfg.lateralMax, cfg.lateralMax)
|
|
487
|
+
},
|
|
488
|
+
depth: clamp(relZ * cfg.depthGain, -cfg.depthMax, cfg.depthMax)
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/** Per-eye blink + mouth-open + smile straight from the ARKit blendshapes. */
|
|
492
|
+
function blinkMouthFromBlendshapes(result) {
|
|
493
|
+
const categories = result.faceBlendshapes?.[0]?.categories ?? [];
|
|
494
|
+
let left = 0;
|
|
495
|
+
let right = 0;
|
|
496
|
+
let jaw = 0;
|
|
497
|
+
let smileL = 0;
|
|
498
|
+
let smileR = 0;
|
|
499
|
+
for (const c of categories) if (c.categoryName === "eyeBlinkLeft") left = c.score;
|
|
500
|
+
else if (c.categoryName === "eyeBlinkRight") right = c.score;
|
|
501
|
+
else if (c.categoryName === "jawOpen") jaw = c.score;
|
|
502
|
+
else if (c.categoryName === "mouthSmileLeft") smileL = c.score;
|
|
503
|
+
else if (c.categoryName === "mouthSmileRight") smileR = c.score;
|
|
504
|
+
return {
|
|
505
|
+
blink: {
|
|
506
|
+
left,
|
|
507
|
+
right
|
|
508
|
+
},
|
|
509
|
+
mouthOpen: jaw,
|
|
510
|
+
smile: (smileL + smileR) / 2
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region ../infra/src/avatar/2d-default-avatar/Face2DProvider.ts
|
|
516
|
+
/**
|
|
517
|
+
* 2D satin-face avatar — MediaPipe-tracked, 2D sprite renderer.
|
|
518
|
+
* **Self-contained** (its own MediaPipe loop; see FACE_AVATAR.md → Isolation and ownership).
|
|
519
|
+
* Carries mobile robustness patterns (FACE_AVATAR.md → Mobile hardening): `window.Module` shield,
|
|
520
|
+
* CPU delegate, transformation matrix OFF, landmark-derived pose, NaN guards.
|
|
521
|
+
*/
|
|
522
|
+
const DEFAULT_MAX_FPS = 30;
|
|
523
|
+
const NEUTRAL_FRAMES = 20;
|
|
524
|
+
const FACE_LOST_MS = 600;
|
|
525
|
+
const HEAD_ALPHA = .4;
|
|
526
|
+
const POS_ALPHA = .35;
|
|
527
|
+
const DEPTH_ALPHA = .2;
|
|
528
|
+
const EXPR_ALPHA = .5;
|
|
529
|
+
const MAX_DETECT_FAILURES = 30;
|
|
530
|
+
const CAL_MOTION_YAWPITCH = .012;
|
|
531
|
+
const CAL_MOTION_ROLL = .02;
|
|
532
|
+
const CAL_FALLBACK_MULT = 3;
|
|
533
|
+
var Avatar2DProvider = class {
|
|
534
|
+
constructor() {
|
|
535
|
+
this.renderer = new SatinSmileyRenderer(512);
|
|
536
|
+
this.videoHandle = null;
|
|
537
|
+
this.landmarker = null;
|
|
538
|
+
this.inputCanvas = document.createElement("canvas");
|
|
539
|
+
this.inputCtx = null;
|
|
540
|
+
this.rafId = null;
|
|
541
|
+
this.lastFrameAt = 0;
|
|
542
|
+
this.minFrameIntervalMs = 1e3 / DEFAULT_MAX_FPS;
|
|
543
|
+
this.stopped = false;
|
|
544
|
+
this.mediapipeModulePath = "";
|
|
545
|
+
this.mediapipeWasmPath = "";
|
|
546
|
+
this.faceLandmarkerModelPath = "";
|
|
547
|
+
this.detectFailures = 0;
|
|
548
|
+
this.current = structuredClonePose(AVATAR2D_NEUTRAL_POSE);
|
|
549
|
+
this.target = structuredClonePose(AVATAR2D_NEUTRAL_POSE);
|
|
550
|
+
this.lastFaceAt = 0;
|
|
551
|
+
this.calibrated = false;
|
|
552
|
+
this.nFrames = 0;
|
|
553
|
+
this.sum = {
|
|
554
|
+
yaw: 0,
|
|
555
|
+
pitch: 0,
|
|
556
|
+
roll: 0,
|
|
557
|
+
cx: 0,
|
|
558
|
+
cy: 0,
|
|
559
|
+
dist: 0,
|
|
560
|
+
faceH: 0
|
|
561
|
+
};
|
|
562
|
+
this.neutral = {
|
|
563
|
+
yaw: 0,
|
|
564
|
+
pitch: 0,
|
|
565
|
+
roll: 0,
|
|
566
|
+
cx: 0,
|
|
567
|
+
cy: 0,
|
|
568
|
+
dist: 1,
|
|
569
|
+
faceH: 1
|
|
570
|
+
};
|
|
571
|
+
this.calAttempts = 0;
|
|
572
|
+
this.prevCalYaw = NaN;
|
|
573
|
+
this.prevCalPitch = NaN;
|
|
574
|
+
this.prevCalRoll = NaN;
|
|
575
|
+
this.seeded = false;
|
|
576
|
+
this.medYaw = new Median();
|
|
577
|
+
this.medPitch = new Median();
|
|
578
|
+
this.medRoll = new Median();
|
|
579
|
+
this.usingRvfc = false;
|
|
580
|
+
this.rvfcHandle = null;
|
|
581
|
+
this.loop = (now) => {
|
|
582
|
+
if (this.stopped) return;
|
|
583
|
+
this.scheduleNextFrame();
|
|
584
|
+
if (now - this.lastFrameAt < this.minFrameIntervalMs) return;
|
|
585
|
+
const dtMs = clamp(this.lastFrameAt > 0 ? now - this.lastFrameAt : this.minFrameIntervalMs, 1, MAX_EMA_DT_MS);
|
|
586
|
+
this.lastFrameAt = now;
|
|
587
|
+
const video = this.videoHandle?.element;
|
|
588
|
+
if (!video || video.readyState < 2 || !this.landmarker) {
|
|
589
|
+
this.renderer.render(this.current);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const vw = video.videoWidth;
|
|
593
|
+
const vh = video.videoHeight;
|
|
594
|
+
if (vw > 0 && vh > 0) {
|
|
595
|
+
if (!this.inputCtx) this.inputCtx = this.inputCanvas.getContext("2d");
|
|
596
|
+
if (this.inputCtx) {
|
|
597
|
+
if (this.inputCanvas.width !== vw || this.inputCanvas.height !== vh) {
|
|
598
|
+
this.inputCanvas.width = vw;
|
|
599
|
+
this.inputCanvas.height = vh;
|
|
600
|
+
}
|
|
601
|
+
this.inputCtx.drawImage(video, 0, 0, vw, vh);
|
|
602
|
+
let result = null;
|
|
603
|
+
try {
|
|
604
|
+
result = this.landmarker.detectForVideo(this.inputCanvas, now);
|
|
605
|
+
this.detectFailures = 0;
|
|
606
|
+
} catch {
|
|
607
|
+
result = null;
|
|
608
|
+
this.detectFailures += 1;
|
|
609
|
+
if (this.detectFailures >= MAX_DETECT_FAILURES) this.recreateLandmarker();
|
|
610
|
+
}
|
|
611
|
+
if (result) this.ingest(result, vw, vh, now);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (now - this.lastFaceAt > FACE_LOST_MS) this.target = structuredClonePose(AVATAR2D_NEUTRAL_POSE);
|
|
615
|
+
this.smoothTowardTarget(dtMs);
|
|
616
|
+
this.renderer.render(this.current);
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
getCanvas() {
|
|
620
|
+
return this.renderer.getCanvas();
|
|
621
|
+
}
|
|
622
|
+
setState(state) {
|
|
623
|
+
this.renderer.setState(state);
|
|
624
|
+
}
|
|
625
|
+
async start(stream, options = {}) {
|
|
626
|
+
const assets = resolveAvatar2DAssets({
|
|
627
|
+
mediapipeModulePath: options.assets?.mediapipeModulePath,
|
|
628
|
+
mediapipeWasmPath: options.assets?.mediapipeWasmPath,
|
|
629
|
+
faceLandmarkerModelPath: options.assets?.faceLandmarkerModelPath
|
|
630
|
+
});
|
|
631
|
+
this.mediapipeModulePath = assets.mediapipeModulePath;
|
|
632
|
+
this.mediapipeWasmPath = assets.mediapipeWasmPath;
|
|
633
|
+
this.faceLandmarkerModelPath = assets.faceLandmarkerModelPath;
|
|
634
|
+
this.minFrameIntervalMs = 1e3 / (options.maxFps ?? DEFAULT_MAX_FPS);
|
|
635
|
+
this.videoHandle = createHiddenVideoElement(stream, { keepStreamAlive: true });
|
|
636
|
+
try {
|
|
637
|
+
this.renderer.init();
|
|
638
|
+
this.landmarker = await this.createLandmarker();
|
|
639
|
+
if (this.stopped) {
|
|
640
|
+
this.landmarker?.close();
|
|
641
|
+
this.landmarker = null;
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const video = this.videoHandle?.element;
|
|
645
|
+
this.usingRvfc = !!(video && asRvfc(video));
|
|
646
|
+
this.scheduleNextFrame();
|
|
647
|
+
} catch (error) {
|
|
648
|
+
this.stop();
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/** rVFC (one callback per camera frame) when available, else capped rAF. (§1.) */
|
|
653
|
+
scheduleNextFrame() {
|
|
654
|
+
if (this.stopped) return;
|
|
655
|
+
const video = this.videoHandle?.element;
|
|
656
|
+
const rvfc = this.usingRvfc && video ? asRvfc(video) : null;
|
|
657
|
+
if (rvfc) this.rvfcHandle = rvfc.requestVideoFrameCallback(this.loop);
|
|
658
|
+
else this.rafId = requestAnimationFrame(this.loop);
|
|
659
|
+
}
|
|
660
|
+
stop() {
|
|
661
|
+
this.stopped = true;
|
|
662
|
+
if (this.rafId != null) {
|
|
663
|
+
cancelAnimationFrame(this.rafId);
|
|
664
|
+
this.rafId = null;
|
|
665
|
+
}
|
|
666
|
+
const stoppingVideo = this.videoHandle?.element;
|
|
667
|
+
if (stoppingVideo && this.rvfcHandle != null) {
|
|
668
|
+
asRvfc(stoppingVideo)?.cancelVideoFrameCallback(this.rvfcHandle);
|
|
669
|
+
this.rvfcHandle = null;
|
|
670
|
+
}
|
|
671
|
+
this.landmarker?.close();
|
|
672
|
+
this.landmarker = null;
|
|
673
|
+
this.renderer.dispose();
|
|
674
|
+
this.videoHandle?.dispose();
|
|
675
|
+
this.videoHandle = null;
|
|
676
|
+
}
|
|
677
|
+
/** Loads the FaceLandmarker via the shared shielded init (see mediapipeShared). */
|
|
678
|
+
createLandmarker() {
|
|
679
|
+
return createFaceLandmarker({
|
|
680
|
+
mediapipeModulePath: this.mediapipeModulePath,
|
|
681
|
+
mediapipeWasmPath: this.mediapipeWasmPath,
|
|
682
|
+
faceLandmarkerModelPath: this.faceLandmarkerModelPath
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/** MediaPipe result → target pose (averaged-neutral calibration). */
|
|
686
|
+
ingest(result, vw, vh, now) {
|
|
687
|
+
const raw0 = computeRawPose(result, vw, vh);
|
|
688
|
+
if (!raw0) return;
|
|
689
|
+
const raw = this.applyMedian(raw0);
|
|
690
|
+
this.lastFaceAt = now;
|
|
691
|
+
if (!this.calibrated) {
|
|
692
|
+
this.calAttempts += 1;
|
|
693
|
+
const dRollMotion = Math.atan2(Math.sin(raw.roll - this.prevCalRoll), Math.cos(raw.roll - this.prevCalRoll));
|
|
694
|
+
const moved = Number.isFinite(this.prevCalYaw) && (Math.abs(raw.yaw - this.prevCalYaw) > CAL_MOTION_YAWPITCH || Math.abs(raw.pitch - this.prevCalPitch) > CAL_MOTION_YAWPITCH || Math.abs(dRollMotion) > CAL_MOTION_ROLL);
|
|
695
|
+
this.prevCalYaw = raw.yaw;
|
|
696
|
+
this.prevCalPitch = raw.pitch;
|
|
697
|
+
this.prevCalRoll = raw.roll;
|
|
698
|
+
const fallback = this.calAttempts >= NEUTRAL_FRAMES * CAL_FALLBACK_MULT;
|
|
699
|
+
if (!moved || fallback) {
|
|
700
|
+
this.nFrames += 1;
|
|
701
|
+
this.sum.yaw += raw.yaw;
|
|
702
|
+
this.sum.pitch += raw.pitch;
|
|
703
|
+
this.sum.roll += raw.roll;
|
|
704
|
+
this.sum.cx += raw.eyeMidX;
|
|
705
|
+
this.sum.cy += raw.eyeMidY;
|
|
706
|
+
this.sum.dist += raw.eyeDist;
|
|
707
|
+
this.sum.faceH += raw.faceH;
|
|
708
|
+
}
|
|
709
|
+
if (this.nFrames >= NEUTRAL_FRAMES) {
|
|
710
|
+
const n = this.nFrames;
|
|
711
|
+
this.neutral = {
|
|
712
|
+
yaw: clamp(this.sum.yaw / n, -AVATAR2D_NEUTRAL_BIAS.yaw, AVATAR2D_NEUTRAL_BIAS.yaw),
|
|
713
|
+
pitch: this.sum.pitch / n,
|
|
714
|
+
roll: clamp(this.sum.roll / n, -AVATAR2D_NEUTRAL_BIAS.roll, AVATAR2D_NEUTRAL_BIAS.roll),
|
|
715
|
+
cx: this.sum.cx / n,
|
|
716
|
+
cy: this.sum.cy / n,
|
|
717
|
+
dist: this.sum.dist / n || 1,
|
|
718
|
+
faceH: this.sum.faceH / n || 1
|
|
719
|
+
};
|
|
720
|
+
this.calibrated = true;
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const mapped = mapToPose(raw, this.neutral, AVATAR2D_POSE_CONFIG);
|
|
725
|
+
const { blink, mouthOpen, smile } = blinkMouthFromBlendshapes(result);
|
|
726
|
+
this.target = {
|
|
727
|
+
tracked: true,
|
|
728
|
+
headEuler: { ...mapped.headEuler },
|
|
729
|
+
rootOffset: { ...mapped.rootOffset },
|
|
730
|
+
depth: mapped.depth,
|
|
731
|
+
blink,
|
|
732
|
+
mouthOpen,
|
|
733
|
+
smile
|
|
734
|
+
};
|
|
735
|
+
if (!this.seeded) {
|
|
736
|
+
copyPoseInto(this.current, this.target);
|
|
737
|
+
this.seeded = true;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Outlier rejection (FACE_AVATAR.md → Pose pipeline): when the face is small in
|
|
742
|
+
* frame the detector emits single-frame spikes the deadzone/EMA can't reject —
|
|
743
|
+
* median-filter yaw/pitch/roll first. When the face is near, pass through and
|
|
744
|
+
* reset the buffers (no stale samples across distance regimes).
|
|
745
|
+
*/
|
|
746
|
+
applyMedian(raw) {
|
|
747
|
+
if (!(raw.faceSizeRatio !== void 0 && raw.faceSizeRatio < MEDIAN_FACE_RATIO)) {
|
|
748
|
+
this.medYaw.reset();
|
|
749
|
+
this.medPitch.reset();
|
|
750
|
+
this.medRoll.reset();
|
|
751
|
+
return raw;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
...raw,
|
|
755
|
+
yaw: this.medYaw.filter(raw.yaw),
|
|
756
|
+
pitch: this.medPitch.filter(raw.pitch),
|
|
757
|
+
roll: this.medRoll.filter(raw.roll)
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
smoothTowardTarget(dtMs) {
|
|
761
|
+
const c = this.current;
|
|
762
|
+
const t = this.target;
|
|
763
|
+
const ha = dtAlpha(HEAD_ALPHA, dtMs);
|
|
764
|
+
const pa = dtAlpha(POS_ALPHA, dtMs);
|
|
765
|
+
const da = dtAlpha(DEPTH_ALPHA, dtMs);
|
|
766
|
+
const ea = dtAlpha(EXPR_ALPHA, dtMs);
|
|
767
|
+
c.tracked = t.tracked;
|
|
768
|
+
c.headEuler.x += (t.headEuler.x - c.headEuler.x) * ha;
|
|
769
|
+
c.headEuler.y += (t.headEuler.y - c.headEuler.y) * ha;
|
|
770
|
+
c.headEuler.z += (t.headEuler.z - c.headEuler.z) * ha;
|
|
771
|
+
c.rootOffset.x += (t.rootOffset.x - c.rootOffset.x) * pa;
|
|
772
|
+
c.rootOffset.y += (t.rootOffset.y - c.rootOffset.y) * pa;
|
|
773
|
+
c.depth += (t.depth - c.depth) * da;
|
|
774
|
+
c.blink.left += (t.blink.left - c.blink.left) * ea;
|
|
775
|
+
c.blink.right += (t.blink.right - c.blink.right) * ea;
|
|
776
|
+
c.mouthOpen += (t.mouthOpen - c.mouthOpen) * ea;
|
|
777
|
+
c.smile += (t.smile - c.smile) * ea;
|
|
778
|
+
}
|
|
779
|
+
recreateLandmarker() {
|
|
780
|
+
this.detectFailures = 0;
|
|
781
|
+
this.landmarker?.close();
|
|
782
|
+
this.landmarker = null;
|
|
783
|
+
this.createLandmarker().then((lm) => {
|
|
784
|
+
if (this.stopped) {
|
|
785
|
+
lm.close();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
this.landmarker = lm;
|
|
789
|
+
}).catch(() => {});
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
function structuredClonePose(p) {
|
|
793
|
+
return {
|
|
794
|
+
tracked: p.tracked,
|
|
795
|
+
headEuler: { ...p.headEuler },
|
|
796
|
+
rootOffset: { ...p.rootOffset },
|
|
797
|
+
depth: p.depth,
|
|
798
|
+
blink: { ...p.blink },
|
|
799
|
+
mouthOpen: p.mouthOpen,
|
|
800
|
+
smile: p.smile
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/** Copy src's values INTO dst's existing (mutated-in-place) objects — used to
|
|
804
|
+
* seed the smoother on the first tracked frame without reallocating `current`. */
|
|
805
|
+
function copyPoseInto(dst, src) {
|
|
806
|
+
dst.tracked = src.tracked;
|
|
807
|
+
dst.headEuler.x = src.headEuler.x;
|
|
808
|
+
dst.headEuler.y = src.headEuler.y;
|
|
809
|
+
dst.headEuler.z = src.headEuler.z;
|
|
810
|
+
dst.rootOffset.x = src.rootOffset.x;
|
|
811
|
+
dst.rootOffset.y = src.rootOffset.y;
|
|
812
|
+
dst.depth = src.depth;
|
|
813
|
+
dst.blink.left = src.blink.left;
|
|
814
|
+
dst.blink.right = src.blink.right;
|
|
815
|
+
dst.mouthOpen = src.mouthOpen;
|
|
816
|
+
dst.smile = src.smile;
|
|
817
|
+
}
|
|
818
|
+
/** Factory used by the avatar actor's provider selection (2D satin-face avatar). */
|
|
819
|
+
function createProvider() {
|
|
820
|
+
return new Avatar2DProvider();
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
//#endregion
|
|
824
|
+
export { createProvider };
|