shaderpad 1.0.0-beta.37 → 1.0.0-beta.39

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 +1 @@
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 LANDMARKS_TEXTURE_WIDTH = 512;\n\nconst LEFT_EYEBROW_INDICES = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285] as const;\nconst LEFT_EYE_INDICES = [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382] as const;\nconst RIGHT_EYEBROW_INDICES = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46] as const;\nconst RIGHT_EYE_INDICES = [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7] as const;\nconst OUTER_MOUTH_INDICES = [\n\t61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146,\n] as const;\nconst INNER_MOUTH_INDICES = [\n\t78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95,\n] as const;\nconst ALL_STANDARD_INDICES = Array.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i);\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: LEFT_EYEBROW_INDICES,\n\tLEFT_EYE: LEFT_EYE_INDICES,\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: RIGHT_EYEBROW_INDICES,\n\tRIGHT_EYE: RIGHT_EYE_INDICES,\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_MOUTH: OUTER_MOUTH_INDICES,\n\tINNER_MOUTH: INNER_MOUTH_INDICES,\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nconst REGION_NAMES = [\n\t'BACKGROUND',\n\t'LEFT_EYEBROW',\n\t'RIGHT_EYEBROW',\n\t'LEFT_EYE',\n\t'RIGHT_EYE',\n\t'OUTER_MOUTH',\n\t'INNER_MOUTH',\n] as const;\nconst nFaceRegions = REGION_NAMES.length - 1;\nconst RED_CHANNEL_VALUES = Object.fromEntries(REGION_NAMES.map((name, i) => [name, i / nFaceRegions])) as Record<\n\t(typeof REGION_NAMES)[number],\n\tnumber\n>;\nconst HALF_GAP = 0.5 / nFaceRegions;\n\nfunction fanTriangulate(indices: readonly number[]): number[] {\n\tconst tris: number[] = [];\n\tfor (let i = 1; i < indices.length - 1; ++i) {\n\t\ttris.push(indices[0], indices[i], indices[i + 1]);\n\t}\n\treturn tris;\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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst mediaPipeCanvas = new OffscreenCanvas(1, 1);\n\t\tconst maskCanvas = new OffscreenCanvas(1, 1);\n\n\t\t// WebGL resources for triangle rendering (no antialiasing).\n\t\tlet maskGl: WebGL2RenderingContext | null = null;\n\t\tlet maskProgram: WebGLProgram | null = null;\n\t\tlet positionBuffer: WebGLBuffer | null = null;\n\t\tlet colorLocation: WebGLUniformLocation | null = null;\n\n\t\tconst regionTriangles: Record<string, number[]> = {\n\t\t\tLEFT_EYEBROW: fanTriangulate(LEFT_EYEBROW_INDICES),\n\t\t\tRIGHT_EYEBROW: fanTriangulate(RIGHT_EYEBROW_INDICES),\n\t\t\tLEFT_EYE: fanTriangulate(LEFT_EYE_INDICES),\n\t\t\tRIGHT_EYE: fanTriangulate(RIGHT_EYE_INDICES),\n\t\t\tOUTER_MOUTH: fanTriangulate(OUTER_MOUTH_INDICES),\n\t\t\tINNER_MOUTH: fanTriangulate(INNER_MOUTH_INDICES),\n\t\t\t// Populated after FaceLandmarker loads.\n\t\t\tTESSELATION: [],\n\t\t\tOVAL: [],\n\t\t};\n\n\t\tfunction initMaskRenderer() {\n\t\t\tmaskGl = maskCanvas.getContext('webgl2', { antialias: false, preserveDrawingBuffer: true });\n\t\t\tif (!maskGl) throw new Error('Failed to get WebGL2 context for mask');\n\n\t\t\tconst vertexShader = maskGl.createShader(maskGl.VERTEX_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tvertexShader,\n\t\t\t\t`#version 300 es\nin vec2 a_pos;\nvoid main() {\n\tgl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);\n}`\n\t\t\t);\n\t\t\tmaskGl.compileShader(vertexShader);\n\n\t\t\tconst fragmentShader = maskGl.createShader(maskGl.FRAGMENT_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tfragmentShader,\n\t\t\t\t`#version 300 es\nprecision mediump float;\nuniform vec4 u_color;\nout vec4 outColor;\nvoid main() { outColor = u_color; }`\n\t\t\t);\n\t\t\tmaskGl.compileShader(fragmentShader);\n\n\t\t\tmaskProgram = maskGl.createProgram()!;\n\t\t\tmaskGl.attachShader(maskProgram, vertexShader);\n\t\t\tmaskGl.attachShader(maskProgram, fragmentShader);\n\t\t\tmaskGl.linkProgram(maskProgram);\n\t\t\tmaskGl.deleteShader(vertexShader);\n\t\t\tmaskGl.deleteShader(fragmentShader);\n\n\t\t\tpositionBuffer = maskGl.createBuffer();\n\t\t\tmaskGl.bindBuffer(maskGl.ARRAY_BUFFER, positionBuffer);\n\t\t\tconst positionLocation = maskGl.getAttribLocation(maskProgram, 'a_pos');\n\t\t\tmaskGl.enableVertexAttribArray(positionLocation);\n\t\t\tmaskGl.vertexAttribPointer(positionLocation, 2, maskGl.FLOAT, false, 0, 0);\n\n\t\t\tcolorLocation = maskGl.getUniformLocation(maskProgram, 'u_color');\n\t\t\tmaskGl.useProgram(maskProgram);\n\n\t\t\t// Enable blending to handle overlapping faces (set once, never disabled).\n\t\t\tmaskGl.enable(maskGl.BLEND);\n\t\t\tmaskGl.blendEquation(maskGl.MAX);\n\t\t}\n\n\t\tfunction drawTriangles(triangleIndices: number[], faceIdx: number, r: number, g: number, b: number) {\n\t\t\tif (!maskGl || !landmarksDataArray || triangleIndices.length === 0) return;\n\n\t\t\tconst vertices = new Float32Array(triangleIndices.length * 2);\n\t\t\tfor (let i = 0; i < triangleIndices.length; ++i) {\n\t\t\t\tconst landmarkIdx = (faceIdx * LANDMARK_COUNT + triangleIndices[i]) * 4;\n\t\t\t\tvertices[i * 2] = landmarksDataArray[landmarkIdx];\n\t\t\t\tvertices[i * 2 + 1] = landmarksDataArray[landmarkIdx + 1];\n\t\t\t}\n\n\t\t\tmaskGl.bufferData(maskGl.ARRAY_BUFFER, vertices, maskGl.DYNAMIC_DRAW);\n\t\t\tmaskGl.uniform4f(colorLocation, r, g, b, 1.0);\n\t\t\tmaskGl.drawArrays(maskGl.TRIANGLES, 0, triangleIndices.length);\n\t\t}\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: mediaPipeCanvas,\n\t\t\t\t\trunningMode: runningMode,\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\n\t\t\t\tconst tesselationConnections = FaceLandmarker.FACE_LANDMARKS_TESSELATION;\n\t\t\t\tregionTriangles.TESSELATION = [];\n\t\t\t\tfor (let i = 0; i < tesselationConnections.length - 2; i += 3) {\n\t\t\t\t\tregionTriangles.TESSELATION.push(\n\t\t\t\t\t\ttesselationConnections[i].start,\n\t\t\t\t\t\ttesselationConnections[i + 1].start,\n\t\t\t\t\t\ttesselationConnections[i + 2].start\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst ovalIndices = FaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start);\n\t\t\t\tregionTriangles.OVAL = fanTriangulate(ovalIndices);\n\n\t\t\t\tinitMaskRenderer();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to initialize:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tdata: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tindices: readonly number[] | 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 indices) {\n\t\t\t\tconst i = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = data[i],\n\t\t\t\t\ty = data[i + 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 += data[i + 2];\n\t\t\t\tavgVisibility += data[i + 3];\n\t\t\t}\n\t\t\treturn [(minX + maxX) / 2, (minY + maxY) / 2, avgZ / indices.length, avgVisibility / indices.length];\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 landmarkIdx = 0; landmarkIdx < STANDARD_LANDMARK_COUNT; ++landmarkIdx) {\n\t\t\t\t\tconst landmark = landmarks[landmarkIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + landmarkIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1;\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, ALL_STANDARD_INDICES);\n\t\t\t\tlandmarksDataArray.set(faceCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4);\n\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, INNER_MOUTH_INDICES);\n\t\t\t\tlandmarksDataArray.set(mouthCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4);\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\tisPartial: true,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction updateMaskTexture(nFaces: number) {\n\t\t\tif (!maskGl || !landmarksDataArray) return;\n\n\t\t\t// Resize and clear.\n\t\t\tmaskCanvas.width = mediaPipeCanvas.width;\n\t\t\tmaskCanvas.height = mediaPipeCanvas.height;\n\t\t\tmaskGl.viewport(0, 0, maskCanvas.width, maskCanvas.height);\n\t\t\tmaskGl.clearColor(0, 0, 0, 0);\n\t\t\tmaskGl.clear(maskGl.COLOR_BUFFER_BIT);\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst b = (faceIdx + 1) / maxFaces;\n\n\t\t\t\t// G channel: face mesh (0.5) and oval (1.0)\n\t\t\t\tdrawTriangles(regionTriangles.TESSELATION, faceIdx, 0, 0.5, b);\n\t\t\t\tdrawTriangles(regionTriangles.OVAL, faceIdx, 0, 1.0, b);\n\n\t\t\t\t// R channel: feature regions\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.LEFT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYE, faceIdx, RED_CHANNEL_VALUES.LEFT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYE, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.OUTER_MOUTH, faceIdx, RED_CHANNEL_VALUES.OUTER_MOUTH, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.INNER_MOUTH, faceIdx, RED_CHANNEL_VALUES.INNER_MOUTH, 0, b);\n\t\t\t}\n\n\t\t\tshaderPad.updateTextures({ u_faceMask: maskCanvas });\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);\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', maskCanvas, {\n\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t});\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\tlandmarksDataArray = new Float32Array(LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{ data: landmarksDataArray, width: LANDMARKS_TEXTURE_WIDTH, height: landmarksTextureHeight },\n\t\t\t\t{ internalFormat: gl.RGBA32F, type: gl.FLOAT, minFilter: gl.NEAREST, magFilter: gl.NEAREST }\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', async (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) lastVideoTime = -1;\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait faceLandmarker.setOptions({ runningMode });\n\t\t\t\t}\n\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) return;\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tprocessFaceResults(faceLandmarker.detectForVideo(source, performance.now()));\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) return;\n\t\t\t\t\tprocessFaceResults(faceLandmarker.detect(source));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tfaceLandmarker?.close();\n\t\t\tfaceLandmarker = null;\n\t\t\tif (maskGl && maskProgram) {\n\t\t\t\tmaskGl.deleteProgram(maskProgram);\n\t\t\t\tmaskGl.deleteBuffer(positionBuffer);\n\t\t\t}\n\t\t\tmaskGl = null;\n\t\t\tmaskProgram = null;\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tconst checkAt = (\n\t\t\tregionMin: keyof typeof RED_CHANNEL_VALUES,\n\t\t\tregionMax: keyof typeof RED_CHANNEL_VALUES = regionMin\n\t\t) =>\n\t\t\t`vec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn (mask.r > ${(RED_CHANNEL_VALUES[regionMin] - HALF_GAP).toFixed(4)} && mask.r < ${(\n\t\t\t\tRED_CHANNEL_VALUES[regionMax] + HALF_GAP\n\t\t\t).toFixed(4)}) ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);`;\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}\n\nvec2 leftEyebrowAt(vec2 pos) {\n\t${checkAt('LEFT_EYEBROW')}\n}\n\nvec2 rightEyebrowAt(vec2 pos) {\n\t${checkAt('RIGHT_EYEBROW')}\n}\n\nvec2 leftEyeAt(vec2 pos) {\n\t${checkAt('LEFT_EYE')}\n}\n\nvec2 rightEyeAt(vec2 pos) {\n\t${checkAt('RIGHT_EYE')}\n}\n\nvec2 lipsAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH')}\n}\n\nvec2 outerMouthAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH', 'INNER_MOUTH')}\n}\n\nvec2 innerMouthAt(vec2 pos) {\n\t${checkAt('INNER_MOUTH')}\n}\n\nvec2 faceOvalAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.75 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\n// Includes face mesh and oval.\nvec2 faceAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.25 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\nvec2 eyeAt(vec2 pos) {\n\tvec2 left = leftEyeAt(pos);\n\treturn left.x > 0.0 ? left : rightEyeAt(pos);\n}\n\nvec2 eyebrowAt(vec2 pos) {\n\tvec2 left = leftEyebrowAt(pos);\n\treturn left.x > 0.0 ? left : rightEyebrowAt(pos);\n}\n\nfloat inEyebrow(vec2 pos) { return eyebrowAt(pos).x; }\nfloat inEye(vec2 pos) { return eyeAt(pos).x; }\nfloat inOuterMouth(vec2 pos) { return outerMouthAt(pos).x; }\nfloat inInnerMouth(vec2 pos) { return innerMouthAt(pos).x; }\nfloat inLips(vec2 pos) { return lipsAt(pos).x; }\nfloat inFace(vec2 pos) { return faceAt(pos).x; }`);\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","LANDMARKS_TEXTURE_WIDTH","LEFT_EYEBROW_INDICES","LEFT_EYE_INDICES","RIGHT_EYEBROW_INDICES","RIGHT_EYE_INDICES","OUTER_MOUTH_INDICES","INNER_MOUTH_INDICES","ALL_STANDARD_INDICES","_","LANDMARK_INDICES","REGION_NAMES","nFaceRegions","RED_CHANNEL_VALUES","name","HALF_GAP","fanTriangulate","indices","tris","i","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","runningMode","textureSources","maxFaces","landmarksTextureHeight","landmarksDataArray","mediaPipeCanvas","maskCanvas","maskGl","maskProgram","positionBuffer","colorLocation","regionTriangles","initMaskRenderer","vertexShader","fragmentShader","positionLocation","drawTriangles","triangleIndices","faceIdx","r","g","b","vertices","landmarkIdx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","tesselationConnections","ovalIndices","start","error","calculateBoundingBoxCenter","data","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","x","y","updateLandmarksTexture","faces","nFaces","totalLandmarks","landmarks","landmark","dataIdx","faceCenter","mouthCenter","rowsToUpdate","updateMaskTexture","processFaceResults","result","updates","source","requiredMode","checkAt","regionMin","regionMax"]}
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\tonReady?: () => void;\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 LANDMARKS_TEXTURE_WIDTH = 512;\n\nconst LEFT_EYEBROW_INDICES = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285] as const;\nconst LEFT_EYE_INDICES = [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382] as const;\nconst RIGHT_EYEBROW_INDICES = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46] as const;\nconst RIGHT_EYE_INDICES = [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7] as const;\nconst OUTER_MOUTH_INDICES = [\n\t61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146,\n] as const;\nconst INNER_MOUTH_INDICES = [\n\t78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95,\n] as const;\nconst ALL_STANDARD_INDICES = Array.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i);\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: LEFT_EYEBROW_INDICES,\n\tLEFT_EYE: LEFT_EYE_INDICES,\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: RIGHT_EYEBROW_INDICES,\n\tRIGHT_EYE: RIGHT_EYE_INDICES,\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_MOUTH: OUTER_MOUTH_INDICES,\n\tINNER_MOUTH: INNER_MOUTH_INDICES,\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nconst REGION_NAMES = [\n\t'BACKGROUND',\n\t'LEFT_EYEBROW',\n\t'RIGHT_EYEBROW',\n\t'LEFT_EYE',\n\t'RIGHT_EYE',\n\t'OUTER_MOUTH',\n\t'INNER_MOUTH',\n] as const;\nconst nFaceRegions = REGION_NAMES.length - 1;\nconst RED_CHANNEL_VALUES = Object.fromEntries(REGION_NAMES.map((name, i) => [name, i / nFaceRegions])) as Record<\n\t(typeof REGION_NAMES)[number],\n\tnumber\n>;\nconst HALF_GAP = 0.5 / nFaceRegions;\n\nfunction fanTriangulate(indices: readonly number[]): number[] {\n\tconst tris: number[] = [];\n\tfor (let i = 1; i < indices.length - 1; ++i) {\n\t\ttris.push(indices[0], indices[i], indices[i + 1]);\n\t}\n\treturn tris;\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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst mediaPipeCanvas = new OffscreenCanvas(1, 1);\n\t\tconst maskCanvas = new OffscreenCanvas(1, 1);\n\n\t\t// WebGL resources for triangle rendering (no antialiasing).\n\t\tlet maskGl: WebGL2RenderingContext | null = null;\n\t\tlet maskProgram: WebGLProgram | null = null;\n\t\tlet positionBuffer: WebGLBuffer | null = null;\n\t\tlet colorLocation: WebGLUniformLocation | null = null;\n\n\t\tconst regionTriangles: Record<string, number[]> = {\n\t\t\tLEFT_EYEBROW: fanTriangulate(LEFT_EYEBROW_INDICES),\n\t\t\tRIGHT_EYEBROW: fanTriangulate(RIGHT_EYEBROW_INDICES),\n\t\t\tLEFT_EYE: fanTriangulate(LEFT_EYE_INDICES),\n\t\t\tRIGHT_EYE: fanTriangulate(RIGHT_EYE_INDICES),\n\t\t\tOUTER_MOUTH: fanTriangulate(OUTER_MOUTH_INDICES),\n\t\t\tINNER_MOUTH: fanTriangulate(INNER_MOUTH_INDICES),\n\t\t\t// Populated after FaceLandmarker loads.\n\t\t\tTESSELATION: [],\n\t\t\tOVAL: [],\n\t\t};\n\n\t\tfunction initMaskRenderer() {\n\t\t\tmaskGl = maskCanvas.getContext('webgl2', { antialias: false, preserveDrawingBuffer: true });\n\t\t\tif (!maskGl) throw new Error('Failed to get WebGL2 context for mask');\n\n\t\t\tconst vertexShader = maskGl.createShader(maskGl.VERTEX_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tvertexShader,\n\t\t\t\t`#version 300 es\nin vec2 a_pos;\nvoid main() {\n\tgl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);\n}`\n\t\t\t);\n\t\t\tmaskGl.compileShader(vertexShader);\n\n\t\t\tconst fragmentShader = maskGl.createShader(maskGl.FRAGMENT_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tfragmentShader,\n\t\t\t\t`#version 300 es\nprecision mediump float;\nuniform vec4 u_color;\nout vec4 outColor;\nvoid main() { outColor = u_color; }`\n\t\t\t);\n\t\t\tmaskGl.compileShader(fragmentShader);\n\n\t\t\tmaskProgram = maskGl.createProgram()!;\n\t\t\tmaskGl.attachShader(maskProgram, vertexShader);\n\t\t\tmaskGl.attachShader(maskProgram, fragmentShader);\n\t\t\tmaskGl.linkProgram(maskProgram);\n\t\t\tmaskGl.deleteShader(vertexShader);\n\t\t\tmaskGl.deleteShader(fragmentShader);\n\n\t\t\tpositionBuffer = maskGl.createBuffer();\n\t\t\tmaskGl.bindBuffer(maskGl.ARRAY_BUFFER, positionBuffer);\n\t\t\tconst positionLocation = maskGl.getAttribLocation(maskProgram, 'a_pos');\n\t\t\tmaskGl.enableVertexAttribArray(positionLocation);\n\t\t\tmaskGl.vertexAttribPointer(positionLocation, 2, maskGl.FLOAT, false, 0, 0);\n\n\t\t\tcolorLocation = maskGl.getUniformLocation(maskProgram, 'u_color');\n\t\t\tmaskGl.useProgram(maskProgram);\n\n\t\t\t// Enable blending to handle overlapping faces (set once, never disabled).\n\t\t\tmaskGl.enable(maskGl.BLEND);\n\t\t\tmaskGl.blendEquation(maskGl.MAX);\n\t\t}\n\n\t\tfunction drawTriangles(triangleIndices: number[], faceIdx: number, r: number, g: number, b: number) {\n\t\t\tif (!maskGl || !landmarksDataArray || triangleIndices.length === 0) return;\n\n\t\t\tconst vertices = new Float32Array(triangleIndices.length * 2);\n\t\t\tfor (let i = 0; i < triangleIndices.length; ++i) {\n\t\t\t\tconst landmarkIdx = (faceIdx * LANDMARK_COUNT + triangleIndices[i]) * 4;\n\t\t\t\tvertices[i * 2] = landmarksDataArray[landmarkIdx];\n\t\t\t\tvertices[i * 2 + 1] = landmarksDataArray[landmarkIdx + 1];\n\t\t\t}\n\n\t\t\tmaskGl.bufferData(maskGl.ARRAY_BUFFER, vertices, maskGl.DYNAMIC_DRAW);\n\t\t\tmaskGl.uniform4f(colorLocation, r, g, b, 1.0);\n\t\t\tmaskGl.drawArrays(maskGl.TRIANGLES, 0, triangleIndices.length);\n\t\t}\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: mediaPipeCanvas,\n\t\t\t\t\trunningMode: runningMode,\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\n\t\t\t\tconst tesselationConnections = FaceLandmarker.FACE_LANDMARKS_TESSELATION;\n\t\t\t\tregionTriangles.TESSELATION = [];\n\t\t\t\tfor (let i = 0; i < tesselationConnections.length - 2; i += 3) {\n\t\t\t\t\tregionTriangles.TESSELATION.push(\n\t\t\t\t\t\ttesselationConnections[i].start,\n\t\t\t\t\t\ttesselationConnections[i + 1].start,\n\t\t\t\t\t\ttesselationConnections[i + 2].start\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst ovalIndices = FaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start);\n\t\t\t\tregionTriangles.OVAL = fanTriangulate(ovalIndices);\n\n\t\t\t\tinitMaskRenderer();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to initialize:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tdata: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tindices: readonly number[] | 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 indices) {\n\t\t\t\tconst i = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = data[i],\n\t\t\t\t\ty = data[i + 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 += data[i + 2];\n\t\t\t\tavgVisibility += data[i + 3];\n\t\t\t}\n\t\t\treturn [(minX + maxX) / 2, (minY + maxY) / 2, avgZ / indices.length, avgVisibility / indices.length];\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 landmarkIdx = 0; landmarkIdx < STANDARD_LANDMARK_COUNT; ++landmarkIdx) {\n\t\t\t\t\tconst landmark = landmarks[landmarkIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + landmarkIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1;\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, ALL_STANDARD_INDICES);\n\t\t\t\tlandmarksDataArray.set(faceCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4);\n\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, INNER_MOUTH_INDICES);\n\t\t\t\tlandmarksDataArray.set(mouthCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4);\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\tisPartial: true,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction updateMaskTexture(nFaces: number) {\n\t\t\tif (!maskGl || !landmarksDataArray) return;\n\n\t\t\t// Resize and clear.\n\t\t\tmaskCanvas.width = mediaPipeCanvas.width;\n\t\t\tmaskCanvas.height = mediaPipeCanvas.height;\n\t\t\tmaskGl.viewport(0, 0, maskCanvas.width, maskCanvas.height);\n\t\t\tmaskGl.clearColor(0, 0, 0, 0);\n\t\t\tmaskGl.clear(maskGl.COLOR_BUFFER_BIT);\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst b = (faceIdx + 1) / maxFaces;\n\n\t\t\t\t// G channel: face mesh (0.5) and oval (1.0)\n\t\t\t\tdrawTriangles(regionTriangles.TESSELATION, faceIdx, 0, 0.5, b);\n\t\t\t\tdrawTriangles(regionTriangles.OVAL, faceIdx, 0, 1.0, b);\n\n\t\t\t\t// R channel: feature regions\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.LEFT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYE, faceIdx, RED_CHANNEL_VALUES.LEFT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYE, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.OUTER_MOUTH, faceIdx, RED_CHANNEL_VALUES.OUTER_MOUTH, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.INNER_MOUTH, faceIdx, RED_CHANNEL_VALUES.INNER_MOUTH, 0, b);\n\t\t\t}\n\n\t\t\tshaderPad.updateTextures({ u_faceMask: maskCanvas });\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);\n\t\t\tshaderPad.updateUniforms({ u_nFaces: nFaces });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tasync function detectFaces(source: TextureSource) {\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) lastVideoTime = -1;\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait faceLandmarker.setOptions({ runningMode });\n\t\t\t\t}\n\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) return;\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tprocessFaceResults(faceLandmarker.detectForVideo(source, performance.now()));\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) return;\n\t\t\t\t\tprocessFaceResults(faceLandmarker.detect(source));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Detection error:', error);\n\t\t\t}\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', maskCanvas, {\n\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t});\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\tlandmarksDataArray = new Float32Array(LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{ data: landmarksDataArray, width: LANDMARKS_TEXTURE_WIDTH, height: landmarksTextureHeight },\n\t\t\t\t{ internalFormat: gl.RGBA32F, type: gl.FLOAT, minFilter: gl.NEAREST, magFilter: gl.NEAREST }\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t\toptions?.onReady?.();\n\t\t});\n\n\t\tshaderPad.registerHook('initializeTexture', (name: string, source: TextureSource) => {\n\t\t\tif (name === textureName) detectFaces(source);\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) detectFaces(source);\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tfaceLandmarker?.close();\n\t\t\tfaceLandmarker = null;\n\t\t\tif (maskGl && maskProgram) {\n\t\t\t\tmaskGl.deleteProgram(maskProgram);\n\t\t\t\tmaskGl.deleteBuffer(positionBuffer);\n\t\t\t}\n\t\t\tmaskGl = null;\n\t\t\tmaskProgram = null;\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tconst checkAt = (\n\t\t\tregionMin: keyof typeof RED_CHANNEL_VALUES,\n\t\t\tregionMax: keyof typeof RED_CHANNEL_VALUES = regionMin\n\t\t) =>\n\t\t\t`vec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn (mask.r > ${(RED_CHANNEL_VALUES[regionMin] - HALF_GAP).toFixed(4)} && mask.r < ${(\n\t\t\t\tRED_CHANNEL_VALUES[regionMax] + HALF_GAP\n\t\t\t).toFixed(4)}) ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);`;\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}\n\nvec2 leftEyebrowAt(vec2 pos) {\n\t${checkAt('LEFT_EYEBROW')}\n}\n\nvec2 rightEyebrowAt(vec2 pos) {\n\t${checkAt('RIGHT_EYEBROW')}\n}\n\nvec2 leftEyeAt(vec2 pos) {\n\t${checkAt('LEFT_EYE')}\n}\n\nvec2 rightEyeAt(vec2 pos) {\n\t${checkAt('RIGHT_EYE')}\n}\n\nvec2 lipsAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH')}\n}\n\nvec2 outerMouthAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH', 'INNER_MOUTH')}\n}\n\nvec2 innerMouthAt(vec2 pos) {\n\t${checkAt('INNER_MOUTH')}\n}\n\nvec2 faceOvalAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.75 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\n// Includes face mesh and oval.\nvec2 faceAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.25 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\nvec2 eyeAt(vec2 pos) {\n\tvec2 left = leftEyeAt(pos);\n\treturn left.x > 0.0 ? left : rightEyeAt(pos);\n}\n\nvec2 eyebrowAt(vec2 pos) {\n\tvec2 left = leftEyebrowAt(pos);\n\treturn left.x > 0.0 ? left : rightEyebrowAt(pos);\n}\n\nfloat inEyebrow(vec2 pos) { return eyebrowAt(pos).x; }\nfloat inEye(vec2 pos) { return eyeAt(pos).x; }\nfloat inOuterMouth(vec2 pos) { return outerMouthAt(pos).x; }\nfloat inInnerMouth(vec2 pos) { return innerMouthAt(pos).x; }\nfloat inLips(vec2 pos) { return lipsAt(pos).x; }\nfloat inFace(vec2 pos) { return faceAt(pos).x; }`);\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","LANDMARKS_TEXTURE_WIDTH","LEFT_EYEBROW_INDICES","LEFT_EYE_INDICES","RIGHT_EYEBROW_INDICES","RIGHT_EYE_INDICES","OUTER_MOUTH_INDICES","INNER_MOUTH_INDICES","ALL_STANDARD_INDICES","_","LANDMARK_INDICES","REGION_NAMES","nFaceRegions","RED_CHANNEL_VALUES","name","HALF_GAP","fanTriangulate","indices","tris","i","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","runningMode","textureSources","maxFaces","landmarksTextureHeight","landmarksDataArray","mediaPipeCanvas","maskCanvas","maskGl","maskProgram","positionBuffer","colorLocation","regionTriangles","initMaskRenderer","vertexShader","fragmentShader","positionLocation","drawTriangles","triangleIndices","faceIdx","r","g","b","vertices","landmarkIdx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","tesselationConnections","ovalIndices","start","error","calculateBoundingBoxCenter","data","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","x","y","updateLandmarksTexture","faces","nFaces","totalLandmarks","landmarks","landmark","dataIdx","faceCenter","mouthCenter","rowsToUpdate","updateMaskTexture","processFaceResults","result","detectFaces","source","requiredMode","updates","checkAt","regionMin","regionMax"]}
@@ -1,14 +1,14 @@
1
- var M=478,oe=2,A=M+oe,I=512,$=[336,296,334,293,300,276,283,282,295,285],K=[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],z=[70,63,105,66,107,55,65,52,53,46],X=[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],j=[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],Y=[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95],ie=Array.from({length:M},(m,l)=>l),O={LEFT_EYEBROW:$,LEFT_EYE:K,LEFT_EYE_CENTER:473,RIGHT_EYEBROW:z,RIGHT_EYE:X,RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_MOUTH:j,INNER_MOUTH:Y,FACE_CENTER:M,MOUTH_CENTER:M+1},q=["BACKGROUND","LEFT_EYEBROW","RIGHT_EYEBROW","LEFT_EYE","RIGHT_EYE","OUTER_MOUTH","INNER_MOUTH"],Z=q.length-1,R=Object.fromEntries(q.map((m,l)=>[m,l/Z])),V=.5/Z;function v(m){let l=[];for(let i=1;i<m.length-1;++i)l.push(m[0],m[i],m[i+1]);return l}function se(m){let{textureName:l,options:i}=m,J="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(u,Q){let{injectGLSL:ee,gl:h}=Q,p=null,k=null,S=-1,C="VIDEO",b=new Map,H=i?.maxFaces??1,y=0,o=null,U=new OffscreenCanvas(1,1),F=new OffscreenCanvas(1,1),t=null,f=null,D=null,w=null,c={LEFT_EYEBROW:v($),RIGHT_EYEBROW:v(z),LEFT_EYE:v(K),RIGHT_EYE:v(X),OUTER_MOUTH:v(j),INNER_MOUTH:v(Y),TESSELATION:[],OVAL:[]};function te(){if(t=F.getContext("webgl2",{antialias:!1,preserveDrawingBuffer:!0}),!t)throw new Error("Failed to get WebGL2 context for mask");let n=t.createShader(t.VERTEX_SHADER);t.shaderSource(n,`#version 300 es
1
+ var M=478,ie=2,R=M+ie,I=512,K=[336,296,334,293,300,276,283,282,295,285],z=[362,398,384,385,386,387,388,466,263,249,390,373,374,380,381,382],X=[70,63,105,66,107,55,65,52,53,46],j=[33,246,161,160,159,158,157,173,133,155,154,153,145,144,163,7],q=[61,185,40,39,37,0,267,269,270,409,291,375,321,405,314,17,84,181,91,146],Y=[78,191,80,81,82,13,312,311,310,415,308,324,318,402,317,14,87,178,88,95],ce=Array.from({length:M},(f,c)=>c),O={LEFT_EYEBROW:K,LEFT_EYE:z,LEFT_EYE_CENTER:473,RIGHT_EYEBROW:X,RIGHT_EYE:j,RIGHT_EYE_CENTER:468,NOSE_TIP:4,OUTER_MOUTH:q,INNER_MOUTH:Y,FACE_CENTER:M,MOUTH_CENTER:M+1},Z=["BACKGROUND","LEFT_EYEBROW","RIGHT_EYEBROW","LEFT_EYE","RIGHT_EYE","OUTER_MOUTH","INNER_MOUTH"],J=Z.length-1,A=Object.fromEntries(Z.map((f,c)=>[f,c/J])),$=.5/J;function v(f){let c=[];for(let i=1;i<f.length-1;++i)c.push(f[0],f[i],f[i+1]);return c}function se(f){let{textureName:c,options:i}=f,Q="https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";return function(s,ee){let{injectGLSL:te,gl:g}=ee,p=null,C=null,S=-1,k="VIDEO",b=new Map,H=i?.maxFaces??1,y=0,r=null,U=new OffscreenCanvas(1,1),F=new OffscreenCanvas(1,1),t=null,u=null,D=null,w=null,E={LEFT_EYEBROW:v(K),RIGHT_EYEBROW:v(X),LEFT_EYE:v(z),RIGHT_EYE:v(j),OUTER_MOUTH:v(q),INNER_MOUTH:v(Y),TESSELATION:[],OVAL:[]};function ne(){if(t=F.getContext("webgl2",{antialias:!1,preserveDrawingBuffer:!0}),!t)throw new Error("Failed to get WebGL2 context for mask");let e=t.createShader(t.VERTEX_SHADER);t.shaderSource(e,`#version 300 es
2
2
  in vec2 a_pos;
3
3
  void main() {
4
4
  gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
5
- }`),t.compileShader(n);let e=t.createShader(t.FRAGMENT_SHADER);t.shaderSource(e,`#version 300 es
5
+ }`),t.compileShader(e);let n=t.createShader(t.FRAGMENT_SHADER);t.shaderSource(n,`#version 300 es
6
6
  precision mediump float;
7
7
  uniform vec4 u_color;
8
8
  out vec4 outColor;
9
- void main() { outColor = u_color; }`),t.compileShader(e),f=t.createProgram(),t.attachShader(f,n),t.attachShader(f,e),t.linkProgram(f),t.deleteShader(n),t.deleteShader(e),D=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,D);let a=t.getAttribLocation(f,"a_pos");t.enableVertexAttribArray(a),t.vertexAttribPointer(a,2,t.FLOAT,!1,0,0),w=t.getUniformLocation(f,"u_color"),t.useProgram(f),t.enable(t.BLEND),t.blendEquation(t.MAX)}function d(n,e,a,s,r){if(!t||!o||n.length===0)return;let _=new Float32Array(n.length*2);for(let E=0;E<n.length;++E){let N=(e*A+n[E])*4;_[E*2]=o[N],_[E*2+1]=o[N+1]}t.bufferData(t.ARRAY_BUFFER,_,t.DYNAMIC_DRAW),t.uniform4f(w,a,s,r,1),t.drawArrays(t.TRIANGLES,0,n.length)}async function ne(){try{let{FilesetResolver:n,FaceLandmarker:e}=await import("@mediapipe/tasks-vision");k=await n.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),p=await e.createFromOptions(k,{baseOptions:{modelAssetPath:i?.modelPath||J,delegate:"GPU"},canvas:U,runningMode:C,numFaces:i?.maxFaces??1,minFaceDetectionConfidence:i?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:i?.minFacePresenceConfidence??.5,minTrackingConfidence:i?.minTrackingConfidence??.5,outputFaceBlendshapes:i?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:i?.outputFacialTransformationMatrixes??!1});let a=e.FACE_LANDMARKS_TESSELATION;c.TESSELATION=[];for(let r=0;r<a.length-2;r+=3)c.TESSELATION.push(a[r].start,a[r+1].start,a[r+2].start);let s=e.FACE_LANDMARKS_FACE_OVAL.map(({start:r})=>r);c.OVAL=v(s),te()}catch(n){throw console.error("[Face Plugin] Failed to initialize:",n),n}}function G(n,e,a){let s=1/0,r=-1/0,_=1/0,E=-1/0,N=0,x=0;for(let g of a){let T=(e*A+g)*4,W=n[T],P=n[T+1];s=Math.min(s,W),r=Math.max(r,W),_=Math.min(_,P),E=Math.max(E,P),N+=n[T+2],x+=n[T+3]}return[(s+r)/2,(_+E)/2,N/a.length,x/a.length]}function ae(n){if(!o)return;let e=n.length,a=e*A;for(let r=0;r<e;++r){let _=n[r];for(let x=0;x<M;++x){let g=_[x],T=(r*A+x)*4;o[T]=g.x,o[T+1]=1-g.y,o[T+2]=g.z??0,o[T+3]=g.visibility??1}let E=G(o,r,ie);o.set(E,(r*A+O.FACE_CENTER)*4);let N=G(o,r,Y);o.set(N,(r*A+O.MOUTH_CENTER)*4)}let s=Math.ceil(a/I);u.updateTextures({u_faceLandmarksTex:{data:o,width:I,height:s,isPartial:!0}})}function re(n){if(!(!t||!o)){F.width=U.width,F.height=U.height,t.viewport(0,0,F.width,F.height),t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT);for(let e=0;e<n;++e){let a=(e+1)/H;d(c.TESSELATION,e,0,.5,a),d(c.OVAL,e,0,1,a),d(c.LEFT_EYEBROW,e,R.LEFT_EYEBROW,0,a),d(c.RIGHT_EYEBROW,e,R.RIGHT_EYEBROW,0,a),d(c.LEFT_EYE,e,R.LEFT_EYE,0,a),d(c.RIGHT_EYE,e,R.RIGHT_EYE,0,a),d(c.OUTER_MOUTH,e,R.OUTER_MOUTH,0,a),d(c.INNER_MOUTH,e,R.INNER_MOUTH,0,a)}u.updateTextures({u_faceMask:F})}}function B(n){if(!n.faceLandmarks||!o)return;let e=n.faceLandmarks.length;ae(n.faceLandmarks),re(e),u.updateUniforms({u_nFaces:e}),i?.onResults?.(n)}u.registerHook("init",async()=>{u.initializeTexture("u_faceMask",F,{minFilter:h.NEAREST,magFilter:h.NEAREST}),u.initializeUniform("u_maxFaces","int",H),u.initializeUniform("u_nFaces","int",0);let n=H*A;y=Math.ceil(n/I),o=new Float32Array(I*y*4),u.initializeTexture("u_faceLandmarksTex",{data:o,width:I,height:y},{internalFormat:h.RGBA32F,type:h.FLOAT,minFilter:h.NEAREST,magFilter:h.NEAREST}),await ne()}),u.registerHook("updateTextures",async n=>{let e=n[l];if(!(!e||(b.get(l)!==e&&(S=-1),b.set(l,e),!p)))try{let s=e instanceof HTMLVideoElement?"VIDEO":"IMAGE";if(C!==s&&(C=s,await p.setOptions({runningMode:C})),e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;e.currentTime!==S&&(S=e.currentTime,B(p.detectForVideo(e,performance.now())))}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;B(p.detect(e))}}catch(s){console.error("[Face Plugin] Detection error:",s)}}),u.registerHook("destroy",()=>{p?.close(),p=null,t&&f&&(t.deleteProgram(f),t.deleteBuffer(D)),t=null,f=null,k=null,b.clear(),o=null});let L=(n,e=n)=>`vec4 mask = texture(u_faceMask, pos);
9
+ void main() { outColor = u_color; }`),t.compileShader(n),u=t.createProgram(),t.attachShader(u,e),t.attachShader(u,n),t.linkProgram(u),t.deleteShader(e),t.deleteShader(n),D=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,D);let a=t.getAttribLocation(u,"a_pos");t.enableVertexAttribArray(a),t.vertexAttribPointer(a,2,t.FLOAT,!1,0,0),w=t.getUniformLocation(u,"u_color"),t.useProgram(u),t.enable(t.BLEND),t.blendEquation(t.MAX)}function d(e,n,a,m,o){if(!t||!r||e.length===0)return;let _=new Float32Array(e.length*2);for(let l=0;l<e.length;++l){let N=(n*R+e[l])*4;_[l*2]=r[N],_[l*2+1]=r[N+1]}t.bufferData(t.ARRAY_BUFFER,_,t.DYNAMIC_DRAW),t.uniform4f(w,a,m,o,1),t.drawArrays(t.TRIANGLES,0,e.length)}async function ae(){try{let{FilesetResolver:e,FaceLandmarker:n}=await import("@mediapipe/tasks-vision");C=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),p=await n.createFromOptions(C,{baseOptions:{modelAssetPath:i?.modelPath||Q,delegate:"GPU"},canvas:U,runningMode:k,numFaces:i?.maxFaces??1,minFaceDetectionConfidence:i?.minFaceDetectionConfidence??.5,minFacePresenceConfidence:i?.minFacePresenceConfidence??.5,minTrackingConfidence:i?.minTrackingConfidence??.5,outputFaceBlendshapes:i?.outputFaceBlendshapes??!1,outputFacialTransformationMatrixes:i?.outputFacialTransformationMatrixes??!1});let a=n.FACE_LANDMARKS_TESSELATION;E.TESSELATION=[];for(let o=0;o<a.length-2;o+=3)E.TESSELATION.push(a[o].start,a[o+1].start,a[o+2].start);let m=n.FACE_LANDMARKS_FACE_OVAL.map(({start:o})=>o);E.OVAL=v(m),ne()}catch(e){throw console.error("[Face Plugin] Failed to initialize:",e),e}}function G(e,n,a){let m=1/0,o=-1/0,_=1/0,l=-1/0,N=0,x=0;for(let h of a){let T=(n*R+h)*4,P=e[T],V=e[T+1];m=Math.min(m,P),o=Math.max(o,P),_=Math.min(_,V),l=Math.max(l,V),N+=e[T+2],x+=e[T+3]}return[(m+o)/2,(_+l)/2,N/a.length,x/a.length]}function oe(e){if(!r)return;let n=e.length,a=n*R;for(let o=0;o<n;++o){let _=e[o];for(let x=0;x<M;++x){let h=_[x],T=(o*R+x)*4;r[T]=h.x,r[T+1]=1-h.y,r[T+2]=h.z??0,r[T+3]=h.visibility??1}let l=G(r,o,ce);r.set(l,(o*R+O.FACE_CENTER)*4);let N=G(r,o,Y);r.set(N,(o*R+O.MOUTH_CENTER)*4)}let m=Math.ceil(a/I);s.updateTextures({u_faceLandmarksTex:{data:r,width:I,height:m,isPartial:!0}})}function re(e){if(!(!t||!r)){F.width=U.width,F.height=U.height,t.viewport(0,0,F.width,F.height),t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT);for(let n=0;n<e;++n){let a=(n+1)/H;d(E.TESSELATION,n,0,.5,a),d(E.OVAL,n,0,1,a),d(E.LEFT_EYEBROW,n,A.LEFT_EYEBROW,0,a),d(E.RIGHT_EYEBROW,n,A.RIGHT_EYEBROW,0,a),d(E.LEFT_EYE,n,A.LEFT_EYE,0,a),d(E.RIGHT_EYE,n,A.RIGHT_EYE,0,a),d(E.OUTER_MOUTH,n,A.OUTER_MOUTH,0,a),d(E.INNER_MOUTH,n,A.INNER_MOUTH,0,a)}s.updateTextures({u_faceMask:F})}}function B(e){if(!e.faceLandmarks||!r)return;let n=e.faceLandmarks.length;oe(e.faceLandmarks),re(n),s.updateUniforms({u_nFaces:n}),i?.onResults?.(e)}async function W(e){if(b.get(c)!==e&&(S=-1),b.set(c,e),!!p)try{let a=e instanceof HTMLVideoElement?"VIDEO":"IMAGE";if(k!==a&&(k=a,await p.setOptions({runningMode:k})),e instanceof HTMLVideoElement){if(e.videoWidth===0||e.videoHeight===0||e.readyState<2)return;e.currentTime!==S&&(S=e.currentTime,B(p.detectForVideo(e,performance.now())))}else if(e instanceof HTMLImageElement||e instanceof HTMLCanvasElement){if(e.width===0||e.height===0)return;B(p.detect(e))}}catch(a){console.error("[Face Plugin] Detection error:",a)}}s.registerHook("init",async()=>{s.initializeTexture("u_faceMask",F,{minFilter:g.NEAREST,magFilter:g.NEAREST}),s.initializeUniform("u_maxFaces","int",H),s.initializeUniform("u_nFaces","int",0);let e=H*R;y=Math.ceil(e/I),r=new Float32Array(I*y*4),s.initializeTexture("u_faceLandmarksTex",{data:r,width:I,height:y},{internalFormat:g.RGBA32F,type:g.FLOAT,minFilter:g.NEAREST,magFilter:g.NEAREST}),await ae(),i?.onReady?.()}),s.registerHook("initializeTexture",(e,n)=>{e===c&&W(n)}),s.registerHook("updateTextures",e=>{let n=e[c];n&&W(n)}),s.registerHook("destroy",()=>{p?.close(),p=null,t&&u&&(t.deleteProgram(u),t.deleteBuffer(D)),t=null,u=null,C=null,b.clear(),r=null});let L=(e,n=e)=>`vec4 mask = texture(u_faceMask, pos);
10
10
  float faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;
11
- return (mask.r > ${(R[n]-V).toFixed(4)} && mask.r < ${(R[e]+V).toFixed(4)}) ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);`;ee(`
11
+ return (mask.r > ${(A[e]-$).toFixed(4)} && mask.r < ${(A[n]+$).toFixed(4)}) ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);`;te(`
12
12
  uniform int u_maxFaces;
13
13
  uniform int u_nFaces;
14
14
  uniform sampler2D u_faceLandmarksTex;
@@ -21,7 +21,7 @@ uniform sampler2D u_faceMask;
21
21
  #define FACE_LANDMARK_MOUTH_CENTER ${O.MOUTH_CENTER}
22
22
 
23
23
  vec4 faceLandmark(int faceIndex, int landmarkIndex) {
24
- int i = faceIndex * ${A} + landmarkIndex;
24
+ int i = faceIndex * ${R} + landmarkIndex;
25
25
  int x = i % ${I};
26
26
  int y = i / ${I};
27
27
  return texelFetch(u_faceLandmarksTex, ivec2(x, y), 0);
@@ -83,5 +83,5 @@ float inEye(vec2 pos) { return eyeAt(pos).x; }
83
83
  float inOuterMouth(vec2 pos) { return outerMouthAt(pos).x; }
84
84
  float inInnerMouth(vec2 pos) { return innerMouthAt(pos).x; }
85
85
  float inLips(vec2 pos) { return lipsAt(pos).x; }
86
- float inFace(vec2 pos) { return faceAt(pos).x; }`)}}var ce=se;export{ce as default};
86
+ float inFace(vec2 pos) { return faceAt(pos).x; }`)}}var Ee=se;export{Ee as default};
87
87
  //# sourceMappingURL=face.mjs.map
@@ -1 +1 @@
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 LANDMARKS_TEXTURE_WIDTH = 512;\n\nconst LEFT_EYEBROW_INDICES = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285] as const;\nconst LEFT_EYE_INDICES = [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382] as const;\nconst RIGHT_EYEBROW_INDICES = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46] as const;\nconst RIGHT_EYE_INDICES = [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7] as const;\nconst OUTER_MOUTH_INDICES = [\n\t61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146,\n] as const;\nconst INNER_MOUTH_INDICES = [\n\t78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95,\n] as const;\nconst ALL_STANDARD_INDICES = Array.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i);\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: LEFT_EYEBROW_INDICES,\n\tLEFT_EYE: LEFT_EYE_INDICES,\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: RIGHT_EYEBROW_INDICES,\n\tRIGHT_EYE: RIGHT_EYE_INDICES,\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_MOUTH: OUTER_MOUTH_INDICES,\n\tINNER_MOUTH: INNER_MOUTH_INDICES,\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nconst REGION_NAMES = [\n\t'BACKGROUND',\n\t'LEFT_EYEBROW',\n\t'RIGHT_EYEBROW',\n\t'LEFT_EYE',\n\t'RIGHT_EYE',\n\t'OUTER_MOUTH',\n\t'INNER_MOUTH',\n] as const;\nconst nFaceRegions = REGION_NAMES.length - 1;\nconst RED_CHANNEL_VALUES = Object.fromEntries(REGION_NAMES.map((name, i) => [name, i / nFaceRegions])) as Record<\n\t(typeof REGION_NAMES)[number],\n\tnumber\n>;\nconst HALF_GAP = 0.5 / nFaceRegions;\n\nfunction fanTriangulate(indices: readonly number[]): number[] {\n\tconst tris: number[] = [];\n\tfor (let i = 1; i < indices.length - 1; ++i) {\n\t\ttris.push(indices[0], indices[i], indices[i + 1]);\n\t}\n\treturn tris;\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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst mediaPipeCanvas = new OffscreenCanvas(1, 1);\n\t\tconst maskCanvas = new OffscreenCanvas(1, 1);\n\n\t\t// WebGL resources for triangle rendering (no antialiasing).\n\t\tlet maskGl: WebGL2RenderingContext | null = null;\n\t\tlet maskProgram: WebGLProgram | null = null;\n\t\tlet positionBuffer: WebGLBuffer | null = null;\n\t\tlet colorLocation: WebGLUniformLocation | null = null;\n\n\t\tconst regionTriangles: Record<string, number[]> = {\n\t\t\tLEFT_EYEBROW: fanTriangulate(LEFT_EYEBROW_INDICES),\n\t\t\tRIGHT_EYEBROW: fanTriangulate(RIGHT_EYEBROW_INDICES),\n\t\t\tLEFT_EYE: fanTriangulate(LEFT_EYE_INDICES),\n\t\t\tRIGHT_EYE: fanTriangulate(RIGHT_EYE_INDICES),\n\t\t\tOUTER_MOUTH: fanTriangulate(OUTER_MOUTH_INDICES),\n\t\t\tINNER_MOUTH: fanTriangulate(INNER_MOUTH_INDICES),\n\t\t\t// Populated after FaceLandmarker loads.\n\t\t\tTESSELATION: [],\n\t\t\tOVAL: [],\n\t\t};\n\n\t\tfunction initMaskRenderer() {\n\t\t\tmaskGl = maskCanvas.getContext('webgl2', { antialias: false, preserveDrawingBuffer: true });\n\t\t\tif (!maskGl) throw new Error('Failed to get WebGL2 context for mask');\n\n\t\t\tconst vertexShader = maskGl.createShader(maskGl.VERTEX_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tvertexShader,\n\t\t\t\t`#version 300 es\nin vec2 a_pos;\nvoid main() {\n\tgl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);\n}`\n\t\t\t);\n\t\t\tmaskGl.compileShader(vertexShader);\n\n\t\t\tconst fragmentShader = maskGl.createShader(maskGl.FRAGMENT_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tfragmentShader,\n\t\t\t\t`#version 300 es\nprecision mediump float;\nuniform vec4 u_color;\nout vec4 outColor;\nvoid main() { outColor = u_color; }`\n\t\t\t);\n\t\t\tmaskGl.compileShader(fragmentShader);\n\n\t\t\tmaskProgram = maskGl.createProgram()!;\n\t\t\tmaskGl.attachShader(maskProgram, vertexShader);\n\t\t\tmaskGl.attachShader(maskProgram, fragmentShader);\n\t\t\tmaskGl.linkProgram(maskProgram);\n\t\t\tmaskGl.deleteShader(vertexShader);\n\t\t\tmaskGl.deleteShader(fragmentShader);\n\n\t\t\tpositionBuffer = maskGl.createBuffer();\n\t\t\tmaskGl.bindBuffer(maskGl.ARRAY_BUFFER, positionBuffer);\n\t\t\tconst positionLocation = maskGl.getAttribLocation(maskProgram, 'a_pos');\n\t\t\tmaskGl.enableVertexAttribArray(positionLocation);\n\t\t\tmaskGl.vertexAttribPointer(positionLocation, 2, maskGl.FLOAT, false, 0, 0);\n\n\t\t\tcolorLocation = maskGl.getUniformLocation(maskProgram, 'u_color');\n\t\t\tmaskGl.useProgram(maskProgram);\n\n\t\t\t// Enable blending to handle overlapping faces (set once, never disabled).\n\t\t\tmaskGl.enable(maskGl.BLEND);\n\t\t\tmaskGl.blendEquation(maskGl.MAX);\n\t\t}\n\n\t\tfunction drawTriangles(triangleIndices: number[], faceIdx: number, r: number, g: number, b: number) {\n\t\t\tif (!maskGl || !landmarksDataArray || triangleIndices.length === 0) return;\n\n\t\t\tconst vertices = new Float32Array(triangleIndices.length * 2);\n\t\t\tfor (let i = 0; i < triangleIndices.length; ++i) {\n\t\t\t\tconst landmarkIdx = (faceIdx * LANDMARK_COUNT + triangleIndices[i]) * 4;\n\t\t\t\tvertices[i * 2] = landmarksDataArray[landmarkIdx];\n\t\t\t\tvertices[i * 2 + 1] = landmarksDataArray[landmarkIdx + 1];\n\t\t\t}\n\n\t\t\tmaskGl.bufferData(maskGl.ARRAY_BUFFER, vertices, maskGl.DYNAMIC_DRAW);\n\t\t\tmaskGl.uniform4f(colorLocation, r, g, b, 1.0);\n\t\t\tmaskGl.drawArrays(maskGl.TRIANGLES, 0, triangleIndices.length);\n\t\t}\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: mediaPipeCanvas,\n\t\t\t\t\trunningMode: runningMode,\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\n\t\t\t\tconst tesselationConnections = FaceLandmarker.FACE_LANDMARKS_TESSELATION;\n\t\t\t\tregionTriangles.TESSELATION = [];\n\t\t\t\tfor (let i = 0; i < tesselationConnections.length - 2; i += 3) {\n\t\t\t\t\tregionTriangles.TESSELATION.push(\n\t\t\t\t\t\ttesselationConnections[i].start,\n\t\t\t\t\t\ttesselationConnections[i + 1].start,\n\t\t\t\t\t\ttesselationConnections[i + 2].start\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst ovalIndices = FaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start);\n\t\t\t\tregionTriangles.OVAL = fanTriangulate(ovalIndices);\n\n\t\t\t\tinitMaskRenderer();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to initialize:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tdata: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tindices: readonly number[] | 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 indices) {\n\t\t\t\tconst i = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = data[i],\n\t\t\t\t\ty = data[i + 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 += data[i + 2];\n\t\t\t\tavgVisibility += data[i + 3];\n\t\t\t}\n\t\t\treturn [(minX + maxX) / 2, (minY + maxY) / 2, avgZ / indices.length, avgVisibility / indices.length];\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 landmarkIdx = 0; landmarkIdx < STANDARD_LANDMARK_COUNT; ++landmarkIdx) {\n\t\t\t\t\tconst landmark = landmarks[landmarkIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + landmarkIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1;\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, ALL_STANDARD_INDICES);\n\t\t\t\tlandmarksDataArray.set(faceCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4);\n\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, INNER_MOUTH_INDICES);\n\t\t\t\tlandmarksDataArray.set(mouthCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4);\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\tisPartial: true,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction updateMaskTexture(nFaces: number) {\n\t\t\tif (!maskGl || !landmarksDataArray) return;\n\n\t\t\t// Resize and clear.\n\t\t\tmaskCanvas.width = mediaPipeCanvas.width;\n\t\t\tmaskCanvas.height = mediaPipeCanvas.height;\n\t\t\tmaskGl.viewport(0, 0, maskCanvas.width, maskCanvas.height);\n\t\t\tmaskGl.clearColor(0, 0, 0, 0);\n\t\t\tmaskGl.clear(maskGl.COLOR_BUFFER_BIT);\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst b = (faceIdx + 1) / maxFaces;\n\n\t\t\t\t// G channel: face mesh (0.5) and oval (1.0)\n\t\t\t\tdrawTriangles(regionTriangles.TESSELATION, faceIdx, 0, 0.5, b);\n\t\t\t\tdrawTriangles(regionTriangles.OVAL, faceIdx, 0, 1.0, b);\n\n\t\t\t\t// R channel: feature regions\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.LEFT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYE, faceIdx, RED_CHANNEL_VALUES.LEFT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYE, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.OUTER_MOUTH, faceIdx, RED_CHANNEL_VALUES.OUTER_MOUTH, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.INNER_MOUTH, faceIdx, RED_CHANNEL_VALUES.INNER_MOUTH, 0, b);\n\t\t\t}\n\n\t\t\tshaderPad.updateTextures({ u_faceMask: maskCanvas });\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);\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', maskCanvas, {\n\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t});\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\tlandmarksDataArray = new Float32Array(LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{ data: landmarksDataArray, width: LANDMARKS_TEXTURE_WIDTH, height: landmarksTextureHeight },\n\t\t\t\t{ internalFormat: gl.RGBA32F, type: gl.FLOAT, minFilter: gl.NEAREST, magFilter: gl.NEAREST }\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', async (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) lastVideoTime = -1;\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait faceLandmarker.setOptions({ runningMode });\n\t\t\t\t}\n\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) return;\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tprocessFaceResults(faceLandmarker.detectForVideo(source, performance.now()));\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) return;\n\t\t\t\t\tprocessFaceResults(faceLandmarker.detect(source));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Detection error:', error);\n\t\t\t}\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tfaceLandmarker?.close();\n\t\t\tfaceLandmarker = null;\n\t\t\tif (maskGl && maskProgram) {\n\t\t\t\tmaskGl.deleteProgram(maskProgram);\n\t\t\t\tmaskGl.deleteBuffer(positionBuffer);\n\t\t\t}\n\t\t\tmaskGl = null;\n\t\t\tmaskProgram = null;\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tconst checkAt = (\n\t\t\tregionMin: keyof typeof RED_CHANNEL_VALUES,\n\t\t\tregionMax: keyof typeof RED_CHANNEL_VALUES = regionMin\n\t\t) =>\n\t\t\t`vec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn (mask.r > ${(RED_CHANNEL_VALUES[regionMin] - HALF_GAP).toFixed(4)} && mask.r < ${(\n\t\t\t\tRED_CHANNEL_VALUES[regionMax] + HALF_GAP\n\t\t\t).toFixed(4)}) ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);`;\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}\n\nvec2 leftEyebrowAt(vec2 pos) {\n\t${checkAt('LEFT_EYEBROW')}\n}\n\nvec2 rightEyebrowAt(vec2 pos) {\n\t${checkAt('RIGHT_EYEBROW')}\n}\n\nvec2 leftEyeAt(vec2 pos) {\n\t${checkAt('LEFT_EYE')}\n}\n\nvec2 rightEyeAt(vec2 pos) {\n\t${checkAt('RIGHT_EYE')}\n}\n\nvec2 lipsAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH')}\n}\n\nvec2 outerMouthAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH', 'INNER_MOUTH')}\n}\n\nvec2 innerMouthAt(vec2 pos) {\n\t${checkAt('INNER_MOUTH')}\n}\n\nvec2 faceOvalAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.75 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\n// Includes face mesh and oval.\nvec2 faceAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.25 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\nvec2 eyeAt(vec2 pos) {\n\tvec2 left = leftEyeAt(pos);\n\treturn left.x > 0.0 ? left : rightEyeAt(pos);\n}\n\nvec2 eyebrowAt(vec2 pos) {\n\tvec2 left = leftEyebrowAt(pos);\n\treturn left.x > 0.0 ? left : rightEyebrowAt(pos);\n}\n\nfloat inEyebrow(vec2 pos) { return eyebrowAt(pos).x; }\nfloat inEye(vec2 pos) { return eyeAt(pos).x; }\nfloat inOuterMouth(vec2 pos) { return outerMouthAt(pos).x; }\nfloat inInnerMouth(vec2 pos) { return innerMouthAt(pos).x; }\nfloat inLips(vec2 pos) { return lipsAt(pos).x; }\nfloat inFace(vec2 pos) { return faceAt(pos).x; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARKS_TEXTURE_WIDTH","LEFT_EYEBROW_INDICES","LEFT_EYE_INDICES","RIGHT_EYEBROW_INDICES","RIGHT_EYE_INDICES","OUTER_MOUTH_INDICES","INNER_MOUTH_INDICES","ALL_STANDARD_INDICES","_","i","LANDMARK_INDICES","REGION_NAMES","nFaceRegions","RED_CHANNEL_VALUES","name","HALF_GAP","fanTriangulate","indices","tris","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","runningMode","textureSources","maxFaces","landmarksTextureHeight","landmarksDataArray","mediaPipeCanvas","maskCanvas","maskGl","maskProgram","positionBuffer","colorLocation","regionTriangles","initMaskRenderer","vertexShader","fragmentShader","positionLocation","drawTriangles","triangleIndices","faceIdx","r","g","b","vertices","landmarkIdx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","tesselationConnections","ovalIndices","start","error","calculateBoundingBoxCenter","data","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","x","y","updateLandmarksTexture","faces","nFaces","totalLandmarks","landmarks","landmark","dataIdx","faceCenter","mouthCenter","rowsToUpdate","updateMaskTexture","processFaceResults","result","updates","source","requiredMode","checkAt","regionMin","regionMax","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\tonReady?: () => void;\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 LANDMARKS_TEXTURE_WIDTH = 512;\n\nconst LEFT_EYEBROW_INDICES = [336, 296, 334, 293, 300, 276, 283, 282, 295, 285] as const;\nconst LEFT_EYE_INDICES = [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382] as const;\nconst RIGHT_EYEBROW_INDICES = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46] as const;\nconst RIGHT_EYE_INDICES = [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7] as const;\nconst OUTER_MOUTH_INDICES = [\n\t61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 375, 321, 405, 314, 17, 84, 181, 91, 146,\n] as const;\nconst INNER_MOUTH_INDICES = [\n\t78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308, 324, 318, 402, 317, 14, 87, 178, 88, 95,\n] as const;\nconst ALL_STANDARD_INDICES = Array.from({ length: STANDARD_LANDMARK_COUNT }, (_, i) => i);\nconst LANDMARK_INDICES = {\n\tLEFT_EYEBROW: LEFT_EYEBROW_INDICES,\n\tLEFT_EYE: LEFT_EYE_INDICES,\n\tLEFT_EYE_CENTER: 473,\n\tRIGHT_EYEBROW: RIGHT_EYEBROW_INDICES,\n\tRIGHT_EYE: RIGHT_EYE_INDICES,\n\tRIGHT_EYE_CENTER: 468,\n\tNOSE_TIP: 4,\n\tOUTER_MOUTH: OUTER_MOUTH_INDICES,\n\tINNER_MOUTH: INNER_MOUTH_INDICES,\n\t// Custom landmarks.\n\tFACE_CENTER: STANDARD_LANDMARK_COUNT,\n\tMOUTH_CENTER: STANDARD_LANDMARK_COUNT + 1,\n};\n\nconst REGION_NAMES = [\n\t'BACKGROUND',\n\t'LEFT_EYEBROW',\n\t'RIGHT_EYEBROW',\n\t'LEFT_EYE',\n\t'RIGHT_EYE',\n\t'OUTER_MOUTH',\n\t'INNER_MOUTH',\n] as const;\nconst nFaceRegions = REGION_NAMES.length - 1;\nconst RED_CHANNEL_VALUES = Object.fromEntries(REGION_NAMES.map((name, i) => [name, i / nFaceRegions])) as Record<\n\t(typeof REGION_NAMES)[number],\n\tnumber\n>;\nconst HALF_GAP = 0.5 / nFaceRegions;\n\nfunction fanTriangulate(indices: readonly number[]): number[] {\n\tconst tris: number[] = [];\n\tfor (let i = 1; i < indices.length - 1; ++i) {\n\t\ttris.push(indices[0], indices[i], indices[i + 1]);\n\t}\n\treturn tris;\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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\n\t\tconst textureSources = new Map<string, TextureSource>();\n\t\tconst maxFaces = options?.maxFaces ?? 1;\n\n\t\tlet landmarksTextureHeight = 0;\n\t\tlet landmarksDataArray: Float32Array | null = null;\n\n\t\tconst mediaPipeCanvas = new OffscreenCanvas(1, 1);\n\t\tconst maskCanvas = new OffscreenCanvas(1, 1);\n\n\t\t// WebGL resources for triangle rendering (no antialiasing).\n\t\tlet maskGl: WebGL2RenderingContext | null = null;\n\t\tlet maskProgram: WebGLProgram | null = null;\n\t\tlet positionBuffer: WebGLBuffer | null = null;\n\t\tlet colorLocation: WebGLUniformLocation | null = null;\n\n\t\tconst regionTriangles: Record<string, number[]> = {\n\t\t\tLEFT_EYEBROW: fanTriangulate(LEFT_EYEBROW_INDICES),\n\t\t\tRIGHT_EYEBROW: fanTriangulate(RIGHT_EYEBROW_INDICES),\n\t\t\tLEFT_EYE: fanTriangulate(LEFT_EYE_INDICES),\n\t\t\tRIGHT_EYE: fanTriangulate(RIGHT_EYE_INDICES),\n\t\t\tOUTER_MOUTH: fanTriangulate(OUTER_MOUTH_INDICES),\n\t\t\tINNER_MOUTH: fanTriangulate(INNER_MOUTH_INDICES),\n\t\t\t// Populated after FaceLandmarker loads.\n\t\t\tTESSELATION: [],\n\t\t\tOVAL: [],\n\t\t};\n\n\t\tfunction initMaskRenderer() {\n\t\t\tmaskGl = maskCanvas.getContext('webgl2', { antialias: false, preserveDrawingBuffer: true });\n\t\t\tif (!maskGl) throw new Error('Failed to get WebGL2 context for mask');\n\n\t\t\tconst vertexShader = maskGl.createShader(maskGl.VERTEX_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tvertexShader,\n\t\t\t\t`#version 300 es\nin vec2 a_pos;\nvoid main() {\n\tgl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);\n}`\n\t\t\t);\n\t\t\tmaskGl.compileShader(vertexShader);\n\n\t\t\tconst fragmentShader = maskGl.createShader(maskGl.FRAGMENT_SHADER)!;\n\t\t\tmaskGl.shaderSource(\n\t\t\t\tfragmentShader,\n\t\t\t\t`#version 300 es\nprecision mediump float;\nuniform vec4 u_color;\nout vec4 outColor;\nvoid main() { outColor = u_color; }`\n\t\t\t);\n\t\t\tmaskGl.compileShader(fragmentShader);\n\n\t\t\tmaskProgram = maskGl.createProgram()!;\n\t\t\tmaskGl.attachShader(maskProgram, vertexShader);\n\t\t\tmaskGl.attachShader(maskProgram, fragmentShader);\n\t\t\tmaskGl.linkProgram(maskProgram);\n\t\t\tmaskGl.deleteShader(vertexShader);\n\t\t\tmaskGl.deleteShader(fragmentShader);\n\n\t\t\tpositionBuffer = maskGl.createBuffer();\n\t\t\tmaskGl.bindBuffer(maskGl.ARRAY_BUFFER, positionBuffer);\n\t\t\tconst positionLocation = maskGl.getAttribLocation(maskProgram, 'a_pos');\n\t\t\tmaskGl.enableVertexAttribArray(positionLocation);\n\t\t\tmaskGl.vertexAttribPointer(positionLocation, 2, maskGl.FLOAT, false, 0, 0);\n\n\t\t\tcolorLocation = maskGl.getUniformLocation(maskProgram, 'u_color');\n\t\t\tmaskGl.useProgram(maskProgram);\n\n\t\t\t// Enable blending to handle overlapping faces (set once, never disabled).\n\t\t\tmaskGl.enable(maskGl.BLEND);\n\t\t\tmaskGl.blendEquation(maskGl.MAX);\n\t\t}\n\n\t\tfunction drawTriangles(triangleIndices: number[], faceIdx: number, r: number, g: number, b: number) {\n\t\t\tif (!maskGl || !landmarksDataArray || triangleIndices.length === 0) return;\n\n\t\t\tconst vertices = new Float32Array(triangleIndices.length * 2);\n\t\t\tfor (let i = 0; i < triangleIndices.length; ++i) {\n\t\t\t\tconst landmarkIdx = (faceIdx * LANDMARK_COUNT + triangleIndices[i]) * 4;\n\t\t\t\tvertices[i * 2] = landmarksDataArray[landmarkIdx];\n\t\t\t\tvertices[i * 2 + 1] = landmarksDataArray[landmarkIdx + 1];\n\t\t\t}\n\n\t\t\tmaskGl.bufferData(maskGl.ARRAY_BUFFER, vertices, maskGl.DYNAMIC_DRAW);\n\t\t\tmaskGl.uniform4f(colorLocation, r, g, b, 1.0);\n\t\t\tmaskGl.drawArrays(maskGl.TRIANGLES, 0, triangleIndices.length);\n\t\t}\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: mediaPipeCanvas,\n\t\t\t\t\trunningMode: runningMode,\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\n\t\t\t\tconst tesselationConnections = FaceLandmarker.FACE_LANDMARKS_TESSELATION;\n\t\t\t\tregionTriangles.TESSELATION = [];\n\t\t\t\tfor (let i = 0; i < tesselationConnections.length - 2; i += 3) {\n\t\t\t\t\tregionTriangles.TESSELATION.push(\n\t\t\t\t\t\ttesselationConnections[i].start,\n\t\t\t\t\t\ttesselationConnections[i + 1].start,\n\t\t\t\t\t\ttesselationConnections[i + 2].start\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst ovalIndices = FaceLandmarker.FACE_LANDMARKS_FACE_OVAL.map(({ start }) => start);\n\t\t\t\tregionTriangles.OVAL = fanTriangulate(ovalIndices);\n\n\t\t\t\tinitMaskRenderer();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Failed to initialize:', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tfunction calculateBoundingBoxCenter(\n\t\t\tdata: Float32Array,\n\t\t\tfaceIdx: number,\n\t\t\tindices: readonly number[] | 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 indices) {\n\t\t\t\tconst i = (faceIdx * LANDMARK_COUNT + idx) * 4;\n\t\t\t\tconst x = data[i],\n\t\t\t\t\ty = data[i + 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 += data[i + 2];\n\t\t\t\tavgVisibility += data[i + 3];\n\t\t\t}\n\t\t\treturn [(minX + maxX) / 2, (minY + maxY) / 2, avgZ / indices.length, avgVisibility / indices.length];\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 landmarkIdx = 0; landmarkIdx < STANDARD_LANDMARK_COUNT; ++landmarkIdx) {\n\t\t\t\t\tconst landmark = landmarks[landmarkIdx];\n\t\t\t\t\tconst dataIdx = (faceIdx * LANDMARK_COUNT + landmarkIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = landmark.visibility ?? 1;\n\t\t\t\t}\n\n\t\t\t\tconst faceCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, ALL_STANDARD_INDICES);\n\t\t\t\tlandmarksDataArray.set(faceCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.FACE_CENTER) * 4);\n\n\t\t\t\tconst mouthCenter = calculateBoundingBoxCenter(landmarksDataArray, faceIdx, INNER_MOUTH_INDICES);\n\t\t\t\tlandmarksDataArray.set(mouthCenter, (faceIdx * LANDMARK_COUNT + LANDMARK_INDICES.MOUTH_CENTER) * 4);\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\tisPartial: true,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tfunction updateMaskTexture(nFaces: number) {\n\t\t\tif (!maskGl || !landmarksDataArray) return;\n\n\t\t\t// Resize and clear.\n\t\t\tmaskCanvas.width = mediaPipeCanvas.width;\n\t\t\tmaskCanvas.height = mediaPipeCanvas.height;\n\t\t\tmaskGl.viewport(0, 0, maskCanvas.width, maskCanvas.height);\n\t\t\tmaskGl.clearColor(0, 0, 0, 0);\n\t\t\tmaskGl.clear(maskGl.COLOR_BUFFER_BIT);\n\n\t\t\tfor (let faceIdx = 0; faceIdx < nFaces; ++faceIdx) {\n\t\t\t\tconst b = (faceIdx + 1) / maxFaces;\n\n\t\t\t\t// G channel: face mesh (0.5) and oval (1.0)\n\t\t\t\tdrawTriangles(regionTriangles.TESSELATION, faceIdx, 0, 0.5, b);\n\t\t\t\tdrawTriangles(regionTriangles.OVAL, faceIdx, 0, 1.0, b);\n\n\t\t\t\t// R channel: feature regions\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.LEFT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYEBROW, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYEBROW, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.LEFT_EYE, faceIdx, RED_CHANNEL_VALUES.LEFT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.RIGHT_EYE, faceIdx, RED_CHANNEL_VALUES.RIGHT_EYE, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.OUTER_MOUTH, faceIdx, RED_CHANNEL_VALUES.OUTER_MOUTH, 0, b);\n\t\t\t\tdrawTriangles(regionTriangles.INNER_MOUTH, faceIdx, RED_CHANNEL_VALUES.INNER_MOUTH, 0, b);\n\t\t\t}\n\n\t\t\tshaderPad.updateTextures({ u_faceMask: maskCanvas });\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);\n\t\t\tshaderPad.updateUniforms({ u_nFaces: nFaces });\n\n\t\t\toptions?.onResults?.(result);\n\t\t}\n\n\t\tasync function detectFaces(source: TextureSource) {\n\t\t\tconst previousSource = textureSources.get(textureName);\n\t\t\tif (previousSource !== source) lastVideoTime = -1;\n\t\t\ttextureSources.set(textureName, source);\n\t\t\tif (!faceLandmarker) return;\n\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait faceLandmarker.setOptions({ runningMode });\n\t\t\t\t}\n\n\t\t\t\tif (source instanceof HTMLVideoElement) {\n\t\t\t\t\tif (source.videoWidth === 0 || source.videoHeight === 0 || source.readyState < 2) return;\n\t\t\t\t\tif (source.currentTime !== lastVideoTime) {\n\t\t\t\t\t\tlastVideoTime = source.currentTime;\n\t\t\t\t\t\tprocessFaceResults(faceLandmarker.detectForVideo(source, performance.now()));\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) return;\n\t\t\t\t\tprocessFaceResults(faceLandmarker.detect(source));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Face Plugin] Detection error:', error);\n\t\t\t}\n\t\t}\n\n\t\tshaderPad.registerHook('init', async () => {\n\t\t\tshaderPad.initializeTexture('u_faceMask', maskCanvas, {\n\t\t\t\tminFilter: gl.NEAREST,\n\t\t\t\tmagFilter: gl.NEAREST,\n\t\t\t});\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\tlandmarksDataArray = new Float32Array(LANDMARKS_TEXTURE_WIDTH * landmarksTextureHeight * 4);\n\n\t\t\tshaderPad.initializeTexture(\n\t\t\t\t'u_faceLandmarksTex',\n\t\t\t\t{ data: landmarksDataArray, width: LANDMARKS_TEXTURE_WIDTH, height: landmarksTextureHeight },\n\t\t\t\t{ internalFormat: gl.RGBA32F, type: gl.FLOAT, minFilter: gl.NEAREST, magFilter: gl.NEAREST }\n\t\t\t);\n\n\t\t\tawait initializeFaceLandmarker();\n\t\t\toptions?.onReady?.();\n\t\t});\n\n\t\tshaderPad.registerHook('initializeTexture', (name: string, source: TextureSource) => {\n\t\t\tif (name === textureName) detectFaces(source);\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) detectFaces(source);\n\t\t});\n\n\t\tshaderPad.registerHook('destroy', () => {\n\t\t\tfaceLandmarker?.close();\n\t\t\tfaceLandmarker = null;\n\t\t\tif (maskGl && maskProgram) {\n\t\t\t\tmaskGl.deleteProgram(maskProgram);\n\t\t\t\tmaskGl.deleteBuffer(positionBuffer);\n\t\t\t}\n\t\t\tmaskGl = null;\n\t\t\tmaskProgram = null;\n\t\t\tvision = null;\n\t\t\ttextureSources.clear();\n\t\t\tlandmarksDataArray = null;\n\t\t});\n\n\t\tconst checkAt = (\n\t\t\tregionMin: keyof typeof RED_CHANNEL_VALUES,\n\t\t\tregionMax: keyof typeof RED_CHANNEL_VALUES = regionMin\n\t\t) =>\n\t\t\t`vec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn (mask.r > ${(RED_CHANNEL_VALUES[regionMin] - HALF_GAP).toFixed(4)} && mask.r < ${(\n\t\t\t\tRED_CHANNEL_VALUES[regionMax] + HALF_GAP\n\t\t\t).toFixed(4)}) ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);`;\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}\n\nvec2 leftEyebrowAt(vec2 pos) {\n\t${checkAt('LEFT_EYEBROW')}\n}\n\nvec2 rightEyebrowAt(vec2 pos) {\n\t${checkAt('RIGHT_EYEBROW')}\n}\n\nvec2 leftEyeAt(vec2 pos) {\n\t${checkAt('LEFT_EYE')}\n}\n\nvec2 rightEyeAt(vec2 pos) {\n\t${checkAt('RIGHT_EYE')}\n}\n\nvec2 lipsAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH')}\n}\n\nvec2 outerMouthAt(vec2 pos) {\n\t${checkAt('OUTER_MOUTH', 'INNER_MOUTH')}\n}\n\nvec2 innerMouthAt(vec2 pos) {\n\t${checkAt('INNER_MOUTH')}\n}\n\nvec2 faceOvalAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.75 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\n// Includes face mesh and oval.\nvec2 faceAt(vec2 pos) {\n\tvec4 mask = texture(u_faceMask, pos);\n\tfloat faceIndex = floor(mask.b * float(u_maxFaces) + 0.5) - 1.0;\n\treturn mask.g > 0.25 ? vec2(1.0, faceIndex) : vec2(0.0, -1.0);\n}\n\nvec2 eyeAt(vec2 pos) {\n\tvec2 left = leftEyeAt(pos);\n\treturn left.x > 0.0 ? left : rightEyeAt(pos);\n}\n\nvec2 eyebrowAt(vec2 pos) {\n\tvec2 left = leftEyebrowAt(pos);\n\treturn left.x > 0.0 ? left : rightEyebrowAt(pos);\n}\n\nfloat inEyebrow(vec2 pos) { return eyebrowAt(pos).x; }\nfloat inEye(vec2 pos) { return eyeAt(pos).x; }\nfloat inOuterMouth(vec2 pos) { return outerMouthAt(pos).x; }\nfloat inInnerMouth(vec2 pos) { return innerMouthAt(pos).x; }\nfloat inLips(vec2 pos) { return lipsAt(pos).x; }\nfloat inFace(vec2 pos) { return faceAt(pos).x; }`);\n\t};\n}\n\nexport default face;\n"],"mappings":"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","names":["STANDARD_LANDMARK_COUNT","CUSTOM_LANDMARK_COUNT","LANDMARK_COUNT","LANDMARKS_TEXTURE_WIDTH","LEFT_EYEBROW_INDICES","LEFT_EYE_INDICES","RIGHT_EYEBROW_INDICES","RIGHT_EYE_INDICES","OUTER_MOUTH_INDICES","INNER_MOUTH_INDICES","ALL_STANDARD_INDICES","_","i","LANDMARK_INDICES","REGION_NAMES","nFaceRegions","RED_CHANNEL_VALUES","name","HALF_GAP","fanTriangulate","indices","tris","face","config","textureName","options","defaultModelPath","shaderPad","context","injectGLSL","gl","faceLandmarker","vision","lastVideoTime","runningMode","textureSources","maxFaces","landmarksTextureHeight","landmarksDataArray","mediaPipeCanvas","maskCanvas","maskGl","maskProgram","positionBuffer","colorLocation","regionTriangles","initMaskRenderer","vertexShader","fragmentShader","positionLocation","drawTriangles","triangleIndices","faceIdx","r","g","b","vertices","landmarkIdx","initializeFaceLandmarker","FilesetResolver","FaceLandmarker","tesselationConnections","ovalIndices","start","error","calculateBoundingBoxCenter","data","minX","maxX","minY","maxY","avgZ","avgVisibility","idx","x","y","updateLandmarksTexture","faces","nFaces","totalLandmarks","landmarks","landmark","dataIdx","faceCenter","mouthCenter","rowsToUpdate","updateMaskTexture","processFaceResults","result","detectFaces","source","requiredMode","updates","checkAt","regionMin","regionMax","face_default"]}
@@ -7,6 +7,7 @@ interface HandsPluginOptions {
7
7
  minHandDetectionConfidence?: number;
8
8
  minHandPresenceConfidence?: number;
9
9
  minTrackingConfidence?: number;
10
+ onReady?: () => void;
10
11
  onResults?: (results: HandLandmarkerResult) => void;
11
12
  }
12
13
  declare function hands(config: {
@@ -7,6 +7,7 @@ interface HandsPluginOptions {
7
7
  minHandDetectionConfidence?: number;
8
8
  minHandPresenceConfidence?: number;
9
9
  minTrackingConfidence?: number;
10
+ onReady?: () => void;
10
11
  onResults?: (results: HandLandmarkerResult) => void;
11
12
  }
12
13
  declare function hands(config: {
@@ -1,4 +1,4 @@
1
- "use strict";var G=Object.create;var R=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var $=(t,i)=>{for(var a in i)R(t,a,{get:i[a],enumerable:!0})},O=(t,i,a,L)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of X(i))!Y.call(t,o)&&o!==a&&R(t,o,{get:()=>i[o],enumerable:!(L=K(i,o))||L.enumerable});return t};var j=(t,i,a)=>(a=t!=null?G(B(t)):{},O(i||!t||!t.__esModule?R(a,"default",{value:t,enumerable:!0}):a,t)),W=t=>O(R({},"__esModule",{value:!0}),t);var nn={};$(nn,{default:()=>Q});module.exports=W(nn);var v=21,Z=1,H=v+Z,q=[0,0,5,9,13,17];function J(t){let{textureName:i,options:a}=t,L="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(o,b){let{injectGLSL:F,gl:y}=b,m=null,A=null,C=-1,M="VIDEO",_=new Map,w=a?.maxHands??2,l=512,N=0,r=null;async function P(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");A=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),m=await n.createFromOptions(A,{baseOptions:{modelAssetPath:a?.modelPath||L,delegate:"GPU"},canvas:new OffscreenCanvas(1,1),runningMode:M,numHands:a?.maxHands??2,minHandDetectionConfidence:a?.minHandDetectionConfidence??.5,minHandPresenceConfidence:a?.minHandPresenceConfidence??.5,minTrackingConfidence:a?.minTrackingConfidence??.5})}catch(e){throw console.error("[Hands Plugin] Failed to initialize:",e),e}}function U(e,n,u){let d=1/0,c=-1/0,s=1/0,k=-1/0,T=0,f=0;for(let z of u){let I=(n*H+z)*4,S=e[I],D=e[I+1];d=Math.min(d,S),c=Math.max(c,S),s=Math.min(s,D),k=Math.max(k,D),T+=e[I+2],f+=e[I+3]}let h=(d+c)/2,x=(s+k)/2,p=T/u.length,g=f/u.length;return[h,x,p,g]}function V(e,n){if(!r)return;let u=e.length,d=u*H;for(let s=0;s<u;++s){let k=e[s],T=n[s]?.[0]?.categoryName==="Right";for(let x=0;x<v;++x){let p=k[x],g=(s*H+x)*4;r[g]=p.x,r[g+1]=1-p.y,r[g+2]=p.z??0,r[g+3]=T?1:0}let f=U(r,s,q),h=(s*H+v)*4;r[h]=f[0],r[h+1]=f[1],r[h+2]=f[2],r[h+3]=T?1:0}let c=Math.ceil(d/l);o.updateTextures({u_handLandmarksTex:{data:r,width:l,height:c,isPartial:!0}})}function E(e){if(!e.landmarks||!r)return;let n=e.landmarks.length;V(e.landmarks,e.handedness),o.updateUniforms({u_nHands:n}),a?.onResults?.(e)}o.registerHook("init",async()=>{o.initializeUniform("u_maxHands","int",w),o.initializeUniform("u_nHands","int",0);let e=w*H;N=Math.ceil(e/l);let n=l*N*4;r=new Float32Array(n),o.initializeTexture("u_handLandmarksTex",{data:r,width:l,height:N},{internalFormat:y.RGBA32F,type:y.FLOAT,minFilter:y.NEAREST,magFilter:y.NEAREST}),await P()}),o.registerHook("updateTextures",async e=>{let n=e[i];if(!(!n||(_.get(i)!==n&&(C=-1),_.set(i,n),!m)))try{let d=n instanceof HTMLVideoElement?"VIDEO":"IMAGE";if(M!==d&&(M=d,await m.setOptions({runningMode:M})),n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==C){C=n.currentTime;let c=m.detectForVideo(n,performance.now());E(c)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let c=m.detect(n);E(c)}}catch(d){console.error("[Hands Plugin] Detection error:",d)}}),o.registerHook("destroy",()=>{m&&(m.close(),m=null),A=null,_.clear(),r=null}),F(`
1
+ "use strict";var G=Object.create;var I=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var $=(a,i)=>{for(var t in i)I(a,t,{get:i[t],enumerable:!0})},O=(a,i,t,L)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of X(i))!Y.call(a,o)&&o!==t&&I(a,o,{get:()=>i[o],enumerable:!(L=K(i,o))||L.enumerable});return a};var j=(a,i,t)=>(t=a!=null?G(B(a)):{},O(i||!a||!a.__esModule?I(t,"default",{value:a,enumerable:!0}):t,a)),W=a=>O(I({},"__esModule",{value:!0}),a);var nn={};$(nn,{default:()=>Q});module.exports=W(nn);var v=21,Z=1,H=v+Z,q=[0,0,5,9,13,17];function J(a){let{textureName:i,options:t}=a,L="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(o,b){let{injectGLSL:F,gl:y}=b,m=null,A=null,C=-1,M="VIDEO",_=new Map,w=t?.maxHands??2,l=512,N=0,r=null;async function P(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");A=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),m=await n.createFromOptions(A,{baseOptions:{modelAssetPath:t?.modelPath||L,delegate:"GPU"},canvas:new OffscreenCanvas(1,1),runningMode:M,numHands:t?.maxHands??2,minHandDetectionConfidence:t?.minHandDetectionConfidence??.5,minHandPresenceConfidence:t?.minHandPresenceConfidence??.5,minTrackingConfidence:t?.minTrackingConfidence??.5})}catch(e){throw console.error("[Hands Plugin] Failed to initialize:",e),e}}function U(e,n,u){let s=1/0,c=-1/0,d=1/0,k=-1/0,T=0,f=0;for(let z of u){let R=(n*H+z)*4,S=e[R],D=e[R+1];s=Math.min(s,S),c=Math.max(c,S),d=Math.min(d,D),k=Math.max(k,D),T+=e[R+2],f+=e[R+3]}let h=(s+c)/2,x=(d+k)/2,p=T/u.length,g=f/u.length;return[h,x,p,g]}function V(e,n){if(!r)return;let u=e.length,s=u*H;for(let d=0;d<u;++d){let k=e[d],T=n[d]?.[0]?.categoryName==="Right";for(let x=0;x<v;++x){let p=k[x],g=(d*H+x)*4;r[g]=p.x,r[g+1]=1-p.y,r[g+2]=p.z??0,r[g+3]=T?1:0}let f=U(r,d,q),h=(d*H+v)*4;r[h]=f[0],r[h+1]=f[1],r[h+2]=f[2],r[h+3]=T?1:0}let c=Math.ceil(s/l);o.updateTextures({u_handLandmarksTex:{data:r,width:l,height:c,isPartial:!0}})}function E(e){if(!e.landmarks||!r)return;let n=e.landmarks.length;V(e.landmarks,e.handedness),o.updateUniforms({u_nHands:n}),t?.onResults?.(e)}o.registerHook("init",async()=>{o.initializeUniform("u_maxHands","int",w),o.initializeUniform("u_nHands","int",0);let e=w*H;N=Math.ceil(e/l);let n=l*N*4;r=new Float32Array(n),o.initializeTexture("u_handLandmarksTex",{data:r,width:l,height:N},{internalFormat:y.RGBA32F,type:y.FLOAT,minFilter:y.NEAREST,magFilter:y.NEAREST}),await P(),t?.onReady?.()}),o.registerHook("updateTextures",async e=>{let n=e[i];if(!(!n||(_.get(i)!==n&&(C=-1),_.set(i,n),!m)))try{let s=n instanceof HTMLVideoElement?"VIDEO":"IMAGE";if(M!==s&&(M=s,await m.setOptions({runningMode:M})),n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==C){C=n.currentTime;let c=m.detectForVideo(n,performance.now());E(c)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let c=m.detect(n);E(c)}}catch(s){console.error("[Hands Plugin] Detection error:",s)}}),o.registerHook("destroy",()=>{m&&(m.close(),m=null),A=null,_.clear(),r=null}),F(`
2
2
  uniform int u_maxHands;
3
3
  uniform int u_nHands;
4
4
  uniform sampler2D u_handLandmarksTex;
@@ -1 +1 @@
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] as const; // Wrist + MCP joints, weighted toward 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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: new OffscreenCanvas(1, 1),\n\t\t\t\t\trunningMode: runningMode,\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('[Hands Plugin] Failed to initialize:', 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: readonly 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[][], handedness: { categoryName: string }[][]) {\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\tconst isRightHand = handedness[handIdx]?.[0]?.categoryName === 'Right';\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 = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = isRightHand ? 1 : 0;\n\t\t\t\t}\n\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + STANDARD_LANDMARK_COUNT) * 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] = isRightHand ? 1 : 0;\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\tisPartial: true,\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, result.handedness);\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', async (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\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait handLandmarker.setOptions({ runningMode: runningMode });\n\t\t\t\t}\n\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 result = handLandmarker.detectForVideo(source, performance.now());\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.error('[Hands Plugin] 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;\n\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\nfloat isRightHand(int handIndex) {\n\treturn handLandmark(handIndex, 0).w;\n}\n\nfloat isLeftHand(int handIndex) {\n\treturn 1.0 - handLandmark(handIndex, 0).w;\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","runningMode","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","handedness","nHands","totalLandmarks","landmarks","isRightHand","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","requiredMode"]}
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\tonReady?: () => void;\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] as const; // Wrist + MCP joints, weighted toward 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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: new OffscreenCanvas(1, 1),\n\t\t\t\t\trunningMode: runningMode,\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('[Hands Plugin] Failed to initialize:', 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: readonly 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[][], handedness: { categoryName: string }[][]) {\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\tconst isRightHand = handedness[handIdx]?.[0]?.categoryName === 'Right';\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 = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = isRightHand ? 1 : 0;\n\t\t\t\t}\n\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + STANDARD_LANDMARK_COUNT) * 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] = isRightHand ? 1 : 0;\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\tisPartial: true,\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, result.handedness);\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\toptions?.onReady?.();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', async (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\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait handLandmarker.setOptions({ runningMode: runningMode });\n\t\t\t\t}\n\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 result = handLandmarker.detectForVideo(source, performance.now());\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.error('[Hands Plugin] 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;\n\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\nfloat isRightHand(int handIndex) {\n\treturn handLandmark(handIndex, 0).w;\n}\n\nfloat isLeftHand(int handIndex) {\n\treturn 1.0 - handLandmark(handIndex, 0).w;\n}`);\n\t};\n}\n\nexport default hands;\n"],"mappings":"0jBAAA,IAAAA,GAAA,GAAAC,EAAAD,GAAA,aAAAE,IAAA,eAAAC,EAAAH,IAaA,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,GAChBC,EAAiC,QAC/BC,EAAiB,IAAI,IACrBC,EAAWX,GAAS,UAAY,EAEhCY,EAA0B,IAC5BC,EAAyB,EACzBC,EAA0C,KAE9C,eAAeC,GAA2B,CACzC,GAAI,CACH,GAAM,CAAE,gBAAAC,EAAiB,eAAAC,CAAe,EAAI,KAAM,QAAO,yBAAyB,EAClFV,EAAS,MAAMS,EAAgB,eAC9B,kEACD,EAEAV,EAAiB,MAAMW,EAAe,kBAAkBV,EAAQ,CAC/D,YAAa,CACZ,eAAgBP,GAAS,WAAaC,EACtC,SAAU,KACX,EACA,OAAQ,IAAI,gBAAgB,EAAG,CAAC,EAChC,YAAaQ,EACb,SAAUT,GAAS,UAAY,EAC/B,2BAA4BA,GAAS,4BAA8B,GACnE,0BAA2BA,GAAS,2BAA6B,GACjE,sBAAuBA,GAAS,uBAAyB,EAC1D,CAAC,CACF,OAASkB,EAAO,CACf,cAAQ,MAAM,uCAAwCA,CAAK,EACrDA,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,EAAUzB,EAAiBiC,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,EAAuBvC,EAA+BwC,EAA0C,CACxG,GAAI,CAACvB,EAAoB,OAEzB,IAAMwB,EAASzC,EAAM,OACf0C,EAAiBD,EAAS3C,EAEhC,QAASyB,EAAU,EAAGA,EAAUkB,EAAQ,EAAElB,EAAS,CAClD,IAAMoB,EAAY3C,EAAMuB,CAAO,EACzBqB,EAAcJ,EAAWjB,CAAO,IAAI,CAAC,GAAG,eAAiB,QAC/D,QAASsB,EAAQ,EAAGA,EAAQjD,EAAyB,EAAEiD,EAAO,CAC7D,IAAMC,EAAWH,EAAUE,CAAK,EAC1Bb,GAAWT,EAAUzB,EAAiB+C,GAAS,EACrD5B,EAAmBe,CAAO,EAAIc,EAAS,EACvC7B,EAAmBe,EAAU,CAAC,EAAI,EAAIc,EAAS,EAC/C7B,EAAmBe,EAAU,CAAC,EAAIc,EAAS,GAAK,EAChD7B,EAAmBe,EAAU,CAAC,EAAIY,EAAc,EAAI,CACrD,CAEA,IAAMG,EAAazB,EAA2BL,EAAoBM,EAASxB,CAAqB,EAC1FiD,GAAiBzB,EAAUzB,EAAiBF,GAA2B,EAC7EqB,EAAmB+B,CAAa,EAAID,EAAW,CAAC,EAChD9B,EAAmB+B,EAAgB,CAAC,EAAID,EAAW,CAAC,EACpD9B,EAAmB+B,EAAgB,CAAC,EAAID,EAAW,CAAC,EACpD9B,EAAmB+B,EAAgB,CAAC,EAAIJ,EAAc,EAAI,CAC3D,CAEA,IAAMK,EAAe,KAAK,KAAKP,EAAiB3B,CAAuB,EACvEV,EAAU,eAAe,CACxB,mBAAoB,CACnB,KAAMY,EACN,MAAOF,EACP,OAAQkC,EACR,UAAW,EACZ,CACD,CAAC,CACF,CAEA,SAASC,EAAmBC,EAA8B,CACzD,GAAI,CAACA,EAAO,WAAa,CAAClC,EAAoB,OAE9C,IAAMwB,EAASU,EAAO,UAAU,OAChCZ,EAAuBY,EAAO,UAAWA,EAAO,UAAU,EAC1D9C,EAAU,eAAe,CAAE,SAAUoC,CAAO,CAAC,EAE7CtC,GAAS,YAAYgD,CAAM,CAC5B,CAEA9C,EAAU,aAAa,OAAQ,SAAY,CAC1CA,EAAU,kBAAkB,aAAc,MAAOS,CAAQ,EACzDT,EAAU,kBAAkB,WAAY,MAAO,CAAC,EAEhD,IAAMqC,EAAiB5B,EAAWhB,EAClCkB,EAAyB,KAAK,KAAK0B,EAAiB3B,CAAuB,EAC3E,IAAMqC,EAAcrC,EAA0BC,EAAyB,EACvEC,EAAqB,IAAI,aAAamC,CAAW,EAEjD/C,EAAU,kBACT,qBACA,CACC,KAAMY,EACN,MAAOF,EACP,OAAQC,CACT,EACA,CACC,eAAgBR,EAAG,QACnB,KAAMA,EAAG,MACT,UAAWA,EAAG,QACd,UAAWA,EAAG,OACf,CACD,EAEA,MAAMU,EAAyB,EAC/Bf,GAAS,UAAU,CACpB,CAAC,EAEDE,EAAU,aAAa,iBAAkB,MAAOgD,GAA2C,CAC1F,IAAMC,EAASD,EAAQnD,CAAW,EASlC,GARI,GAACoD,IAEkBzC,EAAe,IAAIX,CAAW,IAC9BoD,IACtB3C,EAAgB,IAGjBE,EAAe,IAAIX,EAAaoD,CAAM,EAClC,CAAC7C,IAEL,GAAI,CACH,IAAM8C,EAAeD,aAAkB,iBAAmB,QAAU,QAMpE,GALI1C,IAAgB2C,IACnB3C,EAAc2C,EACd,MAAM9C,EAAe,WAAW,CAAE,YAAaG,CAAY,CAAC,GAGzD0C,aAAkB,iBAAkB,CACvC,GAAIA,EAAO,aAAe,GAAKA,EAAO,cAAgB,GAAKA,EAAO,WAAa,EAC9E,OAED,GAAIA,EAAO,cAAgB3C,EAAe,CACzCA,EAAgB2C,EAAO,YACvB,IAAMH,EAAS1C,EAAe,eAAe6C,EAAQ,YAAY,IAAI,CAAC,EACtEJ,EAAmBC,CAAM,CAC1B,CACD,SAAWG,aAAkB,kBAAoBA,aAAkB,kBAAmB,CACrF,GAAIA,EAAO,QAAU,GAAKA,EAAO,SAAW,EAC3C,OAED,IAAMH,EAAS1C,EAAe,OAAO6C,CAAM,EAC3CJ,EAAmBC,CAAM,CAC1B,CACD,OAAS9B,EAAO,CACf,QAAQ,MAAM,kCAAmCA,CAAK,CACvD,CACD,CAAC,EAEDhB,EAAU,aAAa,UAAW,IAAM,CACnCI,IACHA,EAAe,MAAM,EACrBA,EAAiB,MAElBC,EAAS,KACTG,EAAe,MAAM,EACrBI,EAAqB,IACtB,CAAC,EAEDV,EAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAMUT,CAAc;AAAA,eACtBiB,CAAuB;AAAA,eACvBA,CAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUpC,CACD,CACD,CAEA,IAAOrB,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","runningMode","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","handedness","nHands","totalLandmarks","landmarks","isRightHand","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","requiredMode"]}
@@ -1,4 +1,4 @@
1
- var C=21,V=1,x=C+V,z=[0,0,5,9,13,17];function G(E){let{textureName:y,options:d}=E,S="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(o,D){let{injectGLSL:O,gl:T}=D,s=null,M=null,I=-1,p="VIDEO",R=new Map,_=d?.maxHands??2,c=512,A=0,t=null;async function b(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");M=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),s=await n.createFromOptions(M,{baseOptions:{modelAssetPath:d?.modelPath||S,delegate:"GPU"},canvas:new OffscreenCanvas(1,1),runningMode:p,numHands:d?.maxHands??2,minHandDetectionConfidence:d?.minHandDetectionConfidence??.5,minHandPresenceConfidence:d?.minHandPresenceConfidence??.5,minTrackingConfidence:d?.minTrackingConfidence??.5})}catch(e){throw console.error("[Hands Plugin] Failed to initialize:",e),e}}function F(e,n,m){let i=1/0,r=-1/0,a=1/0,g=-1/0,H=0,l=0;for(let U of m){let L=(n*x+U)*4,v=e[L],w=e[L+1];i=Math.min(i,v),r=Math.max(r,v),a=Math.min(a,w),g=Math.max(g,w),H+=e[L+2],l+=e[L+3]}let u=(i+r)/2,f=(a+g)/2,k=H/m.length,h=l/m.length;return[u,f,k,h]}function P(e,n){if(!t)return;let m=e.length,i=m*x;for(let a=0;a<m;++a){let g=e[a],H=n[a]?.[0]?.categoryName==="Right";for(let f=0;f<C;++f){let k=g[f],h=(a*x+f)*4;t[h]=k.x,t[h+1]=1-k.y,t[h+2]=k.z??0,t[h+3]=H?1:0}let l=F(t,a,z),u=(a*x+C)*4;t[u]=l[0],t[u+1]=l[1],t[u+2]=l[2],t[u+3]=H?1:0}let r=Math.ceil(i/c);o.updateTextures({u_handLandmarksTex:{data:t,width:c,height:r,isPartial:!0}})}function N(e){if(!e.landmarks||!t)return;let n=e.landmarks.length;P(e.landmarks,e.handedness),o.updateUniforms({u_nHands:n}),d?.onResults?.(e)}o.registerHook("init",async()=>{o.initializeUniform("u_maxHands","int",_),o.initializeUniform("u_nHands","int",0);let e=_*x;A=Math.ceil(e/c);let n=c*A*4;t=new Float32Array(n),o.initializeTexture("u_handLandmarksTex",{data:t,width:c,height:A},{internalFormat:T.RGBA32F,type:T.FLOAT,minFilter:T.NEAREST,magFilter:T.NEAREST}),await b()}),o.registerHook("updateTextures",async e=>{let n=e[y];if(!(!n||(R.get(y)!==n&&(I=-1),R.set(y,n),!s)))try{let i=n instanceof HTMLVideoElement?"VIDEO":"IMAGE";if(p!==i&&(p=i,await s.setOptions({runningMode:p})),n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==I){I=n.currentTime;let r=s.detectForVideo(n,performance.now());N(r)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let r=s.detect(n);N(r)}}catch(i){console.error("[Hands Plugin] Detection error:",i)}}),o.registerHook("destroy",()=>{s&&(s.close(),s=null),M=null,R.clear(),t=null}),O(`
1
+ var C=21,V=1,x=C+V,z=[0,0,5,9,13,17];function G(E){let{textureName:y,options:o}=E,S="https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task";return function(d,D){let{injectGLSL:O,gl:T}=D,s=null,M=null,R=-1,p="VIDEO",I=new Map,_=o?.maxHands??2,c=512,A=0,t=null;async function b(){try{let{FilesetResolver:e,HandLandmarker:n}=await import("@mediapipe/tasks-vision");M=await e.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"),s=await n.createFromOptions(M,{baseOptions:{modelAssetPath:o?.modelPath||S,delegate:"GPU"},canvas:new OffscreenCanvas(1,1),runningMode:p,numHands:o?.maxHands??2,minHandDetectionConfidence:o?.minHandDetectionConfidence??.5,minHandPresenceConfidence:o?.minHandPresenceConfidence??.5,minTrackingConfidence:o?.minTrackingConfidence??.5})}catch(e){throw console.error("[Hands Plugin] Failed to initialize:",e),e}}function F(e,n,m){let i=1/0,r=-1/0,a=1/0,g=-1/0,H=0,l=0;for(let U of m){let L=(n*x+U)*4,v=e[L],w=e[L+1];i=Math.min(i,v),r=Math.max(r,v),a=Math.min(a,w),g=Math.max(g,w),H+=e[L+2],l+=e[L+3]}let u=(i+r)/2,f=(a+g)/2,k=H/m.length,h=l/m.length;return[u,f,k,h]}function P(e,n){if(!t)return;let m=e.length,i=m*x;for(let a=0;a<m;++a){let g=e[a],H=n[a]?.[0]?.categoryName==="Right";for(let f=0;f<C;++f){let k=g[f],h=(a*x+f)*4;t[h]=k.x,t[h+1]=1-k.y,t[h+2]=k.z??0,t[h+3]=H?1:0}let l=F(t,a,z),u=(a*x+C)*4;t[u]=l[0],t[u+1]=l[1],t[u+2]=l[2],t[u+3]=H?1:0}let r=Math.ceil(i/c);d.updateTextures({u_handLandmarksTex:{data:t,width:c,height:r,isPartial:!0}})}function N(e){if(!e.landmarks||!t)return;let n=e.landmarks.length;P(e.landmarks,e.handedness),d.updateUniforms({u_nHands:n}),o?.onResults?.(e)}d.registerHook("init",async()=>{d.initializeUniform("u_maxHands","int",_),d.initializeUniform("u_nHands","int",0);let e=_*x;A=Math.ceil(e/c);let n=c*A*4;t=new Float32Array(n),d.initializeTexture("u_handLandmarksTex",{data:t,width:c,height:A},{internalFormat:T.RGBA32F,type:T.FLOAT,minFilter:T.NEAREST,magFilter:T.NEAREST}),await b(),o?.onReady?.()}),d.registerHook("updateTextures",async e=>{let n=e[y];if(!(!n||(I.get(y)!==n&&(R=-1),I.set(y,n),!s)))try{let i=n instanceof HTMLVideoElement?"VIDEO":"IMAGE";if(p!==i&&(p=i,await s.setOptions({runningMode:p})),n instanceof HTMLVideoElement){if(n.videoWidth===0||n.videoHeight===0||n.readyState<2)return;if(n.currentTime!==R){R=n.currentTime;let r=s.detectForVideo(n,performance.now());N(r)}}else if(n instanceof HTMLImageElement||n instanceof HTMLCanvasElement){if(n.width===0||n.height===0)return;let r=s.detect(n);N(r)}}catch(i){console.error("[Hands Plugin] Detection error:",i)}}),d.registerHook("destroy",()=>{s&&(s.close(),s=null),M=null,I.clear(),t=null}),O(`
2
2
  uniform int u_maxHands;
3
3
  uniform int u_nHands;
4
4
  uniform sampler2D u_handLandmarksTex;
@@ -1 +1 @@
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] as const; // Wrist + MCP joints, weighted toward 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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: new OffscreenCanvas(1, 1),\n\t\t\t\t\trunningMode: runningMode,\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('[Hands Plugin] Failed to initialize:', 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: readonly 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[][], handedness: { categoryName: string }[][]) {\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\tconst isRightHand = handedness[handIdx]?.[0]?.categoryName === 'Right';\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 = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = isRightHand ? 1 : 0;\n\t\t\t\t}\n\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + STANDARD_LANDMARK_COUNT) * 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] = isRightHand ? 1 : 0;\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\tisPartial: true,\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, result.handedness);\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', async (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\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait handLandmarker.setOptions({ runningMode: runningMode });\n\t\t\t\t}\n\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 result = handLandmarker.detectForVideo(source, performance.now());\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.error('[Hands Plugin] 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;\n\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\nfloat isRightHand(int handIndex) {\n\treturn handLandmark(handIndex, 0).w;\n}\n\nfloat isLeftHand(int handIndex) {\n\treturn 1.0 - handLandmark(handIndex, 0).w;\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","runningMode","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","handedness","nHands","totalLandmarks","landmarks","isRightHand","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","requiredMode","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\tonReady?: () => void;\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] as const; // Wrist + MCP joints, weighted toward 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\tlet runningMode: 'IMAGE' | 'VIDEO' = 'VIDEO';\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\tdelegate: 'GPU',\n\t\t\t\t\t},\n\t\t\t\t\tcanvas: new OffscreenCanvas(1, 1),\n\t\t\t\t\trunningMode: runningMode,\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('[Hands Plugin] Failed to initialize:', 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: readonly 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[][], handedness: { categoryName: string }[][]) {\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\tconst isRightHand = handedness[handIdx]?.[0]?.categoryName === 'Right';\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 = (handIdx * LANDMARK_COUNT + lmIdx) * 4;\n\t\t\t\t\tlandmarksDataArray[dataIdx] = landmark.x;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 1] = 1 - landmark.y;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 2] = landmark.z ?? 0;\n\t\t\t\t\tlandmarksDataArray[dataIdx + 3] = isRightHand ? 1 : 0;\n\t\t\t\t}\n\n\t\t\t\tconst handCenter = calculateBoundingBoxCenter(landmarksDataArray, handIdx, HAND_CENTER_LANDMARKS);\n\t\t\t\tconst handCenterIdx = (handIdx * LANDMARK_COUNT + STANDARD_LANDMARK_COUNT) * 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] = isRightHand ? 1 : 0;\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\tisPartial: true,\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, result.handedness);\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\toptions?.onReady?.();\n\t\t});\n\n\t\tshaderPad.registerHook('updateTextures', async (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\n\t\t\ttry {\n\t\t\t\tconst requiredMode = source instanceof HTMLVideoElement ? 'VIDEO' : 'IMAGE';\n\t\t\t\tif (runningMode !== requiredMode) {\n\t\t\t\t\trunningMode = requiredMode;\n\t\t\t\t\tawait handLandmarker.setOptions({ runningMode: runningMode });\n\t\t\t\t}\n\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 result = handLandmarker.detectForVideo(source, performance.now());\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.error('[Hands Plugin] 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;\n\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\nfloat isRightHand(int handIndex) {\n\treturn handLandmark(handIndex, 0).w;\n}\n\nfloat isLeftHand(int handIndex) {\n\treturn 1.0 - handLandmark(handIndex, 0).w;\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","runningMode","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","handedness","nHands","totalLandmarks","landmarks","isRightHand","lmIdx","landmark","handCenter","handCenterIdx","rowsToUpdate","processHandResults","result","textureSize","updates","source","requiredMode","hands_default"]}
@@ -7,6 +7,7 @@ interface PosePluginOptions {
7
7
  minPoseDetectionConfidence?: number;
8
8
  minPosePresenceConfidence?: number;
9
9
  minTrackingConfidence?: number;
10
+ onReady?: () => void;
10
11
  onResults?: (results: PoseLandmarkerResult) => void;
11
12
  }
12
13
  declare function pose(config: {
@@ -7,6 +7,7 @@ interface PosePluginOptions {
7
7
  minPoseDetectionConfidence?: number;
8
8
  minPosePresenceConfidence?: number;
9
9
  minTrackingConfidence?: number;
10
+ onReady?: () => void;
10
11
  onResults?: (results: PoseLandmarkerResult) => void;
11
12
  }
12
13
  declare function pose(config: {