shaderpad 1.0.0-beta.30 → 1.0.0-beta.31

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.
@@ -1,4 +1,5 @@
1
1
  import ShaderPad, { PluginContext } from '../index.mjs';
2
+ import { FaceLandmarkerResult } from '@mediapipe/tasks-vision';
2
3
 
3
4
  interface FacePluginOptions {
4
5
  modelPath?: string;
@@ -8,6 +9,7 @@ interface FacePluginOptions {
8
9
  minTrackingConfidence?: number;
9
10
  outputFaceBlendshapes?: boolean;
10
11
  outputFacialTransformationMatrixes?: boolean;
12
+ onResults?: (results: FaceLandmarkerResult) => void;
11
13
  }
12
14
  declare function face(config: {
13
15
  textureName: string;
@@ -1,4 +1,5 @@
1
1
  import ShaderPad, { PluginContext } from '../index.js';
2
+ import { FaceLandmarkerResult } from '@mediapipe/tasks-vision';
2
3
 
3
4
  interface FacePluginOptions {
4
5
  modelPath?: string;
@@ -8,6 +9,7 @@ interface FacePluginOptions {
8
9
  minTrackingConfidence?: number;
9
10
  outputFaceBlendshapes?: boolean;
10
11
  outputFacialTransformationMatrixes?: boolean;
12
+ onResults?: (results: FaceLandmarkerResult) => void;
11
13
  }
12
14
  declare function face(config: {
13
15
  textureName: string;
@@ -1,4 +1,4 @@
1
- "use strict";var j=Object.create;var b=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var ee=(o,u)=>{for(var i in u)b(o,i,{get:u[i],enumerable:!0})},P=(o,u,i,A)=>{if(u&&typeof u=="object"||typeof u=="function")for(let c of q(u))!Q.call(o,c)&&c!==i&&b(o,c,{get:()=>u[c],enumerable:!(A=Z(u,c))||A.enumerable});return o};var U=(o,u,i)=>(i=o!=null?j(J(o)):{},P(u||!o||!o.__esModule?b(i,"default",{value:o,enumerable:!0}):i,o)),te=o=>P(b({},"__esModule",{value:!0}),o);var ie={};ee(ie,{default:()=>re});module.exports=te(ie);var k=478,ne=2,d=k+ne,m={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95],FACE_CENTER:k,MOUTH_CENTER:k+1};function ae(o){let{textureName:u,options:i}=o,A="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(c,K){let{injectGLSL:$,gl:N}=K,T=null,I=null,y=-1,O=new Map,v=i?.maxFaces??1,p=512,S=0,t=null,z=512,B=512,l=document.createElement("canvas");l.width=z,l.height=B;let _=l.getContext("2d");_.globalCompositeOperation="lighten";async function G(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");I=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),T=await e.createFromOptions(I,{baseOptions:{modelAssetPath:i?.modelPath||A},runningMode:"VIDEO",numFaces:i?.maxFaces??1,minFaceDetectionConfidence:i?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:i?.minFacePresenceConfidence??.5,minTrackingConfidence:i?.minTrackingConfidence??.5,outputFaceBlendshapes:i?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:i?.outputFacialTransformationMatrixes??!1})}catch(n){throw console.error("Failed to initialize Face Landmarker:",n),n}}function w(n,e,a){let r=1/0,s=-1/0,h=1/0,f=-1/0,E=0,C=0;for(let X of a){let M=(e*d+X)*4,H=n[M],Y=n[M+1];r=Math.min(r,H),s=Math.max(s,H),h=Math.min(h,Y),f=Math.max(f,Y),E+=n[M+2],C+=n[M+3]}let g=(r+s)/2,L=(h+f)/2,x=E/a.length,R=C/a.length;return[g,L,x,R]}function F(n,e,a){if(!t)return;_.fillStyle=`rgb(${Math.round(a.r*255)}, ${Math.round(a.g*255)}, ${Math.round(a.b*255)})`,_.beginPath();let r=(n*d+e[0])*4,s=t[r],h=t[r+1];_.moveTo(s*l.width,h*l.height);for(let f=1;f<e.length;++f){let E=(n*d+e[f])*4,C=t[E],g=t[E+1];_.lineTo(C*l.width,g*l.height)}_.closePath(),_.fill()}async function V(n){if(!T||!t){console.warn("[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");_.clearRect(0,0,l.width,l.height);for(let a=0;a<n;++a)F(a,m.OUTER_LIP,{r:.25,g:0,b:0}),F(a,m.INNER_LIP,{r:.75,g:0,b:0}),F(a,e.FACE_LANDMARKS_TESSELATION.map(({start:r})=>r),{r:0,g:.25,b:0}),F(a,e.FACE_LANDMARKS_FACE_OVAL.map(({start:r})=>r),{r:0,g:1,b:0}),F(a,m.LEFT_EYEBROW,{r:0,g:0,b:.15}),F(a,m.RIGHT_EYEBROW,{r:0,g:0,b:.35}),F(a,m.LEFT_EYE,{r:0,g:0,b:.65}),F(a,m.RIGHT_EYE,{r:0,g:0,b:.85});c.updateTextures({u_faceMask:l})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function W(n){if(!t)return;let e=n.length,a=e*d;for(let s=0;s<e;++s){let h=n[s];for(let L=0;L<k;++L){let x=h[L],R=(s*d+L)*4;t[R]=x.x,t[R+1]=1-x.y,t[R+2]=x.z??0,t[R+3]=x.visibility??1}let f=w(t,s,Array.from({length:k},(L,x)=>x)),E=(s*d+m.FACE_CENTER)*4;t[E]=f[0],t[E+1]=f[1],t[E+2]=0,t[E+3]=1;let C=w(t,s,m.INNER_LIP),g=(s*d+m.MOUTH_CENTER)*4;t[g]=C[0],t[g+1]=C[1],t[g+2]=0,t[g+3]=1}let r=Math.ceil(a/p);c.updateTextures({u_faceLandmarksTex:{data:t,width:p,height:r}})}function D(n){if(!n.faceLandmarks||!t)return;let e=n.faceLandmarks.length;W(n.faceLandmarks),V(e).catch(a=>{console.warn("Mask texture update error:",a)}),c.updateUniforms({u_nFaces:e})}c.registerHook("init",async()=>{c.initializeTexture("u_faceMask",l,{preserveY:!0}),c.initializeUniform("u_maxFaces","int",v),c.initializeUniform("u_nFaces","int",0);let n=v*d;S=Math.ceil(n/p);let e=p*S*4;t=new Float32Array(e),c.initializeTexture("u_faceLandmarksTex",{data:t,width:p,height:S},{internalFormat:N.RGBA32F,type:N.FLOAT,minFilter:N.NEAREST,magFilter:N.NEAREST}),await G()}),c.registerHook("updateTextures",n=>{let e=n[u];if(!(!e||(O.get(u)!==e&&(y=-1),O.set(u,e),!T)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==y){y=e.currentTime;let r=performance.now(),s=T.detectForVideo(e,r);D(s)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let r=T.detect(e);D(r)}}catch(r){console.warn("Face detection error:",r)}}),c.registerHook("destroy",()=>{T&&(T.close(),T=null),I=null,O.clear(),l.remove(),t=null}),$(`
1
+ "use strict";var j=Object.create;var b=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var ee=(o,u)=>{for(var r in u)b(o,r,{get:u[r],enumerable:!0})},P=(o,u,r,A)=>{if(u&&typeof u=="object"||typeof u=="function")for(let c of q(u))!Q.call(o,c)&&c!==r&&b(o,c,{get:()=>u[c],enumerable:!(A=Z(u,c))||A.enumerable});return o};var U=(o,u,r)=>(r=o!=null?j(J(o)):{},P(u||!o||!o.__esModule?b(r,"default",{value:o,enumerable:!0}):r,o)),te=o=>P(b({},"__esModule",{value:!0}),o);var ie={};ee(ie,{default:()=>re});module.exports=te(ie);var k=478,ne=2,E=k+ne,m={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95],FACE_CENTER:k,MOUTH_CENTER:k+1};function ae(o){let{textureName:u,options:r}=o,A="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(c,K){let{injectGLSL:$,gl:N}=K,T=null,I=null,y=-1,O=new Map,v=r?.maxFaces??1,p=512,S=0,t=null,z=512,B=512,l=document.createElement("canvas");l.width=z,l.height=B;let _=l.getContext("2d");_.globalCompositeOperation="lighten";async function G(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");I=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),T=await e.createFromOptions(I,{baseOptions:{modelAssetPath:r?.modelPath||A},runningMode:"VIDEO",numFaces:r?.maxFaces??1,minFaceDetectionConfidence:r?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:r?.minFacePresenceConfidence??.5,minTrackingConfidence:r?.minTrackingConfidence??.5,outputFaceBlendshapes:r?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:r?.outputFacialTransformationMatrixes??!1})}catch(n){throw console.error("Failed to initialize Face Landmarker:",n),n}}function w(n,e,a){let i=1/0,s=-1/0,R=1/0,f=-1/0,d=0,L=0;for(let X of a){let M=(e*E+X)*4,H=n[M],Y=n[M+1];i=Math.min(i,H),s=Math.max(s,H),R=Math.min(R,Y),f=Math.max(f,Y),d+=n[M+2],L+=n[M+3]}let g=(i+s)/2,h=(R+f)/2,x=d/a.length,C=L/a.length;return[g,h,x,C]}function F(n,e,a){if(!t)return;_.fillStyle=`rgb(${Math.round(a.r*255)}, ${Math.round(a.g*255)}, ${Math.round(a.b*255)})`,_.beginPath();let i=(n*E+e[0])*4,s=t[i],R=t[i+1];_.moveTo(s*l.width,R*l.height);for(let f=1;f<e.length;++f){let d=(n*E+e[f])*4,L=t[d],g=t[d+1];_.lineTo(L*l.width,g*l.height)}_.closePath(),_.fill()}async function V(n){if(!T||!t){console.warn("[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");_.clearRect(0,0,l.width,l.height);for(let a=0;a<n;++a)F(a,m.OUTER_LIP,{r:.25,g:0,b:0}),F(a,m.INNER_LIP,{r:.75,g:0,b:0}),F(a,e.FACE_LANDMARKS_TESSELATION.map(({start:i})=>i),{r:0,g:.25,b:0}),F(a,e.FACE_LANDMARKS_FACE_OVAL.map(({start:i})=>i),{r:0,g:1,b:0}),F(a,m.LEFT_EYEBROW,{r:0,g:0,b:.15}),F(a,m.RIGHT_EYEBROW,{r:0,g:0,b:.35}),F(a,m.LEFT_EYE,{r:0,g:0,b:.65}),F(a,m.RIGHT_EYE,{r:0,g:0,b:.85});c.updateTextures({u_faceMask:l})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function W(n){if(!t)return;let e=n.length,a=e*E;for(let s=0;s<e;++s){let R=n[s];for(let h=0;h<k;++h){let x=R[h],C=(s*E+h)*4;t[C]=x.x,t[C+1]=1-x.y,t[C+2]=x.z??0,t[C+3]=x.visibility??1}let f=w(t,s,Array.from({length:k},(h,x)=>x)),d=(s*E+m.FACE_CENTER)*4;t[d]=f[0],t[d+1]=f[1],t[d+2]=0,t[d+3]=1;let L=w(t,s,m.INNER_LIP),g=(s*E+m.MOUTH_CENTER)*4;t[g]=L[0],t[g+1]=L[1],t[g+2]=0,t[g+3]=1}let i=Math.ceil(a/p);c.updateTextures({u_faceLandmarksTex:{data:t,width:p,height:i}})}function D(n){if(!n.faceLandmarks||!t)return;let e=n.faceLandmarks.length;W(n.faceLandmarks),V(e).catch(a=>{console.warn("Mask texture update error:",a)}),c.updateUniforms({u_nFaces:e}),r?.onResults?.(n)}c.registerHook("init",async()=>{c.initializeTexture("u_faceMask",l,{preserveY:!0}),c.initializeUniform("u_maxFaces","int",v),c.initializeUniform("u_nFaces","int",0);let n=v*E;S=Math.ceil(n/p);let e=p*S*4;t=new Float32Array(e),c.initializeTexture("u_faceLandmarksTex",{data:t,width:p,height:S},{internalFormat:N.RGBA32F,type:N.FLOAT,minFilter:N.NEAREST,magFilter:N.NEAREST}),await G()}),c.registerHook("updateTextures",n=>{let e=n[u];if(!(!e||(O.get(u)!==e&&(y=-1),O.set(u,e),!T)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==y){y=e.currentTime;let i=performance.now(),s=T.detectForVideo(e,i);D(s)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let i=T.detect(e);D(i)}}catch(i){console.warn("Face detection error:",i)}}),c.registerHook("destroy",()=>{T&&(T.close(),T=null),I=null,O.clear(),l.remove(),t=null}),$(`
2
2
  uniform int u_maxFaces;
3
3
  uniform int u_nFaces;
4
4
  uniform sampler2D u_faceLandmarksTex;
@@ -11,7 +11,7 @@ uniform sampler2D u_faceMask;
11
11
  #define FACE_LANDMARK_MOUTH_CENTER ${m.MOUTH_CENTER}
12
12
 
13
13
  vec4 faceLandmark(int faceIndex, int landmarkIndex) {
14
- int i = faceIndex * ${d} + landmarkIndex;
14
+ int i = faceIndex * ${E} + landmarkIndex;
15
15
  int x = i % ${p};
16
16
  int y = i / ${p};
17
17
  return texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n}\n\nconst STANDARD_LANDMARK_COUNT = 478;\nconst CUSTOM_LANDMARK_COUNT = 2;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction fillRegion(faceIdx: number, landmarkIndices: number[], color: { r: number; g: number; b: number }) {\n\t\t\tif (!landmarksDataArray) return;\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tconst originIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[0]) * 4;\n\t\t\tconst originX = landmarksDataArray[originIdx];\n\t\t\tconst originY = landmarksDataArray[originIdx + 1];\n\t\t\tfaceMaskCtx.moveTo(originX * faceMaskCanvas.width, originY * faceMaskCanvas.height);\n\n\t\t\tfor (let i = 1; i < landmarkIndices.length; ++i) {\n\t\t\t\tconst destIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[i]) * 4;\n\t\t\t\tconst destX = landmarksDataArray[destIdx];\n\t\t\t\tconst destY = landmarksDataArray[destIdx + 1];\n\t\t\t\tfaceMaskCtx.lineTo(destX * faceMaskCanvas.width, destY * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(nFaces: number) {\n\t\t\tif (!faceLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.OUTER_LIP, { r: 0.25, g: 0, b: 0 });\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.INNER_LIP, { r: 0.75, g: 0, b: 0 });\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t});\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t});\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYE, { r: 0, g: 0, b: 0.65 });\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYE, { r: 0, g: 0, b: 0.85 });\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nFaces = faces.length;\n\t\t\tconst totalLandmarks = nFaces * LANDMARK_COUNT;\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst landmarks = faces[faceIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tfaceIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst faceCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[faceCenterIdx] = faceCenter[0];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 1] = faceCenter[1];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 3] = 1; // Visibility\n\n\t\t\t\t// Mouth center (landmark 479) - uses INNER_LIP landmarks\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, LANDMARK_INDICES.INNER_LIP);\n\t\t\t\tconst mouthCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[mouthCenterIdx] = mouthCenter[0];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 1] = mouthCenter[1];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 3] = 1; // Visibility\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_faceLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processFaceResults(result: any) {\n\t\t\tif (!result.faceLandmarks || !landmarksDataArray) return;\n\n\t\t\tconst nFaces = result.faceLandmarks.length;\n\t\t\tupdateLandmarksTexture(result.faceLandmarks);\n\t\t\tupdateMaskTexture(nFaces).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tshaderPad.updateUniforms({ u_nFaces: nFaces });\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas, { preserveY: true });\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxFaces * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform sampler2D u_faceLandmarksTex;\nuniform sampler2D u_faceMask;\n\n#define FACE_LANDMARK_L_EYE_CENTER ${LANDMARK_INDICES.LEFT_EYE_CENTER}\n#define FACE_LANDMARK_R_EYE_CENTER ${LANDMARK_INDICES.RIGHT_EYE_CENTER}\n#define FACE_LANDMARK_NOSE_TIP ${LANDMARK_INDICES.NOSE_TIP}\n#define FACE_LANDMARK_FACE_CENTER ${LANDMARK_INDICES.FACE_CENTER}\n#define FACE_LANDMARK_MOUTH_CENTER ${LANDMARK_INDICES.MOUTH_CENTER}\n\nvec4 faceLandmark(int faceIndex, int landmarkIndex) {\n\tint i = faceIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);\n}\nfloat inFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat inEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat inMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["face_exports","__export","face_default","__toCommonJS","STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateBoundingBoxCenter","faceIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","fillRegion","color","originIdx","originX","originY","i","destIdx","destX","destY","updateMaskTexture","nFaces","start","updateLandmarksTexture","faces","totalLandmarks","landmarks","lmIdx","landmark","faceCenter","_","faceCenterIdx","mouthCenter","mouthCenterIdx","rowsToUpdate","processFaceResults","result","textureSize","updates","source","timestamp"]}
1
+ {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, FaceLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n\tonResults?: (results: FaceLandmarkerResult) => void;\n}\n\nconst STANDARD_LANDMARK_COUNT = 478;\nconst CUSTOM_LANDMARK_COUNT = 2;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction fillRegion(faceIdx: number, landmarkIndices: number[], color: { r: number; g: number; b: number }) {\n\t\t\tif (!landmarksDataArray) return;\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tconst originIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[0]) * 4;\n\t\t\tconst originX = landmarksDataArray[originIdx];\n\t\t\tconst originY = landmarksDataArray[originIdx + 1];\n\t\t\tfaceMaskCtx.moveTo(originX * faceMaskCanvas.width, originY * faceMaskCanvas.height);\n\n\t\t\tfor (let i = 1; i < landmarkIndices.length; ++i) {\n\t\t\t\tconst destIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[i]) * 4;\n\t\t\t\tconst destX = landmarksDataArray[destIdx];\n\t\t\t\tconst destY = landmarksDataArray[destIdx + 1];\n\t\t\t\tfaceMaskCtx.lineTo(destX * faceMaskCanvas.width, destY * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(nFaces: number) {\n\t\t\tif (!faceLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.OUTER_LIP, { r: 0.25, g: 0, b: 0 });\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.INNER_LIP, { r: 0.75, g: 0, b: 0 });\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t});\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t});\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYE, { r: 0, g: 0, b: 0.65 });\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYE, { r: 0, g: 0, b: 0.85 });\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nFaces = faces.length;\n\t\t\tconst totalLandmarks = nFaces * LANDMARK_COUNT;\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst landmarks = faces[faceIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tfaceIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst faceCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[faceCenterIdx] = faceCenter[0];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 1] = faceCenter[1];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 3] = 1; // Visibility\n\n\t\t\t\t// Mouth center (landmark 479) - uses INNER_LIP landmarks\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, LANDMARK_INDICES.INNER_LIP);\n\t\t\t\tconst mouthCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[mouthCenterIdx] = mouthCenter[0];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 1] = mouthCenter[1];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 3] = 1; // Visibility\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_faceLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processFaceResults(result: FaceLandmarkerResult) {\n\t\t\tif (!result.faceLandmarks || !landmarksDataArray) return;\n\n\t\t\tconst nFaces = result.faceLandmarks.length;\n\t\t\tupdateLandmarksTexture(result.faceLandmarks);\n\t\t\tupdateMaskTexture(nFaces).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\t\t\tshaderPad.updateUniforms({ u_nFaces: nFaces });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas, { preserveY: true });\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxFaces * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform sampler2D u_faceLandmarksTex;\nuniform sampler2D u_faceMask;\n\n#define FACE_LANDMARK_L_EYE_CENTER ${LANDMARK_INDICES.LEFT_EYE_CENTER}\n#define FACE_LANDMARK_R_EYE_CENTER ${LANDMARK_INDICES.RIGHT_EYE_CENTER}\n#define FACE_LANDMARK_NOSE_TIP ${LANDMARK_INDICES.NOSE_TIP}\n#define FACE_LANDMARK_FACE_CENTER ${LANDMARK_INDICES.FACE_CENTER}\n#define FACE_LANDMARK_MOUTH_CENTER ${LANDMARK_INDICES.MOUTH_CENTER}\n\nvec4 faceLandmark(int faceIndex, int landmarkIndex) {\n\tint i = faceIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);\n}\nfloat inFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat inEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat inMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["face_exports","__export","face_default","__toCommonJS","STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateBoundingBoxCenter","faceIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","fillRegion","color","originIdx","originX","originY","i","destIdx","destX","destY","updateMaskTexture","nFaces","start","updateLandmarksTexture","faces","totalLandmarks","landmarks","lmIdx","landmark","faceCenter","_","faceCenterIdx","mouthCenter","mouthCenterIdx","rowsToUpdate","processFaceResults","result","textureSize","updates","source","timestamp"]}
@@ -1,4 +1,4 @@
1
- var L=478,V=2,l=L+V,o={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95],FACE_CENTER:L,MOUTH_CENTER:L+1};function W(D){let{textureName:A,options:f}=D,H="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(s,Y){let{injectGLSL:P,gl:R}=Y,E=null,N=null,M=-1,b=new Map,y=f?.maxFaces??1,g=512,I=0,t=null,U=512,K=512,c=document.createElement("canvas");c.width=U,c.height=K;let d=c.getContext("2d");d.globalCompositeOperation="lighten";async function $(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");N=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),E=await e.createFromOptions(N,{baseOptions:{modelAssetPath:f?.modelPath||H},runningMode:"VIDEO",numFaces:f?.maxFaces??1,minFaceDetectionConfidence:f?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:f?.minFacePresenceConfidence??.5,minTrackingConfidence:f?.minTrackingConfidence??.5,outputFaceBlendshapes:f?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:f?.outputFacialTransformationMatrixes??!1})}catch(n){throw console.error("Failed to initialize Face Landmarker:",n),n}}function O(n,e,a){let r=1/0,i=-1/0,x=1/0,u=-1/0,m=0,p=0;for(let G of a){let k=(e*l+G)*4,v=n[k],w=n[k+1];r=Math.min(r,v),i=Math.max(i,v),x=Math.min(x,w),u=Math.max(u,w),m+=n[k+2],p+=n[k+3]}let _=(r+i)/2,h=(x+u)/2,F=m/a.length,C=p/a.length;return[_,h,F,C]}function T(n,e,a){if(!t)return;d.fillStyle=`rgb(${Math.round(a.r*255)}, ${Math.round(a.g*255)}, ${Math.round(a.b*255)})`,d.beginPath();let r=(n*l+e[0])*4,i=t[r],x=t[r+1];d.moveTo(i*c.width,x*c.height);for(let u=1;u<e.length;++u){let m=(n*l+e[u])*4,p=t[m],_=t[m+1];d.lineTo(p*c.width,_*c.height)}d.closePath(),d.fill()}async function z(n){if(!E||!t){console.warn("[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");d.clearRect(0,0,c.width,c.height);for(let a=0;a<n;++a)T(a,o.OUTER_LIP,{r:.25,g:0,b:0}),T(a,o.INNER_LIP,{r:.75,g:0,b:0}),T(a,e.FACE_LANDMARKS_TESSELATION.map(({start:r})=>r),{r:0,g:.25,b:0}),T(a,e.FACE_LANDMARKS_FACE_OVAL.map(({start:r})=>r),{r:0,g:1,b:0}),T(a,o.LEFT_EYEBROW,{r:0,g:0,b:.15}),T(a,o.RIGHT_EYEBROW,{r:0,g:0,b:.35}),T(a,o.LEFT_EYE,{r:0,g:0,b:.65}),T(a,o.RIGHT_EYE,{r:0,g:0,b:.85});s.updateTextures({u_faceMask:c})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function B(n){if(!t)return;let e=n.length,a=e*l;for(let i=0;i<e;++i){let x=n[i];for(let h=0;h<L;++h){let F=x[h],C=(i*l+h)*4;t[C]=F.x,t[C+1]=1-F.y,t[C+2]=F.z??0,t[C+3]=F.visibility??1}let u=O(t,i,Array.from({length:L},(h,F)=>F)),m=(i*l+o.FACE_CENTER)*4;t[m]=u[0],t[m+1]=u[1],t[m+2]=0,t[m+3]=1;let p=O(t,i,o.INNER_LIP),_=(i*l+o.MOUTH_CENTER)*4;t[_]=p[0],t[_+1]=p[1],t[_+2]=0,t[_+3]=1}let r=Math.ceil(a/g);s.updateTextures({u_faceLandmarksTex:{data:t,width:g,height:r}})}function S(n){if(!n.faceLandmarks||!t)return;let e=n.faceLandmarks.length;B(n.faceLandmarks),z(e).catch(a=>{console.warn("Mask texture update error:",a)}),s.updateUniforms({u_nFaces:e})}s.registerHook("init",async()=>{s.initializeTexture("u_faceMask",c,{preserveY:!0}),s.initializeUniform("u_maxFaces","int",y),s.initializeUniform("u_nFaces","int",0);let n=y*l;I=Math.ceil(n/g);let e=g*I*4;t=new Float32Array(e),s.initializeTexture("u_faceLandmarksTex",{data:t,width:g,height:I},{internalFormat:R.RGBA32F,type:R.FLOAT,minFilter:R.NEAREST,magFilter:R.NEAREST}),await $()}),s.registerHook("updateTextures",n=>{let e=n[A];if(!(!e||(b.get(A)!==e&&(M=-1),b.set(A,e),!E)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==M){M=e.currentTime;let r=performance.now(),i=E.detectForVideo(e,r);S(i)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let r=E.detect(e);S(r)}}catch(r){console.warn("Face detection error:",r)}}),s.registerHook("destroy",()=>{E&&(E.close(),E=null),N=null,b.clear(),c.remove(),t=null}),P(`
1
+ var h=478,V=2,f=h+V,o={LEFT_EYEBROW:[336,296,334,293,300,276,283,282,295,285],LEFT_EYE:[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],LEFT_EYE_CENTER:473,RIGHT_EYEBROW:[70,63,105,66,107,55,65,52,53,46],RIGHT_EYE:[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_LIP:[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],INNER_LIP:[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95],FACE_CENTER:h,MOUTH_CENTER:h+1};function W(D){let{textureName:A,options:l}=D,H="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(s,Y){let{injectGLSL:P,gl:C}=Y,d=null,N=null,M=-1,b=new Map,y=l?.maxFaces??1,g=512,I=0,t=null,U=512,K=512,c=document.createElement("canvas");c.width=U,c.height=K;let E=c.getContext("2d");E.globalCompositeOperation="lighten";async function $(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");N=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),d=await e.createFromOptions(N,{baseOptions:{modelAssetPath:l?.modelPath||H},runningMode:"VIDEO",numFaces:l?.maxFaces??1,minFaceDetectionConfidence:l?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:l?.minFacePresenceConfidence??.5,minTrackingConfidence:l?.minTrackingConfidence??.5,outputFaceBlendshapes:l?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:l?.outputFacialTransformationMatrixes??!1})}catch(n){throw console.error("Failed to initialize Face Landmarker:",n),n}}function O(n,e,a){let r=1/0,i=-1/0,x=1/0,u=-1/0,m=0,p=0;for(let G of a){let k=(e*f+G)*4,v=n[k],w=n[k+1];r=Math.min(r,v),i=Math.max(i,v),x=Math.min(x,w),u=Math.max(u,w),m+=n[k+2],p+=n[k+3]}let _=(r+i)/2,R=(x+u)/2,F=m/a.length,L=p/a.length;return[_,R,F,L]}function T(n,e,a){if(!t)return;E.fillStyle=`rgb(${Math.round(a.r*255)}, ${Math.round(a.g*255)}, ${Math.round(a.b*255)})`,E.beginPath();let r=(n*f+e[0])*4,i=t[r],x=t[r+1];E.moveTo(i*c.width,x*c.height);for(let u=1;u<e.length;++u){let m=(n*f+e[u])*4,p=t[m],_=t[m+1];E.lineTo(p*c.width,_*c.height)}E.closePath(),E.fill()}async function z(n){if(!d||!t){console.warn("[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing");return}try{let{FaceLandmarker:e}=await import("@mediapipe/tasks-vision");E.clearRect(0,0,c.width,c.height);for(let a=0;a<n;++a)T(a,o.OUTER_LIP,{r:.25,g:0,b:0}),T(a,o.INNER_LIP,{r:.75,g:0,b:0}),T(a,e.FACE_LANDMARKS_TESSELATION.map(({start:r})=>r),{r:0,g:.25,b:0}),T(a,e.FACE_LANDMARKS_FACE_OVAL.map(({start:r})=>r),{r:0,g:1,b:0}),T(a,o.LEFT_EYEBROW,{r:0,g:0,b:.15}),T(a,o.RIGHT_EYEBROW,{r:0,g:0,b:.35}),T(a,o.LEFT_EYE,{r:0,g:0,b:.65}),T(a,o.RIGHT_EYE,{r:0,g:0,b:.85});s.updateTextures({u_faceMask:c})}catch(e){console.error("[Face Plugin] Failed to generate mask texture:",e)}}function B(n){if(!t)return;let e=n.length,a=e*f;for(let i=0;i<e;++i){let x=n[i];for(let R=0;R<h;++R){let F=x[R],L=(i*f+R)*4;t[L]=F.x,t[L+1]=1-F.y,t[L+2]=F.z??0,t[L+3]=F.visibility??1}let u=O(t,i,Array.from({length:h},(R,F)=>F)),m=(i*f+o.FACE_CENTER)*4;t[m]=u[0],t[m+1]=u[1],t[m+2]=0,t[m+3]=1;let p=O(t,i,o.INNER_LIP),_=(i*f+o.MOUTH_CENTER)*4;t[_]=p[0],t[_+1]=p[1],t[_+2]=0,t[_+3]=1}let r=Math.ceil(a/g);s.updateTextures({u_faceLandmarksTex:{data:t,width:g,height:r}})}function S(n){if(!n.faceLandmarks||!t)return;let e=n.faceLandmarks.length;B(n.faceLandmarks),z(e).catch(a=>{console.warn("Mask texture update error:",a)}),s.updateUniforms({u_nFaces:e}),l?.onResults?.(n)}s.registerHook("init",async()=>{s.initializeTexture("u_faceMask",c,{preserveY:!0}),s.initializeUniform("u_maxFaces","int",y),s.initializeUniform("u_nFaces","int",0);let n=y*f;I=Math.ceil(n/g);let e=g*I*4;t=new Float32Array(e),s.initializeTexture("u_faceLandmarksTex",{data:t,width:g,height:I},{internalFormat:C.RGBA32F,type:C.FLOAT,minFilter:C.NEAREST,magFilter:C.NEAREST}),await $()}),s.registerHook("updateTextures",n=>{let e=n[A];if(!(!e||(b.get(A)!==e&&(M=-1),b.set(A,e),!d)))try{if(e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;if(e.currentTime!==M){M=e.currentTime;let r=performance.now(),i=d.detectForVideo(e,r);S(i)}}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;let r=d.detect(e);S(r)}}catch(r){console.warn("Face detection error:",r)}}),s.registerHook("destroy",()=>{d&&(d.close(),d=null),N=null,b.clear(),c.remove(),t=null}),P(`
2
2
  uniform int u_maxFaces;
3
3
  uniform int u_nFaces;
4
4
  uniform sampler2D u_faceLandmarksTex;
@@ -11,7 +11,7 @@ uniform sampler2D u_faceMask;
11
11
  #define FACE_LANDMARK_MOUTH_CENTER ${o.MOUTH_CENTER}
12
12
 
13
13
  vec4 faceLandmark(int faceIndex, int landmarkIndex) {
14
- int i = faceIndex * ${l} + landmarkIndex;
14
+ int i = faceIndex * ${f} + landmarkIndex;
15
15
  int x = i % ${g};
16
16
  int y = i / ${g};
17
17
  return texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n}\n\nconst STANDARD_LANDMARK_COUNT = 478;\nconst CUSTOM_LANDMARK_COUNT = 2;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction fillRegion(faceIdx: number, landmarkIndices: number[], color: { r: number; g: number; b: number }) {\n\t\t\tif (!landmarksDataArray) return;\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tconst originIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[0]) * 4;\n\t\t\tconst originX = landmarksDataArray[originIdx];\n\t\t\tconst originY = landmarksDataArray[originIdx + 1];\n\t\t\tfaceMaskCtx.moveTo(originX * faceMaskCanvas.width, originY * faceMaskCanvas.height);\n\n\t\t\tfor (let i = 1; i < landmarkIndices.length; ++i) {\n\t\t\t\tconst destIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[i]) * 4;\n\t\t\t\tconst destX = landmarksDataArray[destIdx];\n\t\t\t\tconst destY = landmarksDataArray[destIdx + 1];\n\t\t\t\tfaceMaskCtx.lineTo(destX * faceMaskCanvas.width, destY * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(nFaces: number) {\n\t\t\tif (!faceLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.OUTER_LIP, { r: 0.25, g: 0, b: 0 });\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.INNER_LIP, { r: 0.75, g: 0, b: 0 });\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t});\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t});\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYE, { r: 0, g: 0, b: 0.65 });\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYE, { r: 0, g: 0, b: 0.85 });\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nFaces = faces.length;\n\t\t\tconst totalLandmarks = nFaces * LANDMARK_COUNT;\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst landmarks = faces[faceIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tfaceIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst faceCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[faceCenterIdx] = faceCenter[0];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 1] = faceCenter[1];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 3] = 1; // Visibility\n\n\t\t\t\t// Mouth center (landmark 479) - uses INNER_LIP landmarks\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, LANDMARK_INDICES.INNER_LIP);\n\t\t\t\tconst mouthCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[mouthCenterIdx] = mouthCenter[0];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 1] = mouthCenter[1];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 3] = 1; // Visibility\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_faceLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processFaceResults(result: any) {\n\t\t\tif (!result.faceLandmarks || !landmarksDataArray) return;\n\n\t\t\tconst nFaces = result.faceLandmarks.length;\n\t\t\tupdateLandmarksTexture(result.faceLandmarks);\n\t\t\tupdateMaskTexture(nFaces).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tshaderPad.updateUniforms({ u_nFaces: nFaces });\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas, { preserveY: true });\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxFaces * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform sampler2D u_faceLandmarksTex;\nuniform sampler2D u_faceMask;\n\n#define FACE_LANDMARK_L_EYE_CENTER ${LANDMARK_INDICES.LEFT_EYE_CENTER}\n#define FACE_LANDMARK_R_EYE_CENTER ${LANDMARK_INDICES.RIGHT_EYE_CENTER}\n#define FACE_LANDMARK_NOSE_TIP ${LANDMARK_INDICES.NOSE_TIP}\n#define FACE_LANDMARK_FACE_CENTER ${LANDMARK_INDICES.FACE_CENTER}\n#define FACE_LANDMARK_MOUTH_CENTER ${LANDMARK_INDICES.MOUTH_CENTER}\n\nvec4 faceLandmark(int faceIndex, int landmarkIndex) {\n\tint i = faceIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);\n}\nfloat inFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat inEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat inMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateBoundingBoxCenter","faceIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","fillRegion","color","originIdx","originX","originY","i","destIdx","destX","destY","updateMaskTexture","nFaces","start","updateLandmarksTexture","faces","totalLandmarks","landmarks","lmIdx","landmark","faceCenter","_","faceCenterIdx","mouthCenter","mouthCenterIdx","rowsToUpdate","processFaceResults","result","textureSize","updates","source","timestamp","face_default"]}
1
+ {"version":3,"sources":["../../src/plugins/face.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { FaceLandmarker, FaceLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface FacePluginOptions {\n\tmodelPath?: string;\n\tmaxFaces?: number;\n\tminFaceDetectionConfidence?: number;\n\tminFacePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputFaceBlendshapes?: boolean;\n\toutputFacialTransformationMatrixes?: boolean;\n\tonResults?: (results: FaceLandmarkerResult) => void;\n}\n\nconst STANDARD_LANDMARK_COUNT = 478;\nconst CUSTOM_LANDMARK_COUNT = 2;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: [336, 296, 334, 293, 300, 276, 283, 282, 295, 285],\n\tLEFT_EYE: [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382],\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: [70, 63, 105, 66, 107, 55, 65, 52, 53, 46],\n\tRIGHT_EYE: [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7],\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_LIP: [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146],\n\tINNER_LIP: [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95],\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nfunction face(config: { textureName: string; options?: FacePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet faceLandmarker: FaceLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst faceMaskCanvas = document.createElement('canvas');\n\t\tfaceMaskCanvas.width = maskWidth;\n\t\tfaceMaskCanvas.height = maskHeight;\n\t\tconst faceMaskCtx = faceMaskCanvas.getContext('2d')!;\n\t\tfaceMaskCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializeFaceLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\tfaceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumFaces: options?.maxFaces ?? 1,\n\t\t\t\t\tminFaceDetectionConfidence: options?.minFaceDetectionConfidence ?? 0.5,\n\t\t\t\t\tminFacePresenceConfidence: options?.minFacePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputFaceBlendshapes: options?.outputFaceBlendshapes ?? false,\n\t\t\t\t\toutputFacialTransformationMatrixes: options?.outputFacialTransformationMatrixes ?? false,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Face Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction fillRegion(faceIdx: number, landmarkIndices: number[], color: { r: number; g: number; b: number }) {\n\t\t\tif (!landmarksDataArray) return;\n\t\t\tfaceMaskCtx.fillStyle = `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(\n\t\t\t\tcolor.b * 255\n\t\t\t)})`;\n\t\t\tfaceMaskCtx.beginPath();\n\t\t\tconst originIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[0]) * 4;\n\t\t\tconst originX = landmarksDataArray[originIdx];\n\t\t\tconst originY = landmarksDataArray[originIdx + 1];\n\t\t\tfaceMaskCtx.moveTo(originX * faceMaskCanvas.width, originY * faceMaskCanvas.height);\n\n\t\t\tfor (let i = 1; i < landmarkIndices.length; ++i) {\n\t\t\t\tconst destIdx = (faceIdx * LANDMARK_COUNT + landmarkIndices[i]) * 4;\n\t\t\t\tconst destX = landmarksDataArray[destIdx];\n\t\t\t\tconst destY = landmarksDataArray[destIdx + 1];\n\t\t\t\tfaceMaskCtx.lineTo(destX * faceMaskCanvas.width, destY * faceMaskCanvas.height);\n\t\t\t}\n\t\t\tfaceMaskCtx.closePath();\n\t\t\tfaceMaskCtx.fill();\n\t\t}\n\n\t\tasync function updateMaskTexture(nFaces: number) {\n\t\t\tif (!faceLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Face Plugin] Cannot update mask: faceLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { FaceLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tfaceMaskCtx.clearRect(0, 0, faceMaskCanvas.width, faceMaskCanvas.height);\n\n\t\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\t\t// Build combined mask with RGBA channels\n\t\t\t\t\t// R: Mouth | G: Face | B: Eyes\n\t\t\t\t\t// Mouth (red channel).\n\t\t\t\t\t// Lips.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.OUTER_LIP, { r: 0.25, g: 0, b: 0 });\n\t\t\t\t\t// Inner mouth.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.INNER_LIP, { r: 0.75, g: 0, b: 0 });\n\n\t\t\t\t\t// Face (green channel).\n\t\t\t\t\t// Entire face.\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_TESSELATION.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 0.25, b: 0 }\n\t\t\t\t\t);\n\t\t\t\t\t// Face contour (excludes nose in profile view).\n\t\t\t\t\tfillRegion(\n\t\t\t\t\t\tfaceIdx,\n\t\t\t\t\t\tFaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start),\n\t\t\t\t\t\t{ r: 0, g: 1, b: 0 }\n\t\t\t\t\t);\n\n\t\t\t\t\t// Eyes (blue channel).\n\t\t\t\t\t// Eyebrows.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.15,\n\t\t\t\t\t});\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYEBROW, {\n\t\t\t\t\t\tr: 0,\n\t\t\t\t\t\tg: 0,\n\t\t\t\t\t\tb: 0.35,\n\t\t\t\t\t});\n\t\t\t\t\t// Eyes.\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.LEFT_EYE, { r: 0, g: 0, b: 0.65 });\n\t\t\t\t\tfillRegion(faceIdx, LANDMARK_INDICES.RIGHT_EYE, { r: 0, g: 0, b: 0.85 });\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_faceMask: faceMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(faces: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nFaces = faces.length;\n\t\t\tconst totalLandmarks = nFaces * LANDMARK_COUNT;\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst landmarks = faces[faceIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tfaceIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst faceCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[faceCenterIdx] = faceCenter[0];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 1] = faceCenter[1];\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[faceCenterIdx + 3] = 1; // Visibility\n\n\t\t\t\t// Mouth center (landmark 479) - uses INNER_LIP landmarks\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, LANDMARK_INDICES.INNER_LIP);\n\t\t\t\tconst mouthCenterIdx = (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[mouthCenterIdx] = mouthCenter[0];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 1] = mouthCenter[1];\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 2] = 0; // Z\n\t\t\t\tlandmarksDataArray[mouthCenterIdx + 3] = 1; // Visibility\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_faceLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processFaceResults(result: FaceLandmarkerResult) {\n\t\t\tif (!result.faceLandmarks || !landmarksDataArray) return;\n\n\t\t\tconst nFaces = result.faceLandmarks.length;\n\t\t\tupdateLandmarksTexture(result.faceLandmarks);\n\t\t\tupdateMaskTexture(nFaces).catch(error => {\n\t\t\t\tconsole.warn('Mask texture update error:', error);\n\t\t\t});\n\t\t\tshaderPad.updateUniforms({ u_nFaces: nFaces });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', faceMaskCanvas, { preserveY: true });\n\t\t\tshaderPad.initializeUniform('u_maxFaces', 'int', maxFaces);\n\t\t\tshaderPad.initializeUniform('u_nFaces', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxFaces * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = faceLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = faceLandmarker.detect(source);\n\t\t\t\t\tprocessFaceResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Face detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (faceLandmarker) {\n\t\t\t\tfaceLandmarker.close();\n\t\t\t\tfaceLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tfaceMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxFaces;\nuniform int u_nFaces;\nuniform sampler2D u_faceLandmarksTex;\nuniform sampler2D u_faceMask;\n\n#define FACE_LANDMARK_L_EYE_CENTER ${LANDMARK_INDICES.LEFT_EYE_CENTER}\n#define FACE_LANDMARK_R_EYE_CENTER ${LANDMARK_INDICES.RIGHT_EYE_CENTER}\n#define FACE_LANDMARK_NOSE_TIP ${LANDMARK_INDICES.NOSE_TIP}\n#define FACE_LANDMARK_FACE_CENTER ${LANDMARK_INDICES.FACE_CENTER}\n#define FACE_LANDMARK_MOUTH_CENTER ${LANDMARK_INDICES.MOUTH_CENTER}\n\nvec4 faceLandmark(int faceIndex, int landmarkIndex) {\n\tint i = faceIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);\n}\nfloat inFace(vec2 pos) { return texture(u_faceMask, pos).g; }\nfloat inEye(vec2 pos) { return texture(u_faceMask, pos).b; }\nfloat inMouth(vec2 pos) { return texture(u_faceMask, pos).r; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","textureSources","maxFaces","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","faceMaskCanvas","faceMaskCtx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","error","calculateBoundingBoxCenter","faceIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","fillRegion","color","originIdx","originX","originY","i","destIdx","destX","destY","updateMaskTexture","nFaces","start","updateLandmarksTexture","faces","totalLandmarks","landmarks","lmIdx","landmark","faceCenter","_","faceCenterIdx","mouthCenter","mouthCenterIdx","rowsToUpdate","processFaceResults","result","textureSize","updates","source","timestamp","face_default"]}
@@ -1,4 +1,5 @@
1
1
  import ShaderPad, { PluginContext } from '../index.mjs';
2
+ import { HandLandmarkerResult } from '@mediapipe/tasks-vision';
2
3
 
3
4
  interface HandsPluginOptions {
4
5
  modelPath?: string;
@@ -6,6 +7,7 @@ interface HandsPluginOptions {
6
7
  minHandDetectionConfidence?: number;
7
8
  minHandPresenceConfidence?: number;
8
9
  minTrackingConfidence?: number;
10
+ onResults?: (results: HandLandmarkerResult) => void;
9
11
  }
10
12
  declare function hands(config: {
11
13
  textureName: string;
@@ -1,4 +1,5 @@
1
1
  import ShaderPad, { PluginContext } from '../index.js';
2
+ import { HandLandmarkerResult } from '@mediapipe/tasks-vision';
2
3
 
3
4
  interface HandsPluginOptions {
4
5
  modelPath?: string;
@@ -6,6 +7,7 @@ interface HandsPluginOptions {
6
7
  minHandDetectionConfidence?: number;
7
8
  minHandPresenceConfidence?: number;
8
9
  minTrackingConfidence?: number;
10
+ onResults?: (results: HandLandmarkerResult) => void;
9
11
  }
10
12
  declare function hands(config: {
11
13
  textureName: string;
@@ -1,9 +1,9 @@
1
- "use strict";var V=Object.create;var y=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,B=Object.prototype.hasOwnProperty;var Y=(t,i)=>{for(var a in i)y(t,a,{get:i[a],enumerable:!0})},w=(t,i,a,g)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of K(i))!B.call(t,o)&&o!==a&&y(t,o,{get:()=>i[o],enumerable:!(g=P(i,o))||g.enumerable});return t};var $=(t,i,a)=>(a=t!=null?V(X(t)):{},w(i||!t||!t.__esModule?y(a,"default",{value:t,enumerable:!0}):a,t)),j=t=>w(y({},"__esModule",{value:!0}),t);var Q={};Y(Q,{default:()=>J});module.exports=j(Q);var G=21,W=1,p=G+W,Z=[0,0,5,9,13,17];function q(t){let{textureName:i,options:a}=t,g="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(o,I){let{injectGLSL:R,gl:k}=I,m=null,_=null,C=-1,A=new Map,N=a?.maxHands??2,l=512,M=0,r=null;async function F(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");_=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),m=await n.createFromOptions(_,{baseOptions:{modelAssetPath:a?.modelPath||g},runningMode:"VIDEO",numHands:a?.maxHands??2,minHandDetectionConfidence:a?.minHandDetectionConfidence??.5,minHandPresenceConfidence:a?.minHandPresenceConfidence??.5,minTrackingConfidence:a?.minTrackingConfidence??.5})}catch(e){throw console.error("Failed to initialize Hand Landmarker:",e),e}}function D(e,n,x){let d=1/0,s=-1/0,T=1/0,c=-1/0,u=0,f=0;for(let U of x){let L=(n*p+U)*4,v=e[L],b=e[L+1];d=Math.min(d,v),s=Math.max(s,v),T=Math.min(T,b),c=Math.max(c,b),u+=e[L+2],f+=e[L+3]}let h=(d+s)/2,H=(T+c)/2,O=u/x.length,z=f/x.length;return[h,H,O,z]}function E(e){if(!r)return;let n=e.length,x=n*p;for(let s=0;s<n;++s){let T=e[s];for(let f=0;f<21;++f){let h=T[f],H=(s*p+f)*4;r[H]=h.x,r[H+1]=1-h.y,r[H+2]=h.z??0,r[H+3]=h.visibility??1}let c=D(r,s,Z),u=(s*p+21)*4;r[u]=c[0],r[u+1]=c[1],r[u+2]=c[2],r[u+3]=c[3]}let d=Math.ceil(x/l);o.updateTextures({u_handLandmarksTex:{data:r,width:l,height:d}})}function S(e){if(!e.landmarks||!r)return;let n=e.landmarks.length;E(e.landmarks),o.updateUniforms({u_nHands:n})}o.registerHook("init",async()=>{o.initializeUniform("u_maxHands","int",N),o.initializeUniform("u_nHands","int",0);let e=N*p;M=Math.ceil(e/l);let n=l*M*4;r=new Float32Array(n),o.initializeTexture("u_handLandmarksTex",{data:r,width:l,height:M},{internalFormat:k.RGBA32F,type:k.FLOAT,minFilter:k.NEAREST,magFilter:k.NEAREST}),await F()}),o.registerHook("updateTextures",e=>{let n=e[i];if(!(!n||(A.get(i)!==n&&(C=-1),A.set(i,n),!m)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==C){C=n.currentTime;let d=performance.now(),s=m.detectForVideo(n,d);S(s)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let d=m.detect(n);S(d)}}catch(d){console.warn("Hand detection error:",d)}}),o.registerHook("destroy",()=>{m&&(m.close(),m=null),_=null,A.clear(),r=null}),R(`
1
+ "use strict";var V=Object.create;var y=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,B=Object.prototype.hasOwnProperty;var Y=(t,i)=>{for(var a in i)y(t,a,{get:i[a],enumerable:!0})},b=(t,i,a,T)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of K(i))!B.call(t,o)&&o!==a&&y(t,o,{get:()=>i[o],enumerable:!(T=P(i,o))||T.enumerable});return t};var $=(t,i,a)=>(a=t!=null?V(X(t)):{},b(i||!t||!t.__esModule?y(a,"default",{value:t,enumerable:!0}):a,t)),j=t=>b(y({},"__esModule",{value:!0}),t);var Q={};Y(Q,{default:()=>J});module.exports=j(Q);var G=21,W=1,k=G+W,Z=[0,0,5,9,13,17];function q(t){let{textureName:i,options:a}=t,T="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(o,w){let{injectGLSL:I,gl:g}=w,m=null,_=null,C=-1,A=new Map,M=a?.maxHands??2,l=512,R=0,r=null;async function F(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");_=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),m=await n.createFromOptions(_,{baseOptions:{modelAssetPath:a?.modelPath||T},runningMode:"VIDEO",numHands:a?.maxHands??2,minHandDetectionConfidence:a?.minHandDetectionConfidence??.5,minHandPresenceConfidence:a?.minHandPresenceConfidence??.5,minTrackingConfidence:a?.minTrackingConfidence??.5})}catch(e){throw console.error("Failed to initialize Hand Landmarker:",e),e}}function D(e,n,x){let d=1/0,s=-1/0,p=1/0,c=-1/0,u=0,f=0;for(let U of x){let L=(n*k+U)*4,S=e[L],v=e[L+1];d=Math.min(d,S),s=Math.max(s,S),p=Math.min(p,v),c=Math.max(c,v),u+=e[L+2],f+=e[L+3]}let h=(d+s)/2,H=(p+c)/2,O=u/x.length,z=f/x.length;return[h,H,O,z]}function E(e){if(!r)return;let n=e.length,x=n*k;for(let s=0;s<n;++s){let p=e[s];for(let f=0;f<21;++f){let h=p[f],H=(s*k+f)*4;r[H]=h.x,r[H+1]=1-h.y,r[H+2]=h.z??0,r[H+3]=h.visibility??1}let c=D(r,s,Z),u=(s*k+21)*4;r[u]=c[0],r[u+1]=c[1],r[u+2]=c[2],r[u+3]=c[3]}let d=Math.ceil(x/l);o.updateTextures({u_handLandmarksTex:{data:r,width:l,height:d}})}function N(e){if(!e.landmarks||!r)return;let n=e.landmarks.length;E(e.landmarks),o.updateUniforms({u_nHands:n}),a?.onResults?.(e)}o.registerHook("init",async()=>{o.initializeUniform("u_maxHands","int",M),o.initializeUniform("u_nHands","int",0);let e=M*k;R=Math.ceil(e/l);let n=l*R*4;r=new Float32Array(n),o.initializeTexture("u_handLandmarksTex",{data:r,width:l,height:R},{internalFormat:g.RGBA32F,type:g.FLOAT,minFilter:g.NEAREST,magFilter:g.NEAREST}),await F()}),o.registerHook("updateTextures",e=>{let n=e[i];if(!(!n||(A.get(i)!==n&&(C=-1),A.set(i,n),!m)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==C){C=n.currentTime;let d=performance.now(),s=m.detectForVideo(n,d);N(s)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let d=m.detect(n);N(d)}}catch(d){console.warn("Hand detection error:",d)}}),o.registerHook("destroy",()=>{m&&(m.close(),m=null),_=null,A.clear(),r=null}),I(`
2
2
  uniform int u_maxHands;
3
3
  uniform int u_nHands;
4
4
  uniform sampler2D u_handLandmarksTex;
5
5
  vec4 handLandmark(int handIndex, int landmarkIndex) {
6
- int i = handIndex * ${p} + landmarkIndex;
6
+ int i = handIndex * ${k} + landmarkIndex;
7
7
  int x = i % ${l};
8
8
  int y = i / ${l};
9
9
  return texelFetch(u_handLandmarksTex, ivec2(x, y), 0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/hands.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { HandLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface HandsPluginOptions {\n\tmodelPath?: string;\n\tmaxHands?: number;\n\tminHandDetectionConfidence?: number;\n\tminHandPresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n}\n\nconst STANDARD_LANDMARK_COUNT = 21; // See https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models.\nconst CUSTOM_LANDMARK_COUNT = 1;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst HAND_CENTER_LANDMARKS = [0, 0, 5, 9, 13, 17]; // Wrist + MCP joints of all fingers, weighted toward the wrist.\n\nfunction hands(config: { textureName: string; options?: HandsPluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet handLandmarker: HandLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxHands = options?.maxHands ?? 2;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tasync function initializeHandLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, HandLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\thandLandmarker = await HandLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumHands: options?.maxHands ?? 2,\n\t\t\t\t\tminHandDetectionConfidence: options?.minHandDetectionConfidence ?? 0.5,\n\t\t\t\t\tminHandPresenceConfidence: options?.minHandPresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Hand Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\thandIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction updateLandmarksTexture(hands: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nHands = hands.length;\n\t\t\tconst totalLandmarks = nHands * LANDMARK_COUNT;\n\n\t\t\tfor (let handIdx = 0; handIdx < nHands; ++handIdx) {\n\t\t\t\tconst landmarks = hands[handIdx];\n\t\t\t\t// Store standard 21 landmarks\n\t\t\t\tfor (let lmIdx = 0; lmIdx < 21; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Calculate and store hand center (landmark 21)\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + 21) * 4;\n\t\t\t\tlandmarksDataArray[handCenterIdx] = handCenter[0];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 1] = handCenter[1];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 2] = handCenter[2];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 3] = handCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_handLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processHandResults(result: any) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nHands = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tshaderPad.updateUniforms({ u_nHands: nHands });\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeUniform('u_maxHands', 'int', maxHands);\n\t\t\tshaderPad.initializeUniform('u_nHands', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxHands * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_handLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeHandLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!handLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = handLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = handLandmarker.detect(source);\n\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Hand detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (handLandmarker) {\n\t\t\t\thandLandmarker.close();\n\t\t\t\thandLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxHands;\nuniform int u_nHands;\nuniform sampler2D u_handLandmarksTex;\nvec4 handLandmark(int handIndex, int landmarkIndex) {\n\tint i = handIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_handLandmarksTex, ivec2(x, y), 0);\n}`);\n\t};\n}\n\nexport default hands;\n"],"mappings":"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","names":["hands_exports","__export","hands_default","__toCommonJS","STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","HAND_CENTER_LANDMARKS","hands","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","handLandmarker","vision","lastVideoTime","textureSources","maxHands","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","initializeHandLandmarker","FilesetResolver","HandLandmarker","error","calculateBoundingBoxCenter","handIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateLandmarksTexture","nHands","totalLandmarks","landmarks","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","timestamp"]}
1
+ {"version":3,"sources":["../../src/plugins/hands.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface HandsPluginOptions {\n\tmodelPath?: string;\n\tmaxHands?: number;\n\tminHandDetectionConfidence?: number;\n\tminHandPresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\tonResults?: (results: HandLandmarkerResult) => void;\n}\n\nconst STANDARD_LANDMARK_COUNT = 21; // See https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models.\nconst CUSTOM_LANDMARK_COUNT = 1;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst HAND_CENTER_LANDMARKS = [0, 0, 5, 9, 13, 17]; // Wrist + MCP joints of all fingers, weighted toward the wrist.\n\nfunction hands(config: { textureName: string; options?: HandsPluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet handLandmarker: HandLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxHands = options?.maxHands ?? 2;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tasync function initializeHandLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, HandLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\thandLandmarker = await HandLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumHands: options?.maxHands ?? 2,\n\t\t\t\t\tminHandDetectionConfidence: options?.minHandDetectionConfidence ?? 0.5,\n\t\t\t\t\tminHandPresenceConfidence: options?.minHandPresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Hand Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\thandIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction updateLandmarksTexture(hands: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nHands = hands.length;\n\t\t\tconst totalLandmarks = nHands * LANDMARK_COUNT;\n\n\t\t\tfor (let handIdx = 0; handIdx < nHands; ++handIdx) {\n\t\t\t\tconst landmarks = hands[handIdx];\n\t\t\t\t// Store standard 21 landmarks\n\t\t\t\tfor (let lmIdx = 0; lmIdx < 21; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Calculate and store hand center (landmark 21)\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + 21) * 4;\n\t\t\t\tlandmarksDataArray[handCenterIdx] = handCenter[0];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 1] = handCenter[1];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 2] = handCenter[2];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 3] = handCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_handLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processHandResults(result: HandLandmarkerResult) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nHands = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tshaderPad.updateUniforms({ u_nHands: nHands });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeUniform('u_maxHands', 'int', maxHands);\n\t\t\tshaderPad.initializeUniform('u_nHands', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxHands * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_handLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeHandLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!handLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = handLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = handLandmarker.detect(source);\n\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Hand detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (handLandmarker) {\n\t\t\t\thandLandmarker.close();\n\t\t\t\thandLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxHands;\nuniform int u_nHands;\nuniform sampler2D u_handLandmarksTex;\nvec4 handLandmark(int handIndex, int landmarkIndex) {\n\tint i = handIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_handLandmarksTex, ivec2(x, y), 0);\n}`);\n\t};\n}\n\nexport default hands;\n"],"mappings":"0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,eAAAC,EAAAH,GAYA,IAAMI,EAA0B,GAC1BC,EAAwB,EACxBC,EAAiBF,EAA0BC,EAC3CE,EAAwB,CAAC,EAAG,EAAG,EAAG,EAAG,GAAI,EAAE,EAEjD,SAASC,EAAMC,EAA+D,CAC7E,GAAM,CAAE,YAAAC,EAAa,QAAAC,CAAQ,EAAIF,EAC3BG,EACL,iHAED,OAAO,SAAUC,EAAsBC,EAAwB,CAC9D,GAAM,CAAE,WAAAC,EAAY,GAAAC,CAAG,EAAIF,EAEvBG,EAAwC,KACxCC,EAAc,KACdC,EAAgB,GACdC,EAAiB,IAAI,IACrBC,EAAWV,GAAS,UAAY,EAEhCW,EAA0B,IAC5BC,EAAyB,EACzBC,EAA0C,KAE9C,eAAeC,GAA2B,CACzC,GAAI,CACH,GAAM,CAAE,gBAAAC,EAAiB,eAAAC,CAAe,EAAI,KAAM,QAAO,yBAAyB,EAClFT,EAAS,MAAMQ,EAAgB,eAC9B,kEACD,EAEAT,EAAiB,MAAMU,EAAe,kBAAkBT,EAAQ,CAC/D,YAAa,CACZ,eAAgBP,GAAS,WAAaC,CACvC,EACA,YAAa,QACb,SAAUD,GAAS,UAAY,EAC/B,2BAA4BA,GAAS,4BAA8B,GACnE,0BAA2BA,GAAS,2BAA6B,GACjE,sBAAuBA,GAAS,uBAAyB,EAC1D,CAAC,CACF,OAASiB,EAAO,CACf,cAAQ,MAAM,wCAAyCA,CAAK,EACtDA,CACP,CACD,CAEA,SAASC,EACRL,EACAM,EACAC,EACmC,CACnC,IAAIC,EAAO,IACVC,EAAO,KACPC,EAAO,IACPC,EAAO,KACPC,EAAO,EACPC,EAAgB,EAEjB,QAAWC,KAAOP,EAAiB,CAClC,IAAMQ,GAAWT,EAAUxB,EAAiBgC,GAAO,EAC7CE,EAAIhB,EAAmBe,CAAO,EAC9BE,EAAIjB,EAAmBe,EAAU,CAAC,EACxCP,EAAO,KAAK,IAAIA,EAAMQ,CAAC,EACvBP,EAAO,KAAK,IAAIA,EAAMO,CAAC,EACvBN,EAAO,KAAK,IAAIA,EAAMO,CAAC,EACvBN,EAAO,KAAK,IAAIA,EAAMM,CAAC,EACvBL,GAAQZ,EAAmBe,EAAU,CAAC,EACtCF,GAAiBb,EAAmBe,EAAU,CAAC,CAChD,CAEA,IAAMG,GAAWV,EAAOC,GAAQ,EAC1BU,GAAWT,EAAOC,GAAQ,EAC1BS,EAAUR,EAAOL,EAAgB,OACjCc,EAAmBR,EAAgBN,EAAgB,OACzD,MAAO,CAACW,EAASC,EAASC,EAASC,CAAgB,CACpD,CAEA,SAASC,EAAuBtC,EAA+B,CAC9D,GAAI,CAACgB,EAAoB,OAEzB,IAAMuB,EAASvC,EAAM,OACfwC,EAAiBD,EAASzC,EAEhC,QAASwB,EAAU,EAAGA,EAAUiB,EAAQ,EAAEjB,EAAS,CAClD,IAAMmB,EAAYzC,EAAMsB,CAAO,EAE/B,QAASoB,EAAQ,EAAGA,EAAQ,GAAI,EAAEA,EAAO,CACxC,IAAMC,EAAWF,EAAUC,CAAK,EAC1BX,GAAWT,EAAUxB,EAAiB4C,GAAS,EACrD1B,EAAmBe,CAAO,EAAIY,EAAS,EACvC3B,EAAmBe,EAAU,CAAC,EAAI,EAAIY,EAAS,EAC/C3B,EAAmBe,EAAU,CAAC,EAAIY,EAAS,GAAK,EAChD3B,EAAmBe,EAAU,CAAC,EAAIY,EAAS,YAAc,CAC1D,CAGA,IAAMC,EAAavB,EAA2BL,EAAoBM,EAASvB,CAAqB,EAC1F8C,GAAiBvB,EAAUxB,EAAiB,IAAM,EACxDkB,EAAmB6B,CAAa,EAAID,EAAW,CAAC,EAChD5B,EAAmB6B,EAAgB,CAAC,EAAID,EAAW,CAAC,EACpD5B,EAAmB6B,EAAgB,CAAC,EAAID,EAAW,CAAC,EACpD5B,EAAmB6B,EAAgB,CAAC,EAAID,EAAW,CAAC,CACrD,CAEA,IAAME,EAAe,KAAK,KAAKN,EAAiB1B,CAAuB,EACvET,EAAU,eAAe,CACxB,mBAAoB,CACnB,KAAMW,EACN,MAAOF,EACP,OAAQgC,CACT,CACD,CAAC,CACF,CAEA,SAASC,EAAmBC,EAA8B,CACzD,GAAI,CAACA,EAAO,WAAa,CAAChC,EAAoB,OAE9C,IAAMuB,EAASS,EAAO,UAAU,OAChCV,EAAuBU,EAAO,SAAS,EACvC3C,EAAU,eAAe,CAAE,SAAUkC,CAAO,CAAC,EAE7CpC,GAAS,YAAY6C,CAAM,CAC5B,CAEA3C,EAAU,aAAa,OAAQ,SAAY,CAC1CA,EAAU,kBAAkB,aAAc,MAAOQ,CAAQ,EACzDR,EAAU,kBAAkB,WAAY,MAAO,CAAC,EAEhD,IAAMmC,EAAiB3B,EAAWf,EAClCiB,EAAyB,KAAK,KAAKyB,EAAiB1B,CAAuB,EAC3E,IAAMmC,EAAcnC,EAA0BC,EAAyB,EACvEC,EAAqB,IAAI,aAAaiC,CAAW,EAEjD5C,EAAU,kBACT,qBACA,CACC,KAAMW,EACN,MAAOF,EACP,OAAQC,CACT,EACA,CACC,eAAgBP,EAAG,QACnB,KAAMA,EAAG,MACT,UAAWA,EAAG,QACd,UAAWA,EAAG,OACf,CACD,EAEA,MAAMS,EAAyB,CAChC,CAAC,EAEDZ,EAAU,aAAa,iBAAmB6C,GAA2C,CACpF,IAAMC,EAASD,EAAQhD,CAAW,EASlC,GARI,GAACiD,IAEkBvC,EAAe,IAAIV,CAAW,IAC9BiD,IACtBxC,EAAgB,IAGjBC,EAAe,IAAIV,EAAaiD,CAAM,EAClC,CAAC1C,IACL,GAAI,CACH,GAAI0C,aAAkB,iBAAkB,CACvC,GAAIA,EAAO,aAAe,GAAKA,EAAO,cAAgB,GAAKA,EAAO,WAAa,EAC9E,OAED,GAAIA,EAAO,cAAgBxC,EAAe,CACzCA,EAAgBwC,EAAO,YACvB,IAAMC,EAAY,YAAY,IAAI,EAC5BJ,EAASvC,EAAe,eAAe0C,EAAQC,CAAS,EAC9DL,EAAmBC,CAAM,CAC1B,CACD,SAAWG,aAAkB,kBAAoBA,aAAkB,kBAAmB,CACrF,GAAIA,EAAO,QAAU,GAAKA,EAAO,SAAW,EAC3C,OAED,IAAMH,EAASvC,EAAe,OAAO0C,CAAM,EAC3CJ,EAAmBC,CAAM,CAC1B,CACD,OAAS5B,EAAO,CACf,QAAQ,KAAK,wBAAyBA,CAAK,CAC5C,CACD,CAAC,EAEDf,EAAU,aAAa,UAAW,IAAM,CACnCI,IACHA,EAAe,MAAM,EACrBA,EAAiB,MAElBC,EAAS,KACTE,EAAe,MAAM,EACrBI,EAAqB,IACtB,CAAC,EAEDT,EAAW;AAAA;AAAA;AAAA;AAAA;AAAA,uBAKUT,CAAc;AAAA,eACtBgB,CAAuB;AAAA,eACvBA,CAAuB;AAAA;AAAA,EAEpC,CACD,CACD,CAEA,IAAOpB,EAAQM","names":["hands_exports","__export","hands_default","__toCommonJS","STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","HAND_CENTER_LANDMARKS","hands","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","handLandmarker","vision","lastVideoTime","textureSources","maxHands","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","initializeHandLandmarker","FilesetResolver","HandLandmarker","error","calculateBoundingBoxCenter","handIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateLandmarksTexture","nHands","totalLandmarks","landmarks","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","timestamp"]}
@@ -1,11 +1,11 @@
1
- var z=21,U=1,h=z+U,V=[0,0,5,9,13,17];function P(S){let{textureName:g,options:l}=S,v="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(r,b){let{injectGLSL:w,gl:p}=b,s=null,k=null,L=-1,y=new Map,C=l?.maxHands??2,d=512,_=0,t=null;async function I(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");k=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),s=await n.createFromOptions(k,{baseOptions:{modelAssetPath:l?.modelPath||v},runningMode:"VIDEO",numHands:l?.maxHands??2,minHandDetectionConfidence:l?.minHandDetectionConfidence??.5,minHandPresenceConfidence:l?.minHandPresenceConfidence??.5,minTrackingConfidence:l?.minTrackingConfidence??.5})}catch(e){throw console.error("Failed to initialize Hand Landmarker:",e),e}}function R(e,n,u){let a=1/0,i=-1/0,H=1/0,o=-1/0,c=0,m=0;for(let O of u){let T=(n*h+O)*4,M=e[T],N=e[T+1];a=Math.min(a,M),i=Math.max(i,M),H=Math.min(H,N),o=Math.max(o,N),c+=e[T+2],m+=e[T+3]}let f=(a+i)/2,x=(H+o)/2,D=c/u.length,E=m/u.length;return[f,x,D,E]}function F(e){if(!t)return;let n=e.length,u=n*h;for(let i=0;i<n;++i){let H=e[i];for(let m=0;m<21;++m){let f=H[m],x=(i*h+m)*4;t[x]=f.x,t[x+1]=1-f.y,t[x+2]=f.z??0,t[x+3]=f.visibility??1}let o=R(t,i,V),c=(i*h+21)*4;t[c]=o[0],t[c+1]=o[1],t[c+2]=o[2],t[c+3]=o[3]}let a=Math.ceil(u/d);r.updateTextures({u_handLandmarksTex:{data:t,width:d,height:a}})}function A(e){if(!e.landmarks||!t)return;let n=e.landmarks.length;F(e.landmarks),r.updateUniforms({u_nHands:n})}r.registerHook("init",async()=>{r.initializeUniform("u_maxHands","int",C),r.initializeUniform("u_nHands","int",0);let e=C*h;_=Math.ceil(e/d);let n=d*_*4;t=new Float32Array(n),r.initializeTexture("u_handLandmarksTex",{data:t,width:d,height:_},{internalFormat:p.RGBA32F,type:p.FLOAT,minFilter:p.NEAREST,magFilter:p.NEAREST}),await I()}),r.registerHook("updateTextures",e=>{let n=e[g];if(!(!n||(y.get(g)!==n&&(L=-1),y.set(g,n),!s)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==L){L=n.currentTime;let a=performance.now(),i=s.detectForVideo(n,a);A(i)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let a=s.detect(n);A(a)}}catch(a){console.warn("Hand detection error:",a)}}),r.registerHook("destroy",()=>{s&&(s.close(),s=null),k=null,y.clear(),t=null}),w(`
1
+ var z=21,U=1,h=z+U,V=[0,0,5,9,13,17];function P(N){let{textureName:T,options:s}=N,S="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(r,v){let{injectGLSL:b,gl:k}=v,d=null,g=null,L=-1,y=new Map,C=s?.maxHands??2,c=512,_=0,t=null;async function w(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");g=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),d=await n.createFromOptions(g,{baseOptions:{modelAssetPath:s?.modelPath||S},runningMode:"VIDEO",numHands:s?.maxHands??2,minHandDetectionConfidence:s?.minHandDetectionConfidence??.5,minHandPresenceConfidence:s?.minHandPresenceConfidence??.5,minTrackingConfidence:s?.minTrackingConfidence??.5})}catch(e){throw console.error("Failed to initialize Hand Landmarker:",e),e}}function I(e,n,u){let i=1/0,a=-1/0,H=1/0,o=-1/0,m=0,l=0;for(let O of u){let p=(n*h+O)*4,R=e[p],M=e[p+1];i=Math.min(i,R),a=Math.max(a,R),H=Math.min(H,M),o=Math.max(o,M),m+=e[p+2],l+=e[p+3]}let f=(i+a)/2,x=(H+o)/2,D=m/u.length,E=l/u.length;return[f,x,D,E]}function F(e){if(!t)return;let n=e.length,u=n*h;for(let a=0;a<n;++a){let H=e[a];for(let l=0;l<21;++l){let f=H[l],x=(a*h+l)*4;t[x]=f.x,t[x+1]=1-f.y,t[x+2]=f.z??0,t[x+3]=f.visibility??1}let o=I(t,a,V),m=(a*h+21)*4;t[m]=o[0],t[m+1]=o[1],t[m+2]=o[2],t[m+3]=o[3]}let i=Math.ceil(u/c);r.updateTextures({u_handLandmarksTex:{data:t,width:c,height:i}})}function A(e){if(!e.landmarks||!t)return;let n=e.landmarks.length;F(e.landmarks),r.updateUniforms({u_nHands:n}),s?.onResults?.(e)}r.registerHook("init",async()=>{r.initializeUniform("u_maxHands","int",C),r.initializeUniform("u_nHands","int",0);let e=C*h;_=Math.ceil(e/c);let n=c*_*4;t=new Float32Array(n),r.initializeTexture("u_handLandmarksTex",{data:t,width:c,height:_},{internalFormat:k.RGBA32F,type:k.FLOAT,minFilter:k.NEAREST,magFilter:k.NEAREST}),await w()}),r.registerHook("updateTextures",e=>{let n=e[T];if(!(!n||(y.get(T)!==n&&(L=-1),y.set(T,n),!d)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==L){L=n.currentTime;let i=performance.now(),a=d.detectForVideo(n,i);A(a)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let i=d.detect(n);A(i)}}catch(i){console.warn("Hand detection error:",i)}}),r.registerHook("destroy",()=>{d&&(d.close(),d=null),g=null,y.clear(),t=null}),b(`
2
2
  uniform int u_maxHands;
3
3
  uniform int u_nHands;
4
4
  uniform sampler2D u_handLandmarksTex;
5
5
  vec4 handLandmark(int handIndex, int landmarkIndex) {
6
6
  int i = handIndex * ${h} + landmarkIndex;
7
- int x = i % ${d};
8
- int y = i / ${d};
7
+ int x = i % ${c};
8
+ int y = i / ${c};
9
9
  return texelFetch(u_handLandmarksTex, ivec2(x, y), 0);
10
10
  }`)}}var K=P;export{K as default};
11
11
  //# sourceMappingURL=hands.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/hands.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { HandLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface HandsPluginOptions {\n\tmodelPath?: string;\n\tmaxHands?: number;\n\tminHandDetectionConfidence?: number;\n\tminHandPresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n}\n\nconst STANDARD_LANDMARK_COUNT = 21; // See https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models.\nconst CUSTOM_LANDMARK_COUNT = 1;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst HAND_CENTER_LANDMARKS = [0, 0, 5, 9, 13, 17]; // Wrist + MCP joints of all fingers, weighted toward the wrist.\n\nfunction hands(config: { textureName: string; options?: HandsPluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet handLandmarker: HandLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxHands = options?.maxHands ?? 2;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tasync function initializeHandLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, HandLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\thandLandmarker = await HandLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumHands: options?.maxHands ?? 2,\n\t\t\t\t\tminHandDetectionConfidence: options?.minHandDetectionConfidence ?? 0.5,\n\t\t\t\t\tminHandPresenceConfidence: options?.minHandPresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Hand Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\thandIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction updateLandmarksTexture(hands: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nHands = hands.length;\n\t\t\tconst totalLandmarks = nHands * LANDMARK_COUNT;\n\n\t\t\tfor (let handIdx = 0; handIdx < nHands; ++handIdx) {\n\t\t\t\tconst landmarks = hands[handIdx];\n\t\t\t\t// Store standard 21 landmarks\n\t\t\t\tfor (let lmIdx = 0; lmIdx < 21; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Calculate and store hand center (landmark 21)\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + 21) * 4;\n\t\t\t\tlandmarksDataArray[handCenterIdx] = handCenter[0];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 1] = handCenter[1];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 2] = handCenter[2];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 3] = handCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_handLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processHandResults(result: any) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nHands = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tshaderPad.updateUniforms({ u_nHands: nHands });\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeUniform('u_maxHands', 'int', maxHands);\n\t\t\tshaderPad.initializeUniform('u_nHands', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxHands * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_handLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeHandLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!handLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = handLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = handLandmarker.detect(source);\n\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Hand detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (handLandmarker) {\n\t\t\t\thandLandmarker.close();\n\t\t\t\thandLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxHands;\nuniform int u_nHands;\nuniform sampler2D u_handLandmarksTex;\nvec4 handLandmark(int handIndex, int landmarkIndex) {\n\tint i = handIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_handLandmarksTex, ivec2(x, y), 0);\n}`);\n\t};\n}\n\nexport default hands;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","HAND_CENTER_LANDMARKS","hands","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","handLandmarker","vision","lastVideoTime","textureSources","maxHands","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","initializeHandLandmarker","FilesetResolver","HandLandmarker","error","calculateBoundingBoxCenter","handIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateLandmarksTexture","nHands","totalLandmarks","landmarks","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","timestamp","hands_default"]}
1
+ {"version":3,"sources":["../../src/plugins/hands.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface HandsPluginOptions {\n\tmodelPath?: string;\n\tmaxHands?: number;\n\tminHandDetectionConfidence?: number;\n\tminHandPresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\tonResults?: (results: HandLandmarkerResult) => void;\n}\n\nconst STANDARD_LANDMARK_COUNT = 21; // See https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker#models.\nconst CUSTOM_LANDMARK_COUNT = 1;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst HAND_CENTER_LANDMARKS = [0, 0, 5, 9, 13, 17]; // Wrist + MCP joints of all fingers, weighted toward the wrist.\n\nfunction hands(config: { textureName: string; options?: HandsPluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet handLandmarker: HandLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxHands = options?.maxHands ?? 2;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tasync function initializeHandLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, HandLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\n\t\t\t\thandLandmarker = await HandLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumHands: options?.maxHands ?? 2,\n\t\t\t\t\tminHandDetectionConfidence: options?.minHandDetectionConfidence ?? 0.5,\n\t\t\t\t\tminHandPresenceConfidence: options?.minHandPresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to initialize Hand Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\thandIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tfunction updateLandmarksTexture(hands: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nHands = hands.length;\n\t\t\tconst totalLandmarks = nHands * LANDMARK_COUNT;\n\n\t\t\tfor (let handIdx = 0; handIdx < nHands; ++handIdx) {\n\t\t\t\tconst landmarks = hands[handIdx];\n\t\t\t\t// Store standard 21 landmarks\n\t\t\t\tfor (let lmIdx = 0; lmIdx < 21; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Calculate and store hand center (landmark 21)\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + 21) * 4;\n\t\t\t\tlandmarksDataArray[handCenterIdx] = handCenter[0];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 1] = handCenter[1];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 2] = handCenter[2];\n\t\t\t\tlandmarksDataArray[handCenterIdx + 3] = handCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_handLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processHandResults(result: HandLandmarkerResult) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nHands = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tshaderPad.updateUniforms({ u_nHands: nHands });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeUniform('u_maxHands', 'int', maxHands);\n\t\t\tshaderPad.initializeUniform('u_nHands', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxHands * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_handLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializeHandLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!handLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = handLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = handLandmarker.detect(source);\n\t\t\t\t\tprocessHandResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn('Hand detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (handLandmarker) {\n\t\t\t\thandLandmarker.close();\n\t\t\t\thandLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tinjectGLSL(`\nuniform int u_maxHands;\nuniform int u_nHands;\nuniform sampler2D u_handLandmarksTex;\nvec4 handLandmark(int handIndex, int landmarkIndex) {\n\tint i = handIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_handLandmarksTex, ivec2(x, y), 0);\n}`);\n\t};\n}\n\nexport default hands;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","HAND_CENTER_LANDMARKS","hands","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","handLandmarker","vision","lastVideoTime","textureSources","maxHands","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","initializeHandLandmarker","FilesetResolver","HandLandmarker","error","calculateBoundingBoxCenter","handIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateLandmarksTexture","nHands","totalLandmarks","landmarks","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","timestamp","hands_default"]}
@@ -1,4 +1,5 @@
1
1
  import ShaderPad, { PluginContext } from '../index.mjs';
2
+ import { PoseLandmarkerResult } from '@mediapipe/tasks-vision';
2
3
 
3
4
  interface PosePluginOptions {
4
5
  modelPath?: string;
@@ -7,6 +8,7 @@ interface PosePluginOptions {
7
8
  minPosePresenceConfidence?: number;
8
9
  minTrackingConfidence?: number;
9
10
  outputSegmentationMasks?: boolean;
11
+ onResults?: (results: PoseLandmarkerResult) => void;
10
12
  }
11
13
  declare function pose(config: {
12
14
  textureName: string;
@@ -1,4 +1,5 @@
1
1
  import ShaderPad, { PluginContext } from '../index.js';
2
+ import { PoseLandmarkerResult } from '@mediapipe/tasks-vision';
2
3
 
3
4
  interface PosePluginOptions {
4
5
  modelPath?: string;
@@ -7,6 +8,7 @@ interface PosePluginOptions {
7
8
  minPosePresenceConfidence?: number;
8
9
  minTrackingConfidence?: number;
9
10
  outputSegmentationMasks?: boolean;
11
+ onResults?: (results: PoseLandmarkerResult) => void;
10
12
  }
11
13
  declare function pose(config: {
12
14
  textureName: string;
@@ -1,4 +1,4 @@
1
- "use strict";var Q=Object.create;var y=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var ne=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var ie=(s,_)=>{for(var a in _)y(s,a,{get:_[a],enumerable:!0})},W=(s,_,a,S)=>{if(_&&typeof _=="object"||typeof _=="function")for(let E of te(_))!oe.call(s,E)&&E!==a&&y(s,E,{get:()=>_[E],enumerable:!(S=ee(_,E))||S.enumerable});return s};var re=(s,_,a)=>(a=s!=null?Q(ne(s)):{},W(_||!s||!s.__esModule?y(a,"default",{value:s,enumerable:!0}):a,s)),se=s=>W(y({},"__esModule",{value:!0}),s);var Te={};ie(Te,{default:()=>_e});module.exports=se(Te);var L=33,ae=6,u=L+ae,t={LEFT_EYE:2,RIGHT_EYE:5,LEFT_SHOULDER:11,RIGHT_SHOULDER:12,LEFT_ELBOW:13,RIGHT_ELBOW:14,LEFT_HIP:23,RIGHT_HIP:24,LEFT_KNEE:25,RIGHT_KNEE:26,LEFT_WRIST:15,RIGHT_WRIST:16,LEFT_PINKY:17,RIGHT_PINKY:18,LEFT_INDEX:19,RIGHT_INDEX:20,LEFT_THUMB:21,RIGHT_THUMB:22,LEFT_ANKLE:27,RIGHT_ANKLE:28,LEFT_HEEL:29,RIGHT_HEEL:30,LEFT_FOOT_INDEX:31,RIGHT_FOOT_INDEX:32,BODY_CENTER:L,LEFT_HAND_CENTER:L+1,RIGHT_HAND_CENTER:L+2,LEFT_FOOT_CENTER:L+3,RIGHT_FOOT_CENTER:L+4,TORSO_CENTER:L+5};function Ee(s){let{textureName:_,options:a}=s,S="https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task";return function(E,X){let{injectGLSL:z,gl:k}=X,I=null,w=null,U=-1,v=new Map,$=a?.maxPoses??1,H=512,b=0,e=null,V=512,j=512,m=document.createElement("canvas");m.width=V,m.height=j;let M=m.getContext("2d"),p=document.createElement("canvas"),B=p.getContext("2d");M.globalCompositeOperation=B.globalCompositeOperation="lighten";async function Z(){try{let{FilesetResolver:o,PoseLandmarker:n}=await import("@mediapipe/tasks-vision");w=await o.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),I=await n.createFromOptions(w,{baseOptions:{modelAssetPath:a?.modelPath||S},runningMode:"VIDEO",numPoses:a?.maxPoses??1,minPoseDetectionConfidence:a?.minPoseDetectionConfidence??.5,minPosePresenceConfidence:a?.minPosePresenceConfidence??.5,minTrackingConfidence:a?.minTrackingConfidence??.5,outputSegmentationMasks:a?.outputSegmentationMasks??!0})}catch(o){throw console.error("[Pose Plugin] Failed to initialize Pose Landmarker:",o),o}}function g(o,n,T){let r=1/0,i=-1/0,N=1/0,l=-1/0,d=0,c=0;for(let D of T){let R=(n*u+D)*4,f=o[R],O=o[R+1];r=Math.min(r,f),i=Math.max(i,f),N=Math.min(N,O),l=Math.max(l,O),d+=o[R+2],c+=o[R+3]}let F=(r+i)/2,P=(N+l)/2,A=d/T.length,C=c/T.length;return[F,P,A,C]}async function q(o){if(!I||!e){console.warn("[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing");return}try{M.clearRect(0,0,m.width,m.height),o&&o.length>0&&o.forEach(n=>{if(!n)return;let{width:T,height:r}=n,i=n.getAsUint8Array(),N=T*r,l=new Uint8ClampedArray(N*4);for(let c=0;c<N;c++)l[c*4+1]=i[c],l[c*4+3]=255;let d=new ImageData(l,T,r);T===m.width&&r===m.height?M.putImageData(d,0,0):(p.width!==T&&(p.width=T),p.height!==r&&(p.height=r),B.putImageData(d,0,0),M.drawImage(p,0,0,m.width,m.height))}),E.updateTextures({u_poseMask:m})}catch(n){console.error("[Pose Plugin] Failed to generate mask texture:",n)}}function J(o){if(!e)return;let n=o.length,T=n*u;for(let i=0;i<n;++i){let N=o[i];for(let h=0;h<L;++h){let x=N[h],K=(i*u+h)*4;e[K]=x.x,e[K+1]=1-x.y,e[K+2]=x.z??0,e[K+3]=x.visibility??1}let l=g(e,i,Array.from({length:L},(h,x)=>x)),d=(i*u+t.BODY_CENTER)*4;e[d]=l[0],e[d+1]=l[1],e[d+2]=l[2],e[d+3]=l[3];let c=g(e,i,[t.LEFT_WRIST,t.LEFT_PINKY,t.LEFT_THUMB,t.LEFT_INDEX]),F=(i*u+t.LEFT_HAND_CENTER)*4;e[F]=c[0],e[F+1]=c[1],e[F+2]=c[2],e[F+3]=c[3];let P=g(e,i,[t.RIGHT_WRIST,t.RIGHT_PINKY,t.RIGHT_THUMB,t.RIGHT_INDEX]),A=(i*u+t.RIGHT_HAND_CENTER)*4;e[A]=P[0],e[A+1]=P[1],e[A+2]=P[2],e[A+3]=P[3];let C=g(e,i,[t.LEFT_ANKLE,t.LEFT_HEEL,t.LEFT_FOOT_INDEX]),D=(i*u+t.LEFT_FOOT_CENTER)*4;e[D]=C[0],e[D+1]=C[1],e[D+2]=C[2],e[D+3]=C[3];let R=g(e,i,[t.RIGHT_ANKLE,t.RIGHT_HEEL,t.RIGHT_FOOT_INDEX]),f=(i*u+t.RIGHT_FOOT_CENTER)*4;e[f]=R[0],e[f+1]=R[1],e[f+2]=R[2],e[f+3]=R[3];let O=g(e,i,[t.LEFT_SHOULDER,t.RIGHT_SHOULDER,t.LEFT_HIP,t.RIGHT_HIP]),G=(i*u+t.TORSO_CENTER)*4;e[G]=O[0],e[G+1]=O[1],e[G+2]=O[2],e[G+3]=O[3]}let r=Math.ceil(T/H);E.updateTextures({u_poseLandmarksTex:{data:e,width:H,height:r}})}function Y(o){if(!o.landmarks||!e)return;let n=o.landmarks.length;J(o.landmarks),q(o.segmentationMasks).catch(T=>{console.warn("[Pose Plugin] Mask texture update error:",T)}),E.updateUniforms({u_nPoses:n})}E.registerHook("init",async()=>{E.initializeTexture("u_poseMask",m),E.initializeUniform("u_maxPoses","int",$),E.initializeUniform("u_nPoses","int",0);let o=$*u;b=Math.ceil(o/H);let n=H*b*4;e=new Float32Array(n),E.initializeTexture("u_poseLandmarksTex",{data:e,width:H,height:b},{internalFormat:k.RGBA32F,type:k.FLOAT,minFilter:k.NEAREST,magFilter:k.NEAREST}),await Z()}),E.registerHook("updateTextures",o=>{let n=o[_];if(!(!n||(v.get(_)!==n&&(U=-1),v.set(_,n),!I)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==U){U=n.currentTime;let r=performance.now(),i=I.detectForVideo(n,r);Y(i)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let r=I.detect(n);Y(r)}}catch(r){console.error("[Pose Plugin] Pose detection error:",r)}}),E.registerHook("destroy",()=>{I&&(I.close(),I=null),w=null,v.clear(),m.remove(),e=null}),z(`
1
+ "use strict";var Q=Object.create;var y=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var ne=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var ie=(a,_)=>{for(var s in _)y(a,s,{get:_[s],enumerable:!0})},W=(a,_,s,k)=>{if(_&&typeof _=="object"||typeof _=="function")for(let E of te(_))!oe.call(a,E)&&E!==s&&y(a,E,{get:()=>_[E],enumerable:!(k=ee(_,E))||k.enumerable});return a};var se=(a,_,s)=>(s=a!=null?Q(ne(a)):{},W(_||!a||!a.__esModule?y(s,"default",{value:a,enumerable:!0}):s,a)),re=a=>W(y({},"__esModule",{value:!0}),a);var Te={};ie(Te,{default:()=>_e});module.exports=re(Te);var L=33,ae=6,u=L+ae,t={LEFT_EYE:2,RIGHT_EYE:5,LEFT_SHOULDER:11,RIGHT_SHOULDER:12,LEFT_ELBOW:13,RIGHT_ELBOW:14,LEFT_HIP:23,RIGHT_HIP:24,LEFT_KNEE:25,RIGHT_KNEE:26,LEFT_WRIST:15,RIGHT_WRIST:16,LEFT_PINKY:17,RIGHT_PINKY:18,LEFT_INDEX:19,RIGHT_INDEX:20,LEFT_THUMB:21,RIGHT_THUMB:22,LEFT_ANKLE:27,RIGHT_ANKLE:28,LEFT_HEEL:29,RIGHT_HEEL:30,LEFT_FOOT_INDEX:31,RIGHT_FOOT_INDEX:32,BODY_CENTER:L,LEFT_HAND_CENTER:L+1,RIGHT_HAND_CENTER:L+2,LEFT_FOOT_CENTER:L+3,RIGHT_FOOT_CENTER:L+4,TORSO_CENTER:L+5};function Ee(a){let{textureName:_,options:s}=a,k="https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task";return function(E,X){let{injectGLSL:z,gl:S}=X,I=null,w=null,U=-1,v=new Map,$=s?.maxPoses??1,H=512,b=0,e=null,V=512,j=512,l=document.createElement("canvas");l.width=V,l.height=j;let M=l.getContext("2d"),p=document.createElement("canvas"),B=p.getContext("2d");M.globalCompositeOperation=B.globalCompositeOperation="lighten";async function Z(){try{let{FilesetResolver:o,PoseLandmarker:n}=await import("@mediapipe/tasks-vision");w=await o.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),I=await n.createFromOptions(w,{baseOptions:{modelAssetPath:s?.modelPath||k},runningMode:"VIDEO",numPoses:s?.maxPoses??1,minPoseDetectionConfidence:s?.minPoseDetectionConfidence??.5,minPosePresenceConfidence:s?.minPosePresenceConfidence??.5,minTrackingConfidence:s?.minTrackingConfidence??.5,outputSegmentationMasks:s?.outputSegmentationMasks??!0})}catch(o){throw console.error("[Pose Plugin] Failed to initialize Pose Landmarker:",o),o}}function P(o,n,T){let r=1/0,i=-1/0,N=1/0,m=-1/0,d=0,c=0;for(let D of T){let R=(n*u+D)*4,f=o[R],O=o[R+1];r=Math.min(r,f),i=Math.max(i,f),N=Math.min(N,O),m=Math.max(m,O),d+=o[R+2],c+=o[R+3]}let g=(r+i)/2,F=(N+m)/2,A=d/T.length,C=c/T.length;return[g,F,A,C]}async function q(o){if(!I||!e){console.warn("[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing");return}try{M.clearRect(0,0,l.width,l.height),o&&o.length>0&&o.forEach(n=>{if(!n)return;let{width:T,height:r}=n,i=n.getAsUint8Array(),N=T*r,m=new Uint8ClampedArray(N*4);for(let c=0;c<N;c++)m[c*4+1]=i[c],m[c*4+3]=255;let d=new ImageData(m,T,r);T===l.width&&r===l.height?M.putImageData(d,0,0):(p.width!==T&&(p.width=T),p.height!==r&&(p.height=r),B.putImageData(d,0,0),M.drawImage(p,0,0,l.width,l.height))}),E.updateTextures({u_poseMask:l})}catch(n){console.error("[Pose Plugin] Failed to generate mask texture:",n)}}function J(o){if(!e)return;let n=o.length,T=n*u;for(let i=0;i<n;++i){let N=o[i];for(let h=0;h<L;++h){let x=N[h],K=(i*u+h)*4;e[K]=x.x,e[K+1]=1-x.y,e[K+2]=x.z??0,e[K+3]=x.visibility??1}let m=P(e,i,Array.from({length:L},(h,x)=>x)),d=(i*u+t.BODY_CENTER)*4;e[d]=m[0],e[d+1]=m[1],e[d+2]=m[2],e[d+3]=m[3];let c=P(e,i,[t.LEFT_WRIST,t.LEFT_PINKY,t.LEFT_THUMB,t.LEFT_INDEX]),g=(i*u+t.LEFT_HAND_CENTER)*4;e[g]=c[0],e[g+1]=c[1],e[g+2]=c[2],e[g+3]=c[3];let F=P(e,i,[t.RIGHT_WRIST,t.RIGHT_PINKY,t.RIGHT_THUMB,t.RIGHT_INDEX]),A=(i*u+t.RIGHT_HAND_CENTER)*4;e[A]=F[0],e[A+1]=F[1],e[A+2]=F[2],e[A+3]=F[3];let C=P(e,i,[t.LEFT_ANKLE,t.LEFT_HEEL,t.LEFT_FOOT_INDEX]),D=(i*u+t.LEFT_FOOT_CENTER)*4;e[D]=C[0],e[D+1]=C[1],e[D+2]=C[2],e[D+3]=C[3];let R=P(e,i,[t.RIGHT_ANKLE,t.RIGHT_HEEL,t.RIGHT_FOOT_INDEX]),f=(i*u+t.RIGHT_FOOT_CENTER)*4;e[f]=R[0],e[f+1]=R[1],e[f+2]=R[2],e[f+3]=R[3];let O=P(e,i,[t.LEFT_SHOULDER,t.RIGHT_SHOULDER,t.LEFT_HIP,t.RIGHT_HIP]),G=(i*u+t.TORSO_CENTER)*4;e[G]=O[0],e[G+1]=O[1],e[G+2]=O[2],e[G+3]=O[3]}let r=Math.ceil(T/H);E.updateTextures({u_poseLandmarksTex:{data:e,width:H,height:r}})}function Y(o){if(!o.landmarks||!e)return;let n=o.landmarks.length;J(o.landmarks),q(o.segmentationMasks).catch(T=>{console.warn("[Pose Plugin] Mask texture update error:",T)}),E.updateUniforms({u_nPoses:n}),s?.onResults?.(o)}E.registerHook("init",async()=>{E.initializeTexture("u_poseMask",l),E.initializeUniform("u_maxPoses","int",$),E.initializeUniform("u_nPoses","int",0);let o=$*u;b=Math.ceil(o/H);let n=H*b*4;e=new Float32Array(n),E.initializeTexture("u_poseLandmarksTex",{data:e,width:H,height:b},{internalFormat:S.RGBA32F,type:S.FLOAT,minFilter:S.NEAREST,magFilter:S.NEAREST}),await Z()}),E.registerHook("updateTextures",o=>{let n=o[_];if(!(!n||(v.get(_)!==n&&(U=-1),v.set(_,n),!I)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==U){U=n.currentTime;let r=performance.now(),i=I.detectForVideo(n,r);Y(i)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let r=I.detect(n);Y(r)}}catch(r){console.error("[Pose Plugin] Pose detection error:",r)}}),E.registerHook("destroy",()=>{I&&(I.close(),I=null),w=null,v.clear(),l.remove(),e=null}),z(`
2
2
  uniform int u_maxPoses;
3
3
  uniform int u_nPoses;
4
4
  uniform sampler2D u_poseLandmarksTex;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/pose.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { PoseLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface PosePluginOptions {\n\tmodelPath?: string;\n\tmaxPoses?: number;\n\tminPoseDetectionConfidence?: number;\n\tminPosePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputSegmentationMasks?: boolean;\n}\n\nconst STANDARD_LANDMARK_COUNT = 33; // See https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model.\nconst CUSTOM_LANDMARK_COUNT = 6;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\t// Standard landmarks.\n\tLEFT_EYE: 2,\n\tRIGHT_EYE: 5,\n\tLEFT_SHOULDER: 11,\n\tRIGHT_SHOULDER: 12,\n\tLEFT_ELBOW: 13,\n\tRIGHT_ELBOW: 14,\n\tLEFT_HIP: 23,\n\tRIGHT_HIP: 24,\n\tLEFT_KNEE: 25,\n\tRIGHT_KNEE: 26,\n\tLEFT_WRIST: 15,\n\tRIGHT_WRIST: 16,\n\tLEFT_PINKY: 17,\n\tRIGHT_PINKY: 18,\n\tLEFT_INDEX: 19,\n\tRIGHT_INDEX: 20,\n\tLEFT_THUMB: 21,\n\tRIGHT_THUMB: 22,\n\tLEFT_ANKLE: 27,\n\tRIGHT_ANKLE: 28,\n\tLEFT_HEEL: 29,\n\tRIGHT_HEEL: 30,\n\tLEFT_FOOT_INDEX: 31,\n\tRIGHT_FOOT_INDEX: 32,\n\t// Custom landmarks.\n\tBODY_CENTER: STANDARD_LANDMARK_COUNT,\n\tLEFT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 1,\n\tRIGHT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 2,\n\tLEFT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 3,\n\tRIGHT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 4,\n\tTORSO_CENTER: STANDARD_LANDMARK_COUNT + 5,\n};\n\nfunction pose(config: { textureName: string; options?: PosePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet poseLandmarker: PoseLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxPoses = options?.maxPoses ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst poseMaskCanvas = document.createElement('canvas');\n\t\tposeMaskCanvas.width = maskWidth;\n\t\tposeMaskCanvas.height = maskHeight;\n\t\tconst poseMaskCtx = poseMaskCanvas.getContext('2d')!;\n\t\tconst segmentationCanvas = document.createElement('canvas');\n\t\tconst segmentationCtx = segmentationCanvas.getContext('2d')!;\n\t\tposeMaskCtx.globalCompositeOperation = segmentationCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializePoseLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, PoseLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\t\t\t\tposeLandmarker = await PoseLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumPoses: options?.maxPoses ?? 1,\n\t\t\t\t\tminPoseDetectionConfidence: options?.minPoseDetectionConfidence ?? 0.5,\n\t\t\t\t\tminPosePresenceConfidence: options?.minPosePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputSegmentationMasks: options?.outputSegmentationMasks ?? true,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to initialize Pose Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tposeIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tasync function updateMaskTexture(segmentationMasks?: any[]) {\n\t\t\tif (!poseLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tposeMaskCtx.clearRect(0, 0, poseMaskCanvas.width, poseMaskCanvas.height);\n\n\t\t\t\t// Draw the segmentation masks.\n\t\t\t\tif (segmentationMasks && segmentationMasks.length > 0) {\n\t\t\t\t\tsegmentationMasks.forEach(mask => {\n\t\t\t\t\t\tif (!mask) return;\n\n\t\t\t\t\t\tconst { width, height } = mask;\n\t\t\t\t\t\tconst maskData = mask.getAsUint8Array();\n\t\t\t\t\t\tconst pixelCount = width * height;\n\t\t\t\t\t\tconst outputData = new Uint8ClampedArray(pixelCount * 4);\n\n\t\t\t\t\t\tfor (let i = 0; i < pixelCount; i++) {\n\t\t\t\t\t\t\toutputData[i * 4 + 1] = maskData[i]; // G (body mask)\n\t\t\t\t\t\t\toutputData[i * 4 + 3] = 255; // A (required for compositing)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst rgbaMask = new ImageData(outputData, width, height);\n\n\t\t\t\t\t\t// Resize mask to canvas size if needed.\n\t\t\t\t\t\tif (width === poseMaskCanvas.width && height === poseMaskCanvas.height) {\n\t\t\t\t\t\t\tposeMaskCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (segmentationCanvas.width !== width) segmentationCanvas.width = width;\n\t\t\t\t\t\t\tif (segmentationCanvas.height !== height) segmentationCanvas.height = height;\n\t\t\t\t\t\t\tsegmentationCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t\tposeMaskCtx.drawImage(\n\t\t\t\t\t\t\t\tsegmentationCanvas,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tposeMaskCanvas.width,\n\t\t\t\t\t\t\t\tposeMaskCanvas.height\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_poseMask: poseMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(poses: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nPoses = poses.length;\n\t\t\tconst totalLandmarks = nPoses * LANDMARK_COUNT;\n\n\t\t\tfor (let poseIdx = 0; poseIdx < nPoses; ++poseIdx) {\n\t\t\t\tconst landmarks = poses[poseIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Body center (landmark 33) - calculated from all standard landmarks\n\t\t\t\tconst bodyCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tposeIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst bodyCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.BODY_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[bodyCenterIdx] = bodyCenter[0];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 1] = bodyCenter[1];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 2] = bodyCenter[2];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 3] = bodyCenter[3];\n\n\t\t\t\t// Left hand center (landmark 34) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst leftHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx] = leftHandCenter[0];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 1] = leftHandCenter[1];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 2] = leftHandCenter[2];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 3] = leftHandCenter[3];\n\n\t\t\t\t// Right hand center (landmark 35) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst rightHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx] = rightHandCenter[0];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 1] = rightHandCenter[1];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 2] = rightHandCenter[2];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 3] = rightHandCenter[3];\n\n\t\t\t\t// Left foot center (landmark 36) - center of ankle, heel, foot index\n\t\t\t\tconst leftFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx] = leftFootCenter[0];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 1] = leftFootCenter[1];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 2] = leftFootCenter[2];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 3] = leftFootCenter[3];\n\n\t\t\t\t// Right foot center (landmark 37) - center of ankle, heel, foot index\n\t\t\t\tconst rightFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx] = rightFootCenter[0];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 1] = rightFootCenter[1];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 2] = rightFootCenter[2];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 3] = rightFootCenter[3];\n\n\t\t\t\t// Torso center (landmark 38) - center of shoulders and hips\n\t\t\t\tconst torsoCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HIP,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HIP,\n\t\t\t\t]);\n\t\t\t\tconst torsoCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.TORSO_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[torsoCenterIdx] = torsoCenter[0];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 1] = torsoCenter[1];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 2] = torsoCenter[2];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 3] = torsoCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_poseLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processPoseResults(result: any) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nPoses = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tupdateMaskTexture(result.segmentationMasks).catch(error => {\n\t\t\t\tconsole.warn('[Pose Plugin] Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tshaderPad.updateUniforms({ u_nPoses: nPoses });\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_poseMask', poseMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxPoses', 'int', maxPoses);\n\t\t\tshaderPad.initializeUniform('u_nPoses', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxPoses * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_poseLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializePoseLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!poseLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = poseLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = poseLandmarker.detect(source);\n\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Pose detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (poseLandmarker) {\n\t\t\t\tposeLandmarker.close();\n\t\t\t\tposeLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tposeMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\t\t// TODO: inBody shouldn't rely on alpha. Does it? Seems so in the example.\n\t\t// Might be because putImageData ignores alpha compositing.\n\t\tinjectGLSL(`\nuniform int u_maxPoses;\nuniform int u_nPoses;\nuniform sampler2D u_poseLandmarksTex;\nuniform sampler2D u_poseMask;\n\n#define POSE_LANDMARK_LEFT_EYE ${LANDMARK_INDICES.LEFT_EYE}\n#define POSE_LANDMARK_RIGHT_EYE ${LANDMARK_INDICES.RIGHT_EYE}\n#define POSE_LANDMARK_LEFT_SHOULDER ${LANDMARK_INDICES.LEFT_SHOULDER}\n#define POSE_LANDMARK_RIGHT_SHOULDER ${LANDMARK_INDICES.RIGHT_SHOULDER}\n#define POSE_LANDMARK_LEFT_ELBOW ${LANDMARK_INDICES.LEFT_ELBOW}\n#define POSE_LANDMARK_RIGHT_ELBOW ${LANDMARK_INDICES.RIGHT_ELBOW}\n#define POSE_LANDMARK_LEFT_HIP ${LANDMARK_INDICES.LEFT_HIP}\n#define POSE_LANDMARK_RIGHT_HIP ${LANDMARK_INDICES.RIGHT_HIP}\n#define POSE_LANDMARK_LEFT_KNEE ${LANDMARK_INDICES.LEFT_KNEE}\n#define POSE_LANDMARK_RIGHT_KNEE ${LANDMARK_INDICES.RIGHT_KNEE}\n#define POSE_LANDMARK_BODY_CENTER ${LANDMARK_INDICES.BODY_CENTER}\n#define POSE_LANDMARK_LEFT_HAND_CENTER ${LANDMARK_INDICES.LEFT_HAND_CENTER}\n#define POSE_LANDMARK_RIGHT_HAND_CENTER ${LANDMARK_INDICES.RIGHT_HAND_CENTER}\n#define POSE_LANDMARK_LEFT_FOOT_CENTER ${LANDMARK_INDICES.LEFT_FOOT_CENTER}\n#define POSE_LANDMARK_RIGHT_FOOT_CENTER ${LANDMARK_INDICES.RIGHT_FOOT_CENTER}\n#define POSE_LANDMARK_TORSO_CENTER ${LANDMARK_INDICES.TORSO_CENTER}\n\nvec4 poseLandmark(int poseIndex, int landmarkIndex) {\n\tint i = poseIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_poseLandmarksTex, ivec2(x, y), 0);\n}\nfloat inBody(vec2 pos) { return texture(u_poseMask, pos).g; }`);\n\t};\n}\n\nexport default pose;\n"],"mappings":"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","names":["pose_exports","__export","pose_default","__toCommonJS","STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","pose","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","poseLandmarker","vision","lastVideoTime","textureSources","maxPoses","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","poseMaskCanvas","poseMaskCtx","segmentationCanvas","segmentationCtx","initializePoseLandmarker","FilesetResolver","PoseLandmarker","error","calculateBoundingBoxCenter","poseIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateMaskTexture","segmentationMasks","mask","width","height","maskData","pixelCount","outputData","i","rgbaMask","updateLandmarksTexture","poses","nPoses","totalLandmarks","landmarks","lmIdx","landmark","bodyCenter","_","bodyCenterIdx","leftHandCenter","leftHandCenterIdx","rightHandCenter","rightHandCenterIdx","leftFootCenter","leftFootCenterIdx","rightFootCenter","rightFootCenterIdx","torsoCenter","torsoCenterIdx","rowsToUpdate","processPoseResults","result","textureSize","updates","source","timestamp"]}
1
+ {"version":3,"sources":["../../src/plugins/pose.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { PoseLandmarker, PoseLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface PosePluginOptions {\n\tmodelPath?: string;\n\tmaxPoses?: number;\n\tminPoseDetectionConfidence?: number;\n\tminPosePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputSegmentationMasks?: boolean;\n\tonResults?: (results: PoseLandmarkerResult) => void;\n}\n\nconst STANDARD_LANDMARK_COUNT = 33; // See https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model.\nconst CUSTOM_LANDMARK_COUNT = 6;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\t// Standard landmarks.\n\tLEFT_EYE: 2,\n\tRIGHT_EYE: 5,\n\tLEFT_SHOULDER: 11,\n\tRIGHT_SHOULDER: 12,\n\tLEFT_ELBOW: 13,\n\tRIGHT_ELBOW: 14,\n\tLEFT_HIP: 23,\n\tRIGHT_HIP: 24,\n\tLEFT_KNEE: 25,\n\tRIGHT_KNEE: 26,\n\tLEFT_WRIST: 15,\n\tRIGHT_WRIST: 16,\n\tLEFT_PINKY: 17,\n\tRIGHT_PINKY: 18,\n\tLEFT_INDEX: 19,\n\tRIGHT_INDEX: 20,\n\tLEFT_THUMB: 21,\n\tRIGHT_THUMB: 22,\n\tLEFT_ANKLE: 27,\n\tRIGHT_ANKLE: 28,\n\tLEFT_HEEL: 29,\n\tRIGHT_HEEL: 30,\n\tLEFT_FOOT_INDEX: 31,\n\tRIGHT_FOOT_INDEX: 32,\n\t// Custom landmarks.\n\tBODY_CENTER: STANDARD_LANDMARK_COUNT,\n\tLEFT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 1,\n\tRIGHT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 2,\n\tLEFT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 3,\n\tRIGHT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 4,\n\tTORSO_CENTER: STANDARD_LANDMARK_COUNT + 5,\n};\n\nfunction pose(config: { textureName: string; options?: PosePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet poseLandmarker: PoseLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxPoses = options?.maxPoses ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst poseMaskCanvas = document.createElement('canvas');\n\t\tposeMaskCanvas.width = maskWidth;\n\t\tposeMaskCanvas.height = maskHeight;\n\t\tconst poseMaskCtx = poseMaskCanvas.getContext('2d')!;\n\t\tconst segmentationCanvas = document.createElement('canvas');\n\t\tconst segmentationCtx = segmentationCanvas.getContext('2d')!;\n\t\tposeMaskCtx.globalCompositeOperation = segmentationCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializePoseLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, PoseLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\t\t\t\tposeLandmarker = await PoseLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumPoses: options?.maxPoses ?? 1,\n\t\t\t\t\tminPoseDetectionConfidence: options?.minPoseDetectionConfidence ?? 0.5,\n\t\t\t\t\tminPosePresenceConfidence: options?.minPosePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputSegmentationMasks: options?.outputSegmentationMasks ?? true,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to initialize Pose Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tposeIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tasync function updateMaskTexture(segmentationMasks?: any[]) {\n\t\t\tif (!poseLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tposeMaskCtx.clearRect(0, 0, poseMaskCanvas.width, poseMaskCanvas.height);\n\n\t\t\t\t// Draw the segmentation masks.\n\t\t\t\tif (segmentationMasks && segmentationMasks.length > 0) {\n\t\t\t\t\tsegmentationMasks.forEach(mask => {\n\t\t\t\t\t\tif (!mask) return;\n\n\t\t\t\t\t\tconst { width, height } = mask;\n\t\t\t\t\t\tconst maskData = mask.getAsUint8Array();\n\t\t\t\t\t\tconst pixelCount = width * height;\n\t\t\t\t\t\tconst outputData = new Uint8ClampedArray(pixelCount * 4);\n\n\t\t\t\t\t\tfor (let i = 0; i < pixelCount; i++) {\n\t\t\t\t\t\t\toutputData[i * 4 + 1] = maskData[i]; // G (body mask)\n\t\t\t\t\t\t\toutputData[i * 4 + 3] = 255; // A (required for compositing)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst rgbaMask = new ImageData(outputData, width, height);\n\n\t\t\t\t\t\t// Resize mask to canvas size if needed.\n\t\t\t\t\t\tif (width === poseMaskCanvas.width && height === poseMaskCanvas.height) {\n\t\t\t\t\t\t\tposeMaskCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (segmentationCanvas.width !== width) segmentationCanvas.width = width;\n\t\t\t\t\t\t\tif (segmentationCanvas.height !== height) segmentationCanvas.height = height;\n\t\t\t\t\t\t\tsegmentationCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t\tposeMaskCtx.drawImage(\n\t\t\t\t\t\t\t\tsegmentationCanvas,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tposeMaskCanvas.width,\n\t\t\t\t\t\t\t\tposeMaskCanvas.height\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_poseMask: poseMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(poses: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nPoses = poses.length;\n\t\t\tconst totalLandmarks = nPoses * LANDMARK_COUNT;\n\n\t\t\tfor (let poseIdx = 0; poseIdx < nPoses; ++poseIdx) {\n\t\t\t\tconst landmarks = poses[poseIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Body center (landmark 33) - calculated from all standard landmarks\n\t\t\t\tconst bodyCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tposeIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst bodyCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.BODY_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[bodyCenterIdx] = bodyCenter[0];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 1] = bodyCenter[1];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 2] = bodyCenter[2];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 3] = bodyCenter[3];\n\n\t\t\t\t// Left hand center (landmark 34) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst leftHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx] = leftHandCenter[0];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 1] = leftHandCenter[1];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 2] = leftHandCenter[2];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 3] = leftHandCenter[3];\n\n\t\t\t\t// Right hand center (landmark 35) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst rightHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx] = rightHandCenter[0];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 1] = rightHandCenter[1];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 2] = rightHandCenter[2];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 3] = rightHandCenter[3];\n\n\t\t\t\t// Left foot center (landmark 36) - center of ankle, heel, foot index\n\t\t\t\tconst leftFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx] = leftFootCenter[0];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 1] = leftFootCenter[1];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 2] = leftFootCenter[2];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 3] = leftFootCenter[3];\n\n\t\t\t\t// Right foot center (landmark 37) - center of ankle, heel, foot index\n\t\t\t\tconst rightFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx] = rightFootCenter[0];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 1] = rightFootCenter[1];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 2] = rightFootCenter[2];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 3] = rightFootCenter[3];\n\n\t\t\t\t// Torso center (landmark 38) - center of shoulders and hips\n\t\t\t\tconst torsoCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HIP,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HIP,\n\t\t\t\t]);\n\t\t\t\tconst torsoCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.TORSO_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[torsoCenterIdx] = torsoCenter[0];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 1] = torsoCenter[1];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 2] = torsoCenter[2];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 3] = torsoCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_poseLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processPoseResults(result: PoseLandmarkerResult) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nPoses = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tupdateMaskTexture(result.segmentationMasks).catch(error => {\n\t\t\t\tconsole.warn('[Pose Plugin] Mask texture update error:', error);\n\t\t\t});\n\t\t\tshaderPad.updateUniforms({ u_nPoses: nPoses });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_poseMask', poseMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxPoses', 'int', maxPoses);\n\t\t\tshaderPad.initializeUniform('u_nPoses', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxPoses * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_poseLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializePoseLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!poseLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = poseLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = poseLandmarker.detect(source);\n\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Pose detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (poseLandmarker) {\n\t\t\t\tposeLandmarker.close();\n\t\t\t\tposeLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tposeMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\t\t// TODO: inBody shouldn't rely on alpha. Does it? Seems so in the example.\n\t\t// Might be because putImageData ignores alpha compositing.\n\t\tinjectGLSL(`\nuniform int u_maxPoses;\nuniform int u_nPoses;\nuniform sampler2D u_poseLandmarksTex;\nuniform sampler2D u_poseMask;\n\n#define POSE_LANDMARK_LEFT_EYE ${LANDMARK_INDICES.LEFT_EYE}\n#define POSE_LANDMARK_RIGHT_EYE ${LANDMARK_INDICES.RIGHT_EYE}\n#define POSE_LANDMARK_LEFT_SHOULDER ${LANDMARK_INDICES.LEFT_SHOULDER}\n#define POSE_LANDMARK_RIGHT_SHOULDER ${LANDMARK_INDICES.RIGHT_SHOULDER}\n#define POSE_LANDMARK_LEFT_ELBOW ${LANDMARK_INDICES.LEFT_ELBOW}\n#define POSE_LANDMARK_RIGHT_ELBOW ${LANDMARK_INDICES.RIGHT_ELBOW}\n#define POSE_LANDMARK_LEFT_HIP ${LANDMARK_INDICES.LEFT_HIP}\n#define POSE_LANDMARK_RIGHT_HIP ${LANDMARK_INDICES.RIGHT_HIP}\n#define POSE_LANDMARK_LEFT_KNEE ${LANDMARK_INDICES.LEFT_KNEE}\n#define POSE_LANDMARK_RIGHT_KNEE ${LANDMARK_INDICES.RIGHT_KNEE}\n#define POSE_LANDMARK_BODY_CENTER ${LANDMARK_INDICES.BODY_CENTER}\n#define POSE_LANDMARK_LEFT_HAND_CENTER ${LANDMARK_INDICES.LEFT_HAND_CENTER}\n#define POSE_LANDMARK_RIGHT_HAND_CENTER ${LANDMARK_INDICES.RIGHT_HAND_CENTER}\n#define POSE_LANDMARK_LEFT_FOOT_CENTER ${LANDMARK_INDICES.LEFT_FOOT_CENTER}\n#define POSE_LANDMARK_RIGHT_FOOT_CENTER ${LANDMARK_INDICES.RIGHT_FOOT_CENTER}\n#define POSE_LANDMARK_TORSO_CENTER ${LANDMARK_INDICES.TORSO_CENTER}\n\nvec4 poseLandmark(int poseIndex, int landmarkIndex) {\n\tint i = poseIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_poseLandmarksTex, ivec2(x, y), 0);\n}\nfloat inBody(vec2 pos) { return texture(u_poseMask, pos).g; }`);\n\t};\n}\n\nexport default pose;\n"],"mappings":"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","names":["pose_exports","__export","pose_default","__toCommonJS","STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","pose","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","poseLandmarker","vision","lastVideoTime","textureSources","maxPoses","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","poseMaskCanvas","poseMaskCtx","segmentationCanvas","segmentationCtx","initializePoseLandmarker","FilesetResolver","PoseLandmarker","error","calculateBoundingBoxCenter","poseIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateMaskTexture","segmentationMasks","mask","width","height","maskData","pixelCount","outputData","i","rgbaMask","updateLandmarksTexture","poses","nPoses","totalLandmarks","landmarks","lmIdx","landmark","bodyCenter","_","bodyCenterIdx","leftHandCenter","leftHandCenterIdx","rightHandCenter","rightHandCenterIdx","leftFootCenter","leftFootCenterIdx","rightFootCenter","rightFootCenterIdx","torsoCenter","torsoCenterIdx","rowsToUpdate","processPoseResults","result","textureSize","updates","source","timestamp"]}
@@ -1,4 +1,4 @@
1
- var u=33,q=6,T=u+q,t={LEFT_EYE:2,RIGHT_EYE:5,LEFT_SHOULDER:11,RIGHT_SHOULDER:12,LEFT_ELBOW:13,RIGHT_ELBOW:14,LEFT_HIP:23,RIGHT_HIP:24,LEFT_KNEE:25,RIGHT_KNEE:26,LEFT_WRIST:15,RIGHT_WRIST:16,LEFT_PINKY:17,RIGHT_PINKY:18,LEFT_INDEX:19,RIGHT_INDEX:20,LEFT_THUMB:21,RIGHT_THUMB:22,LEFT_ANKLE:27,RIGHT_ANKLE:28,LEFT_HEEL:29,RIGHT_HEEL:30,LEFT_FOOT_INDEX:31,RIGHT_FOOT_INDEX:32,BODY_CENTER:u,LEFT_HAND_CENTER:u+1,RIGHT_HAND_CENTER:u+2,LEFT_FOOT_CENTER:u+3,RIGHT_FOOT_CENTER:u+4,TORSO_CENTER:u+5};function J($){let{textureName:M,options:L}=$,B="https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task";return function(c,Y){let{injectGLSL:W,gl:x}=Y,d=null,G=null,K=-1,y=new Map,U=L?.maxPoses??1,I=512,w=0,e=null,X=512,z=512,E=document.createElement("canvas");E.width=X,E.height=z;let h=E.getContext("2d"),f=document.createElement("canvas"),v=f.getContext("2d");h.globalCompositeOperation=v.globalCompositeOperation="lighten";async function V(){try{let{FilesetResolver:o,PoseLandmarker:n}=await import("@mediapipe/tasks-vision");G=await o.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),d=await n.createFromOptions(G,{baseOptions:{modelAssetPath:L?.modelPath||B},runningMode:"VIDEO",numPoses:L?.maxPoses??1,minPoseDetectionConfidence:L?.minPoseDetectionConfidence??.5,minPosePresenceConfidence:L?.minPosePresenceConfidence??.5,minTrackingConfidence:L?.minTrackingConfidence??.5,outputSegmentationMasks:L?.outputSegmentationMasks??!0})}catch(o){throw console.error("[Pose Plugin] Failed to initialize Pose Landmarker:",o),o}}function O(o,n,s){let r=1/0,i=-1/0,R=1/0,_=-1/0,m=0,a=0;for(let A of s){let l=(n*T+A)*4,N=o[l],H=o[l+1];r=Math.min(r,N),i=Math.max(i,N),R=Math.min(R,H),_=Math.max(_,H),m+=o[l+2],a+=o[l+3]}let p=(r+i)/2,g=(R+_)/2,F=m/s.length,P=a/s.length;return[p,g,F,P]}async function j(o){if(!d||!e){console.warn("[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing");return}try{h.clearRect(0,0,E.width,E.height),o&&o.length>0&&o.forEach(n=>{if(!n)return;let{width:s,height:r}=n,i=n.getAsUint8Array(),R=s*r,_=new Uint8ClampedArray(R*4);for(let a=0;a<R;a++)_[a*4+1]=i[a],_[a*4+3]=255;let m=new ImageData(_,s,r);s===E.width&&r===E.height?h.putImageData(m,0,0):(f.width!==s&&(f.width=s),f.height!==r&&(f.height=r),v.putImageData(m,0,0),h.drawImage(f,0,0,E.width,E.height))}),c.updateTextures({u_poseMask:E})}catch(n){console.error("[Pose Plugin] Failed to generate mask texture:",n)}}function Z(o){if(!e)return;let n=o.length,s=n*T;for(let i=0;i<n;++i){let R=o[i];for(let D=0;D<u;++D){let C=R[D],k=(i*T+D)*4;e[k]=C.x,e[k+1]=1-C.y,e[k+2]=C.z??0,e[k+3]=C.visibility??1}let _=O(e,i,Array.from({length:u},(D,C)=>C)),m=(i*T+t.BODY_CENTER)*4;e[m]=_[0],e[m+1]=_[1],e[m+2]=_[2],e[m+3]=_[3];let a=O(e,i,[t.LEFT_WRIST,t.LEFT_PINKY,t.LEFT_THUMB,t.LEFT_INDEX]),p=(i*T+t.LEFT_HAND_CENTER)*4;e[p]=a[0],e[p+1]=a[1],e[p+2]=a[2],e[p+3]=a[3];let g=O(e,i,[t.RIGHT_WRIST,t.RIGHT_PINKY,t.RIGHT_THUMB,t.RIGHT_INDEX]),F=(i*T+t.RIGHT_HAND_CENTER)*4;e[F]=g[0],e[F+1]=g[1],e[F+2]=g[2],e[F+3]=g[3];let P=O(e,i,[t.LEFT_ANKLE,t.LEFT_HEEL,t.LEFT_FOOT_INDEX]),A=(i*T+t.LEFT_FOOT_CENTER)*4;e[A]=P[0],e[A+1]=P[1],e[A+2]=P[2],e[A+3]=P[3];let l=O(e,i,[t.RIGHT_ANKLE,t.RIGHT_HEEL,t.RIGHT_FOOT_INDEX]),N=(i*T+t.RIGHT_FOOT_CENTER)*4;e[N]=l[0],e[N+1]=l[1],e[N+2]=l[2],e[N+3]=l[3];let H=O(e,i,[t.LEFT_SHOULDER,t.RIGHT_SHOULDER,t.LEFT_HIP,t.RIGHT_HIP]),S=(i*T+t.TORSO_CENTER)*4;e[S]=H[0],e[S+1]=H[1],e[S+2]=H[2],e[S+3]=H[3]}let r=Math.ceil(s/I);c.updateTextures({u_poseLandmarksTex:{data:e,width:I,height:r}})}function b(o){if(!o.landmarks||!e)return;let n=o.landmarks.length;Z(o.landmarks),j(o.segmentationMasks).catch(s=>{console.warn("[Pose Plugin] Mask texture update error:",s)}),c.updateUniforms({u_nPoses:n})}c.registerHook("init",async()=>{c.initializeTexture("u_poseMask",E),c.initializeUniform("u_maxPoses","int",U),c.initializeUniform("u_nPoses","int",0);let o=U*T;w=Math.ceil(o/I);let n=I*w*4;e=new Float32Array(n),c.initializeTexture("u_poseLandmarksTex",{data:e,width:I,height:w},{internalFormat:x.RGBA32F,type:x.FLOAT,minFilter:x.NEAREST,magFilter:x.NEAREST}),await V()}),c.registerHook("updateTextures",o=>{let n=o[M];if(!(!n||(y.get(M)!==n&&(K=-1),y.set(M,n),!d)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==K){K=n.currentTime;let r=performance.now(),i=d.detectForVideo(n,r);b(i)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let r=d.detect(n);b(r)}}catch(r){console.error("[Pose Plugin] Pose detection error:",r)}}),c.registerHook("destroy",()=>{d&&(d.close(),d=null),G=null,y.clear(),E.remove(),e=null}),W(`
1
+ var u=33,q=6,T=u+q,t={LEFT_EYE:2,RIGHT_EYE:5,LEFT_SHOULDER:11,RIGHT_SHOULDER:12,LEFT_ELBOW:13,RIGHT_ELBOW:14,LEFT_HIP:23,RIGHT_HIP:24,LEFT_KNEE:25,RIGHT_KNEE:26,LEFT_WRIST:15,RIGHT_WRIST:16,LEFT_PINKY:17,RIGHT_PINKY:18,LEFT_INDEX:19,RIGHT_INDEX:20,LEFT_THUMB:21,RIGHT_THUMB:22,LEFT_ANKLE:27,RIGHT_ANKLE:28,LEFT_HEEL:29,RIGHT_HEEL:30,LEFT_FOOT_INDEX:31,RIGHT_FOOT_INDEX:32,BODY_CENTER:u,LEFT_HAND_CENTER:u+1,RIGHT_HAND_CENTER:u+2,LEFT_FOOT_CENTER:u+3,RIGHT_FOOT_CENTER:u+4,TORSO_CENTER:u+5};function J($){let{textureName:M,options:d}=$,B="https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task";return function(c,Y){let{injectGLSL:W,gl:x}=Y,R=null,G=null,K=-1,y=new Map,U=d?.maxPoses??1,I=512,w=0,e=null,X=512,z=512,E=document.createElement("canvas");E.width=X,E.height=z;let h=E.getContext("2d"),f=document.createElement("canvas"),v=f.getContext("2d");h.globalCompositeOperation=v.globalCompositeOperation="lighten";async function V(){try{let{FilesetResolver:o,PoseLandmarker:n}=await import("@mediapipe/tasks-vision");G=await o.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),R=await n.createFromOptions(G,{baseOptions:{modelAssetPath:d?.modelPath||B},runningMode:"VIDEO",numPoses:d?.maxPoses??1,minPoseDetectionConfidence:d?.minPoseDetectionConfidence??.5,minPosePresenceConfidence:d?.minPosePresenceConfidence??.5,minTrackingConfidence:d?.minTrackingConfidence??.5,outputSegmentationMasks:d?.outputSegmentationMasks??!0})}catch(o){throw console.error("[Pose Plugin] Failed to initialize Pose Landmarker:",o),o}}function O(o,n,r){let s=1/0,i=-1/0,L=1/0,_=-1/0,l=0,a=0;for(let A of r){let m=(n*T+A)*4,N=o[m],H=o[m+1];s=Math.min(s,N),i=Math.max(i,N),L=Math.min(L,H),_=Math.max(_,H),l+=o[m+2],a+=o[m+3]}let p=(s+i)/2,P=(L+_)/2,g=l/r.length,F=a/r.length;return[p,P,g,F]}async function j(o){if(!R||!e){console.warn("[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing");return}try{h.clearRect(0,0,E.width,E.height),o&&o.length>0&&o.forEach(n=>{if(!n)return;let{width:r,height:s}=n,i=n.getAsUint8Array(),L=r*s,_=new Uint8ClampedArray(L*4);for(let a=0;a<L;a++)_[a*4+1]=i[a],_[a*4+3]=255;let l=new ImageData(_,r,s);r===E.width&&s===E.height?h.putImageData(l,0,0):(f.width!==r&&(f.width=r),f.height!==s&&(f.height=s),v.putImageData(l,0,0),h.drawImage(f,0,0,E.width,E.height))}),c.updateTextures({u_poseMask:E})}catch(n){console.error("[Pose Plugin] Failed to generate mask texture:",n)}}function Z(o){if(!e)return;let n=o.length,r=n*T;for(let i=0;i<n;++i){let L=o[i];for(let D=0;D<u;++D){let C=L[D],S=(i*T+D)*4;e[S]=C.x,e[S+1]=1-C.y,e[S+2]=C.z??0,e[S+3]=C.visibility??1}let _=O(e,i,Array.from({length:u},(D,C)=>C)),l=(i*T+t.BODY_CENTER)*4;e[l]=_[0],e[l+1]=_[1],e[l+2]=_[2],e[l+3]=_[3];let a=O(e,i,[t.LEFT_WRIST,t.LEFT_PINKY,t.LEFT_THUMB,t.LEFT_INDEX]),p=(i*T+t.LEFT_HAND_CENTER)*4;e[p]=a[0],e[p+1]=a[1],e[p+2]=a[2],e[p+3]=a[3];let P=O(e,i,[t.RIGHT_WRIST,t.RIGHT_PINKY,t.RIGHT_THUMB,t.RIGHT_INDEX]),g=(i*T+t.RIGHT_HAND_CENTER)*4;e[g]=P[0],e[g+1]=P[1],e[g+2]=P[2],e[g+3]=P[3];let F=O(e,i,[t.LEFT_ANKLE,t.LEFT_HEEL,t.LEFT_FOOT_INDEX]),A=(i*T+t.LEFT_FOOT_CENTER)*4;e[A]=F[0],e[A+1]=F[1],e[A+2]=F[2],e[A+3]=F[3];let m=O(e,i,[t.RIGHT_ANKLE,t.RIGHT_HEEL,t.RIGHT_FOOT_INDEX]),N=(i*T+t.RIGHT_FOOT_CENTER)*4;e[N]=m[0],e[N+1]=m[1],e[N+2]=m[2],e[N+3]=m[3];let H=O(e,i,[t.LEFT_SHOULDER,t.RIGHT_SHOULDER,t.LEFT_HIP,t.RIGHT_HIP]),k=(i*T+t.TORSO_CENTER)*4;e[k]=H[0],e[k+1]=H[1],e[k+2]=H[2],e[k+3]=H[3]}let s=Math.ceil(r/I);c.updateTextures({u_poseLandmarksTex:{data:e,width:I,height:s}})}function b(o){if(!o.landmarks||!e)return;let n=o.landmarks.length;Z(o.landmarks),j(o.segmentationMasks).catch(r=>{console.warn("[Pose Plugin] Mask texture update error:",r)}),c.updateUniforms({u_nPoses:n}),d?.onResults?.(o)}c.registerHook("init",async()=>{c.initializeTexture("u_poseMask",E),c.initializeUniform("u_maxPoses","int",U),c.initializeUniform("u_nPoses","int",0);let o=U*T;w=Math.ceil(o/I);let n=I*w*4;e=new Float32Array(n),c.initializeTexture("u_poseLandmarksTex",{data:e,width:I,height:w},{internalFormat:x.RGBA32F,type:x.FLOAT,minFilter:x.NEAREST,magFilter:x.NEAREST}),await V()}),c.registerHook("updateTextures",o=>{let n=o[M];if(!(!n||(y.get(M)!==n&&(K=-1),y.set(M,n),!R)))try{if(n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==K){K=n.currentTime;let s=performance.now(),i=R.detectForVideo(n,s);b(i)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let s=R.detect(n);b(s)}}catch(s){console.error("[Pose Plugin] Pose detection error:",s)}}),c.registerHook("destroy",()=>{R&&(R.close(),R=null),G=null,y.clear(),E.remove(),e=null}),W(`
2
2
  uniform int u_maxPoses;
3
3
  uniform int u_nPoses;
4
4
  uniform sampler2D u_poseLandmarksTex;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/pose.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { PoseLandmarker, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface PosePluginOptions {\n\tmodelPath?: string;\n\tmaxPoses?: number;\n\tminPoseDetectionConfidence?: number;\n\tminPosePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputSegmentationMasks?: boolean;\n}\n\nconst STANDARD_LANDMARK_COUNT = 33; // See https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model.\nconst CUSTOM_LANDMARK_COUNT = 6;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\t// Standard landmarks.\n\tLEFT_EYE: 2,\n\tRIGHT_EYE: 5,\n\tLEFT_SHOULDER: 11,\n\tRIGHT_SHOULDER: 12,\n\tLEFT_ELBOW: 13,\n\tRIGHT_ELBOW: 14,\n\tLEFT_HIP: 23,\n\tRIGHT_HIP: 24,\n\tLEFT_KNEE: 25,\n\tRIGHT_KNEE: 26,\n\tLEFT_WRIST: 15,\n\tRIGHT_WRIST: 16,\n\tLEFT_PINKY: 17,\n\tRIGHT_PINKY: 18,\n\tLEFT_INDEX: 19,\n\tRIGHT_INDEX: 20,\n\tLEFT_THUMB: 21,\n\tRIGHT_THUMB: 22,\n\tLEFT_ANKLE: 27,\n\tRIGHT_ANKLE: 28,\n\tLEFT_HEEL: 29,\n\tRIGHT_HEEL: 30,\n\tLEFT_FOOT_INDEX: 31,\n\tRIGHT_FOOT_INDEX: 32,\n\t// Custom landmarks.\n\tBODY_CENTER: STANDARD_LANDMARK_COUNT,\n\tLEFT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 1,\n\tRIGHT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 2,\n\tLEFT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 3,\n\tRIGHT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 4,\n\tTORSO_CENTER: STANDARD_LANDMARK_COUNT + 5,\n};\n\nfunction pose(config: { textureName: string; options?: PosePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet poseLandmarker: PoseLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxPoses = options?.maxPoses ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst poseMaskCanvas = document.createElement('canvas');\n\t\tposeMaskCanvas.width = maskWidth;\n\t\tposeMaskCanvas.height = maskHeight;\n\t\tconst poseMaskCtx = poseMaskCanvas.getContext('2d')!;\n\t\tconst segmentationCanvas = document.createElement('canvas');\n\t\tconst segmentationCtx = segmentationCanvas.getContext('2d')!;\n\t\tposeMaskCtx.globalCompositeOperation = segmentationCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializePoseLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, PoseLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\t\t\t\tposeLandmarker = await PoseLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumPoses: options?.maxPoses ?? 1,\n\t\t\t\t\tminPoseDetectionConfidence: options?.minPoseDetectionConfidence ?? 0.5,\n\t\t\t\t\tminPosePresenceConfidence: options?.minPosePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputSegmentationMasks: options?.outputSegmentationMasks ?? true,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to initialize Pose Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tposeIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tasync function updateMaskTexture(segmentationMasks?: any[]) {\n\t\t\tif (!poseLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tposeMaskCtx.clearRect(0, 0, poseMaskCanvas.width, poseMaskCanvas.height);\n\n\t\t\t\t// Draw the segmentation masks.\n\t\t\t\tif (segmentationMasks && segmentationMasks.length > 0) {\n\t\t\t\t\tsegmentationMasks.forEach(mask => {\n\t\t\t\t\t\tif (!mask) return;\n\n\t\t\t\t\t\tconst { width, height } = mask;\n\t\t\t\t\t\tconst maskData = mask.getAsUint8Array();\n\t\t\t\t\t\tconst pixelCount = width * height;\n\t\t\t\t\t\tconst outputData = new Uint8ClampedArray(pixelCount * 4);\n\n\t\t\t\t\t\tfor (let i = 0; i < pixelCount; i++) {\n\t\t\t\t\t\t\toutputData[i * 4 + 1] = maskData[i]; // G (body mask)\n\t\t\t\t\t\t\toutputData[i * 4 + 3] = 255; // A (required for compositing)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst rgbaMask = new ImageData(outputData, width, height);\n\n\t\t\t\t\t\t// Resize mask to canvas size if needed.\n\t\t\t\t\t\tif (width === poseMaskCanvas.width && height === poseMaskCanvas.height) {\n\t\t\t\t\t\t\tposeMaskCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (segmentationCanvas.width !== width) segmentationCanvas.width = width;\n\t\t\t\t\t\t\tif (segmentationCanvas.height !== height) segmentationCanvas.height = height;\n\t\t\t\t\t\t\tsegmentationCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t\tposeMaskCtx.drawImage(\n\t\t\t\t\t\t\t\tsegmentationCanvas,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tposeMaskCanvas.width,\n\t\t\t\t\t\t\t\tposeMaskCanvas.height\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_poseMask: poseMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(poses: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nPoses = poses.length;\n\t\t\tconst totalLandmarks = nPoses * LANDMARK_COUNT;\n\n\t\t\tfor (let poseIdx = 0; poseIdx < nPoses; ++poseIdx) {\n\t\t\t\tconst landmarks = poses[poseIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Body center (landmark 33) - calculated from all standard landmarks\n\t\t\t\tconst bodyCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tposeIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst bodyCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.BODY_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[bodyCenterIdx] = bodyCenter[0];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 1] = bodyCenter[1];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 2] = bodyCenter[2];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 3] = bodyCenter[3];\n\n\t\t\t\t// Left hand center (landmark 34) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst leftHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx] = leftHandCenter[0];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 1] = leftHandCenter[1];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 2] = leftHandCenter[2];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 3] = leftHandCenter[3];\n\n\t\t\t\t// Right hand center (landmark 35) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst rightHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx] = rightHandCenter[0];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 1] = rightHandCenter[1];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 2] = rightHandCenter[2];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 3] = rightHandCenter[3];\n\n\t\t\t\t// Left foot center (landmark 36) - center of ankle, heel, foot index\n\t\t\t\tconst leftFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx] = leftFootCenter[0];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 1] = leftFootCenter[1];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 2] = leftFootCenter[2];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 3] = leftFootCenter[3];\n\n\t\t\t\t// Right foot center (landmark 37) - center of ankle, heel, foot index\n\t\t\t\tconst rightFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx] = rightFootCenter[0];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 1] = rightFootCenter[1];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 2] = rightFootCenter[2];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 3] = rightFootCenter[3];\n\n\t\t\t\t// Torso center (landmark 38) - center of shoulders and hips\n\t\t\t\tconst torsoCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HIP,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HIP,\n\t\t\t\t]);\n\t\t\t\tconst torsoCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.TORSO_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[torsoCenterIdx] = torsoCenter[0];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 1] = torsoCenter[1];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 2] = torsoCenter[2];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 3] = torsoCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_poseLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processPoseResults(result: any) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nPoses = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tupdateMaskTexture(result.segmentationMasks).catch(error => {\n\t\t\t\tconsole.warn('[Pose Plugin] Mask texture update error:', error);\n\t\t\t});\n\n\t\t\tshaderPad.updateUniforms({ u_nPoses: nPoses });\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_poseMask', poseMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxPoses', 'int', maxPoses);\n\t\t\tshaderPad.initializeUniform('u_nPoses', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxPoses * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_poseLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializePoseLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!poseLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = poseLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = poseLandmarker.detect(source);\n\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Pose detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (poseLandmarker) {\n\t\t\t\tposeLandmarker.close();\n\t\t\t\tposeLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tposeMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\t\t// TODO: inBody shouldn't rely on alpha. Does it? Seems so in the example.\n\t\t// Might be because putImageData ignores alpha compositing.\n\t\tinjectGLSL(`\nuniform int u_maxPoses;\nuniform int u_nPoses;\nuniform sampler2D u_poseLandmarksTex;\nuniform sampler2D u_poseMask;\n\n#define POSE_LANDMARK_LEFT_EYE ${LANDMARK_INDICES.LEFT_EYE}\n#define POSE_LANDMARK_RIGHT_EYE ${LANDMARK_INDICES.RIGHT_EYE}\n#define POSE_LANDMARK_LEFT_SHOULDER ${LANDMARK_INDICES.LEFT_SHOULDER}\n#define POSE_LANDMARK_RIGHT_SHOULDER ${LANDMARK_INDICES.RIGHT_SHOULDER}\n#define POSE_LANDMARK_LEFT_ELBOW ${LANDMARK_INDICES.LEFT_ELBOW}\n#define POSE_LANDMARK_RIGHT_ELBOW ${LANDMARK_INDICES.RIGHT_ELBOW}\n#define POSE_LANDMARK_LEFT_HIP ${LANDMARK_INDICES.LEFT_HIP}\n#define POSE_LANDMARK_RIGHT_HIP ${LANDMARK_INDICES.RIGHT_HIP}\n#define POSE_LANDMARK_LEFT_KNEE ${LANDMARK_INDICES.LEFT_KNEE}\n#define POSE_LANDMARK_RIGHT_KNEE ${LANDMARK_INDICES.RIGHT_KNEE}\n#define POSE_LANDMARK_BODY_CENTER ${LANDMARK_INDICES.BODY_CENTER}\n#define POSE_LANDMARK_LEFT_HAND_CENTER ${LANDMARK_INDICES.LEFT_HAND_CENTER}\n#define POSE_LANDMARK_RIGHT_HAND_CENTER ${LANDMARK_INDICES.RIGHT_HAND_CENTER}\n#define POSE_LANDMARK_LEFT_FOOT_CENTER ${LANDMARK_INDICES.LEFT_FOOT_CENTER}\n#define POSE_LANDMARK_RIGHT_FOOT_CENTER ${LANDMARK_INDICES.RIGHT_FOOT_CENTER}\n#define POSE_LANDMARK_TORSO_CENTER ${LANDMARK_INDICES.TORSO_CENTER}\n\nvec4 poseLandmark(int poseIndex, int landmarkIndex) {\n\tint i = poseIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_poseLandmarksTex, ivec2(x, y), 0);\n}\nfloat inBody(vec2 pos) { return texture(u_poseMask, pos).g; }`);\n\t};\n}\n\nexport default pose;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","pose","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","poseLandmarker","vision","lastVideoTime","textureSources","maxPoses","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","poseMaskCanvas","poseMaskCtx","segmentationCanvas","segmentationCtx","initializePoseLandmarker","FilesetResolver","PoseLandmarker","error","calculateBoundingBoxCenter","poseIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateMaskTexture","segmentationMasks","mask","width","height","maskData","pixelCount","outputData","i","rgbaMask","updateLandmarksTexture","poses","nPoses","totalLandmarks","landmarks","lmIdx","landmark","bodyCenter","_","bodyCenterIdx","leftHandCenter","leftHandCenterIdx","rightHandCenter","rightHandCenterIdx","leftFootCenter","leftFootCenterIdx","rightFootCenter","rightFootCenterIdx","torsoCenter","torsoCenterIdx","rowsToUpdate","processPoseResults","result","textureSize","updates","source","timestamp","pose_default"]}
1
+ {"version":3,"sources":["../../src/plugins/pose.ts"],"sourcesContent":["import ShaderPad, { PluginContext, TextureSource } from '../index';\nimport type { PoseLandmarker, PoseLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision';\n\nexport interface PosePluginOptions {\n\tmodelPath?: string;\n\tmaxPoses?: number;\n\tminPoseDetectionConfidence?: number;\n\tminPosePresenceConfidence?: number;\n\tminTrackingConfidence?: number;\n\toutputSegmentationMasks?: boolean;\n\tonResults?: (results: PoseLandmarkerResult) => void;\n}\n\nconst STANDARD_LANDMARK_COUNT = 33; // See https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker#pose_landmarker_model.\nconst CUSTOM_LANDMARK_COUNT = 6;\nconst LANDMARK_COUNT = STANDARD_LANDMARK_COUNT + CUSTOM_LANDMARK_COUNT;\nconst LANDMARK_INDICES = {\n\t// Standard landmarks.\n\tLEFT_EYE: 2,\n\tRIGHT_EYE: 5,\n\tLEFT_SHOULDER: 11,\n\tRIGHT_SHOULDER: 12,\n\tLEFT_ELBOW: 13,\n\tRIGHT_ELBOW: 14,\n\tLEFT_HIP: 23,\n\tRIGHT_HIP: 24,\n\tLEFT_KNEE: 25,\n\tRIGHT_KNEE: 26,\n\tLEFT_WRIST: 15,\n\tRIGHT_WRIST: 16,\n\tLEFT_PINKY: 17,\n\tRIGHT_PINKY: 18,\n\tLEFT_INDEX: 19,\n\tRIGHT_INDEX: 20,\n\tLEFT_THUMB: 21,\n\tRIGHT_THUMB: 22,\n\tLEFT_ANKLE: 27,\n\tRIGHT_ANKLE: 28,\n\tLEFT_HEEL: 29,\n\tRIGHT_HEEL: 30,\n\tLEFT_FOOT_INDEX: 31,\n\tRIGHT_FOOT_INDEX: 32,\n\t// Custom landmarks.\n\tBODY_CENTER: STANDARD_LANDMARK_COUNT,\n\tLEFT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 1,\n\tRIGHT_HAND_CENTER: STANDARD_LANDMARK_COUNT + 2,\n\tLEFT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 3,\n\tRIGHT_FOOT_CENTER: STANDARD_LANDMARK_COUNT + 4,\n\tTORSO_CENTER: STANDARD_LANDMARK_COUNT + 5,\n};\n\nfunction pose(config: { textureName: string; options?: PosePluginOptions }) {\n\tconst { textureName, options } = config;\n\tconst defaultModelPath =\n\t\t'https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task';\n\n\treturn function (shaderPad: ShaderPad, context: PluginContext) {\n\t\tconst { injectGLSL, gl } = context;\n\n\t\tlet poseLandmarker: PoseLandmarker | null = null;\n\t\tlet vision: any = null;\n\t\tlet lastVideoTime = -1;\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxPoses = options?.maxPoses ?? 1;\n\n\t\tconst LANDMARKS_TEXTURE_WIDTH = 512;\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst maskWidth = 512;\n\t\tconst maskHeight = 512;\n\t\tconst poseMaskCanvas = document.createElement('canvas');\n\t\tposeMaskCanvas.width = maskWidth;\n\t\tposeMaskCanvas.height = maskHeight;\n\t\tconst poseMaskCtx = poseMaskCanvas.getContext('2d')!;\n\t\tconst segmentationCanvas = document.createElement('canvas');\n\t\tconst segmentationCtx = segmentationCanvas.getContext('2d')!;\n\t\tposeMaskCtx.globalCompositeOperation = segmentationCtx.globalCompositeOperation = 'lighten'; // Keep the highest value of each channel.\n\n\t\tasync function initializePoseLandmarker() {\n\t\t\ttry {\n\t\t\t\tconst { FilesetResolver, PoseLandmarker } = await import('@mediapipe/tasks-vision');\n\t\t\t\tvision = await FilesetResolver.forVisionTasks(\n\t\t\t\t\t'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n\t\t\t\t);\n\t\t\t\tposeLandmarker = await PoseLandmarker.createFromOptions(vision, {\n\t\t\t\t\tbaseOptions: {\n\t\t\t\t\t\tmodelAssetPath: options?.modelPath || defaultModelPath,\n\t\t\t\t\t},\n\t\t\t\t\trunningMode: 'VIDEO',\n\t\t\t\t\tnumPoses: options?.maxPoses ?? 1,\n\t\t\t\t\tminPoseDetectionConfidence: options?.minPoseDetectionConfidence ?? 0.5,\n\t\t\t\t\tminPosePresenceConfidence: options?.minPosePresenceConfidence ?? 0.5,\n\t\t\t\t\tminTrackingConfidence: options?.minTrackingConfidence ?? 0.5,\n\t\t\t\t\toutputSegmentationMasks: options?.outputSegmentationMasks ?? true,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to initialize Pose Landmarker:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tlandmarksDataArray: Float32Array,\n\t\t\tposeIdx: number,\n\t\t\tlandmarkIndices: number[]\n\t\t): [number, number, number, number] {\n\t\t\tlet minX = Infinity,\n\t\t\t\tmaxX = -Infinity,\n\t\t\t\tminY = Infinity,\n\t\t\t\tmaxY = -Infinity,\n\t\t\t\tavgZ = 0,\n\t\t\t\tavgVisibility = 0;\n\n\t\t\tfor (const idx of landmarkIndices) {\n\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = landmarksDataArray[dataIdx];\n\t\t\t\tconst y = landmarksDataArray[dataIdx + 1];\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\tavgZ += landmarksDataArray[dataIdx + 2];\n\t\t\t\tavgVisibility += landmarksDataArray[dataIdx + 3];\n\t\t\t}\n\n\t\t\tconst centerX = (minX + maxX) / 2;\n\t\t\tconst centerY = (minY + maxY) / 2;\n\t\t\tconst centerZ = avgZ / landmarkIndices.length;\n\t\t\tconst centerVisibility = avgVisibility / landmarkIndices.length;\n\t\t\treturn [centerX, centerY, centerZ, centerVisibility];\n\t\t}\n\n\t\tasync function updateMaskTexture(segmentationMasks?: any[]) {\n\t\t\tif (!poseLandmarker || !landmarksDataArray) {\n\t\t\t\tconsole.warn('[Pose Plugin] Cannot update mask: poseLandmarker or landmarksDataArray missing');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tposeMaskCtx.clearRect(0, 0, poseMaskCanvas.width, poseMaskCanvas.height);\n\n\t\t\t\t// Draw the segmentation masks.\n\t\t\t\tif (segmentationMasks && segmentationMasks.length > 0) {\n\t\t\t\t\tsegmentationMasks.forEach(mask => {\n\t\t\t\t\t\tif (!mask) return;\n\n\t\t\t\t\t\tconst { width, height } = mask;\n\t\t\t\t\t\tconst maskData = mask.getAsUint8Array();\n\t\t\t\t\t\tconst pixelCount = width * height;\n\t\t\t\t\t\tconst outputData = new Uint8ClampedArray(pixelCount * 4);\n\n\t\t\t\t\t\tfor (let i = 0; i < pixelCount; i++) {\n\t\t\t\t\t\t\toutputData[i * 4 + 1] = maskData[i]; // G (body mask)\n\t\t\t\t\t\t\toutputData[i * 4 + 3] = 255; // A (required for compositing)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst rgbaMask = new ImageData(outputData, width, height);\n\n\t\t\t\t\t\t// Resize mask to canvas size if needed.\n\t\t\t\t\t\tif (width === poseMaskCanvas.width && height === poseMaskCanvas.height) {\n\t\t\t\t\t\t\tposeMaskCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (segmentationCanvas.width !== width) segmentationCanvas.width = width;\n\t\t\t\t\t\t\tif (segmentationCanvas.height !== height) segmentationCanvas.height = height;\n\t\t\t\t\t\t\tsegmentationCtx.putImageData(rgbaMask, 0, 0);\n\t\t\t\t\t\t\tposeMaskCtx.drawImage(\n\t\t\t\t\t\t\t\tsegmentationCanvas,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tposeMaskCanvas.width,\n\t\t\t\t\t\t\t\tposeMaskCanvas.height\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tshaderPad.updateTextures({ u_poseMask: poseMaskCanvas });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Failed to generate mask texture:', error);\n\t\t\t}\n\t\t}\n\n\t\tfunction updateLandmarksTexture(poses: NormalizedLandmark[][]) {\n\t\t\tif (!landmarksDataArray) return;\n\n\t\t\tconst nPoses = poses.length;\n\t\t\tconst totalLandmarks = nPoses * LANDMARK_COUNT;\n\n\t\t\tfor (let poseIdx = 0; poseIdx < nPoses; ++poseIdx) {\n\t\t\t\tconst landmarks = poses[poseIdx];\n\t\t\t\tfor (let lmIdx = 0; lmIdx < STANDARD_LANDMARK_COUNT; ++lmIdx) {\n\t\t\t\t\tconst landmark = landmarks[lmIdx];\n\t\t\t\t\tconst dataIdx = (poseIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x; // R (X)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y; // G (Inverted Y)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0; // B (Z)\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1; // A (Visibility)\n\t\t\t\t}\n\n\t\t\t\t// Body center (landmark 33) - calculated from all standard landmarks\n\t\t\t\tconst bodyCenter = calculateBoundingBoxCenter(\n\t\t\t\t\tlandmarksDataArray,\n\t\t\t\t\tposeIdx,\n\t\t\t\t\tArray.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i)\n\t\t\t\t);\n\t\t\t\tconst bodyCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.BODY_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[bodyCenterIdx] = bodyCenter[0];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 1] = bodyCenter[1];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 2] = bodyCenter[2];\n\t\t\t\tlandmarksDataArray[bodyCenterIdx + 3] = bodyCenter[3];\n\n\t\t\t\t// Left hand center (landmark 34) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst leftHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx] = leftHandCenter[0];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 1] = leftHandCenter[1];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 2] = leftHandCenter[2];\n\t\t\t\tlandmarksDataArray[leftHandCenterIdx + 3] = leftHandCenter[3];\n\n\t\t\t\t// Right hand center (landmark 35) - center of pinky, thumb, wrist, index.\n\t\t\t\tconst rightHandCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_WRIST,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_PINKY,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_THUMB,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightHandCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_HAND_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx] = rightHandCenter[0];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 1] = rightHandCenter[1];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 2] = rightHandCenter[2];\n\t\t\t\tlandmarksDataArray[rightHandCenterIdx + 3] = rightHandCenter[3];\n\n\t\t\t\t// Left foot center (landmark 36) - center of ankle, heel, foot index\n\t\t\t\tconst leftFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst leftFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.LEFT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx] = leftFootCenter[0];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 1] = leftFootCenter[1];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 2] = leftFootCenter[2];\n\t\t\t\tlandmarksDataArray[leftFootCenterIdx + 3] = leftFootCenter[3];\n\n\t\t\t\t// Right foot center (landmark 37) - center of ankle, heel, foot index\n\t\t\t\tconst rightFootCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_ANKLE,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HEEL,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_FOOT_INDEX,\n\t\t\t\t]);\n\t\t\t\tconst rightFootCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.RIGHT_FOOT_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx] = rightFootCenter[0];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 1] = rightFootCenter[1];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 2] = rightFootCenter[2];\n\t\t\t\tlandmarksDataArray[rightFootCenterIdx + 3] = rightFootCenter[3];\n\n\t\t\t\t// Torso center (landmark 38) - center of shoulders and hips\n\t\t\t\tconst torsoCenter = calculateBoundingBoxCenter(landmarksDataArray, poseIdx, [\n\t\t\t\t\tLANDMARK_INDICES.LEFT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_SHOULDER,\n\t\t\t\t\tLANDMARK_INDICES.LEFT_HIP,\n\t\t\t\t\tLANDMARK_INDICES.RIGHT_HIP,\n\t\t\t\t]);\n\t\t\t\tconst torsoCenterIdx = (poseIdx * LANDMARK_COUNT + LANDMARK_INDICES.TORSO_CENTER) * 4;\n\t\t\t\tlandmarksDataArray[torsoCenterIdx] = torsoCenter[0];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 1] = torsoCenter[1];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 2] = torsoCenter[2];\n\t\t\t\tlandmarksDataArray[torsoCenterIdx + 3] = torsoCenter[3];\n\t\t\t}\n\n\t\t\tconst rowsToUpdate = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tshaderPad.updateTextures({\n\t\t\t\tu_poseLandmarksTex: {\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: rowsToUpdate,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction processPoseResults(result: PoseLandmarkerResult) {\n\t\t\tif (!result.landmarks || !landmarksDataArray) return;\n\n\t\t\tconst nPoses = result.landmarks.length;\n\t\t\tupdateLandmarksTexture(result.landmarks);\n\t\t\tupdateMaskTexture(result.segmentationMasks).catch(error => {\n\t\t\t\tconsole.warn('[Pose Plugin] Mask texture update error:', error);\n\t\t\t});\n\t\t\tshaderPad.updateUniforms({ u_nPoses: nPoses });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_poseMask', poseMaskCanvas);\n\t\t\tshaderPad.initializeUniform('u_maxPoses', 'int', maxPoses);\n\t\t\tshaderPad.initializeUniform('u_nPoses', 'int', 0);\n\n\t\t\tconst totalLandmarks = maxPoses * LANDMARK_COUNT;\n\t\t\tlandmarksTextureHeight = Math.ceil(totalLandmarks / LANDMARKS_TEXTURE_WIDTH);\n\t\t\tconst textureSize = LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4;\n\t\t\tlandmarksDataArray = new Float32Array(textureSize);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_poseLandmarksTex',\n\t\t\t\t{\n\t\t\t\t\tdata: landmarksDataArray,\n\t\t\t\t\twidth: LANDMARKS_TEXTURE_WIDTH,\n\t\t\t\t\theight: landmarksTextureHeight,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinternalFormat: gl.RGBA32F,\n\t\t\t\t\ttype: gl.FLOAT,\n\t\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tawait initializePoseLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', (updates: Record<string, TextureSource>) => {\n\t\t\tconst source = updates[textureName];\n\t\t\tif (!source) return;\n\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) {\n\t\t\t\tlastVideoTime = -1;\n\t\t\t}\n\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!poseLandmarker) return;\n\t\t\ttry {\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tconst timestamp = performance.now();\n\t\t\t\t\t\tconst result = poseLandmarker.detectForVideo(source, timestamp);\n\t\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t\t}\n\t\t\t\t} else if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {\n\t\t\t\t\tif (source.width === 0 || source.height === 0) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = poseLandmarker.detect(source);\n\t\t\t\t\tprocessPoseResults(result);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Pose Plugin] Pose detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tif (poseLandmarker) {\n\t\t\t\tposeLandmarker.close();\n\t\t\t\tposeLandmarker = null;\n\t\t\t}\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tposeMaskCanvas.remove();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\t\t// TODO: inBody shouldn't rely on alpha. Does it? Seems so in the example.\n\t\t// Might be because putImageData ignores alpha compositing.\n\t\tinjectGLSL(`\nuniform int u_maxPoses;\nuniform int u_nPoses;\nuniform sampler2D u_poseLandmarksTex;\nuniform sampler2D u_poseMask;\n\n#define POSE_LANDMARK_LEFT_EYE ${LANDMARK_INDICES.LEFT_EYE}\n#define POSE_LANDMARK_RIGHT_EYE ${LANDMARK_INDICES.RIGHT_EYE}\n#define POSE_LANDMARK_LEFT_SHOULDER ${LANDMARK_INDICES.LEFT_SHOULDER}\n#define POSE_LANDMARK_RIGHT_SHOULDER ${LANDMARK_INDICES.RIGHT_SHOULDER}\n#define POSE_LANDMARK_LEFT_ELBOW ${LANDMARK_INDICES.LEFT_ELBOW}\n#define POSE_LANDMARK_RIGHT_ELBOW ${LANDMARK_INDICES.RIGHT_ELBOW}\n#define POSE_LANDMARK_LEFT_HIP ${LANDMARK_INDICES.LEFT_HIP}\n#define POSE_LANDMARK_RIGHT_HIP ${LANDMARK_INDICES.RIGHT_HIP}\n#define POSE_LANDMARK_LEFT_KNEE ${LANDMARK_INDICES.LEFT_KNEE}\n#define POSE_LANDMARK_RIGHT_KNEE ${LANDMARK_INDICES.RIGHT_KNEE}\n#define POSE_LANDMARK_BODY_CENTER ${LANDMARK_INDICES.BODY_CENTER}\n#define POSE_LANDMARK_LEFT_HAND_CENTER ${LANDMARK_INDICES.LEFT_HAND_CENTER}\n#define POSE_LANDMARK_RIGHT_HAND_CENTER ${LANDMARK_INDICES.RIGHT_HAND_CENTER}\n#define POSE_LANDMARK_LEFT_FOOT_CENTER ${LANDMARK_INDICES.LEFT_FOOT_CENTER}\n#define POSE_LANDMARK_RIGHT_FOOT_CENTER ${LANDMARK_INDICES.RIGHT_FOOT_CENTER}\n#define POSE_LANDMARK_TORSO_CENTER ${LANDMARK_INDICES.TORSO_CENTER}\n\nvec4 poseLandmark(int poseIndex, int landmarkIndex) {\n\tint i = poseIndex * ${LANDMARK_COUNT} + landmarkIndex;\n\tint x = i % ${LANDMARKS_TEXTURE_WIDTH};\n\tint y = i / ${LANDMARKS_TEXTURE_WIDTH};\n\treturn texelFetch(u_poseLandmarksTex, ivec2(x, y), 0);\n}\nfloat inBody(vec2 pos) { return texture(u_poseMask, pos).g; }`);\n\t};\n}\n\nexport default pose;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARK_INDICES","pose","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","poseLandmarker","vision","lastVideoTime","textureSources","maxPoses","LANDMARKS_TEXTURE_WIDTH","landmarksTextureHeight","landmarksDataArray","maskWidth","maskHeight","poseMaskCanvas","poseMaskCtx","segmentationCanvas","segmentationCtx","initializePoseLandmarker","FilesetResolver","PoseLandmarker","error","calculateBoundingBoxCenter","poseIdx","landmarkIndices","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","dataIdx","x","y","centerX","centerY","centerZ","centerVisibility","updateMaskTexture","segmentationMasks","mask","width","height","maskData","pixelCount","outputData","i","rgbaMask","updateLandmarksTexture","poses","nPoses","totalLandmarks","landmarks","lmIdx","landmark","bodyCenter","_","bodyCenterIdx","leftHandCenter","leftHandCenterIdx","rightHandCenter","rightHandCenterIdx","leftFootCenter","leftFootCenterIdx","rightFootCenter","rightFootCenterIdx","torsoCenter","torsoCenterIdx","rowsToUpdate","processPoseResults","result","textureSize","updates","source","timestamp","pose_default"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shaderpad",
3
- "version": "1.0.0-beta.30",
3
+ "version": "1.0.0-beta.31",
4
4
  "description": "A lightweight, dependency-free library to reduce boilerplate when writing fragment shaders.",
5
5
  "keywords": [
6
6
  "shaders",