diff --git a/app/pages/PageEnigma/Editor/debug.ts b/app/pages/PageEnigma/Editor/debug.ts index 2aea662631..68267532bb 100644 --- a/app/pages/PageEnigma/Editor/debug.ts +++ b/app/pages/PageEnigma/Editor/debug.ts @@ -1,125 +1,198 @@ import * as THREE from "three"; //import * as Kalidokit from "kalidokit"; -import * as Kalidokit from "kalidokit" -import { TFace } from "kalidokit"; +import * as Kalidokit from "kalidokit"; +import { + Vector, + Utils, + Hand, + Face, + Pose, + HandKeys, + Side, + THand, + TFace, + TPose, +} from "kalidokit"; //import { Pose } from "kalidokit"; //const Kalidokit = require('kalidokit'); -import { FilesetResolver, PoseLandmarker, PoseLandmarkerResult, HolisticLandmarker, HolisticLandmarkerResult } from "@mediapipe/tasks-vision" +import { + FilesetResolver, + PoseLandmarker, + PoseLandmarkerResult, + HolisticLandmarker, + HolisticLandmarkerResult, +} from "@mediapipe/tasks-vision"; import { CharacterPoseHelper } from "./Engines/Helpers/CharacterPoseHelper"; import { loadImageFromAnonymousOriginUrl } from "~/Helpers/ImageHelpers"; +const leftHandBones = [ + "LeftRingProximal", + "LeftRingIntermediate", + "LeftRingDistal", + "LeftIndexProximal", + "LeftIndexIntermediate", + "LeftIndexDistal", + "LeftMiddleProximal", + "LeftMiddleIntermediate", + "LeftMiddleDistal", + "LeftThumbProximal", + "LeftThumbDistal", + "LeftLittleProximal", + "LeftLittleIntermediate", + "LeftLittleDistal", +]; +const rightHandBones = [ + "RightRingProximal", + "RightRingIntermediate", + "RightRingDistal", + "RightIndexProximal", + "RightIndexIntermediate", + "RightIndexDistal", + "RightMiddleProximal", + "RightMiddleIntermediate", + "RightMiddleDistal", + "RightThumbProximal", + "RightThumbDistal", + "RightLittleProximal", + "RightLittleIntermediate", + "RightLittleDistal", +]; + +const leftHandMixamoBonesMap: { [key: string]: string } = { + LeftRingProximal: "mixamorigLeftHandRing1", + LeftRingIntermediate: "mixamorigLeftHandRing2", + LeftRingDistal: "mixamorigLeftHandRing3", + LeftIndexProximal: "mixamorigLeftHandIndex1", + LeftIndexIntermediate: "mixamorigLeftHandIndex2", + LeftIndexDistal: "mixamorigLeftHandIndex3", + LeftMiddleProximal: "mixamorigLeftHandMiddle1", + LeftMiddleIntermediate: "mixamorigLeftHandMiddle2", + LeftMiddleDistal: "mixamorigLeftHandMiddle3", + LeftThumbProximal: "mixamorigLeftHandThumb1", + LeftThumbDistal: "mixamorigLeftHandThumb3", + LeftLittleProximal: "mixamorigLeftHandPinky1", + LeftLittleIntermediate: "mixamorigLeftHandPinky2", + LeftLittleDistal: "mixamorigLeftHandPinky3", +}; //import '@mediapipe/holistic/holistic'; //import '@mediapipe/camera_utils/camera_utils'; // TODO(bt,2025-01-28): I don't understand this codebase well yet, and I'm trying to apply bone rotations. -// This is a simple set of experiments for me to come up to speed with Threejs, our code, and the theoretical +// This is a simple set of experiments for me to come up to speed with Threejs, our code, and the theoretical // task at hand. IF THIS CODE IS PRESENT IN THE FUTURE IT SHOULD BE REMOVED AS IT SERVES NO OTHER PURPOSE. const DEBUG_ENABLED = true; const DEBUG_PRINT_ENABLED = true; // NB: From James Bond image. -const EXAMPLE_POSE : any = { - "RightUpperArm": { - "x": -0.4230379402555594, - "y": 1.7737168782435317, - "z": -1.0277683620346483 +const EXAMPLE_POSE: any = { + RightUpperArm: { + x: -0.4230379402555594, + y: 1.7737168782435317, + z: -1.0277683620346483, }, - "RightLowerArm": { - "x": -0.3, - "y": 0.9865853333958371, - "z": 0.3779985683628639 + RightLowerArm: { + x: -0.3, + y: 0.9865853333958371, + z: 0.3779985683628639, }, - "LeftUpperArm": { - "x": 0.005554997408462992, - "y": -1.4626850489681587, - "z": 1.1226644661733884 + LeftUpperArm: { + x: 0.005554997408462992, + y: -1.4626850489681587, + z: 1.1226644661733884, }, - "LeftLowerArm": { - "x": -0.015415961257577574, - "y": -0.3543613298595948, - "z": 0 + LeftLowerArm: { + x: -0.015415961257577574, + y: -0.3543613298595948, + z: 0, }, - "RightHand": { - "x": -0.42668629235907013, - "y": -0.47942290499788864, - "z": 0.5513363407475719 + RightHand: { + x: -0.42668629235907013, + y: -0.47942290499788864, + z: 0.5513363407475719, }, - "LeftHand": { - "x": 0.1791247835953645, - "y": 0.6, - "z": 0.9815028962744002 + LeftHand: { + x: 0.1791247835953645, + y: 0.6, + z: 0.9815028962744002, }, - "RightUpperLeg": { - "x": 0.48739810481744406, - "y": 0.5335122086099788, - "z": -0.26540331612091095, - "rotationOrder": "XYZ" + RightUpperLeg: { + x: 0.48739810481744406, + y: 0.5335122086099788, + z: -0.26540331612091095, + rotationOrder: "XYZ", }, - "RightLowerLeg": { - "x": -1.987357750417811, - "y": 0, - "z": 0, - "rotationOrder": "XYZ" + RightLowerLeg: { + x: -1.987357750417811, + y: 0, + z: 0, + rotationOrder: "XYZ", }, - "LeftUpperLeg": { - "x": 0.5327440321782165, - "y": 0.40825017329318186, - "z": -0.31162463497702436, - "rotationOrder": "XYZ" + LeftUpperLeg: { + x: 0.5327440321782165, + y: 0.40825017329318186, + z: -0.31162463497702436, + rotationOrder: "XYZ", }, - "LeftLowerLeg": { - "x": -1.7621100159383252, - "y": 0, - "z": 0, - "rotationOrder": "XYZ" + LeftLowerLeg: { + x: -1.7621100159383252, + y: 0, + z: 0, + rotationOrder: "XYZ", }, - "Hips": { - "position": { - "x": 0.009826546907424905, - "y": 0, - "z": -0.4867018217668233 - }, - "worldPosition": { - "x": -0.00453158195232334, - "y": 0, - "z": -0.4611571078848962 - }, - "rotation": { - "x": 0, - "y": 0.2743397512486982, - "z": 0.0003709216445107318 - } + Hips: { + position: { + x: 0.009826546907424905, + y: 0, + z: -0.4867018217668233, + }, + worldPosition: { + x: -0.00453158195232334, + y: 0, + z: -0.4611571078848962, + }, + rotation: { + x: 0, + y: 0.2743397512486982, + z: 0.0003709216445107318, + }, + }, + Spine: { + x: 0, + y: 0.2714237930344275, + z: 0.07454435135366712, }, - "Spine": { - "x": 0, - "y": 0.2714237930344275, - "z": 0.07454435135366712 - } }; export function print_children(obj: THREE.Object3D) { - if(DEBUG_PRINT_ENABLED) { + if (DEBUG_PRINT_ENABLED) { do_print_children(obj); } } -function do_print_children(obj: THREE.Object3D, level: number = 0) { +function do_print_children( + obj: THREE.Object3D, + level: number = 0, +) { const space = " ".repeat(level); console.log(`${space} - ${obj.name}`); - for(let i in obj.children) { + for (let i in obj.children) { let child = obj.children[i]; do_print_children(child, level + 1); } } -function rotateChildBone(obj: THREE.Object3D, name: string, x: number, y: number, z: number) { - // https://discourse.threejs.org/t/solved-how-to-rotate-arm-of-skinned-and-rigged-character/5572 - // https://jsfiddle.net/bdmrg4oc/1/ +function rotateChildBone( + obj: THREE.Object3D, + name: string, + x: number, + y: number, + z: number, +) { const child = obj.getObjectByName(name); if (!!!child) { return; @@ -129,59 +202,398 @@ function rotateChildBone(obj: THREE.Object3D, name: stri child.rotation.z = z; } -function mapRotation(obj: THREE.Object3D, sourceName: string, destinationName: string) { +function mapRotation( + obj: THREE.Object3D, + sourceName: string, + destinationName: string, + rotation = { x: 0, y: 0, z: 0 }, +) { const sourceRotation = EXAMPLE_POSE[sourceName]; if (!!!sourceRotation) { - console.error(`No rotation named ${sourceName}`) + console.error(`No rotation named ${sourceName}`); return; } - rotateChildBone(obj, destinationName, sourceRotation.x, sourceRotation.y, sourceRotation.z); + rotateChildBone( + obj, + destinationName, + sourceRotation.x, + sourceRotation.y, + sourceRotation.z, + ); } -function mapRotationFrom(obj: THREE.Object3D, source: any, sourceName: string, destinationName: string) { +function mapRotationFrom( + obj: THREE.Object3D, + source: any, + sourceName: string, + destinationName: string, +) { const sourceRotation = source[sourceName]; if (!!!sourceRotation) { - console.error(`No rotation named ${sourceName}`) + console.error(`No rotation named ${sourceName}`); return; } - rotateChildBone(obj, destinationName, sourceRotation.x, sourceRotation.y, sourceRotation.z); + rotateChildBone( + obj, + destinationName, + sourceRotation.x, + sourceRotation.y, + sourceRotation.z, + ); +} + +const rigLeftHand = ( + obj: THREE.Object3D, + riggedLeftHand: THand<"Left">, + riggedPose: TPose, +) => { + mapRotation(obj, "LeftHand", "mixamorigLeftHand"); + + // const leftHandKeys: HandKeys<"Left"> = ; + + for (const name of leftHandBones) { + mapRotation( + obj, + name, + leftHandMixamoBonesMap[name], + riggedLeftHand[name as HandKeys<"Left">] as Vector, + ); + } +}; + +const rigRotation = ( + obj: THREE.Object3D, + name: string, + rotation = { x: 0, y: 0, z: 0 }, + dampener = 1, + lerpAmount = 0.3, +) => { + if (!obj) { + return; + } + const child = obj.getObjectByName(name); + if (!!!child) { + return; + } + + let euler = new THREE.Euler( + rotation.x * dampener, + rotation.y * dampener, + rotation.z * dampener, + "XYZ", + ); + let quaternion = new THREE.Quaternion().setFromEuler(euler); + child.quaternion.slerp(quaternion, lerpAmount); // interpolate +}; + +// Animate Position Helper Function +const rigPosition = ( + obj: THREE.Object3D, + name: string, + position = { x: 0, y: 0, z: 0 }, + dampener = 1, + lerpAmount = 0.3, +) => { + if (!obj) { + return; + } + const child = obj.getObjectByName(name); + if (!!!child) { + return; + } + + let vector = new THREE.Vector3( + position.x * dampener, + position.y * dampener, + position.z * dampener, + ); + // child.apply = child.position.lerp(vector, lerpAmount); // interpolate +}; + +import * as THREE from "three"; + +// Define the structure of the Kalidokit pose +interface PoseRotation { + x: number; + y: number; + z: number; +} + +interface PosePosition { + x: number; + y: number; + z: number; +} + +interface HipsPose { + position: PosePosition; + worldPosition: PosePosition; + rotation: PoseRotation; +} + +interface KalidokitPose { + RightUpperArm: PoseRotation; + RightLowerArm: PoseRotation; + LeftUpperArm: PoseRotation; + LeftLowerArm: PoseRotation; + RightHand: PoseRotation; + LeftHand: PoseRotation; + RightUpperLeg: PoseRotation; + RightLowerLeg: PoseRotation; + LeftUpperLeg: PoseRotation; + LeftLowerLeg: PoseRotation; + Hips: HipsPose; + Spine: PoseRotation; +} + +// Define the mapping from Kalidokit bones to Mixamo bones +const BONE_MAPPING: Record = { + // Spine & Hips + Spine: ["mixamorigSpine", "mixamorigSpine1", "mixamorigSpine2"], + // Hips: ["mixamorigHips"], + + // Arms + RightUpperArm: ["mixamorigRightArm"], + RightLowerArm: ["mixamorigRightForeArm"], + LeftUpperArm: ["mixamorigLeftArm"], + LeftLowerArm: ["mixamorigLeftForeArm"], + + // Legs + RightUpperLeg: ["mixamorigRightUpLeg"], + RightLowerLeg: ["mixamorigRightLeg"], + LeftUpperLeg: ["mixamorigLeftUpLeg"], + LeftLowerLeg: ["mixamorigLeftLeg"], + + // Hands + RightHand: ["mixamorigRightHand"], + LeftHand: ["mixamorigLeftHand"], +}; + +function findSkinnedMesh(object: THREE.Object3D): THREE.SkinnedMesh | null { + let skinnedMesh: THREE.SkinnedMesh | null = null; + + object.traverse((child) => { + if (child instanceof THREE.SkinnedMesh) { + skinnedMesh = child; + } + }); + + return skinnedMesh; +} + +function rigBoneRotation( + bones: THREE.Bone[], + boneName: string, + rotation: PoseRotation, + dampener: number = 1, + lerpAmount: number = 0.3, +) { + const bone = bones.find((b) => b.name === boneName); + if (!bone) return; + + const euler = new THREE.Euler( + rotation.x * dampener, + rotation.y * dampener, + rotation.z * dampener, + "XYZ", + ); + + const quaternion = new THREE.Quaternion().setFromEuler(euler); + + bone.quaternion.premultiply(quaternion); + bone.updateMatrixWorld(true); +} + +//TODO(kasisnu, 29/01/25): This might be useful for hips or other bones later, not sure as of writing +function rigBonePosition( + bones: THREE.Bone[], + boneName: string, + position: PosePosition, + dampener: number = 1, + lerpAmount: number = 0.3, +) { + const bone = bones.find((b) => b.name === boneName); + if (!bone) return; + + const vector = new THREE.Vector3( + position.x * dampener, + position.y * dampener, + position.z * dampener, + ); + + bone.position.lerp(vector, lerpAmount); // Smooth transition +} + +function findBoneRecursive( + rootBone: THREE.Bone, + name: string, +): THREE.Bone | null { + if (rootBone.name === name) return rootBone; + + for (const child of rootBone.children) { + const foundBone = findBoneRecursive(child as THREE.Bone, name); + if (foundBone) return foundBone; + } + return null; +} +function getBoneUpdateOrder( + rootBone: THREE.Bone, + order: string[] = [], +): string[] { + order.push(rootBone.name); + for (const child of rootBone.children) { + getBoneUpdateOrder(child as THREE.Bone, order); + } + return order; +} + +function applyPoseToMixamo( + character: THREE.Object3D, + kalidokitPose: KalidokitPose, +) { + const skinnedMesh = findSkinnedMesh(character); + if (!skinnedMesh) { + return; + } + const bones = skinnedMesh.skeleton.bones; + if (!bones) { + return; + } + + const rootBone = bones[0]; + const boneUpdateOrder = getBoneUpdateOrder(rootBone); + console.log("Bone Update Order:", boneUpdateOrder); + + const missingBones: string[] = []; + const missingKalidokitBones: string[] = []; + + for (const boneName of boneUpdateOrder) { + const kalidokitBone = Object.keys(BONE_MAPPING).find((key) => + BONE_MAPPING[key as keyof typeof BONE_MAPPING].includes(boneName), + ) as keyof KalidokitPose | undefined; + + if (!kalidokitBone) { + missingKalidokitBones.push(boneName); + continue; + } + + const bone = findBoneRecursive(rootBone, boneName); + if (!bone) { + missingBones.push(boneName); + continue; + } + + const poseRotation = + kalidokitBone === "Hips" + ? kalidokitPose.Hips.rotation // Only use rotation for hips + : kalidokitPose[kalidokitBone as keyof Omit]; + + if (poseRotation && typeof poseRotation === "object") { + rigBoneRotation(bones, boneName, poseRotation); + } + } + + // Log missing bones if any + if (missingBones.length > 0) { + console.warn("⚠️ Missing Mixamo Bones:", missingBones); + } else { + console.log("All Mixamo bones found and mapped correctly!"); + } + + if (missingKalidokitBones.length > 0) { + console.warn("⚠️ Missing Kalidokit Bones:", missingKalidokitBones); + console.warn( + "Available Kalidokit Bones:", + boneUpdateOrder + .map((b) => b) + .filter((b) => !missingKalidokitBones.includes(b)), + ); + } else { + console.log("All Kalidokit bones found and mapped correctly!"); + } + + skinnedMesh.skeleton.update(); + skinnedMesh.updateMatrixWorld(true); } export function testDeformBody(obj: THREE.Object3D) { if (!DEBUG_ENABLED) { return; } - mapRotation(obj, "Spine", "mixamorigSpine"); - //mapRotation(obj, "RightUpperArm", "mixamorigRightArm"); - mapRotation(obj, "RightHand", "mixamorigRightHand"); - mapRotation(obj, "LeftHand", "mixamorigLeftHand"); + applyPoseToMixamo(obj, EXAMPLE_POSE); - //mapRotation(obj, "RightUpperArm", "mixamorigRightShoulder"); - mapRotation(obj, "RightUpperArm", "mixamorigRightArm"); - //mapRotation(obj, "LeftUpperArm", "mixamorigLeftShoulder"); - mapRotation(obj, "LeftUpperArm", "mixamorigLeftArm"); + // rigRotation(obj, "Hips", EXAMPLE_POSE.Hips, 0.7); + // rigPosition(obj, "Hips", EXAMPLE_POSE.Hips.position, 0.7); + // rigPosition( + // obj, + // "Hips", + // { + // x: EXAMPLE_POSE.Hips.position.x, // Reverse direction + // y: EXAMPLE_POSE.Hips.position.y + 1, // Add a bit of height + // z: -EXAMPLE_POSE.Hips.position.z, // Reverse direction + // }, + // 1, + // 0.07, + // ); - mapRotation(obj, "RightLowerArm", "mixamorigRightForeArm"); - mapRotation(obj, "LeftLowerArm", "mixamorigLeftForeArm"); + // rigRotation(obj, "Chest", EXAMPLE_POSE.Spine, 0.25, 0.3); + // rigRotation(obj, "Spine", EXAMPLE_POSE.Spine, 0.45, 0.3); + // rigRotation(obj, "Chest", EXAMPLE_POSE.Spine, 0.25, 0.3); + // rigPosition(obj, "Chest", EXAMPLE_POSE.Spine, 0.7); + // rigRotation(obj, "Spine", EXAMPLE_POSE.Spine, 0.45, 0.3); + // rigPosition(obj, "Spine", EXAMPLE_POSE.Spine, 0.7); + + // rigRotation(obj, "RightUpperArm", EXAMPLE_POSE.RightUpperArm, 1, 0.3); + // rigPosition(obj, "RightUpperArm", EXAMPLE_POSE.RightUpperArm, 0.7); + // rigRotation(obj, "RightLowerArm", EXAMPLE_POSE.RightLowerArm, 1, 0.3); + // rigPosition(obj, "RightLowerArm", EXAMPLE_POSE.RightLowerArm, 0.7); + // rigRotation(obj, "LeftUpperArm", EXAMPLE_POSE.LeftUpperArm, 1, 0.3); + // rigPosition(obj, "LeftUpperArm", EXAMPLE_POSE.LeftUpperArm, 0.7); + // rigRotation(obj, "LeftLowerArm", EXAMPLE_POSE.LeftLowerArm, 1, 0.3); + // rigPosition(obj, "LeftLowerArm", EXAMPLE_POSE.LeftLowerArm, 0.7); + + // rigRotation(obj, "LeftUpperLeg", EXAMPLE_POSE.LeftUpperLeg, 1, 0.3); + // rigRotation(obj, "LeftLowerLeg", EXAMPLE_POSE.LeftLowerLeg, 1, 0.3); + // rigRotation(obj, "RightUpperLeg", EXAMPLE_POSE.RightUpperLeg, 1, 0.3); + // rigRotation(obj, "RightLowerLeg", EXAMPLE_POSE.RightLowerLeg, 1, 0.3); + // // mapRotation(obj, "Hips", "mixamorigHips"); + // // mapRotation(obj, "Chest", "mixamorigSpine1"); + // mapRotation(obj, "Spine", "mixamorigSpine"); + + // mapRotation(obj, "RightUpperArm", "mixamorigRightArm"); + // mapRotation(obj, "RightHand", "mixamorigRightHand"); + // mapRotation(obj, "LeftHand", "mixamorigLeftHand"); + // // rigLeftHand(obj, EXAMPLE_POSE.LeftHand, EXAMPLE_POSE); + + // mapRotation(obj, "RightUpperArm", "mixamorigRightShoulder"); + // mapRotation(obj, "RightUpperArm", "mixamorigRightArm"); + // mapRotation(obj, "LeftUpperArm", "mixamorigLeftShoulder"); + // mapRotation(obj, "LeftUpperArm", "mixamorigLeftArm"); + + // mapRotation(obj, "RightLowerArm", "mixamorigRightForeArm"); + // mapRotation(obj, "LeftLowerArm", "mixamorigLeftForeArm"); //obj.rotation.x = EXAMPLE_POSE.Hips.rotation.x; //obj.rotation.y = EXAMPLE_POSE.Hips.rotation.y; //obj.rotation.z = EXAMPLE_POSE.Hips.rotation.z; - } - export async function testGlobalExperiment() { - const firstFrameUrl : string | undefined = (window as any).firstFrameMediaUrl; - const characterRig : THREE.Object3D | undefined = (window as any).lastCharacter; + const firstFrameUrl: string | undefined = (window as any).firstFrameMediaUrl; + const characterRig: THREE.Object3D | undefined = ( + window as any + ).lastCharacter; if (!!!firstFrameUrl || !!!characterRig) { return; } doTest(firstFrameUrl, characterRig); } -async function doTest(firstFrameUrl: string, characterRig: THREE.Object3D) { +async function doTest( + firstFrameUrl: string, + characterRig: THREE.Object3D, +) { //rotateChildBone(characterRig, "mixamorigLeftLeg", 1, 2, 0); //rotateChildBone(characterRig, "mixamorigRightArm", 0, 1, 2); //rotateChildBone(characterRig, "mixamorigLeftShoulder", 2, 0, 2); @@ -203,33 +615,46 @@ async function doTest(firstFrameUrl: string, characterRig: THREE.Object3D { +async function solveForImageUrl( + imageUrl: string, +): Promise { const image = await loadImageFromAnonymousOriginUrl(imageUrl); console.debug("Loaded image for inference", image, image.width, image.height); return solveForImage(image); } -async function solveForImage(image: HTMLImageElement) : Promise { +async function solveForImage( + image: HTMLImageElement, +): Promise { //const image : string = await loadImageFromAnonymousOriginUrl(imageUrl); //console.debug("Loaded image for inference", image, image.width, image.height); // TODO: Cache this. const filesetResolver = await FilesetResolver.forVisionTasks( - "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm" + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm", ); - + const numPoses = 1; const runningMode = "IMAGE"; - const poseLandmarker = await PoseLandmarker.createFromOptions(filesetResolver, { - baseOptions: { - modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task`, - delegate: "GPU" - }, - runningMode: runningMode, - numPoses: numPoses - }); + const poseLandmarker = await PoseLandmarker.createFromOptions( + filesetResolver, + { + baseOptions: { + modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_full/float16/1/pose_landmarker_full.task`, - const poseResults = poseLandmarker.detect(image) + // modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task`, + delegate: "GPU", + }, + runningMode: runningMode, + numPoses: numPoses, + }, + ); + + const poseResults = poseLandmarker.detect(image); console.debug("Pose results: ", poseResults); return poseResults; } -async function solveHolisticForImage(image: HTMLImageElement) : Promise { +async function solveHolisticForImage( + image: HTMLImageElement, +): Promise { // TODO: Cache this. const filesetResolver = await FilesetResolver.forVisionTasks( - "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm" + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm", ); //let holistic = new Holistic({locateFile: (file) => { @@ -309,13 +744,17 @@ async function solveHolisticForImage(image: HTMLImageElement) : Promise