mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-10 20:11:49 +00:00
vendor in kalidokit
This commit is contained in:
1 parent
4b07553e3c
commit
d637a16af5
60 files changed
+32107
-11
No files matched your search
@@ -1,5 +1,11 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
//import * as Kalidokit from "kalidokit";
|
||||
import * as Kalidokit from "kalidokit"
|
||||
import { TFace } from "kalidokit";
|
||||
//import { Pose } from "kalidokit";
|
||||
//const Kalidokit = require('kalidokit');
|
||||
|
||||
import { FilesetResolver, PoseLandmarker, PoseLandmarkerResult } from "@mediapipe/tasks-vision"
|
||||
import { CharacterPoseHelper } from "./Engines/Helpers/CharacterPoseHelper";
|
||||
import { loadImageFromAnonymousOriginUrl } from "~/Helpers/ImageHelpers";
|
||||
@@ -79,14 +85,14 @@ async function doTest(firstFrameUrl: string, characterRig: THREE.Object3D<THREE.
|
||||
const poseWorld3DArray : any = solutions.worldLandmarks;
|
||||
const poseLandmarkArray : any = solutions.landmarks;
|
||||
|
||||
/*let solution = Kalidokit.Pose.solve(poseWorld3DArray, poseLandmarkArray, {
|
||||
let solution = Kalidokit.Pose.solve(poseWorld3DArray, poseLandmarkArray, {
|
||||
runtime:'mediapipe', // default is 'mediapipe'
|
||||
//video: HTMLVideoElement,// specify an html video or manually set image size
|
||||
imageSize:{
|
||||
width: 640,
|
||||
height: 480,
|
||||
}
|
||||
});*/
|
||||
});
|
||||
|
||||
console.log('kalidokit solution', solution);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 13,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: yeemachine
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
name: Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm i -g npm@latest
|
||||
- run: npm ci
|
||||
- run: npm run jest
|
||||
- run: npm run build
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 yeemachine
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,283 @@
|
||||
<img src="https://github.com/yeemachine/kalidokit/blob/main/docs/kalidokit-logo.png?raw=true" alt="Kalidokit Template" width="200ps" style="margin-bottom:10px"/>
|
||||
|
||||
[![NPM Package][npm]][npm-url]
|
||||
[![NPM Bundle Size][minimized-size]][npm-url]
|
||||
[![jsDelivr hits (npm)][js-delivr]][js-delivr-url]
|
||||
[![NPM Downloads][npm-downloads]][npmtrends-url]
|
||||
[![Website][website]][website-url]
|
||||
[![Discord Shield][discord]][discord-url]
|
||||
|
||||
| :exclamation: KalidoKit is now officially deprecated and solutions will be directly integrated into [MediaPipe](https://mediapipe-studio.webapps.google.com/demo/face_landmarker) :exclamation:|
|
||||
|--------------------------------------|
|
||||
|
||||
## Face, Pose, and Hand Tracking Calculator
|
||||
|
||||
Kalidokit is a blendshape and kinematics solver for Mediapipe/Tensorflow.js face, eyes, pose, and hand tracking models, compatible with [Facemesh](https://github.com/tensorflow/tfjs-models/tree/master/face-landmarks-detection), [Blazepose](https://github.com/tensorflow/tfjs-models/tree/master/pose-detection), [Handpose](https://google.github.io/mediapipe/solutions/hands.html), and [Holistic](https://google.github.io/mediapipe/solutions/holistic.html). It takes predicted 3D landmarks and calculates simple euler rotations and blendshape face values.
|
||||
|
||||
As the core to Vtuber web apps, [Kalidoface](https://kalidoface.com) and [Kalidoface 3D](https://3d.kalidoface.com), KalidoKit is designed specifically for rigging 3D VRM models and Live2D avatars!
|
||||
|
||||
NOTE: This is a JS library meant for developers using Mediapipe pretrained models and not a complete app by itself. Please visit [Kalidoface](https://kalidoface.com) for the full vtuber experience 😉!
|
||||
|
||||
<a href="https://glitch.com/edit/#!/kalidokit"><img src="https://github.com/yeemachine/kalidokit/blob/main/docs/kalidokit_glitch.gif?raw=true" alt="Kalidokit Template" width="48%"/></a>
|
||||
<a href="https://glitch.com/edit/#!/kalidokit-live2d"><img src="https://github.com/yeemachine/kalidokit/blob/main/docs/kalidokit-live2d_glitch.gif?raw=true" alt="Kalidokit Template" width="48%"/></a>
|
||||
|
||||
[](https://ko-fi.com/B0B75DIY1)
|
||||
|
||||
## Install
|
||||
|
||||
#### Via NPM
|
||||
|
||||
```
|
||||
npm install kalidokit
|
||||
```
|
||||
|
||||
```js
|
||||
import * as Kalidokit from "kalidokit";
|
||||
|
||||
// or only import the class you need
|
||||
|
||||
import { Face, Pose, Hand } from "kalidokit";
|
||||
```
|
||||
|
||||
#### Via CDN
|
||||
|
||||
```js
|
||||
<script src="https://cdn.jsdelivr.net/npm/kalidokit@1.1/dist/kalidokit.umd.js"></script>
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
Kalidokit is composed of 3 classes for Face, Pose, and Hand calculations. They accept landmark outputs from models like Facemesh, Blazepose, Handpose, and Holistic.
|
||||
|
||||
```js
|
||||
// Accepts an array(468 or 478 with iris tracking) of vectors
|
||||
Kalidokit.Face.solve(facelandmarkArray, {
|
||||
runtime: "tfjs", // `mediapipe` or `tfjs`
|
||||
video: HTMLVideoElement,
|
||||
imageSize: { height: 0, width: 0 },
|
||||
smoothBlink: false, // smooth left and right eye blink delays
|
||||
blinkSettings: [0.25, 0.75], // adjust upper and lower bound blink sensitivity
|
||||
});
|
||||
|
||||
// Accepts arrays(33) of Pose keypoints and 3D Pose keypoints
|
||||
Kalidokit.Pose.solve(poseWorld3DArray, poseLandmarkArray, {
|
||||
runtime: "tfjs", // `mediapipe` or `tfjs`
|
||||
video: HTMLVideoElement,
|
||||
imageSize: { height: 0, width: 0 },
|
||||
enableLegs: true,
|
||||
});
|
||||
|
||||
// Accepts array(21) of hand landmark vectors; specify 'Right' or 'Left' side
|
||||
Kalidokit.Hand.solve(handLandmarkArray, "Right");
|
||||
|
||||
// Using exported classes directly
|
||||
Face.solve(facelandmarkArray);
|
||||
Pose.solve(poseWorld3DArray, poseLandmarkArray);
|
||||
Hand.solve(handLandmarkArray, "Right");
|
||||
```
|
||||
|
||||
Additional Utils
|
||||
|
||||
```js
|
||||
// Stabilizes left/right blink delays + wink by providing blenshapes and head rotation
|
||||
Kalidokit.Face.stabilizeBlink(
|
||||
{ r: 0, l: 1 }, // left and right eye blendshape values
|
||||
headRotationY, // head rotation in radians
|
||||
{
|
||||
noWink = false, // disables winking
|
||||
maxRot = 0.5 // max head rotation in radians before interpolating obscured eyes
|
||||
});
|
||||
|
||||
// The internal vector math class
|
||||
Kalidokit.Vector();
|
||||
```
|
||||
|
||||
## VRM and Live2D Sample Code
|
||||
|
||||
Quick-start your Vtuber app with these simple remixable examples.
|
||||
|
||||
### VRM Glitch Template
|
||||
|
||||
Face, full-body, and hand tracking in under 350 lines of javascript. This demo uses Mediapipe Holistic for body tracking, Three.js + Three-VRM for rendering models, and KalidoKit for the kinematic calculations. This [demo](https://glitch.com/edit/#!/kalidokit) uses a minimal amount of easing to smooth animations, but feel free to make it your own! VRM model from [Vroid Hub](https://hub.vroid.com/en/characters/6617701103638326208/models/8892157442595230149).
|
||||
|
||||
<a href="https://glitch.com/edit/#!/kalidokit"><img alt="Remix on Glitch" src="https://cdn.gomix.com/f3620a78-0ad3-4f81-a271-c8a4faa20f86%2Fremix-button.svg"></a>
|
||||
|
||||
### Live2D Glitch Template
|
||||
|
||||
This demo uses Mediapipe FaceMesh for face tracking, Pixi.js + pixi-live2d-display for rendering models, and KalidoKit for the kinematic calculations. This [demo](https://glitch.com/edit/#!/kalidokit-live2d) uses an official sample Live2D model, [Hiyori](https://www.live2d.com/en/download/sample-data/).
|
||||
|
||||
<a href="https://glitch.com/edit/#!/kalidokit-live2d"><img alt="Remix on Glitch" src="https://cdn.gomix.com/f3620a78-0ad3-4f81-a271-c8a4faa20f86%2Fremix-button.svg"></a>
|
||||
|
||||
### Local VRM and Live2D Examples
|
||||
|
||||
Interested in contributing? If you clone this project, the `docs` folder includes both VRM and Live2D KalidoKit templates. Run the `test` script to build the library and start up the dev server. The VRM demo will be hosted on `localhost:3000` and Live2D on `localhost:3000/live2d/`.
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run test
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<img src="https://github.com/yeemachine/kalidokit/blob/main/docs/kalidokit-pipeline.png?raw=true" alt="Kalidokit Template" width="100%"/>
|
||||
|
||||
The implementation may vary depending on what pose and face detection model you choose to use, but the principle is still the same. This example uses Mediapipe Holistic which concisely combines them together.
|
||||
|
||||
```js
|
||||
import * as Kalidokit from 'kalidokit'
|
||||
import '@mediapipe/holistic/holistic';
|
||||
import '@mediapipe/camera_utils/camera_utils';
|
||||
|
||||
let holistic = new Holistic({locateFile: (file) => {
|
||||
return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.4.1633559476/${file}`;
|
||||
}});
|
||||
|
||||
holistic.onResults(results=>{
|
||||
// do something with prediction results
|
||||
// landmark names may change depending on TFJS/Mediapipe model version
|
||||
let facelm = results.faceLandmarks;
|
||||
let poselm = results.poseLandmarks;
|
||||
let poselm3D = results.ea;
|
||||
let rightHandlm = results.rightHandLandmarks;
|
||||
let leftHandlm = results.leftHandLandmarks;
|
||||
|
||||
let faceRig = Kalidokit.Face.solve(facelm,{runtime:'mediapipe',video:HTMLVideoElement})
|
||||
let poseRig = Kalidokit.Pose.solve(poselm3d,poselm,{runtime:'mediapipe',video:HTMLVideoElement})
|
||||
let rightHandRig = Kalidokit.Hand.solve(rightHandlm,"Right")
|
||||
let leftHandRig = Kalidokit.Hand.solve(leftHandlm,"Left")
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
// use Mediapipe's webcam utils to send video to holistic every frame
|
||||
const camera = new Camera(HTMLVideoElement, {
|
||||
onFrame: async () => {
|
||||
await holistic.send({image: HTMLVideoElement});
|
||||
},
|
||||
width: 640,
|
||||
height: 480
|
||||
});
|
||||
camera.start();
|
||||
```
|
||||
|
||||
## Slight differences with Mediapipe and Tensorflow.js
|
||||
|
||||
Due to slight differences in the results from Mediapipe and Tensorflow.js, it is recommended to specify which runtime version you are using as well as the video input/image size as a reference.
|
||||
|
||||
```js
|
||||
Kalidokit.Pose.solve(poselm3D,poselm,{
|
||||
runtime:'tfjs', // default is 'mediapipe'
|
||||
video: HTMLVideoElement,// specify an html video or manually set image size
|
||||
imageSize:{
|
||||
width: 640,
|
||||
height: 480,
|
||||
};
|
||||
})
|
||||
|
||||
Kalidokit.Face.solve(facelm,{
|
||||
runtime:'mediapipe', // default is 'tfjs'
|
||||
video: HTMLVideoElement,// specify an html video or manually set image size
|
||||
imageSize:{
|
||||
width: 640,
|
||||
height: 480,
|
||||
};
|
||||
})
|
||||
```
|
||||
|
||||
## Outputs
|
||||
|
||||
Below are the expected results from KalidoKit solvers.
|
||||
|
||||
```js
|
||||
// Kalidokit.Face.solve()
|
||||
// Head rotations in radians
|
||||
// Degrees and normalized rotations also available
|
||||
{
|
||||
eye: {l: 1,r: 1},
|
||||
mouth: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
shape: {A:0, E:0, I:0, O:0, U:0}
|
||||
},
|
||||
head: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
width: 0.3,
|
||||
height: 0.6,
|
||||
position: {x: 0.5, y: 0.5, z: 0}
|
||||
},
|
||||
brow: 0,
|
||||
pupil: {x: 0, y: 0}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// Kalidokit.Pose.solve()
|
||||
// Joint rotations in radians, leg calculators are a WIP
|
||||
{
|
||||
RightUpperArm: {x: 0, y: 0, z: -1.25},
|
||||
LeftUpperArm: {x: 0, y: 0, z: 1.25},
|
||||
RightLowerArm: {x: 0, y: 0, z: 0},
|
||||
LeftLowerArm: {x: 0, y: 0, z: 0},
|
||||
LeftUpperLeg: {x: 0, y: 0, z: 0},
|
||||
RightUpperLeg: {x: 0, y: 0, z: 0},
|
||||
RightLowerLeg: {x: 0, y: 0, z: 0},
|
||||
LeftLowerLeg: {x: 0, y: 0, z: 0},
|
||||
LeftHand: {x: 0, y: 0, z: 0},
|
||||
RightHand: {x: 0, y: 0, z: 0},
|
||||
Spine: {x: 0, y: 0, z: 0},
|
||||
Hips: {
|
||||
worldPosition: {x: 0, y: 0, z: 0},
|
||||
position: {x: 0, y: 0, z: 0},
|
||||
rotation: {x: 0, y: 0, z: 0},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// Kalidokit.Hand.solve()
|
||||
// Joint rotations in radians
|
||||
// only wrist and thumb have 3 degrees of freedom
|
||||
// all other finger joints move in the Z axis only
|
||||
{
|
||||
RightWrist: {x: -0.13, y: -0.07, z: -1.04},
|
||||
RightRingProximal: {x: 0, y: 0, z: -0.13},
|
||||
RightRingIntermediate: {x: 0, y: 0, z: -0.4},
|
||||
RightRingDistal: {x: 0, y: 0, z: -0.04},
|
||||
RightIndexProximal: {x: 0, y: 0, z: -0.24},
|
||||
RightIndexIntermediate: {x: 0, y: 0, z: -0.25},
|
||||
RightIndexDistal: {x: 0, y: 0, z: -0.06},
|
||||
RightMiddleProximal: {x: 0, y: 0, z: -0.09},
|
||||
RightMiddleIntermediate: {x: 0, y: 0, z: -0.44},
|
||||
RightMiddleDistal: {x: 0, y: 0, z: -0.06},
|
||||
RightThumbProximal: {x: -0.23, y: -0.33, z: -0.12},
|
||||
RightThumbIntermediate: {x: -0.2, y: -0.19, z: -0.01},
|
||||
RightThumbDistal: {x: -0.2, y: 0.002, z: 0.15},
|
||||
RightLittleProximal: {x: 0, y: 0, z: -0.09},
|
||||
RightLittleIntermediate: {x: 0, y: 0, z: -0.22},
|
||||
RightLittleDistal: {x: 0, y: 0, z: -0.1}
|
||||
}
|
||||
```
|
||||
|
||||
## Community Showcase + Contributing
|
||||
|
||||
If you'd like to share a creative use of KalidoKit, please reach out or send a pull request! Feel free to also use our Twitter hashtag, [#kalidokit](https://twitter.com/search?q=%23kalidokit).
|
||||
|
||||
Kalidoface/KalidoKit also now has a [Discord server][discord-url]! Come and join us!
|
||||
|
||||
The current library is a work in progress and contributions to improve it are very welcome! Some plans include: better facial blendshapes, full leg tracking, and improved efficiency.
|
||||
|
||||
[](https://kalidoface.com) [](https://3d.kalidoface.com)
|
||||
|
||||
[npm]: https://img.shields.io/npm/v/kalidokit
|
||||
[npm-url]: https://www.npmjs.com/package/kalidokit
|
||||
[minimized-size]: https://img.shields.io/bundlephobia/min/kalidokit
|
||||
[js-delivr]: https://img.shields.io/jsdelivr/npm/hw/kalidokit
|
||||
[js-delivr-url]: https://www.jsdelivr.com/package/npm/kalidokit
|
||||
[website]: https://img.shields.io/website?down_color=lightgrey&down_message=offline&up_color=brightgreen&up_message=online&url=https%3A%2F%2Fkalidoface.com
|
||||
[website-url]: https://kalidoface.com
|
||||
[npm-downloads]: https://img.shields.io/npm/dw/kalidokit
|
||||
[npmtrends-url]: https://www.npmtrends.com/kalidokit
|
||||
[discord]: https://discordapp.com/api/guilds/869435838746984468/widget.png?style=shield
|
||||
[discord-url]: https://discord.gg/XMqdfzVZ2E
|
||||
@@ -0,0 +1 @@
|
||||
kit.kalidoface.com
|
||||
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>KalidoKit - Face and Body Rig Solver</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Track face and body rigs just your browser webcam!"
|
||||
/>
|
||||
|
||||
<!-- Twitter Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="kit.kalidoface.com" />
|
||||
<meta property="twitter:url" content="https://kit.kalidoface.com/" />
|
||||
<meta name="twitter:title" content="KalidoKit - Face and Body Rig Solver" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Track face and body rigs just your browser webcam!"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://cdn.glitch.com/239c5934-4d83-4c5c-bef6-44dcdb04c8fb%2Fkalidoface-meta.jpg?v=1630110302224"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="https://yeemachine.github.io/kalidoface-live2d-models/Icons/icon-circle.svg"
|
||||
/>
|
||||
<!-- Basic Three.js -->
|
||||
<script src="https://unpkg.com/three@0.133.0/build/three.js"></script>
|
||||
<!-- GLTF Loader for Three.js -->
|
||||
<script src="https://unpkg.com/three@0.133.0/examples/js/loaders/GLTFLoader.js"></script>
|
||||
<!-- Orbit Controls for Three.js -->
|
||||
<script src="https://unpkg.com/three@0.133.0/examples/js/controls/OrbitControls.js"></script>
|
||||
<!-- VRM Loader for Three.js -->
|
||||
<script src="https://unpkg.com/@pixiv/three-vrm@0.6.7/lib/three-vrm.js"></script>
|
||||
<!-- Mediapipe or Tensorflow.js -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.5.1635989137/holistic.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<!-- Mediapipe Drawing Tools -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<!-- Mediapipe Camera Tools -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="preview">
|
||||
<video class="input_video" width="1280px" height="720px"></video>
|
||||
<canvas class="guides"></canvas>
|
||||
<section>
|
||||
<a href="/live2d/"><p>Live2D</p></a>
|
||||
<a class="current" href="/"><p>VRM</p></a>
|
||||
</section>
|
||||
</div>
|
||||
<h1 class="notranslate">
|
||||
<a href="https://3d.kalidoface.com">kalidoface</a>
|
||||
</h1>
|
||||
<nav>
|
||||
<a href="https://www.npmjs.com/package/kalidokit">
|
||||
<img
|
||||
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Fnpm.png?v=1635133318451"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/yeemachine/kalidokit">
|
||||
<img
|
||||
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Fgithub.png?v=1635133310517"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://twitter.com/yeemachine">
|
||||
<img
|
||||
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Ftwitter.png?v=1635133322561"
|
||||
/>
|
||||
</a>
|
||||
</nav>
|
||||
<p class="linkOut">
|
||||
Visit
|
||||
<a id="full" href="https://kalidoface.com">the full Vtuber App</a>!
|
||||
</p>
|
||||
<script src="./script.js" type="module" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 489 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.3 MiB |
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<!-- HTML Meta Tags -->
|
||||
<title>KalidoKit - Face and Body Rig Solver</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Track face and body rigs just your browser webcam!"
|
||||
/>
|
||||
|
||||
<!-- Twitter Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="kit.kalidoface.com" />
|
||||
<meta property="twitter:url" content="https://kit.kalidoface.com/" />
|
||||
<meta name="twitter:title" content="KalidoKit - Face and Body Rig Solver" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Track face and body rigs just your browser webcam!"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://cdn.glitch.com/239c5934-4d83-4c5c-bef6-44dcdb04c8fb%2Fkalidoface-meta.jpg?v=1630110302224"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="https://yeemachine.github.io/kalidoface-live2d-models/Icons/icon-circle.svg"
|
||||
/>
|
||||
<!-- Live2D SDK -->
|
||||
<script src="https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/dylanNew/live2d/webgl/Live2D/lib/live2d.min.js"></script>
|
||||
<!-- PixiJS Renderer -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js"></script>
|
||||
<!-- PixiJS Live2D Plugin -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/pixi-live2d-display/dist/index.min.js"></script>
|
||||
<!-- Mediapipe or Tensorflow.js -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<!-- Mediapipe Drawing Tools -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<!-- Mediapipe Camera Tools -->
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<!-- Kalidokit Kinematic Solver -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/kalidokit@1.1/dist/kalidokit.umd.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="../style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="preview">
|
||||
<video class="input_video" width="1280px" height="720px"></video>
|
||||
<canvas class="guides"></canvas>
|
||||
<section>
|
||||
<a class="current" href="/live2d/"><p>Live2D</p></a>
|
||||
<a href="/"><p>VRM</p></a>
|
||||
</section>
|
||||
</div>
|
||||
<h1 class="notranslate">
|
||||
<a href="https://3d.kalidoface.com">kalidoface</a>
|
||||
</h1>
|
||||
<nav>
|
||||
<a href="https://www.npmjs.com/package/kalidokit">
|
||||
<img
|
||||
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Fnpm.png?v=1635133318451"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/yeemachine/kalidokit">
|
||||
<img
|
||||
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Fgithub.png?v=1635133310517"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://twitter.com/yeemachine">
|
||||
<img
|
||||
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Ftwitter.png?v=1635133322561"
|
||||
/>
|
||||
</a>
|
||||
</nav>
|
||||
<canvas id="live2d"></canvas>
|
||||
<p class="linkOut">
|
||||
Visit
|
||||
<a id="full" href="https://kalidoface.com">the full Vtuber App</a>!
|
||||
</p>
|
||||
<script type="module" src="./script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,211 @@
|
||||
const {
|
||||
Application,
|
||||
live2d: { Live2DModel },
|
||||
} = PIXI;
|
||||
|
||||
// Kalidokit provides a simple easing function
|
||||
// (linear interpolation) used for animation smoothness
|
||||
// you can use a more advanced easing function if you want
|
||||
const {
|
||||
Face,
|
||||
Vector: { lerp },
|
||||
Utils: { clamp },
|
||||
} = Kalidokit;
|
||||
|
||||
// Url to Live2D
|
||||
const modelUrl = "../models/hiyori/hiyori_pro_t10.model3.json";
|
||||
|
||||
let currentModel, facemesh;
|
||||
|
||||
const videoElement = document.querySelector(".input_video"),
|
||||
guideCanvas = document.querySelector("canvas.guides");
|
||||
|
||||
(async function main() {
|
||||
// create pixi application
|
||||
const app = new PIXI.Application({
|
||||
view: document.getElementById("live2d"),
|
||||
autoStart: true,
|
||||
backgroundAlpha: 0,
|
||||
backgroundColor: 0xffffff,
|
||||
resizeTo: window,
|
||||
});
|
||||
|
||||
// load live2d model
|
||||
currentModel = await Live2DModel.from(modelUrl, { autoInteract: false });
|
||||
currentModel.scale.set(0.4);
|
||||
currentModel.interactive = true;
|
||||
currentModel.anchor.set(0.5, 0.5);
|
||||
currentModel.position.set(window.innerWidth * 0.5, window.innerHeight * 0.8);
|
||||
|
||||
// Add events to drag model
|
||||
currentModel.on("pointerdown", (e) => {
|
||||
currentModel.offsetX = e.data.global.x - currentModel.position.x;
|
||||
currentModel.offsetY = e.data.global.y - currentModel.position.y;
|
||||
currentModel.dragging = true;
|
||||
});
|
||||
currentModel.on("pointerup", (e) => {
|
||||
currentModel.dragging = false;
|
||||
});
|
||||
currentModel.on("pointermove", (e) => {
|
||||
if (currentModel.dragging) {
|
||||
currentModel.position.set(e.data.global.x - currentModel.offsetX, e.data.global.y - currentModel.offsetY);
|
||||
}
|
||||
});
|
||||
|
||||
// Add mousewheel events to scale model
|
||||
document.querySelector("#live2d").addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
currentModel.scale.set(clamp(currentModel.scale.x + event.deltaY * -0.001, -0.5, 10));
|
||||
});
|
||||
|
||||
// add live2d model to stage
|
||||
app.stage.addChild(currentModel);
|
||||
|
||||
// create media pipe facemesh instance
|
||||
facemesh = new FaceMesh({
|
||||
locateFile: (file) => {
|
||||
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`;
|
||||
},
|
||||
});
|
||||
|
||||
// set facemesh config
|
||||
facemesh.setOptions({
|
||||
maxNumFaces: 1,
|
||||
refineLandmarks: true,
|
||||
minDetectionConfidence: 0.5,
|
||||
minTrackingConfidence: 0.5,
|
||||
});
|
||||
|
||||
// pass facemesh callback function
|
||||
facemesh.onResults(onResults);
|
||||
|
||||
startCamera();
|
||||
})();
|
||||
|
||||
const onResults = (results) => {
|
||||
drawResults(results.multiFaceLandmarks[0]);
|
||||
animateLive2DModel(results.multiFaceLandmarks[0]);
|
||||
};
|
||||
|
||||
// draw connectors and landmarks on output canvas
|
||||
const drawResults = (points) => {
|
||||
if (!guideCanvas || !videoElement || !points) return;
|
||||
guideCanvas.width = videoElement.videoWidth;
|
||||
guideCanvas.height = videoElement.videoHeight;
|
||||
let canvasCtx = guideCanvas.getContext("2d");
|
||||
canvasCtx.save();
|
||||
canvasCtx.clearRect(0, 0, guideCanvas.width, guideCanvas.height);
|
||||
// Use `Mediapipe` drawing functions
|
||||
drawConnectors(canvasCtx, points, FACEMESH_TESSELATION, {
|
||||
color: "#C0C0C070",
|
||||
lineWidth: 1,
|
||||
});
|
||||
if (points && points.length === 478) {
|
||||
//draw pupils
|
||||
drawLandmarks(canvasCtx, [points[468], points[468 + 5]], {
|
||||
color: "#ffe603",
|
||||
lineWidth: 2,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const animateLive2DModel = (points) => {
|
||||
if (!currentModel || !points) return;
|
||||
|
||||
let riggedFace;
|
||||
|
||||
if (points) {
|
||||
// use kalidokit face solver
|
||||
riggedFace = Face.solve(points, {
|
||||
runtime: "mediapipe",
|
||||
video: videoElement,
|
||||
});
|
||||
rigFace(riggedFace, 0.5);
|
||||
}
|
||||
};
|
||||
|
||||
// update live2d model internal state
|
||||
const rigFace = (result, lerpAmount = 0.7) => {
|
||||
if (!currentModel || !result) return;
|
||||
const coreModel = currentModel.internalModel.coreModel;
|
||||
|
||||
currentModel.internalModel.motionManager.update = (...args) => {
|
||||
// disable default blink animation
|
||||
currentModel.internalModel.eyeBlink = undefined;
|
||||
|
||||
coreModel.setParameterValueById(
|
||||
"ParamEyeBallX",
|
||||
lerp(result.pupil.x, coreModel.getParameterValueById("ParamEyeBallX"), lerpAmount)
|
||||
);
|
||||
coreModel.setParameterValueById(
|
||||
"ParamEyeBallY",
|
||||
lerp(result.pupil.y, coreModel.getParameterValueById("ParamEyeBallY"), lerpAmount)
|
||||
);
|
||||
|
||||
// X and Y axis rotations are swapped for Live2D parameters
|
||||
// because it is a 2D system and KalidoKit is a 3D system
|
||||
coreModel.setParameterValueById(
|
||||
"ParamAngleX",
|
||||
lerp(result.head.degrees.y, coreModel.getParameterValueById("ParamAngleX"), lerpAmount)
|
||||
);
|
||||
coreModel.setParameterValueById(
|
||||
"ParamAngleY",
|
||||
lerp(result.head.degrees.x, coreModel.getParameterValueById("ParamAngleY"), lerpAmount)
|
||||
);
|
||||
coreModel.setParameterValueById(
|
||||
"ParamAngleZ",
|
||||
lerp(result.head.degrees.z, coreModel.getParameterValueById("ParamAngleZ"), lerpAmount)
|
||||
);
|
||||
|
||||
// update body params for models without head/body param sync
|
||||
const dampener = 0.3;
|
||||
coreModel.setParameterValueById(
|
||||
"ParamBodyAngleX",
|
||||
lerp(result.head.degrees.y * dampener, coreModel.getParameterValueById("ParamBodyAngleX"), lerpAmount)
|
||||
);
|
||||
coreModel.setParameterValueById(
|
||||
"ParamBodyAngleY",
|
||||
lerp(result.head.degrees.x * dampener, coreModel.getParameterValueById("ParamBodyAngleY"), lerpAmount)
|
||||
);
|
||||
coreModel.setParameterValueById(
|
||||
"ParamBodyAngleZ",
|
||||
lerp(result.head.degrees.z * dampener, coreModel.getParameterValueById("ParamBodyAngleZ"), lerpAmount)
|
||||
);
|
||||
|
||||
// Simple example without winking.
|
||||
// Interpolate based on old blendshape, then stabilize blink with `Kalidokit` helper function.
|
||||
let stabilizedEyes = Kalidokit.Face.stabilizeBlink(
|
||||
{
|
||||
l: lerp(result.eye.l, coreModel.getParameterValueById("ParamEyeLOpen"), 0.7),
|
||||
r: lerp(result.eye.r, coreModel.getParameterValueById("ParamEyeROpen"), 0.7),
|
||||
},
|
||||
result.head.y
|
||||
);
|
||||
// eye blink
|
||||
coreModel.setParameterValueById("ParamEyeLOpen", stabilizedEyes.l);
|
||||
coreModel.setParameterValueById("ParamEyeROpen", stabilizedEyes.r);
|
||||
|
||||
// mouth
|
||||
coreModel.setParameterValueById(
|
||||
"ParamMouthOpenY",
|
||||
lerp(result.mouth.y, coreModel.getParameterValueById("ParamMouthOpenY"), 0.3)
|
||||
);
|
||||
// Adding 0.3 to ParamMouthForm to make default more of a "smile"
|
||||
coreModel.setParameterValueById(
|
||||
"ParamMouthForm",
|
||||
0.3 + lerp(result.mouth.x, coreModel.getParameterValueById("ParamMouthForm"), 0.3)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
// start camera using mediapipe camera utils
|
||||
const startCamera = () => {
|
||||
const camera = new Camera(videoElement, {
|
||||
onFrame: async () => {
|
||||
await facemesh.send({ image: videoElement });
|
||||
},
|
||||
width: 640,
|
||||
height: 480,
|
||||
});
|
||||
camera.start();
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -0,0 +1,533 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"Parameters": [
|
||||
{
|
||||
"Id": "ParamAngleX",
|
||||
"GroupId": "ParamGroupFace",
|
||||
"Name": "Angle X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAngleY",
|
||||
"GroupId": "ParamGroupFace",
|
||||
"Name": "Angle Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamAngleZ",
|
||||
"GroupId": "ParamGroupFace",
|
||||
"Name": "Angle Z"
|
||||
},
|
||||
{
|
||||
"Id": "ParamCheek",
|
||||
"GroupId": "ParamGroupFace",
|
||||
"Name": "Cheek"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLOpen",
|
||||
"GroupId": "ParamGroupEyes",
|
||||
"Name": "Eye L Open"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeLSmile",
|
||||
"GroupId": "ParamGroupEyes",
|
||||
"Name": "Eye L Smile"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeROpen",
|
||||
"GroupId": "ParamGroupEyes",
|
||||
"Name": "Eye R Open"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeRSmile",
|
||||
"GroupId": "ParamGroupEyes",
|
||||
"Name": "Eye R Smile"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallX",
|
||||
"GroupId": "ParamGroupEyeballs",
|
||||
"Name": "Eyeball X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamEyeBallY",
|
||||
"GroupId": "ParamGroupEyeballs",
|
||||
"Name": "Eyeball Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLY",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow L Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRY",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow R Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLX",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow L X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRX",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow R X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLAngle",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow L Angle"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRAngle",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow R Angle"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowLForm",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow L Form"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBrowRForm",
|
||||
"GroupId": "ParamGroupBrows",
|
||||
"Name": "Brow R Form"
|
||||
},
|
||||
{
|
||||
"Id": "ParamMouthForm",
|
||||
"GroupId": "ParamGroupMouth",
|
||||
"Name": "Mouth Form"
|
||||
},
|
||||
{
|
||||
"Id": "ParamMouthOpenY",
|
||||
"GroupId": "ParamGroupMouth",
|
||||
"Name": "Mouth Open"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyAngleX",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "Body X"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyAngleY",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "Body Y"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBodyAngleZ",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "Body Z"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBreath",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "Breath"
|
||||
},
|
||||
{
|
||||
"Id": "ParamShoulder",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "Shoulder"
|
||||
},
|
||||
{
|
||||
"Id": "ParamLeg",
|
||||
"GroupId": "ParamGroupBody",
|
||||
"Name": "Leg"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmLA",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Arm L A"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmRA",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Arm R A"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmLB",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Arm L B"
|
||||
},
|
||||
{
|
||||
"Id": "ParamArmRB",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Arm R B"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHandLB",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Hand L B Rotation"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHandRB",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Hand R B Rotation"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHandL",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Hand L"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHandR",
|
||||
"GroupId": "ParamGroupArms",
|
||||
"Name": "Hand R"
|
||||
},
|
||||
{
|
||||
"Id": "ParamBustY",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Bust Bounce"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairAhoge",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Hair Move Ahoge"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairFront",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Hair Move Front"
|
||||
},
|
||||
{
|
||||
"Id": "ParamHairBack",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Hair Move Back"
|
||||
},
|
||||
{
|
||||
"Id": "ParamSideupRibbon",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Move Side Up Ribbon"
|
||||
},
|
||||
{
|
||||
"Id": "ParamRibbon",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Move Butterfly Tie"
|
||||
},
|
||||
{
|
||||
"Id": "ParamSkirt",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Move Skirt"
|
||||
},
|
||||
{
|
||||
"Id": "ParamSkirt2",
|
||||
"GroupId": "ParamGroupSway",
|
||||
"Name": "Curl Skirt"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_1_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[0]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_2_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[1]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_3_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[2]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_4_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[3]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_5_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[4]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_6_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[5]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_7_ArtMesh62",
|
||||
"GroupId": "ParamGroup2",
|
||||
"Name": "[6]Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_1_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[0]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_2_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[1]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_3_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[2]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_4_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[3]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_5_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[4]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_6_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[5]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_7_ArtMesh61",
|
||||
"GroupId": "ParamGroup",
|
||||
"Name": "[6]Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_1_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[0]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_2_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[1]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_3_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[2]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_4_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[3]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_5_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[4]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_6_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[5]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_7_ArtMesh55",
|
||||
"GroupId": "ParamGroup4",
|
||||
"Name": "[6]Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_1_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[0]Hair Front R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_2_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[1]Hair Front R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_3_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[2]Hair Front R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_4_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[3]Hair Front R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_5_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[4]Hair Front R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_6_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[5]Hair Front R"
|
||||
},
|
||||
{
|
||||
"Id": "Param_Angle_Rotation_7_ArtMesh54",
|
||||
"GroupId": "ParamGroup3",
|
||||
"Name": "[6]Hair Front R"
|
||||
}
|
||||
],
|
||||
"ParameterGroups": [
|
||||
{
|
||||
"Id": "ParamGroupFace",
|
||||
"GroupId": "",
|
||||
"Name": "Face"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupEyes",
|
||||
"GroupId": "",
|
||||
"Name": "Eye"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupEyeballs",
|
||||
"GroupId": "",
|
||||
"Name": "Eyeball"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupBrows",
|
||||
"GroupId": "",
|
||||
"Name": "Brow"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupMouth",
|
||||
"GroupId": "",
|
||||
"Name": "Mouth"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupBody",
|
||||
"GroupId": "",
|
||||
"Name": "Body"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupArms",
|
||||
"GroupId": "",
|
||||
"Name": "Arm"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroupSway",
|
||||
"GroupId": "",
|
||||
"Name": "Move"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup2",
|
||||
"GroupId": "",
|
||||
"Name": "Move Hair Side Up L"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup",
|
||||
"GroupId": "",
|
||||
"Name": "Move Hair Side Up R"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup4",
|
||||
"GroupId": "",
|
||||
"Name": "Move Hair Front L"
|
||||
},
|
||||
{
|
||||
"Id": "ParamGroup3",
|
||||
"GroupId": "",
|
||||
"Name": "Move Hair Front R"
|
||||
}
|
||||
],
|
||||
"Parts": [
|
||||
{
|
||||
"Id": "PartCore",
|
||||
"Name": "Core"
|
||||
},
|
||||
{
|
||||
"Id": "PartCheek",
|
||||
"Name": "Cheek"
|
||||
},
|
||||
{
|
||||
"Id": "PartBrow",
|
||||
"Name": "Brow"
|
||||
},
|
||||
{
|
||||
"Id": "PartEye",
|
||||
"Name": "Eye"
|
||||
},
|
||||
{
|
||||
"Id": "PartNose",
|
||||
"Name": "Nose"
|
||||
},
|
||||
{
|
||||
"Id": "PartMouth",
|
||||
"Name": "Mouth"
|
||||
},
|
||||
{
|
||||
"Id": "PartFace",
|
||||
"Name": "Face"
|
||||
},
|
||||
{
|
||||
"Id": "PartEar",
|
||||
"Name": "Ear"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairSide",
|
||||
"Name": "Hair Side"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairFront",
|
||||
"Name": "Hair Front"
|
||||
},
|
||||
{
|
||||
"Id": "PartHairBack",
|
||||
"Name": "Hair Back"
|
||||
},
|
||||
{
|
||||
"Id": "PartNeck",
|
||||
"Name": "Neck"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmA",
|
||||
"Name": "Arm A"
|
||||
},
|
||||
{
|
||||
"Id": "PartArmB",
|
||||
"Name": "Arm B"
|
||||
},
|
||||
{
|
||||
"Id": "PartBody",
|
||||
"Name": "Body"
|
||||
},
|
||||
{
|
||||
"Id": "PartBackground",
|
||||
"Name": "Background"
|
||||
},
|
||||
{
|
||||
"Id": "PartSketch",
|
||||
"Name": "[Guide Image]"
|
||||
},
|
||||
{
|
||||
"Id": "PartEyeBall",
|
||||
"Name": "Eyeball"
|
||||
},
|
||||
{
|
||||
"Id": "ArtMesh55_Skinning",
|
||||
"Name": "Hair Front L Skinning"
|
||||
},
|
||||
{
|
||||
"Id": "Part4",
|
||||
"Name": "Hair Front L Rotation"
|
||||
},
|
||||
{
|
||||
"Id": "ArtMesh54_Skinning",
|
||||
"Name": "Hair Front R Skinning"
|
||||
},
|
||||
{
|
||||
"Id": "Part3",
|
||||
"Name": "Hair Front R Rotation"
|
||||
},
|
||||
{
|
||||
"Id": "ArtMesh61_Skinning",
|
||||
"Name": "Hair Side Up R Skinning"
|
||||
},
|
||||
{
|
||||
"Id": "Part",
|
||||
"Name": "Hair Side Up R Rotation"
|
||||
},
|
||||
{
|
||||
"Id": "ArtMesh62_Skinning",
|
||||
"Name": "Hair Side Up L Skinning"
|
||||
},
|
||||
{
|
||||
"Id": "Part2",
|
||||
"Name": "Hair Side Up L Rotation"
|
||||
}
|
||||
],
|
||||
"CombinedParameters": [
|
||||
[
|
||||
"ParamAngleX",
|
||||
"ParamAngleY"
|
||||
],
|
||||
[
|
||||
"ParamMouthForm",
|
||||
"ParamMouthOpenY"
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"FileReferences": {
|
||||
"Moc": "hiyori_pro_t10.moc3",
|
||||
"Textures": [
|
||||
"hiyori_pro_t10.2048/texture_00.png",
|
||||
"hiyori_pro_t10.2048/texture_01.png"
|
||||
],
|
||||
"Physics": "hiyori_pro_t10.physics3.json",
|
||||
"Pose": "hiyori_pro_t10.pose3.json",
|
||||
"DisplayInfo": "hiyori_pro_t10.cdi3.json",
|
||||
"Motions": {
|
||||
"Idle": [
|
||||
{
|
||||
"File": "motion/hiyori_m01.motion3.json"
|
||||
},
|
||||
{
|
||||
"File": "motion/hiyori_m02.motion3.json"
|
||||
},
|
||||
{
|
||||
"File": "motion/hiyori_m05.motion3.json"
|
||||
}
|
||||
],
|
||||
"Flick": [
|
||||
{
|
||||
"File": "motion/hiyori_m03.motion3.json"
|
||||
}
|
||||
],
|
||||
"FlickDown": [
|
||||
{
|
||||
"File": "motion/hiyori_m04.motion3.json"
|
||||
}
|
||||
],
|
||||
"FlickUp": [
|
||||
{
|
||||
"File": "motion/hiyori_m06.motion3.json"
|
||||
}
|
||||
],
|
||||
"Tap": [
|
||||
{
|
||||
"File": "motion/hiyori_m07.motion3.json"
|
||||
},
|
||||
{
|
||||
"File": "motion/hiyori_m08.motion3.json"
|
||||
}
|
||||
],
|
||||
"Tap@Body": [
|
||||
{
|
||||
"File": "motion/hiyori_m09.motion3.json"
|
||||
}
|
||||
],
|
||||
"Flick@Body": [
|
||||
{
|
||||
"File": "motion/hiyori_m10.motion3.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Groups": [
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Name": "LipSync",
|
||||
"Ids": [
|
||||
"ParamMouthOpenY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Name": "EyeBlink",
|
||||
"Ids": [
|
||||
"ParamEyeLOpen",
|
||||
"ParamEyeROpen"
|
||||
]
|
||||
}
|
||||
],
|
||||
"HitAreas": [
|
||||
{
|
||||
"Id": "HitArea",
|
||||
"Name": "Body"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"Type": "Live2D Pose",
|
||||
"Groups": [
|
||||
[
|
||||
{
|
||||
"Id": "PartArmA",
|
||||
"Link": []
|
||||
},
|
||||
{
|
||||
"Id": "PartArmB",
|
||||
"Link": []
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,882 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"Meta": {
|
||||
"Duration": 4.43,
|
||||
"Fps": 30.0,
|
||||
"Loop": true,
|
||||
"AreBeziersRestricted": false,
|
||||
"CurveCount": 31,
|
||||
"TotalSegmentCount": 106,
|
||||
"TotalPointCount": 287,
|
||||
"UserDataCount": 0,
|
||||
"TotalUserDataSize": 0
|
||||
},
|
||||
"Curves": [
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleX",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.211,
|
||||
1,
|
||||
0.422,
|
||||
0,
|
||||
0.633,
|
||||
0,
|
||||
1,
|
||||
0.911,
|
||||
0,
|
||||
1.189,
|
||||
5,
|
||||
1.467,
|
||||
5,
|
||||
1,
|
||||
1.689,
|
||||
5,
|
||||
1.911,
|
||||
-16,
|
||||
2.133,
|
||||
-16,
|
||||
1,
|
||||
2.356,
|
||||
-16,
|
||||
2.578,
|
||||
13.871,
|
||||
2.8,
|
||||
13.871,
|
||||
1,
|
||||
2.956,
|
||||
13.871,
|
||||
3.111,
|
||||
0,
|
||||
3.267,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.211,
|
||||
0,
|
||||
0.422,
|
||||
0,
|
||||
0.633,
|
||||
0,
|
||||
1,
|
||||
0.911,
|
||||
0,
|
||||
1.189,
|
||||
-25,
|
||||
1.467,
|
||||
-25,
|
||||
1,
|
||||
1.689,
|
||||
-25,
|
||||
1.911,
|
||||
-15.225,
|
||||
2.133,
|
||||
-11,
|
||||
1,
|
||||
2.356,
|
||||
-6.775,
|
||||
2.578,
|
||||
-5.127,
|
||||
2.8,
|
||||
-2.5,
|
||||
1,
|
||||
2.956,
|
||||
-0.661,
|
||||
3.111,
|
||||
0,
|
||||
3.267,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleZ",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.222,
|
||||
0,
|
||||
0.444,
|
||||
0,
|
||||
0.667,
|
||||
0,
|
||||
1,
|
||||
0.756,
|
||||
0,
|
||||
0.844,
|
||||
-4,
|
||||
0.933,
|
||||
-4,
|
||||
1,
|
||||
1.122,
|
||||
-4,
|
||||
1.311,
|
||||
18,
|
||||
1.5,
|
||||
18,
|
||||
1,
|
||||
1.722,
|
||||
18,
|
||||
1.944,
|
||||
-14,
|
||||
2.167,
|
||||
-14,
|
||||
1,
|
||||
2.567,
|
||||
-14,
|
||||
2.967,
|
||||
-14,
|
||||
3.367,
|
||||
-14,
|
||||
1,
|
||||
3.511,
|
||||
-14,
|
||||
3.656,
|
||||
-12,
|
||||
3.8,
|
||||
-12,
|
||||
0,
|
||||
4.433,
|
||||
-12
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamCheek",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
4.433,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Segments": [
|
||||
0,
|
||||
1.2,
|
||||
1,
|
||||
0.389,
|
||||
1.2,
|
||||
0.778,
|
||||
1.148,
|
||||
1.167,
|
||||
1,
|
||||
1,
|
||||
1.211,
|
||||
0.983,
|
||||
1.256,
|
||||
0,
|
||||
1.3,
|
||||
0,
|
||||
1,
|
||||
1.322,
|
||||
0,
|
||||
1.344,
|
||||
0,
|
||||
1.367,
|
||||
0,
|
||||
1,
|
||||
1.422,
|
||||
0,
|
||||
1.478,
|
||||
1,
|
||||
1.533,
|
||||
1,
|
||||
1,
|
||||
1.944,
|
||||
1,
|
||||
2.356,
|
||||
1,
|
||||
2.767,
|
||||
1,
|
||||
1,
|
||||
2.811,
|
||||
1,
|
||||
2.856,
|
||||
0,
|
||||
2.9,
|
||||
0,
|
||||
1,
|
||||
2.922,
|
||||
0,
|
||||
2.944,
|
||||
0,
|
||||
2.967,
|
||||
0,
|
||||
1,
|
||||
3.022,
|
||||
0,
|
||||
3.078,
|
||||
1,
|
||||
3.133,
|
||||
1,
|
||||
0,
|
||||
4.433,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeROpen",
|
||||
"Segments": [
|
||||
0,
|
||||
1.2,
|
||||
1,
|
||||
0.389,
|
||||
1.2,
|
||||
0.778,
|
||||
1.148,
|
||||
1.167,
|
||||
1,
|
||||
1,
|
||||
1.211,
|
||||
0.983,
|
||||
1.256,
|
||||
0,
|
||||
1.3,
|
||||
0,
|
||||
1,
|
||||
1.322,
|
||||
0,
|
||||
1.344,
|
||||
0,
|
||||
1.367,
|
||||
0,
|
||||
1,
|
||||
1.422,
|
||||
0,
|
||||
1.478,
|
||||
1,
|
||||
1.533,
|
||||
1,
|
||||
1,
|
||||
1.944,
|
||||
1,
|
||||
2.356,
|
||||
1,
|
||||
2.767,
|
||||
1,
|
||||
1,
|
||||
2.811,
|
||||
1,
|
||||
2.856,
|
||||
0,
|
||||
2.9,
|
||||
0,
|
||||
1,
|
||||
2.922,
|
||||
0,
|
||||
2.944,
|
||||
0,
|
||||
2.967,
|
||||
0,
|
||||
1,
|
||||
3.022,
|
||||
0,
|
||||
3.078,
|
||||
1,
|
||||
3.133,
|
||||
1,
|
||||
0,
|
||||
4.433,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeBallX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.211,
|
||||
0,
|
||||
0.422,
|
||||
0,
|
||||
0.633,
|
||||
0,
|
||||
1,
|
||||
0.911,
|
||||
0,
|
||||
1.189,
|
||||
-0.44,
|
||||
1.467,
|
||||
-0.44,
|
||||
1,
|
||||
1.689,
|
||||
-0.44,
|
||||
1.911,
|
||||
0.79,
|
||||
2.133,
|
||||
0.79,
|
||||
1,
|
||||
2.511,
|
||||
0.79,
|
||||
2.889,
|
||||
0,
|
||||
3.267,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeBallY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.211,
|
||||
0,
|
||||
0.422,
|
||||
0,
|
||||
0.633,
|
||||
0,
|
||||
1,
|
||||
0.911,
|
||||
0,
|
||||
1.189,
|
||||
-1,
|
||||
1.467,
|
||||
-1,
|
||||
1,
|
||||
1.689,
|
||||
-1,
|
||||
1.911,
|
||||
-1,
|
||||
2.133,
|
||||
-1,
|
||||
1,
|
||||
2.511,
|
||||
-1,
|
||||
2.889,
|
||||
0,
|
||||
3.267,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLY",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.544,
|
||||
1,
|
||||
1.089,
|
||||
1,
|
||||
1.633,
|
||||
1,
|
||||
1,
|
||||
1.856,
|
||||
1,
|
||||
2.078,
|
||||
0,
|
||||
2.3,
|
||||
0,
|
||||
1,
|
||||
2.5,
|
||||
0,
|
||||
2.7,
|
||||
1,
|
||||
2.9,
|
||||
1,
|
||||
0,
|
||||
4.433,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRY",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.544,
|
||||
1,
|
||||
1.089,
|
||||
1,
|
||||
1.633,
|
||||
1,
|
||||
1,
|
||||
1.856,
|
||||
1,
|
||||
2.078,
|
||||
0,
|
||||
2.3,
|
||||
0,
|
||||
1,
|
||||
2.5,
|
||||
0,
|
||||
2.7,
|
||||
1,
|
||||
2.9,
|
||||
1,
|
||||
0,
|
||||
4.433,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLForm",
|
||||
"Segments": [
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
4.433,
|
||||
-1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRForm",
|
||||
"Segments": [
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
4.433,
|
||||
-1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamMouthForm",
|
||||
"Segments": [
|
||||
0,
|
||||
-2,
|
||||
0,
|
||||
4.433,
|
||||
-2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamMouthOpenY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.244,
|
||||
0,
|
||||
0.489,
|
||||
0,
|
||||
0.733,
|
||||
0,
|
||||
1,
|
||||
0.933,
|
||||
0,
|
||||
1.133,
|
||||
-7,
|
||||
1.333,
|
||||
-7,
|
||||
1,
|
||||
1.644,
|
||||
-7,
|
||||
1.956,
|
||||
0,
|
||||
2.267,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.244,
|
||||
0,
|
||||
0.489,
|
||||
0,
|
||||
0.733,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ",
|
||||
"Segments": [
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
0.233,
|
||||
2,
|
||||
0.467,
|
||||
0,
|
||||
0.7,
|
||||
0,
|
||||
1,
|
||||
0.733,
|
||||
0,
|
||||
0.767,
|
||||
0,
|
||||
0.8,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1.2,
|
||||
-4,
|
||||
1.4,
|
||||
-4,
|
||||
1,
|
||||
1.711,
|
||||
-4,
|
||||
2.022,
|
||||
5,
|
||||
2.333,
|
||||
5,
|
||||
1,
|
||||
2.567,
|
||||
5,
|
||||
2.8,
|
||||
3.64,
|
||||
3.033,
|
||||
0,
|
||||
1,
|
||||
3.133,
|
||||
-1.56,
|
||||
3.233,
|
||||
-3,
|
||||
3.333,
|
||||
-3,
|
||||
1,
|
||||
3.467,
|
||||
-3,
|
||||
3.6,
|
||||
-2,
|
||||
3.733,
|
||||
-2,
|
||||
0,
|
||||
4.433,
|
||||
-2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBreath",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.189,
|
||||
0,
|
||||
0.378,
|
||||
1,
|
||||
0.567,
|
||||
1,
|
||||
1,
|
||||
0.711,
|
||||
1,
|
||||
0.856,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1.222,
|
||||
0,
|
||||
1.444,
|
||||
1,
|
||||
1.667,
|
||||
1,
|
||||
1,
|
||||
1.889,
|
||||
1,
|
||||
2.111,
|
||||
0,
|
||||
2.333,
|
||||
0,
|
||||
1,
|
||||
2.544,
|
||||
0,
|
||||
2.756,
|
||||
1,
|
||||
2.967,
|
||||
1,
|
||||
1,
|
||||
3.167,
|
||||
1,
|
||||
3.367,
|
||||
0,
|
||||
3.567,
|
||||
0,
|
||||
0,
|
||||
4.433,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamShoulder",
|
||||
"Segments": [
|
||||
0,
|
||||
0.1,
|
||||
1,
|
||||
0.467,
|
||||
0.1,
|
||||
0.933,
|
||||
1,
|
||||
1.4,
|
||||
1,
|
||||
1,
|
||||
1.844,
|
||||
1,
|
||||
2.289,
|
||||
1,
|
||||
2.733,
|
||||
1,
|
||||
1,
|
||||
2.967,
|
||||
1,
|
||||
3.2,
|
||||
-1,
|
||||
3.433,
|
||||
-1,
|
||||
0,
|
||||
4.433,
|
||||
-1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamLeg",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
4.433,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmLA",
|
||||
"Segments": [
|
||||
0,
|
||||
-10,
|
||||
0,
|
||||
4.433,
|
||||
-10
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmRA",
|
||||
"Segments": [
|
||||
0,
|
||||
-10,
|
||||
0,
|
||||
4.433,
|
||||
-10
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairAhoge",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.3,
|
||||
0,
|
||||
0.6,
|
||||
0,
|
||||
0.9,
|
||||
-0.012,
|
||||
1,
|
||||
1.067,
|
||||
-0.019,
|
||||
1.233,
|
||||
-6.827,
|
||||
1.4,
|
||||
-6.827,
|
||||
1,
|
||||
1.511,
|
||||
-6.827,
|
||||
1.622,
|
||||
7.958,
|
||||
1.733,
|
||||
7.958,
|
||||
1,
|
||||
1.944,
|
||||
7.958,
|
||||
2.156,
|
||||
-7.565,
|
||||
2.367,
|
||||
-7.565,
|
||||
1,
|
||||
2.5,
|
||||
-7.565,
|
||||
2.633,
|
||||
9.434,
|
||||
2.767,
|
||||
9.434,
|
||||
1,
|
||||
2.978,
|
||||
9.434,
|
||||
3.189,
|
||||
-8.871,
|
||||
3.4,
|
||||
-8.871,
|
||||
1,
|
||||
3.5,
|
||||
-8.871,
|
||||
3.6,
|
||||
7.588,
|
||||
3.7,
|
||||
7.588,
|
||||
1,
|
||||
3.789,
|
||||
7.588,
|
||||
3.878,
|
||||
-3.904,
|
||||
3.967,
|
||||
-3.904,
|
||||
1,
|
||||
4.011,
|
||||
-3.904,
|
||||
4.056,
|
||||
-0.032,
|
||||
4.1,
|
||||
-0.032,
|
||||
0,
|
||||
4.433,
|
||||
-0.032
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "PartOpacity",
|
||||
"Id": "PartArmA",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
4.43,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "PartOpacity",
|
||||
"Id": "PartArmB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.43,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,991 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"Meta": {
|
||||
"Duration": 1.9,
|
||||
"Fps": 30.0,
|
||||
"Loop": true,
|
||||
"AreBeziersRestricted": false,
|
||||
"CurveCount": 32,
|
||||
"TotalSegmentCount": 121,
|
||||
"TotalPointCount": 331,
|
||||
"UserDataCount": 0,
|
||||
"TotalUserDataSize": 0
|
||||
},
|
||||
"Curves": [
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleZ",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.444,
|
||||
0,
|
||||
0.556,
|
||||
8,
|
||||
0.667,
|
||||
8,
|
||||
0,
|
||||
1.9,
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamCheek",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.111,
|
||||
1,
|
||||
0.222,
|
||||
1,
|
||||
0.333,
|
||||
1,
|
||||
1,
|
||||
0.378,
|
||||
1,
|
||||
0.422,
|
||||
0,
|
||||
0.467,
|
||||
0,
|
||||
1,
|
||||
0.522,
|
||||
0,
|
||||
0.578,
|
||||
1.2,
|
||||
0.633,
|
||||
1.2,
|
||||
1,
|
||||
0.744,
|
||||
1.2,
|
||||
0.856,
|
||||
1.2,
|
||||
0.967,
|
||||
1.2,
|
||||
1,
|
||||
0.989,
|
||||
1.2,
|
||||
1.011,
|
||||
0,
|
||||
1.033,
|
||||
0,
|
||||
1,
|
||||
1.067,
|
||||
0,
|
||||
1.1,
|
||||
1.2,
|
||||
1.133,
|
||||
1.2,
|
||||
1,
|
||||
1.167,
|
||||
1.2,
|
||||
1.2,
|
||||
1.2,
|
||||
1.233,
|
||||
1.2,
|
||||
1,
|
||||
1.267,
|
||||
1.2,
|
||||
1.3,
|
||||
0,
|
||||
1.333,
|
||||
0,
|
||||
1,
|
||||
1.356,
|
||||
0,
|
||||
1.378,
|
||||
1.2,
|
||||
1.4,
|
||||
1.2,
|
||||
0,
|
||||
1.9,
|
||||
1.2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeROpen",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.111,
|
||||
1,
|
||||
0.222,
|
||||
1,
|
||||
0.333,
|
||||
1,
|
||||
1,
|
||||
0.378,
|
||||
1,
|
||||
0.422,
|
||||
0,
|
||||
0.467,
|
||||
0,
|
||||
1,
|
||||
0.522,
|
||||
0,
|
||||
0.578,
|
||||
1.2,
|
||||
0.633,
|
||||
1.2,
|
||||
1,
|
||||
0.744,
|
||||
1.2,
|
||||
0.856,
|
||||
1.2,
|
||||
0.967,
|
||||
1.2,
|
||||
1,
|
||||
0.989,
|
||||
1.2,
|
||||
1.011,
|
||||
0,
|
||||
1.033,
|
||||
0,
|
||||
1,
|
||||
1.067,
|
||||
0,
|
||||
1.1,
|
||||
1.2,
|
||||
1.133,
|
||||
1.2,
|
||||
1,
|
||||
1.167,
|
||||
1.2,
|
||||
1.2,
|
||||
1.2,
|
||||
1.233,
|
||||
1.2,
|
||||
1,
|
||||
1.267,
|
||||
1.2,
|
||||
1.3,
|
||||
0,
|
||||
1.333,
|
||||
0,
|
||||
1,
|
||||
1.356,
|
||||
0,
|
||||
1.378,
|
||||
1.2,
|
||||
1.4,
|
||||
1.2,
|
||||
0,
|
||||
1.9,
|
||||
1.2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0,
|
||||
0.667,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeBallX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0,
|
||||
0.667,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeBallY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0,
|
||||
0.667,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.26,
|
||||
0.667,
|
||||
0.26,
|
||||
0,
|
||||
1.9,
|
||||
0.26
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.36,
|
||||
0.667,
|
||||
0.36,
|
||||
0,
|
||||
1.9,
|
||||
0.36
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0,
|
||||
0.667,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.27,
|
||||
0.667,
|
||||
0.27,
|
||||
0,
|
||||
1.9,
|
||||
0.27
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.26,
|
||||
0.667,
|
||||
0.26,
|
||||
0,
|
||||
1.9,
|
||||
0.26
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
-0.03,
|
||||
0.667,
|
||||
-0.03,
|
||||
0,
|
||||
1.9,
|
||||
-0.03
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLForm",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.33,
|
||||
0.667,
|
||||
0.33,
|
||||
0,
|
||||
1.9,
|
||||
0.33
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRForm",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.21,
|
||||
0.667,
|
||||
0.21,
|
||||
0,
|
||||
1.9,
|
||||
0.21
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamMouthForm",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.111,
|
||||
1,
|
||||
0.222,
|
||||
1,
|
||||
0.333,
|
||||
1,
|
||||
1,
|
||||
0.356,
|
||||
1,
|
||||
0.378,
|
||||
1,
|
||||
0.4,
|
||||
1,
|
||||
1,
|
||||
0.489,
|
||||
1,
|
||||
0.578,
|
||||
-2,
|
||||
0.667,
|
||||
-2,
|
||||
0,
|
||||
1.9,
|
||||
-2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamMouthOpenY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.356,
|
||||
0,
|
||||
0.378,
|
||||
0,
|
||||
0.4,
|
||||
0,
|
||||
1,
|
||||
0.489,
|
||||
0,
|
||||
0.578,
|
||||
0.75,
|
||||
0.667,
|
||||
0.75,
|
||||
0,
|
||||
1.9,
|
||||
0.75
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.444,
|
||||
0,
|
||||
0.556,
|
||||
-6,
|
||||
0.667,
|
||||
-6,
|
||||
0,
|
||||
1.9,
|
||||
-6
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.422,
|
||||
0,
|
||||
0.511,
|
||||
10,
|
||||
0.6,
|
||||
10,
|
||||
1,
|
||||
0.667,
|
||||
10,
|
||||
0.733,
|
||||
-6,
|
||||
0.8,
|
||||
-6,
|
||||
1,
|
||||
0.833,
|
||||
-6,
|
||||
0.867,
|
||||
5,
|
||||
0.9,
|
||||
5,
|
||||
1,
|
||||
1.011,
|
||||
5,
|
||||
1.122,
|
||||
0,
|
||||
1.233,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.444,
|
||||
0,
|
||||
0.556,
|
||||
-3,
|
||||
0.667,
|
||||
-3,
|
||||
0,
|
||||
1.9,
|
||||
-3
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBreath",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamShoulder",
|
||||
"Segments": [
|
||||
0,
|
||||
-0.062,
|
||||
1,
|
||||
0.111,
|
||||
-0.062,
|
||||
0.222,
|
||||
-0.103,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.589,
|
||||
0.238,
|
||||
0.844,
|
||||
1,
|
||||
1.1,
|
||||
1,
|
||||
0,
|
||||
1.9,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmLA",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.478,
|
||||
0,
|
||||
0.622,
|
||||
-10,
|
||||
0.767,
|
||||
-10,
|
||||
1,
|
||||
0.811,
|
||||
-10,
|
||||
0.856,
|
||||
-8.2,
|
||||
0.9,
|
||||
-8.2,
|
||||
0,
|
||||
1.9,
|
||||
-8.2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmRA",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
1,
|
||||
0.478,
|
||||
0,
|
||||
0.622,
|
||||
-10,
|
||||
0.767,
|
||||
-10,
|
||||
1,
|
||||
0.811,
|
||||
-10,
|
||||
0.856,
|
||||
-7.2,
|
||||
0.9,
|
||||
-7.2,
|
||||
0,
|
||||
1.9,
|
||||
-7.2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmLB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmRB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
0,
|
||||
0.333,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairAhoge",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.111,
|
||||
0,
|
||||
0.222,
|
||||
1.9,
|
||||
0.333,
|
||||
5.2,
|
||||
1,
|
||||
0.444,
|
||||
8.5,
|
||||
0.556,
|
||||
9.926,
|
||||
0.667,
|
||||
9.926,
|
||||
1,
|
||||
0.744,
|
||||
9.926,
|
||||
0.822,
|
||||
-10,
|
||||
0.9,
|
||||
-10,
|
||||
1,
|
||||
0.956,
|
||||
-10,
|
||||
1.011,
|
||||
6,
|
||||
1.067,
|
||||
6,
|
||||
1,
|
||||
1.144,
|
||||
6,
|
||||
1.222,
|
||||
-4,
|
||||
1.3,
|
||||
-4,
|
||||
1,
|
||||
1.367,
|
||||
-4,
|
||||
1.433,
|
||||
0,
|
||||
1.5,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "PartOpacity",
|
||||
"Id": "PartArmA",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1.9,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "PartOpacity",
|
||||
"Id": "PartArmB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1.9,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,974 @@
|
||||
{
|
||||
"Version": 3,
|
||||
"Meta": {
|
||||
"Duration": 4.17,
|
||||
"Fps": 30.0,
|
||||
"Loop": true,
|
||||
"AreBeziersRestricted": false,
|
||||
"CurveCount": 33,
|
||||
"TotalSegmentCount": 118,
|
||||
"TotalPointCount": 321,
|
||||
"UserDataCount": 0,
|
||||
"TotalUserDataSize": 0
|
||||
},
|
||||
"Curves": [
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.4,
|
||||
0,
|
||||
0.6,
|
||||
0,
|
||||
0.8,
|
||||
0,
|
||||
1,
|
||||
1.067,
|
||||
0,
|
||||
1.333,
|
||||
1.041,
|
||||
1.6,
|
||||
1.041,
|
||||
1,
|
||||
1.844,
|
||||
1.041,
|
||||
2.089,
|
||||
-8,
|
||||
2.333,
|
||||
-8,
|
||||
1,
|
||||
2.656,
|
||||
-8,
|
||||
2.978,
|
||||
6,
|
||||
3.3,
|
||||
6,
|
||||
0,
|
||||
4.167,
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.344,
|
||||
0,
|
||||
0.489,
|
||||
-30,
|
||||
0.633,
|
||||
-30,
|
||||
0,
|
||||
4.167,
|
||||
-30
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamAngleZ",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamCheek",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeLOpen",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.067,
|
||||
1,
|
||||
0.133,
|
||||
1,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
0.311,
|
||||
1,
|
||||
0.422,
|
||||
0.988,
|
||||
0.533,
|
||||
0.8,
|
||||
1,
|
||||
0.589,
|
||||
0.706,
|
||||
0.644,
|
||||
0,
|
||||
0.7,
|
||||
0,
|
||||
1,
|
||||
0.722,
|
||||
0,
|
||||
0.744,
|
||||
0,
|
||||
0.767,
|
||||
0,
|
||||
1,
|
||||
0.822,
|
||||
0,
|
||||
0.878,
|
||||
0.8,
|
||||
0.933,
|
||||
0.8,
|
||||
1,
|
||||
1.422,
|
||||
0.8,
|
||||
1.911,
|
||||
0.8,
|
||||
2.4,
|
||||
0.8,
|
||||
1,
|
||||
2.456,
|
||||
0.8,
|
||||
2.511,
|
||||
0,
|
||||
2.567,
|
||||
0,
|
||||
1,
|
||||
2.589,
|
||||
0,
|
||||
2.611,
|
||||
0,
|
||||
2.633,
|
||||
0,
|
||||
1,
|
||||
2.689,
|
||||
0,
|
||||
2.744,
|
||||
0.8,
|
||||
2.8,
|
||||
0.8,
|
||||
0,
|
||||
4.167,
|
||||
0.8
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeLSmile",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeROpen",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.067,
|
||||
1,
|
||||
0.133,
|
||||
1,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
0.311,
|
||||
1,
|
||||
0.422,
|
||||
0.988,
|
||||
0.533,
|
||||
0.8,
|
||||
1,
|
||||
0.589,
|
||||
0.706,
|
||||
0.644,
|
||||
0,
|
||||
0.7,
|
||||
0,
|
||||
1,
|
||||
0.722,
|
||||
0,
|
||||
0.744,
|
||||
0,
|
||||
0.767,
|
||||
0,
|
||||
1,
|
||||
0.822,
|
||||
0,
|
||||
0.878,
|
||||
0.8,
|
||||
0.933,
|
||||
0.8,
|
||||
1,
|
||||
1.422,
|
||||
0.8,
|
||||
1.911,
|
||||
0.8,
|
||||
2.4,
|
||||
0.8,
|
||||
1,
|
||||
2.456,
|
||||
0.8,
|
||||
2.511,
|
||||
0,
|
||||
2.567,
|
||||
0,
|
||||
1,
|
||||
2.589,
|
||||
0,
|
||||
2.611,
|
||||
0,
|
||||
2.633,
|
||||
0,
|
||||
1,
|
||||
2.689,
|
||||
0,
|
||||
2.744,
|
||||
0.8,
|
||||
2.8,
|
||||
0.8,
|
||||
0,
|
||||
4.167,
|
||||
0.8
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeRSmile",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
0,
|
||||
0.433,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeBallX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
0,
|
||||
0.433,
|
||||
0,
|
||||
1,
|
||||
0.667,
|
||||
0,
|
||||
0.9,
|
||||
0.004,
|
||||
1.133,
|
||||
-0.01,
|
||||
1,
|
||||
1.4,
|
||||
-0.025,
|
||||
1.667,
|
||||
-0.43,
|
||||
1.933,
|
||||
-0.43,
|
||||
1,
|
||||
2.211,
|
||||
-0.43,
|
||||
2.489,
|
||||
0.283,
|
||||
2.767,
|
||||
0.283,
|
||||
0,
|
||||
4.167,
|
||||
0.283
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamEyeBallY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
-1,
|
||||
0.433,
|
||||
-1,
|
||||
0,
|
||||
4.167,
|
||||
-1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
0.19,
|
||||
0.433,
|
||||
0.19,
|
||||
0,
|
||||
4.167,
|
||||
0.19
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
0.11,
|
||||
0.433,
|
||||
0.11,
|
||||
0,
|
||||
4.167,
|
||||
0.11
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
-0.48,
|
||||
0.433,
|
||||
-0.48,
|
||||
0,
|
||||
4.167,
|
||||
-0.48
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
0.29,
|
||||
0.433,
|
||||
0.29,
|
||||
0,
|
||||
4.167,
|
||||
0.29
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLAngle",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
1,
|
||||
0.433,
|
||||
1,
|
||||
0,
|
||||
4.167,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRAngle",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
0.85,
|
||||
0.433,
|
||||
0.85,
|
||||
0,
|
||||
4.167,
|
||||
0.85
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowLForm",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
-0.75,
|
||||
0.433,
|
||||
-0.75,
|
||||
0,
|
||||
4.167,
|
||||
-0.75
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBrowRForm",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
-0.87,
|
||||
0.433,
|
||||
-0.87,
|
||||
0,
|
||||
4.167,
|
||||
-0.87
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamMouthForm",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.067,
|
||||
1,
|
||||
0.133,
|
||||
1,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
0.278,
|
||||
1,
|
||||
0.356,
|
||||
-1,
|
||||
0.433,
|
||||
-1,
|
||||
0,
|
||||
4.167,
|
||||
-1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamMouthOpenY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.278,
|
||||
0,
|
||||
0.356,
|
||||
1,
|
||||
0.433,
|
||||
1,
|
||||
0,
|
||||
4.167,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleX",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.444,
|
||||
0,
|
||||
0.689,
|
||||
0,
|
||||
0.933,
|
||||
0,
|
||||
1,
|
||||
1.211,
|
||||
0,
|
||||
1.489,
|
||||
0,
|
||||
1.767,
|
||||
0,
|
||||
1,
|
||||
2.056,
|
||||
0,
|
||||
2.344,
|
||||
-6,
|
||||
2.633,
|
||||
-6,
|
||||
1,
|
||||
3.033,
|
||||
-6,
|
||||
3.433,
|
||||
10,
|
||||
3.833,
|
||||
10,
|
||||
0,
|
||||
4.167,
|
||||
10
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleY",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBodyAngleZ",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.8,
|
||||
0,
|
||||
1.4,
|
||||
-2,
|
||||
2,
|
||||
-2,
|
||||
1,
|
||||
2.456,
|
||||
-2,
|
||||
2.911,
|
||||
8.125,
|
||||
3.367,
|
||||
8.125,
|
||||
0,
|
||||
4.167,
|
||||
8.125
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamBreath",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamShoulder",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamLeg",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0.667,
|
||||
1,
|
||||
1.333,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2.267,
|
||||
1,
|
||||
2.533,
|
||||
0.948,
|
||||
2.8,
|
||||
0.948,
|
||||
0,
|
||||
4.167,
|
||||
0.948
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmLA",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.233,
|
||||
0,
|
||||
0.267,
|
||||
0,
|
||||
0.3,
|
||||
0,
|
||||
1,
|
||||
0.478,
|
||||
0,
|
||||
0.656,
|
||||
-10,
|
||||
0.833,
|
||||
-10,
|
||||
1,
|
||||
0.922,
|
||||
-10,
|
||||
1.011,
|
||||
-8.846,
|
||||
1.1,
|
||||
-8.846,
|
||||
1,
|
||||
1.467,
|
||||
-8.846,
|
||||
1.833,
|
||||
-8.835,
|
||||
2.2,
|
||||
-9.1,
|
||||
1,
|
||||
2.622,
|
||||
-9.405,
|
||||
3.044,
|
||||
-10,
|
||||
3.467,
|
||||
-10,
|
||||
0,
|
||||
4.167,
|
||||
-10
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmRA",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.233,
|
||||
0,
|
||||
0.267,
|
||||
0,
|
||||
0.3,
|
||||
0,
|
||||
1,
|
||||
0.478,
|
||||
0,
|
||||
0.656,
|
||||
-10,
|
||||
0.833,
|
||||
-10,
|
||||
1,
|
||||
0.922,
|
||||
-10,
|
||||
1.011,
|
||||
-8.972,
|
||||
1.1,
|
||||
-8.846,
|
||||
1,
|
||||
1.467,
|
||||
-8.328,
|
||||
1.833,
|
||||
-8.2,
|
||||
2.2,
|
||||
-8.2,
|
||||
1,
|
||||
2.622,
|
||||
-8.2,
|
||||
3.044,
|
||||
-10,
|
||||
3.467,
|
||||
-10,
|
||||
0,
|
||||
4.167,
|
||||
-10
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmLB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamArmRB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.167,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "Parameter",
|
||||
"Id": "ParamHairAhoge",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0.067,
|
||||
0,
|
||||
0.133,
|
||||
0,
|
||||
0.2,
|
||||
0,
|
||||
1,
|
||||
0.233,
|
||||
0,
|
||||
0.267,
|
||||
-5,
|
||||
0.3,
|
||||
-5,
|
||||
1,
|
||||
0.378,
|
||||
-5,
|
||||
0.456,
|
||||
10,
|
||||
0.533,
|
||||
10,
|
||||
1,
|
||||
0.633,
|
||||
10,
|
||||
0.733,
|
||||
4,
|
||||
0.833,
|
||||
4,
|
||||
0,
|
||||
4.167,
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "PartOpacity",
|
||||
"Id": "PartArmA",
|
||||
"Segments": [
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
4.17,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"Target": "PartOpacity",
|
||||
"Id": "PartArmB",
|
||||
"Segments": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4.17,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
import * as Kalidokit from "../dist";
|
||||
//Import Helper Functions from Kalidokit
|
||||
const remap = Kalidokit.Utils.remap;
|
||||
const clamp = Kalidokit.Utils.clamp;
|
||||
const lerp = Kalidokit.Vector.lerp;
|
||||
|
||||
/* THREEJS WORLD SETUP */
|
||||
let currentVrm;
|
||||
|
||||
// renderer
|
||||
const renderer = new THREE.WebGLRenderer({ alpha: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// camera
|
||||
const orbitCamera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
orbitCamera.position.set(0.0, 1.4, 0.7);
|
||||
|
||||
// controls
|
||||
const orbitControls = new THREE.OrbitControls(orbitCamera, renderer.domElement);
|
||||
orbitControls.screenSpacePanning = true;
|
||||
orbitControls.target.set(0.0, 1.4, 0.0);
|
||||
orbitControls.update();
|
||||
|
||||
// scene
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// light
|
||||
const light = new THREE.DirectionalLight(0xffffff);
|
||||
light.position.set(1.0, 1.0, 1.0).normalize();
|
||||
scene.add(light);
|
||||
|
||||
// Main Render Loop
|
||||
const clock = new THREE.Clock();
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
if (currentVrm) {
|
||||
// Update model to render physics
|
||||
currentVrm.update(clock.getDelta());
|
||||
}
|
||||
renderer.render(scene, orbitCamera);
|
||||
}
|
||||
animate();
|
||||
|
||||
/* VRM CHARACTER SETUP */
|
||||
|
||||
// Import Character VRM
|
||||
const loader = new THREE.GLTFLoader();
|
||||
loader.crossOrigin = "anonymous";
|
||||
// Import model from URL, add your own model here
|
||||
loader.load(
|
||||
"https://cdn.glitch.com/29e07830-2317-4b15-a044-135e73c7f840%2FAshtra.vrm?v=1630342336981",
|
||||
|
||||
(gltf) => {
|
||||
THREE.VRMUtils.removeUnnecessaryJoints(gltf.scene);
|
||||
|
||||
THREE.VRM.from(gltf).then((vrm) => {
|
||||
scene.add(vrm.scene);
|
||||
currentVrm = vrm;
|
||||
currentVrm.scene.rotation.y = Math.PI; // Rotate model 180deg to face camera
|
||||
});
|
||||
},
|
||||
|
||||
(progress) => console.log("Loading model...", 100.0 * (progress.loaded / progress.total), "%"),
|
||||
|
||||
(error) => console.error(error)
|
||||
);
|
||||
|
||||
// Animate Rotation Helper function
|
||||
const rigRotation = (name, rotation = { x: 0, y: 0, z: 0 }, dampener = 1, lerpAmount = 0.3) => {
|
||||
if (!currentVrm) {
|
||||
return;
|
||||
}
|
||||
const Part = currentVrm.humanoid.getBoneNode(THREE.VRMSchema.HumanoidBoneName[name]);
|
||||
if (!Part) {
|
||||
return;
|
||||
}
|
||||
|
||||
let euler = new THREE.Euler(
|
||||
rotation.x * dampener,
|
||||
rotation.y * dampener,
|
||||
rotation.z * dampener,
|
||||
rotation.rotationOrder || "XYZ"
|
||||
);
|
||||
let quaternion = new THREE.Quaternion().setFromEuler(euler);
|
||||
Part.quaternion.slerp(quaternion, lerpAmount); // interpolate
|
||||
};
|
||||
|
||||
// Animate Position Helper Function
|
||||
const rigPosition = (name, position = { x: 0, y: 0, z: 0 }, dampener = 1, lerpAmount = 0.3) => {
|
||||
if (!currentVrm) {
|
||||
return;
|
||||
}
|
||||
const Part = currentVrm.humanoid.getBoneNode(THREE.VRMSchema.HumanoidBoneName[name]);
|
||||
if (!Part) {
|
||||
return;
|
||||
}
|
||||
let vector = new THREE.Vector3(position.x * dampener, position.y * dampener, position.z * dampener);
|
||||
Part.position.lerp(vector, lerpAmount); // interpolate
|
||||
};
|
||||
|
||||
let oldLookTarget = new THREE.Euler();
|
||||
const rigFace = (riggedFace) => {
|
||||
if (!currentVrm) {
|
||||
return;
|
||||
}
|
||||
rigRotation("Neck", riggedFace.head, 0.7);
|
||||
|
||||
// Blendshapes and Preset Name Schema
|
||||
const Blendshape = currentVrm.blendShapeProxy;
|
||||
const PresetName = THREE.VRMSchema.BlendShapePresetName;
|
||||
|
||||
// Simple example without winking. Interpolate based on old blendshape, then stabilize blink with `Kalidokit` helper function.
|
||||
// for VRM, 1 is closed, 0 is open.
|
||||
riggedFace.eye.l = lerp(clamp(1 - riggedFace.eye.l, 0, 1), Blendshape.getValue(PresetName.Blink), 0.5);
|
||||
riggedFace.eye.r = lerp(clamp(1 - riggedFace.eye.r, 0, 1), Blendshape.getValue(PresetName.Blink), 0.5);
|
||||
riggedFace.eye = Kalidokit.Face.stabilizeBlink(riggedFace.eye, riggedFace.head.y);
|
||||
Blendshape.setValue(PresetName.Blink, riggedFace.eye.l);
|
||||
|
||||
// Interpolate and set mouth blendshapes
|
||||
Blendshape.setValue(PresetName.I, lerp(riggedFace.mouth.shape.I, Blendshape.getValue(PresetName.I), 0.5));
|
||||
Blendshape.setValue(PresetName.A, lerp(riggedFace.mouth.shape.A, Blendshape.getValue(PresetName.A), 0.5));
|
||||
Blendshape.setValue(PresetName.E, lerp(riggedFace.mouth.shape.E, Blendshape.getValue(PresetName.E), 0.5));
|
||||
Blendshape.setValue(PresetName.O, lerp(riggedFace.mouth.shape.O, Blendshape.getValue(PresetName.O), 0.5));
|
||||
Blendshape.setValue(PresetName.U, lerp(riggedFace.mouth.shape.U, Blendshape.getValue(PresetName.U), 0.5));
|
||||
|
||||
//PUPILS
|
||||
//interpolate pupil and keep a copy of the value
|
||||
let lookTarget = new THREE.Euler(
|
||||
lerp(oldLookTarget.x, riggedFace.pupil.y, 0.4),
|
||||
lerp(oldLookTarget.y, riggedFace.pupil.x, 0.4),
|
||||
0,
|
||||
"XYZ"
|
||||
);
|
||||
oldLookTarget.copy(lookTarget);
|
||||
currentVrm.lookAt.applyer.lookAt(lookTarget);
|
||||
};
|
||||
|
||||
/* VRM Character Animator */
|
||||
const animateVRM = (vrm, results) => {
|
||||
if (!vrm) {
|
||||
return;
|
||||
}
|
||||
// Take the results from `Holistic` and animate character based on its Face, Pose, and Hand Keypoints.
|
||||
let riggedPose, riggedLeftHand, riggedRightHand, riggedFace;
|
||||
|
||||
const faceLandmarks = results.faceLandmarks;
|
||||
// Pose 3D Landmarks are with respect to Hip distance in meters
|
||||
const pose3DLandmarks = results.ea;
|
||||
// Pose 2D landmarks are with respect to videoWidth and videoHeight
|
||||
const pose2DLandmarks = results.poseLandmarks;
|
||||
// Be careful, hand landmarks may be reversed
|
||||
const leftHandLandmarks = results.rightHandLandmarks;
|
||||
const rightHandLandmarks = results.leftHandLandmarks;
|
||||
|
||||
// Animate Face
|
||||
if (faceLandmarks) {
|
||||
riggedFace = Kalidokit.Face.solve(faceLandmarks, {
|
||||
runtime: "mediapipe",
|
||||
video: videoElement,
|
||||
});
|
||||
rigFace(riggedFace);
|
||||
}
|
||||
|
||||
// Animate Pose
|
||||
if (pose2DLandmarks && pose3DLandmarks) {
|
||||
riggedPose = Kalidokit.Pose.solve(pose3DLandmarks, pose2DLandmarks, {
|
||||
runtime: "mediapipe",
|
||||
video: videoElement,
|
||||
});
|
||||
rigRotation("Hips", riggedPose.Hips.rotation, 0.7);
|
||||
rigPosition(
|
||||
"Hips",
|
||||
{
|
||||
x: riggedPose.Hips.position.x, // Reverse direction
|
||||
y: riggedPose.Hips.position.y + 1, // Add a bit of height
|
||||
z: -riggedPose.Hips.position.z, // Reverse direction
|
||||
},
|
||||
1,
|
||||
0.07
|
||||
);
|
||||
|
||||
rigRotation("Chest", riggedPose.Spine, 0.25, 0.3);
|
||||
rigRotation("Spine", riggedPose.Spine, 0.45, 0.3);
|
||||
|
||||
rigRotation("RightUpperArm", riggedPose.RightUpperArm, 1, 0.3);
|
||||
rigRotation("RightLowerArm", riggedPose.RightLowerArm, 1, 0.3);
|
||||
rigRotation("LeftUpperArm", riggedPose.LeftUpperArm, 1, 0.3);
|
||||
rigRotation("LeftLowerArm", riggedPose.LeftLowerArm, 1, 0.3);
|
||||
|
||||
rigRotation("LeftUpperLeg", riggedPose.LeftUpperLeg, 1, 0.3);
|
||||
rigRotation("LeftLowerLeg", riggedPose.LeftLowerLeg, 1, 0.3);
|
||||
rigRotation("RightUpperLeg", riggedPose.RightUpperLeg, 1, 0.3);
|
||||
rigRotation("RightLowerLeg", riggedPose.RightLowerLeg, 1, 0.3);
|
||||
}
|
||||
|
||||
// Animate Hands
|
||||
if (leftHandLandmarks) {
|
||||
riggedLeftHand = Kalidokit.Hand.solve(leftHandLandmarks, "Left");
|
||||
rigRotation("LeftHand", {
|
||||
// Combine pose rotation Z and hand rotation X Y
|
||||
z: riggedPose.LeftHand.z,
|
||||
y: riggedLeftHand.LeftWrist.y,
|
||||
x: riggedLeftHand.LeftWrist.x,
|
||||
});
|
||||
rigRotation("LeftRingProximal", riggedLeftHand.LeftRingProximal);
|
||||
rigRotation("LeftRingIntermediate", riggedLeftHand.LeftRingIntermediate);
|
||||
rigRotation("LeftRingDistal", riggedLeftHand.LeftRingDistal);
|
||||
rigRotation("LeftIndexProximal", riggedLeftHand.LeftIndexProximal);
|
||||
rigRotation("LeftIndexIntermediate", riggedLeftHand.LeftIndexIntermediate);
|
||||
rigRotation("LeftIndexDistal", riggedLeftHand.LeftIndexDistal);
|
||||
rigRotation("LeftMiddleProximal", riggedLeftHand.LeftMiddleProximal);
|
||||
rigRotation("LeftMiddleIntermediate", riggedLeftHand.LeftMiddleIntermediate);
|
||||
rigRotation("LeftMiddleDistal", riggedLeftHand.LeftMiddleDistal);
|
||||
rigRotation("LeftThumbProximal", riggedLeftHand.LeftThumbProximal);
|
||||
rigRotation("LeftThumbIntermediate", riggedLeftHand.LeftThumbIntermediate);
|
||||
rigRotation("LeftThumbDistal", riggedLeftHand.LeftThumbDistal);
|
||||
rigRotation("LeftLittleProximal", riggedLeftHand.LeftLittleProximal);
|
||||
rigRotation("LeftLittleIntermediate", riggedLeftHand.LeftLittleIntermediate);
|
||||
rigRotation("LeftLittleDistal", riggedLeftHand.LeftLittleDistal);
|
||||
}
|
||||
if (rightHandLandmarks) {
|
||||
riggedRightHand = Kalidokit.Hand.solve(rightHandLandmarks, "Right");
|
||||
rigRotation("RightHand", {
|
||||
// Combine Z axis from pose hand and X/Y axis from hand wrist rotation
|
||||
z: riggedPose.RightHand.z,
|
||||
y: riggedRightHand.RightWrist.y,
|
||||
x: riggedRightHand.RightWrist.x,
|
||||
});
|
||||
rigRotation("RightRingProximal", riggedRightHand.RightRingProximal);
|
||||
rigRotation("RightRingIntermediate", riggedRightHand.RightRingIntermediate);
|
||||
rigRotation("RightRingDistal", riggedRightHand.RightRingDistal);
|
||||
rigRotation("RightIndexProximal", riggedRightHand.RightIndexProximal);
|
||||
rigRotation("RightIndexIntermediate", riggedRightHand.RightIndexIntermediate);
|
||||
rigRotation("RightIndexDistal", riggedRightHand.RightIndexDistal);
|
||||
rigRotation("RightMiddleProximal", riggedRightHand.RightMiddleProximal);
|
||||
rigRotation("RightMiddleIntermediate", riggedRightHand.RightMiddleIntermediate);
|
||||
rigRotation("RightMiddleDistal", riggedRightHand.RightMiddleDistal);
|
||||
rigRotation("RightThumbProximal", riggedRightHand.RightThumbProximal);
|
||||
rigRotation("RightThumbIntermediate", riggedRightHand.RightThumbIntermediate);
|
||||
rigRotation("RightThumbDistal", riggedRightHand.RightThumbDistal);
|
||||
rigRotation("RightLittleProximal", riggedRightHand.RightLittleProximal);
|
||||
rigRotation("RightLittleIntermediate", riggedRightHand.RightLittleIntermediate);
|
||||
rigRotation("RightLittleDistal", riggedRightHand.RightLittleDistal);
|
||||
}
|
||||
};
|
||||
|
||||
/* SETUP MEDIAPIPE HOLISTIC INSTANCE */
|
||||
let videoElement = document.querySelector(".input_video"),
|
||||
guideCanvas = document.querySelector("canvas.guides");
|
||||
|
||||
const onResults = (results) => {
|
||||
// Draw landmark guides
|
||||
drawResults(results);
|
||||
// Animate model
|
||||
animateVRM(currentVrm, results);
|
||||
};
|
||||
|
||||
const holistic = new Holistic({
|
||||
locateFile: (file) => {
|
||||
return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.5.1635989137/${file}`;
|
||||
},
|
||||
});
|
||||
|
||||
holistic.setOptions({
|
||||
modelComplexity: 1,
|
||||
smoothLandmarks: true,
|
||||
minDetectionConfidence: 0.7,
|
||||
minTrackingConfidence: 0.7,
|
||||
refineFaceLandmarks: true,
|
||||
});
|
||||
// Pass holistic a callback function
|
||||
holistic.onResults(onResults);
|
||||
|
||||
const drawResults = (results) => {
|
||||
guideCanvas.width = videoElement.videoWidth;
|
||||
guideCanvas.height = videoElement.videoHeight;
|
||||
let canvasCtx = guideCanvas.getContext("2d");
|
||||
canvasCtx.save();
|
||||
canvasCtx.clearRect(0, 0, guideCanvas.width, guideCanvas.height);
|
||||
// Use `Mediapipe` drawing functions
|
||||
drawConnectors(canvasCtx, results.poseLandmarks, POSE_CONNECTIONS, {
|
||||
color: "#00cff7",
|
||||
lineWidth: 4,
|
||||
});
|
||||
drawLandmarks(canvasCtx, results.poseLandmarks, {
|
||||
color: "#ff0364",
|
||||
lineWidth: 2,
|
||||
});
|
||||
drawConnectors(canvasCtx, results.faceLandmarks, FACEMESH_TESSELATION, {
|
||||
color: "#C0C0C070",
|
||||
lineWidth: 1,
|
||||
});
|
||||
if (results.faceLandmarks && results.faceLandmarks.length === 478) {
|
||||
//draw pupils
|
||||
drawLandmarks(canvasCtx, [results.faceLandmarks[468], results.faceLandmarks[468 + 5]], {
|
||||
color: "#ffe603",
|
||||
lineWidth: 2,
|
||||
});
|
||||
}
|
||||
drawConnectors(canvasCtx, results.leftHandLandmarks, HAND_CONNECTIONS, {
|
||||
color: "#eb1064",
|
||||
lineWidth: 5,
|
||||
});
|
||||
drawLandmarks(canvasCtx, results.leftHandLandmarks, {
|
||||
color: "#00cff7",
|
||||
lineWidth: 2,
|
||||
});
|
||||
drawConnectors(canvasCtx, results.rightHandLandmarks, HAND_CONNECTIONS, {
|
||||
color: "#22c3e3",
|
||||
lineWidth: 5,
|
||||
});
|
||||
drawLandmarks(canvasCtx, results.rightHandLandmarks, {
|
||||
color: "#ff0364",
|
||||
lineWidth: 2,
|
||||
});
|
||||
};
|
||||
|
||||
// Use `Mediapipe` utils to get camera - lower resolution = higher fps
|
||||
const camera = new Camera(videoElement, {
|
||||
onFrame: async () => {
|
||||
await holistic.send({ image: videoElement });
|
||||
},
|
||||
width: 640,
|
||||
height: 480,
|
||||
});
|
||||
camera.start();
|
||||
@@ -0,0 +1,124 @@
|
||||
@font-face {
|
||||
font-family: "Jelly";
|
||||
font-display: block;
|
||||
src: url("https://yeemachine.github.io/kalidoface-live2d-models/Font/kalidoface-jelly.ttf") format("truetype");
|
||||
}
|
||||
h1 {
|
||||
font-family: "Jelly";
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 16px;
|
||||
font-weight: normal;
|
||||
-webkit-font-feature-settings: "liga";
|
||||
font-feature-settings: "liga";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
h1:hover {
|
||||
color: #13a3f3;
|
||||
}
|
||||
a,
|
||||
a:visited,
|
||||
a:link {
|
||||
text-decoration: none;
|
||||
color: #222;
|
||||
display: inline-block;
|
||||
}
|
||||
a:hover,
|
||||
a:visited:hover,
|
||||
a:link:hover {
|
||||
color: #13a3f3;
|
||||
cursor: pointer;
|
||||
}
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #222;
|
||||
}
|
||||
video {
|
||||
max-width: 400px;
|
||||
height: auto;
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
.preview canvas {
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
.guides {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
nav {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 16px;
|
||||
display: flex;
|
||||
}
|
||||
nav img {
|
||||
width: 36px;
|
||||
height: auto;
|
||||
margin-left: 22px;
|
||||
}
|
||||
section {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 32px 0 16px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background: rgb(0, 0, 0);
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
color: white;
|
||||
z-index: 2;
|
||||
/* border-radius: 40px; */
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue",
|
||||
sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
.preview a {
|
||||
border-radius: 24px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
.preview a:hover {
|
||||
background: #ffffff20;
|
||||
}
|
||||
a.current {
|
||||
background: #13a3f3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.linkOut {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
display: inline;
|
||||
color: black;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
video {
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
Generated
+10024
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "kalidokit",
|
||||
"version": "1.1.5",
|
||||
"repository": "github:yeemachine/kalidokit",
|
||||
"main": "dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite docs",
|
||||
"prebuild": "npm run fmt && rimraf dist",
|
||||
"build": "vite build && npm run build:lib",
|
||||
"build:watch": "vite build --watch && npm run build:lib:watch",
|
||||
"build:lib": "tsc -p .",
|
||||
"build:lib:watch": "tsc-watch -p .",
|
||||
"serve": "vite",
|
||||
"prepare": "npm run build",
|
||||
"fmt": "prettier --write \"src/**/*.ts\"",
|
||||
"test": "npm run build && npm run dev",
|
||||
"lint": "eslint \"src/**/*.ts\" --fix",
|
||||
"jest": "jest",
|
||||
"jest:watch": "jest -o --watch "
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"prettier": "^2.4.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup-plugin-friendly-type-imports": "^1.0.1",
|
||||
"ts-jest": "^27.1.1",
|
||||
"tsc-watch": "^4.5.0",
|
||||
"typescript": "^4.4.4",
|
||||
"vite": "2.7.1",
|
||||
"vite-plugin-banner": "0.1.3",
|
||||
"vite-plugin-ts": "^1.1.8"
|
||||
},
|
||||
"prettier": {
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120,
|
||||
"singleQuote": false
|
||||
},
|
||||
"description": "Blendshape and kinematics calculator for Mediapipe/Tensorflow.js Face, Eyes, Pose, and Finger tracking models.",
|
||||
"bugs": {
|
||||
"url": "https://github.com/yeemachine/kalidokit/issues"
|
||||
},
|
||||
"homepage": "https://github.com/yeemachine/kalidokit#readme",
|
||||
"keywords": [
|
||||
"mediapipe",
|
||||
"tensorflow",
|
||||
"face-tracking",
|
||||
"pose-tracking",
|
||||
"finger-tracking",
|
||||
"detection"
|
||||
],
|
||||
"author": "yeemachine",
|
||||
"license": "MIT",
|
||||
"year": "2020-2021"
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import Vector from "../utils/vector";
|
||||
import { clamp, remap } from "../utils/helpers";
|
||||
import { Results, XYZ, Side } from "../Types";
|
||||
import { RIGHT, LEFT } from "./../constants";
|
||||
|
||||
/**
|
||||
* Landmark points labeled for eye, brow, and pupils
|
||||
*/
|
||||
const points = {
|
||||
eye: {
|
||||
[LEFT]: [130, 133, 160, 159, 158, 144, 145, 153],
|
||||
[RIGHT]: [263, 362, 387, 386, 385, 373, 374, 380],
|
||||
},
|
||||
brow: {
|
||||
[LEFT]: [35, 244, 63, 105, 66, 229, 230, 231],
|
||||
[RIGHT]: [265, 464, 293, 334, 296, 449, 450, 451],
|
||||
},
|
||||
pupil: {
|
||||
[LEFT]: [468, 469, 470, 471, 472],
|
||||
[RIGHT]: [473, 474, 475, 476, 477],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate eye open ratios and remap to 0-1
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
* @param {Side} side : designate left or right
|
||||
* @param {Number} high : ratio at which eye is considered open
|
||||
* @param {Number} low : ratio at which eye is comsidered closed
|
||||
*/
|
||||
export const getEyeOpen = (lm: Results, side: Side = LEFT, { high = 0.85, low = 0.55 } = {}) => {
|
||||
const eyePoints = points.eye[side];
|
||||
const eyeDistance = eyeLidRatio(
|
||||
lm[eyePoints[0]],
|
||||
lm[eyePoints[1]],
|
||||
lm[eyePoints[2]],
|
||||
lm[eyePoints[3]],
|
||||
lm[eyePoints[4]],
|
||||
lm[eyePoints[5]],
|
||||
lm[eyePoints[6]],
|
||||
lm[eyePoints[7]]
|
||||
);
|
||||
// human eye width to height ratio is roughly .3
|
||||
const maxRatio = 0.285;
|
||||
// compare ratio against max ratio
|
||||
const ratio = clamp(eyeDistance / maxRatio, 0, 2);
|
||||
// remap eye open and close ratios to increase sensitivity
|
||||
const eyeOpenRatio = remap(ratio, low, high);
|
||||
return {
|
||||
// remapped ratio
|
||||
norm: eyeOpenRatio,
|
||||
// ummapped ratio
|
||||
raw: ratio,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate eyelid distance ratios based on landmarks on the face
|
||||
*/
|
||||
export const eyeLidRatio = (
|
||||
eyeOuterCorner: XYZ | Vector | number[],
|
||||
eyeInnerCorner: XYZ | Vector | number[],
|
||||
eyeOuterUpperLid: XYZ | Vector | number[],
|
||||
eyeMidUpperLid: XYZ | Vector | number[],
|
||||
eyeInnerUpperLid: XYZ | Vector | number[],
|
||||
eyeOuterLowerLid: XYZ | Vector | number[],
|
||||
eyeMidLowerLid: XYZ | Vector | number[],
|
||||
eyeInnerLowerLid: XYZ | Vector | number[]
|
||||
) => {
|
||||
eyeOuterCorner = new Vector(eyeOuterCorner);
|
||||
eyeInnerCorner = new Vector(eyeInnerCorner);
|
||||
|
||||
eyeOuterUpperLid = new Vector(eyeOuterUpperLid);
|
||||
eyeMidUpperLid = new Vector(eyeMidUpperLid);
|
||||
eyeInnerUpperLid = new Vector(eyeInnerUpperLid);
|
||||
|
||||
eyeOuterLowerLid = new Vector(eyeOuterLowerLid);
|
||||
eyeMidLowerLid = new Vector(eyeMidLowerLid);
|
||||
eyeInnerLowerLid = new Vector(eyeInnerLowerLid);
|
||||
|
||||
//use 2D Distances instead of 3D for less jitter
|
||||
const eyeWidth = (eyeOuterCorner as Vector).distance(eyeInnerCorner as Vector, 2);
|
||||
const eyeOuterLidDistance = (eyeOuterUpperLid as Vector).distance(eyeOuterLowerLid as Vector, 2);
|
||||
const eyeMidLidDistance = (eyeMidUpperLid as Vector).distance(eyeMidLowerLid as Vector, 2);
|
||||
const eyeInnerLidDistance = (eyeInnerUpperLid as Vector).distance(eyeInnerLowerLid as Vector, 2);
|
||||
const eyeLidAvg = (eyeOuterLidDistance + eyeMidLidDistance + eyeInnerLidDistance) / 3;
|
||||
const ratio = eyeLidAvg / eyeWidth;
|
||||
|
||||
return ratio;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate pupil position [-1,1]
|
||||
* @param {Results} lm : array of results from tfjs or mediapipe
|
||||
* @param {Side} side : left or right
|
||||
*/
|
||||
export const pupilPos = (lm: Results, side: Side = LEFT) => {
|
||||
const eyeOuterCorner = new Vector(lm[points.eye[side][0]]);
|
||||
const eyeInnerCorner = new Vector(lm[points.eye[side][1]]);
|
||||
const eyeWidth = eyeOuterCorner.distance(eyeInnerCorner, 2);
|
||||
const midPoint = eyeOuterCorner.lerp(eyeInnerCorner, 0.5);
|
||||
const pupil = new Vector(lm[points.pupil[side][0]]);
|
||||
const dx = midPoint.x - pupil.x;
|
||||
//eye center y is slightly above midpoint
|
||||
const dy = midPoint.y - eyeWidth * 0.075 - pupil.y;
|
||||
let ratioX = dx / (eyeWidth / 2);
|
||||
let ratioY = dy / (eyeWidth / 4);
|
||||
|
||||
ratioX *= 4;
|
||||
ratioY *= 4;
|
||||
|
||||
return { x: ratioX, y: ratioY };
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to stabilize blink speeds to fix inconsistent eye open/close timing
|
||||
* @param {Object} eye : object with left and right eye values
|
||||
* @param {Number} headY : head y axis rotation in radians
|
||||
* @param {Object} options: Options for blink stabilization
|
||||
*/
|
||||
export const stabilizeBlink = (
|
||||
eye: Record<"r" | "l", number>,
|
||||
headY: number,
|
||||
{
|
||||
enableWink = true,
|
||||
maxRot = 0.5,
|
||||
}: {
|
||||
/**
|
||||
* Enable wink detection
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
enableWink?: boolean;
|
||||
/**
|
||||
* Maximum rotation of head to trigger wink
|
||||
* @default 0.5
|
||||
* @type {Number}
|
||||
*/
|
||||
maxRot?: number;
|
||||
} = {}
|
||||
) => {
|
||||
eye.r = clamp(eye.r, 0, 1);
|
||||
eye.l = clamp(eye.l, 0, 1);
|
||||
//difference between each eye
|
||||
const blinkDiff = Math.abs(eye.l - eye.r);
|
||||
//theshold to which difference is considered a wink
|
||||
const blinkThresh = enableWink ? 0.8 : 1.2;
|
||||
//detect when both eyes are closing
|
||||
const isClosing = eye.l < 0.3 && eye.r < 0.3;
|
||||
//detect when both eyes are opening
|
||||
const isOpen = eye.l > 0.6 && eye.r > 0.6;
|
||||
|
||||
// sets obstructed eye to the opposite eye value
|
||||
if (headY > maxRot) {
|
||||
return { l: eye.r, r: eye.r };
|
||||
}
|
||||
if (headY < -maxRot) {
|
||||
return { l: eye.l, r: eye.l };
|
||||
}
|
||||
|
||||
// returns either a wink or averaged blink values
|
||||
return {
|
||||
l:
|
||||
blinkDiff >= blinkThresh && !isClosing && !isOpen
|
||||
? eye.l
|
||||
: eye.r > eye.l
|
||||
? Vector.lerp(eye.r, eye.l, 0.95)
|
||||
: Vector.lerp(eye.r, eye.l, 0.05),
|
||||
r:
|
||||
blinkDiff >= blinkThresh && !isClosing && !isOpen
|
||||
? eye.r
|
||||
: eye.r > eye.l
|
||||
? Vector.lerp(eye.r, eye.l, 0.95)
|
||||
: Vector.lerp(eye.r, eye.l, 0.05),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate Eyes
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
*/
|
||||
export const calcEyes = (
|
||||
lm: Results,
|
||||
{
|
||||
high = 0.85,
|
||||
low = 0.55,
|
||||
}: {
|
||||
/**
|
||||
* Upper bound for eye open ratio
|
||||
* @default 0.85
|
||||
* @type {Number}
|
||||
*/
|
||||
high?: number;
|
||||
/**
|
||||
* Lower bound for eye open ratio
|
||||
* @default 0.55
|
||||
* @type {Number}
|
||||
**/
|
||||
low?: number;
|
||||
} = {}
|
||||
) => {
|
||||
//return early if no iris tracking
|
||||
if (lm.length !== 478) {
|
||||
return {
|
||||
l: 1,
|
||||
r: 1,
|
||||
};
|
||||
}
|
||||
//open [0,1]
|
||||
const leftEyeLid = getEyeOpen(lm, LEFT, { high: high, low: low });
|
||||
const rightEyeLid = getEyeOpen(lm, RIGHT, { high: high, low: low });
|
||||
|
||||
return {
|
||||
l: leftEyeLid.norm || 0,
|
||||
r: rightEyeLid.norm || 0,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate pupil location normalized to eye bounds
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
*/
|
||||
export const calcPupils = (lm: Results) => {
|
||||
//pupil x:[-1,1],y:[-1,1]
|
||||
if (lm.length !== 478) {
|
||||
return { x: 0, y: 0 };
|
||||
} else {
|
||||
//track pupils using left eye
|
||||
const pupilL = pupilPos(lm, LEFT);
|
||||
const pupilR = pupilPos(lm, RIGHT);
|
||||
|
||||
return {
|
||||
x: (pupilL.x + pupilR.x) * 0.5 || 0,
|
||||
y: (pupilL.y + pupilR.y) * 0.5 || 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate brow raise
|
||||
* @param {Results} lm : array of results from tfjs or mediapipe
|
||||
* @param {Side} side : designate left or right
|
||||
*/
|
||||
export const getBrowRaise = (lm: Results, side: Side = LEFT) => {
|
||||
const browPoints = points.brow[side];
|
||||
const browDistance = eyeLidRatio(
|
||||
lm[browPoints[0]],
|
||||
lm[browPoints[1]],
|
||||
lm[browPoints[2]],
|
||||
lm[browPoints[3]],
|
||||
lm[browPoints[4]],
|
||||
lm[browPoints[5]],
|
||||
lm[browPoints[6]],
|
||||
lm[browPoints[7]]
|
||||
);
|
||||
|
||||
const maxBrowRatio = 1.15;
|
||||
const browHigh = 0.125;
|
||||
const browLow = 0.07;
|
||||
const browRatio = browDistance / maxBrowRatio - 1;
|
||||
const browRaiseRatio = (clamp(browRatio, browLow, browHigh) - browLow) / (browHigh - browLow);
|
||||
return browRaiseRatio;
|
||||
};
|
||||
|
||||
/**
|
||||
* Take the average of left and right eyebrow raise values
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
*/
|
||||
export const calcBrow = (lm: Results) => {
|
||||
if (lm.length !== 478) {
|
||||
return 0;
|
||||
} else {
|
||||
const leftBrow = getBrowRaise(lm, LEFT);
|
||||
const rightBrow = getBrowRaise(lm, RIGHT);
|
||||
return (leftBrow + rightBrow) / 2 || 0;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Results } from "../Types";
|
||||
import Vector from "../utils/vector";
|
||||
import { PI } from "./../constants";
|
||||
|
||||
/**
|
||||
* Calculate stable plane (triangle) from 4 face landmarks
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
*/
|
||||
export const createEulerPlane = (lm: Results) => {
|
||||
//create face detection square bounds
|
||||
const p1 = new Vector(lm[21]); //top left
|
||||
const p2 = new Vector(lm[251]); //top right
|
||||
const p3 = new Vector(lm[397]); //bottom right
|
||||
const p4 = new Vector(lm[172]); //bottom left
|
||||
const p3mid = p3.lerp(p4, 0.5); // bottom midpoint
|
||||
return {
|
||||
vector: [p1, p2, p3mid],
|
||||
points: [p1, p2, p3, p4],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate roll, pitch, yaw, centerpoint, and rough dimentions of face plane
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
*/
|
||||
export const calcHead = (lm: Results) => {
|
||||
// find 3 vectors that form a plane to represent the head
|
||||
const plane = createEulerPlane(lm).vector;
|
||||
// calculate roll pitch and yaw from vectors
|
||||
const rotate = Vector.rollPitchYaw(plane[0], plane[1], plane[2]);
|
||||
// find the center of the face detection box
|
||||
const midPoint = plane[0].lerp(plane[1], 0.5);
|
||||
// find the dimensions roughly of the face detection box
|
||||
const width = plane[0].distance(plane[1]);
|
||||
const height = midPoint.distance(plane[2]);
|
||||
//flip
|
||||
rotate.x *= -1;
|
||||
rotate.z *= -1;
|
||||
|
||||
return {
|
||||
//defaults to radians for rotation around x,y,z axis
|
||||
y: rotate.y * PI, //left right
|
||||
x: rotate.x * PI, //up down
|
||||
z: rotate.z * PI, //side to side
|
||||
width: width,
|
||||
height: height,
|
||||
//center of face detection square
|
||||
position: midPoint.lerp(plane[2], 0.5),
|
||||
//returns euler angles normalized between -1 and 1
|
||||
normalized: {
|
||||
y: rotate.y,
|
||||
x: rotate.x,
|
||||
z: rotate.z,
|
||||
},
|
||||
degrees: {
|
||||
y: rotate.y * 180,
|
||||
x: rotate.x * 180,
|
||||
z: rotate.z * 180,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import Vector from "../utils/vector";
|
||||
import { remap, clamp } from "../utils/helpers";
|
||||
import { Results } from "../Types";
|
||||
|
||||
/**
|
||||
* Calculate Mouth Shape
|
||||
* @param {Array} lm : array of results from tfjs or mediapipe
|
||||
*/
|
||||
export const calcMouth = (lm: Results) => {
|
||||
// eye keypoints
|
||||
const eyeInnerCornerL = new Vector(lm[133]);
|
||||
const eyeInnerCornerR = new Vector(lm[362]);
|
||||
const eyeOuterCornerL = new Vector(lm[130]);
|
||||
const eyeOuterCornerR = new Vector(lm[263]);
|
||||
|
||||
// eye keypoint distances
|
||||
const eyeInnerDistance = eyeInnerCornerL.distance(eyeInnerCornerR);
|
||||
const eyeOuterDistance = eyeOuterCornerL.distance(eyeOuterCornerR);
|
||||
|
||||
// mouth keypoints
|
||||
const upperInnerLip = new Vector(lm[13]);
|
||||
const lowerInnerLip = new Vector(lm[14]);
|
||||
const mouthCornerLeft = new Vector(lm[61]);
|
||||
const mouthCornerRight = new Vector(lm[291]);
|
||||
|
||||
// mouth keypoint distances
|
||||
const mouthOpen = upperInnerLip.distance(lowerInnerLip);
|
||||
const mouthWidth = mouthCornerLeft.distance(mouthCornerRight);
|
||||
|
||||
// mouth open and mouth shape ratios
|
||||
// let ratioXY = mouthWidth / mouthOpen;
|
||||
let ratioY = mouthOpen / eyeInnerDistance;
|
||||
let ratioX = mouthWidth / eyeOuterDistance;
|
||||
|
||||
// normalize and scale mouth open
|
||||
ratioY = remap(ratioY, 0.15, 0.7);
|
||||
|
||||
// normalize and scale mouth shape
|
||||
ratioX = remap(ratioX, 0.45, 0.9);
|
||||
ratioX = (ratioX - 0.3) * 2;
|
||||
|
||||
// const mouthX = remap(ratioX - 0.4, 0, 0.5);
|
||||
const mouthX = ratioX;
|
||||
const mouthY = remap(mouthOpen / eyeInnerDistance, 0.17, 0.5);
|
||||
|
||||
//Depricated: Change sensitivity due to facemesh and holistic have different point outputs.
|
||||
// const fixFacemesh = runtime === "tfjs" ? 1.3 : 0;
|
||||
|
||||
// let ratioI = remap(mouthXY, 1.3 + fixFacemesh * 0.8, 2.6 + fixFacemesh) * remap(mouthY, 0, 1);
|
||||
const ratioI = clamp(remap(mouthX, 0, 1) * 2 * remap(mouthY, 0.2, 0.7), 0, 1);
|
||||
const ratioA = mouthY * 0.4 + mouthY * (1 - ratioI) * 0.6;
|
||||
const ratioU = mouthY * remap(1 - ratioI, 0, 0.3) * 0.1;
|
||||
const ratioE = remap(ratioU, 0.2, 1) * (1 - ratioI) * 0.3;
|
||||
const ratioO = (1 - ratioI) * remap(mouthY, 0.3, 1) * 0.4;
|
||||
|
||||
return {
|
||||
x: ratioX || 0,
|
||||
y: ratioY || 0,
|
||||
shape: {
|
||||
A: ratioA || 0,
|
||||
E: ratioE || 0,
|
||||
I: ratioI || 0,
|
||||
O: ratioO || 0,
|
||||
U: ratioU || 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import { calcHead } from "./calcHead";
|
||||
import { calcEyes, stabilizeBlink, calcPupils, calcBrow } from "./calcEyes";
|
||||
import { calcMouth } from "./calcMouth";
|
||||
import { Results, IFaceSolveOptions, TFace } from "../Types";
|
||||
|
||||
/** Class representing face solver. */
|
||||
export class FaceSolver {
|
||||
/** expose blink stabilizer as a static method */
|
||||
static stabilizeBlink = stabilizeBlink;
|
||||
|
||||
/**
|
||||
* Combines head, eye, pupil, and eyebrow calcs into one method
|
||||
* @param {Results} lm : array of results from tfjs or mediapipe
|
||||
* @param {String} runtime: set as either "tfjs" or "mediapipe"
|
||||
* @param {IFaceSolveOptions} options: options for face solver
|
||||
*/
|
||||
static solve(
|
||||
lm: Results,
|
||||
{
|
||||
runtime = "tfjs",
|
||||
video = null,
|
||||
imageSize = null,
|
||||
smoothBlink = false,
|
||||
blinkSettings = [],
|
||||
}: Partial<IFaceSolveOptions> = {}
|
||||
): TFace | undefined {
|
||||
if (!lm) {
|
||||
console.error("Need Face Landmarks");
|
||||
return;
|
||||
}
|
||||
|
||||
// set image size based on video
|
||||
if (video) {
|
||||
const videoEl = (typeof video === "string" ? document.querySelector(video) : video) as HTMLVideoElement;
|
||||
imageSize = {
|
||||
width: videoEl.videoWidth,
|
||||
height: videoEl.videoHeight,
|
||||
};
|
||||
}
|
||||
|
||||
//if runtime is mediapipe, we need the image dimentions for accurate calculations
|
||||
if (runtime === "mediapipe" && imageSize) {
|
||||
for (const e of lm) {
|
||||
e.x *= imageSize.width;
|
||||
e.y *= imageSize.height;
|
||||
e.z *= imageSize.width;
|
||||
}
|
||||
}
|
||||
|
||||
const getHead = calcHead(lm);
|
||||
const getMouth = calcMouth(lm);
|
||||
|
||||
//set high and low remapping values based on the runtime (tfjs vs mediapipe) of the results
|
||||
blinkSettings = blinkSettings.length > 0 ? blinkSettings : runtime === "tfjs" ? [0.55, 0.85] : [0.35, 0.5];
|
||||
|
||||
let getEye = calcEyes(lm, {
|
||||
high: blinkSettings[1],
|
||||
low: blinkSettings[0],
|
||||
});
|
||||
|
||||
// apply blink stabilizer if true
|
||||
if (smoothBlink) {
|
||||
getEye = stabilizeBlink(getEye, getHead.y);
|
||||
}
|
||||
|
||||
const getPupils = calcPupils(lm);
|
||||
const getBrow = calcBrow(lm);
|
||||
|
||||
return {
|
||||
head: getHead,
|
||||
eye: getEye,
|
||||
brow: getBrow,
|
||||
pupil: getPupils,
|
||||
mouth: getMouth,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import Vector from "../utils/vector";
|
||||
import { clamp } from "../utils/helpers";
|
||||
import { Results, THand, THandUnsafe, Side } from "../Types";
|
||||
import { RIGHT, LEFT, PI } from "./../constants";
|
||||
|
||||
/** Class representing hand solver. */
|
||||
export class HandSolver {
|
||||
/**
|
||||
* Calculates finger and wrist as euler rotations
|
||||
* @param {Array} lm : array of 3D hand vectors from tfjs or mediapipe
|
||||
* @param {Side} side: left or right
|
||||
*/
|
||||
static solve(lm: Results, side: Side = RIGHT): THand<typeof side> | undefined {
|
||||
if (!lm) {
|
||||
console.error("Need Hand Landmarks");
|
||||
return;
|
||||
}
|
||||
const palm = [
|
||||
new Vector(lm[0]),
|
||||
new Vector(lm[side === RIGHT ? 17 : 5]),
|
||||
new Vector(lm[side === RIGHT ? 5 : 17]),
|
||||
];
|
||||
const handRotation = Vector.rollPitchYaw(palm[0], palm[1], palm[2]);
|
||||
handRotation.y = handRotation.z;
|
||||
handRotation.y -= side === LEFT ? 0.4 : 0.4;
|
||||
|
||||
let hand: Record<string, unknown> = {};
|
||||
hand[side + "Wrist"] = { x: handRotation.x, y: handRotation.y, z: handRotation.z };
|
||||
hand[side + "RingProximal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[0], lm[13], lm[14]) };
|
||||
hand[side + "RingIntermediate"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[13], lm[14], lm[15]) };
|
||||
hand[side + "RingDistal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[14], lm[15], lm[16]) };
|
||||
hand[side + "IndexProximal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[0], lm[5], lm[6]) };
|
||||
hand[side + "IndexIntermediate"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[5], lm[6], lm[7]) };
|
||||
hand[side + "IndexDistal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[6], lm[7], lm[8]) };
|
||||
hand[side + "MiddleProximal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[0], lm[9], lm[10]) };
|
||||
hand[side + "MiddleIntermediate"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[9], lm[10], lm[11]) };
|
||||
hand[side + "MiddleDistal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[10], lm[11], lm[12]) };
|
||||
hand[side + "ThumbProximal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[0], lm[1], lm[2]) };
|
||||
hand[side + "ThumbIntermediate"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[1], lm[2], lm[3]) };
|
||||
hand[side + "ThumbDistal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[2], lm[3], lm[4]) };
|
||||
hand[side + "LittleProximal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[0], lm[17], lm[18]) };
|
||||
hand[side + "LittleIntermediate"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[17], lm[18], lm[19]) };
|
||||
hand[side + "LittleDistal"] = { x: 0, y: 0, z: Vector.angleBetween3DCoords(lm[18], lm[19], lm[20]) };
|
||||
|
||||
hand = rigFingers(hand as THand<typeof side>, side);
|
||||
|
||||
return hand as THand<typeof side>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts normalized rotation values into radians clamped by human limits
|
||||
* @param {Object} hand : object of labeled joint with normalized rotation values
|
||||
* @param {Side} side : left or right
|
||||
*/
|
||||
const rigFingers = (hand: THandUnsafe<typeof side>, side: Side = RIGHT): THand<typeof side> => {
|
||||
// Invert modifier based on left vs right side
|
||||
const invert = side === RIGHT ? 1 : -1;
|
||||
const digits = ["Ring", "Index", "Little", "Thumb", "Middle"];
|
||||
const segments = ["Proximal", "Intermediate", "Distal"];
|
||||
|
||||
hand[side + "Wrist"].x = clamp(hand[side + "Wrist"].x * 2 * invert, -0.3, 0.3); // twist
|
||||
hand[side + "Wrist"].y = clamp(
|
||||
hand[side + "Wrist"].y * 2.3,
|
||||
side === RIGHT ? -1.2 : -0.6,
|
||||
side === RIGHT ? 0.6 : 1.6
|
||||
);
|
||||
hand[side + "Wrist"].z = hand[side + "Wrist"].z * -2.3 * invert; //left right
|
||||
|
||||
digits.forEach((e) => {
|
||||
segments.forEach((j) => {
|
||||
const trackedFinger = hand[side + e + j];
|
||||
|
||||
if (e === "Thumb") {
|
||||
//dampen thumb rotation depending on segment
|
||||
const dampener = {
|
||||
x: j === "Proximal" ? 2.2 : j === "Intermediate" ? 0 : 0,
|
||||
y: j === "Proximal" ? 2.2 : j === "Intermediate" ? 0.7 : 1,
|
||||
z: j === "Proximal" ? 0.5 : j === "Intermediate" ? 0.5 : 0.5,
|
||||
};
|
||||
const startPos = {
|
||||
x: j === "Proximal" ? 1.2 : j === "Distal" ? -0.2 : -0.2,
|
||||
y: j === "Proximal" ? 1.1 * invert : j === "Distal" ? 0.1 * invert : 0.1 * invert,
|
||||
z: j === "Proximal" ? 0.2 * invert : j === "Distal" ? 0.2 * invert : 0.2 * invert,
|
||||
};
|
||||
const newThumb = { x: 0, y: 0, z: 0 };
|
||||
if (j === "Proximal") {
|
||||
newThumb.z = clamp(
|
||||
startPos.z + trackedFinger.z * -PI * dampener.z * invert,
|
||||
side === RIGHT ? -0.6 : -0.3,
|
||||
side === RIGHT ? 0.3 : 0.6
|
||||
);
|
||||
newThumb.x = clamp(startPos.x + trackedFinger.z * -PI * dampener.x, -0.6, 0.3);
|
||||
newThumb.y = clamp(
|
||||
startPos.y + trackedFinger.z * -PI * dampener.y * invert,
|
||||
side === RIGHT ? -1 : -0.3,
|
||||
side === RIGHT ? 0.3 : 1
|
||||
);
|
||||
} else {
|
||||
newThumb.z = clamp(startPos.z + trackedFinger.z * -PI * dampener.z * invert, -2, 2);
|
||||
newThumb.x = clamp(startPos.x + trackedFinger.z * -PI * dampener.x, -2, 2);
|
||||
newThumb.y = clamp(startPos.y + trackedFinger.z * -PI * dampener.y * invert, -2, 2);
|
||||
}
|
||||
trackedFinger.x = newThumb.x;
|
||||
trackedFinger.y = newThumb.y;
|
||||
trackedFinger.z = newThumb.z;
|
||||
} else {
|
||||
//will document human limits later
|
||||
trackedFinger.z = clamp(
|
||||
trackedFinger.z * -PI * invert,
|
||||
side === RIGHT ? -PI : 0,
|
||||
side === RIGHT ? 0 : PI
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
return hand as THand<typeof side>;
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
import Vector from "../utils/vector";
|
||||
import { clamp } from "../utils/helpers";
|
||||
import { Results, Side } from "../Types";
|
||||
import { RIGHT, LEFT, PI } from "./../constants";
|
||||
|
||||
/**
|
||||
* Calculates arm rotation as euler angles
|
||||
* @param {Array} lm : array of 3D pose vectors from tfjs or mediapipe
|
||||
*/
|
||||
export const calcArms = (lm: Results) => {
|
||||
//Pure Rotation Calculations
|
||||
const UpperArm = {
|
||||
r: Vector.findRotation(lm[11], lm[13]),
|
||||
l: Vector.findRotation(lm[12], lm[14]),
|
||||
};
|
||||
UpperArm.r.y = Vector.angleBetween3DCoords(lm[12], lm[11], lm[13]);
|
||||
UpperArm.l.y = Vector.angleBetween3DCoords(lm[11], lm[12], lm[14]);
|
||||
|
||||
const LowerArm = {
|
||||
r: Vector.findRotation(lm[13], lm[15]),
|
||||
l: Vector.findRotation(lm[14], lm[16]),
|
||||
};
|
||||
LowerArm.r.y = Vector.angleBetween3DCoords(lm[11], lm[13], lm[15]);
|
||||
LowerArm.l.y = Vector.angleBetween3DCoords(lm[12], lm[14], lm[16]);
|
||||
LowerArm.r.z = clamp(LowerArm.r.z, -2.14, 0);
|
||||
LowerArm.l.z = clamp(LowerArm.l.z, -2.14, 0);
|
||||
const Hand = {
|
||||
r: Vector.findRotation(
|
||||
Vector.fromArray(lm[15]),
|
||||
Vector.lerp(Vector.fromArray(lm[17]), Vector.fromArray(lm[19]), 0.5)
|
||||
),
|
||||
l: Vector.findRotation(
|
||||
Vector.fromArray(lm[16]),
|
||||
Vector.lerp(Vector.fromArray(lm[18]), Vector.fromArray(lm[20]), 0.5)
|
||||
),
|
||||
};
|
||||
|
||||
//Modify Rotations slightly for more natural movement
|
||||
const rightArmRig = rigArm(UpperArm.r, LowerArm.r, Hand.r, RIGHT);
|
||||
const leftArmRig = rigArm(UpperArm.l, LowerArm.l, Hand.l, LEFT);
|
||||
|
||||
return {
|
||||
//Scaled
|
||||
UpperArm: {
|
||||
r: rightArmRig.UpperArm,
|
||||
l: leftArmRig.UpperArm,
|
||||
},
|
||||
LowerArm: {
|
||||
r: rightArmRig.LowerArm,
|
||||
l: leftArmRig.LowerArm,
|
||||
},
|
||||
Hand: {
|
||||
r: rightArmRig.Hand,
|
||||
l: leftArmRig.Hand,
|
||||
},
|
||||
//Unscaled
|
||||
Unscaled: {
|
||||
UpperArm: UpperArm,
|
||||
LowerArm: LowerArm,
|
||||
Hand: Hand,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts normalized rotation values into radians clamped by human limits
|
||||
* @param {Object} UpperArm : normalized rotation values
|
||||
* @param {Object} LowerArm : normalized rotation values
|
||||
* @param {Object} Hand : normalized rotation values
|
||||
* @param {Side} side : left or right
|
||||
*/
|
||||
export const rigArm = (UpperArm: Vector, LowerArm: Vector, Hand: Vector, side: Side = RIGHT) => {
|
||||
// Invert modifier based on left vs right side
|
||||
const invert = side === RIGHT ? 1 : -1;
|
||||
|
||||
UpperArm.z *= -2.3 * invert;
|
||||
//Modify UpperArm rotationY by LowerArm X and Z rotations
|
||||
UpperArm.y *= PI * invert;
|
||||
UpperArm.y -= Math.max(LowerArm.x);
|
||||
UpperArm.y -= -invert * Math.max(LowerArm.z, 0);
|
||||
UpperArm.x -= 0.3 * invert;
|
||||
|
||||
LowerArm.z *= -2.14 * invert;
|
||||
LowerArm.y *= 2.14 * invert;
|
||||
LowerArm.x *= 2.14 * invert;
|
||||
|
||||
//Clamp values to human limits
|
||||
UpperArm.x = clamp(UpperArm.x, -0.5, PI);
|
||||
LowerArm.x = clamp(LowerArm.x, -0.3, 0.3);
|
||||
|
||||
Hand.y = clamp(Hand.z * 2, -0.6, 0.6); //side to side
|
||||
Hand.z = Hand.z * -2.3 * invert; //up down
|
||||
|
||||
return {
|
||||
//Returns Values in Radians for direct 3D usage
|
||||
UpperArm: UpperArm,
|
||||
LowerArm: LowerArm,
|
||||
Hand: Hand,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import Vector from "../utils/vector";
|
||||
import { clamp, remap } from "../utils/helpers";
|
||||
import { IHips, XYZ, TFVectorPose } from "../Types";
|
||||
import { PI } from "./../constants";
|
||||
|
||||
/**
|
||||
* Calculates Hip rotation and world position
|
||||
* @param {Array} lm3d : array of 3D pose vectors from tfjs or mediapipe
|
||||
* @param {Array} lm2d : array of 2D pose vectors from tfjs or mediapipe
|
||||
*/
|
||||
export const calcHips = (lm3d: TFVectorPose, lm2d: Omit<TFVectorPose, "z">) => {
|
||||
//Find 2D normalized Hip and Shoulder Joint Positions/Distances
|
||||
const hipLeft2d = Vector.fromArray(lm2d[23]);
|
||||
const hipRight2d = Vector.fromArray(lm2d[24]);
|
||||
const shoulderLeft2d = Vector.fromArray(lm2d[11]);
|
||||
const shoulderRight2d = Vector.fromArray(lm2d[12]);
|
||||
const hipCenter2d = hipLeft2d.lerp(hipRight2d, 1);
|
||||
const shoulderCenter2d = shoulderLeft2d.lerp(shoulderRight2d, 1);
|
||||
const spineLength = hipCenter2d.distance(shoulderCenter2d);
|
||||
|
||||
const hips: IHips = {
|
||||
position: {
|
||||
x: clamp(hipCenter2d.x - 0.4, -1, 1), //subtract .4 to bring closer to 0,0 center
|
||||
y: 0,
|
||||
z: clamp(spineLength - 1, -2, 0),
|
||||
},
|
||||
};
|
||||
hips.worldPosition = {
|
||||
x: hips.position.x,
|
||||
y: 0,
|
||||
z: hips.position.z * Math.pow(hips.position.z * -2, 2),
|
||||
};
|
||||
hips.worldPosition.x *= hips.worldPosition.z;
|
||||
|
||||
hips.rotation = Vector.rollPitchYaw(lm3d[23], lm3d[24]);
|
||||
//fix -PI, PI jumping
|
||||
if (hips.rotation.y > 0.5) {
|
||||
hips.rotation.y -= 2;
|
||||
}
|
||||
hips.rotation.y += 0.5;
|
||||
//Stop jumping between left and right shoulder tilt
|
||||
if (hips.rotation.z > 0) {
|
||||
hips.rotation.z = 1 - hips.rotation.z;
|
||||
}
|
||||
if (hips.rotation.z < 0) {
|
||||
hips.rotation.z = -1 - hips.rotation.z;
|
||||
}
|
||||
const turnAroundAmountHips = remap(Math.abs(hips.rotation.y), 0.2, 0.4);
|
||||
hips.rotation.z *= 1 - turnAroundAmountHips;
|
||||
hips.rotation.x = 0; //temp fix for inaccurate X axis
|
||||
|
||||
const spine = Vector.rollPitchYaw(lm3d[11], lm3d[12]);
|
||||
//fix -PI, PI jumping
|
||||
if (spine.y > 0.5) {
|
||||
spine.y -= 2;
|
||||
}
|
||||
spine.y += 0.5;
|
||||
//Stop jumping between left and right shoulder tilt
|
||||
if (spine.z > 0) {
|
||||
spine.z = 1 - spine.z;
|
||||
}
|
||||
if (spine.z < 0) {
|
||||
spine.z = -1 - spine.z;
|
||||
}
|
||||
//fix weird large numbers when 2 shoulder points get too close
|
||||
const turnAroundAmount = remap(Math.abs(spine.y), 0.2, 0.4);
|
||||
spine.z *= 1 - turnAroundAmount;
|
||||
spine.x = 0; //temp fix for inaccurate X axis
|
||||
|
||||
return rigHips(hips, spine);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts normalized rotations to radians and estimates world position of hips
|
||||
* @param {Object} hips : hip position and rotation values
|
||||
* @param {Object} spine : spine position and rotation values
|
||||
*/
|
||||
export const rigHips = (hips: IHips, spine: Vector | XYZ) => {
|
||||
//convert normalized values to radians
|
||||
if (hips.rotation) {
|
||||
hips.rotation.x *= Math.PI;
|
||||
hips.rotation.y *= Math.PI;
|
||||
hips.rotation.z *= Math.PI;
|
||||
}
|
||||
|
||||
spine.x *= PI;
|
||||
spine.y *= PI;
|
||||
spine.z *= PI;
|
||||
|
||||
return {
|
||||
Hips: hips,
|
||||
Spine: spine as XYZ,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import Vector from "../utils/vector";
|
||||
import Euler from "../utils/euler";
|
||||
import { clamp } from "../utils/helpers";
|
||||
import { Results, Side } from "../Types";
|
||||
import { RIGHT, LEFT, PI } from "./../constants";
|
||||
|
||||
export const offsets = {
|
||||
upperLeg: {
|
||||
z: 0.1,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates leg rotation angles
|
||||
* @param {Results} lm : array of 3D pose vectors from tfjs or mediapipe
|
||||
*/
|
||||
export const calcLegs = (lm: Results) => {
|
||||
const rightUpperLegSphericalCoords = Vector.getSphericalCoords(lm[23], lm[25], { x: "y", y: "z", z: "x" });
|
||||
const leftUpperLegSphericalCoords = Vector.getSphericalCoords(lm[24], lm[26], { x: "y", y: "z", z: "x" });
|
||||
const rightLowerLegSphericalCoords = Vector.getRelativeSphericalCoords(lm[23], lm[25], lm[27], {
|
||||
x: "y",
|
||||
y: "z",
|
||||
z: "x",
|
||||
});
|
||||
const leftLowerLegSphericalCoords = Vector.getRelativeSphericalCoords(lm[24], lm[26], lm[28], {
|
||||
x: "y",
|
||||
y: "z",
|
||||
z: "x",
|
||||
});
|
||||
const hipRotation = Vector.findRotation(lm[23], lm[24]);
|
||||
|
||||
const UpperLeg = {
|
||||
r: new Vector({
|
||||
x: rightUpperLegSphericalCoords.theta,
|
||||
y: rightLowerLegSphericalCoords.phi,
|
||||
z: rightUpperLegSphericalCoords.phi - hipRotation.z,
|
||||
}),
|
||||
l: new Vector({
|
||||
x: leftUpperLegSphericalCoords.theta,
|
||||
y: leftLowerLegSphericalCoords.phi,
|
||||
z: leftUpperLegSphericalCoords.phi - hipRotation.z,
|
||||
}),
|
||||
};
|
||||
|
||||
const LowerLeg = {
|
||||
r: new Vector({
|
||||
x: -Math.abs(rightLowerLegSphericalCoords.theta),
|
||||
y: 0, // not relevant
|
||||
z: 0, // not relevant
|
||||
}),
|
||||
l: new Vector({
|
||||
x: -Math.abs(leftLowerLegSphericalCoords.theta),
|
||||
y: 0, // not relevant
|
||||
z: 0, // not relevant
|
||||
}),
|
||||
};
|
||||
|
||||
//Modify Rotations slightly for more natural movement
|
||||
const rightLegRig = rigLeg(UpperLeg.r, LowerLeg.r, RIGHT);
|
||||
const leftLegRig = rigLeg(UpperLeg.l, LowerLeg.l, LEFT);
|
||||
|
||||
return {
|
||||
//Scaled
|
||||
UpperLeg: {
|
||||
r: rightLegRig.UpperLeg,
|
||||
l: leftLegRig.UpperLeg,
|
||||
},
|
||||
LowerLeg: {
|
||||
r: rightLegRig.LowerLeg,
|
||||
l: leftLegRig.LowerLeg,
|
||||
},
|
||||
//Unscaled
|
||||
Unscaled: {
|
||||
UpperLeg,
|
||||
LowerLeg,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts normalized rotation values into radians clamped by human limits
|
||||
* @param {Object} UpperLeg : normalized rotation values
|
||||
* @param {Object} LowerLeg : normalized rotation values
|
||||
* @param {Side} side : left or right
|
||||
*/
|
||||
export const rigLeg = (UpperLeg: Vector, LowerLeg: Vector, side: Side = RIGHT) => {
|
||||
const invert = side === RIGHT ? 1 : -1;
|
||||
|
||||
const rigedUpperLeg = new Euler({
|
||||
x: clamp(UpperLeg.x, 0, 0.5) * PI,
|
||||
y: clamp(UpperLeg.y, -0.25, 0.25) * PI,
|
||||
z: clamp(UpperLeg.z, -0.5, 0.5) * PI + invert * offsets.upperLeg.z,
|
||||
rotationOrder: "XYZ",
|
||||
});
|
||||
const rigedLowerLeg = new Euler({
|
||||
x: LowerLeg.x * PI,
|
||||
y: LowerLeg.y * PI,
|
||||
z: LowerLeg.z * PI,
|
||||
});
|
||||
|
||||
return {
|
||||
UpperLeg: rigedUpperLeg,
|
||||
LowerLeg: rigedLowerLeg,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import { TFVectorPose, IPoseSolveOptions, TPose } from "../Types";
|
||||
import { RestingDefault } from "../utils/helpers";
|
||||
import { calcArms } from "./calcArms";
|
||||
import { calcHips } from "./calcHips";
|
||||
import { calcLegs } from "./calcLegs";
|
||||
|
||||
/** Class representing pose solver. */
|
||||
export class PoseSolver {
|
||||
/** expose arm rotation calculator as a static method */
|
||||
static calcArms = calcArms;
|
||||
/** expose hips position and rotation calculator as a static method */
|
||||
static calcHips = calcHips;
|
||||
/** expose leg rotation calculator as a static method */
|
||||
static calcLegs = calcLegs;
|
||||
/**
|
||||
* Combines arm, hips, and leg calcs into one method
|
||||
* @param {Array} lm3d : array of 3D pose vectors from tfjs or mediapipe
|
||||
* @param {Array} lm2d : array of 2D pose vectors from tfjs or mediapipe
|
||||
* @param {String} runtime: set as either "tfjs" or "mediapipe"
|
||||
* @param {IPoseSolveOptions} options: options object
|
||||
*/
|
||||
static solve(
|
||||
lm3d: TFVectorPose,
|
||||
lm2d: Omit<TFVectorPose, "z">,
|
||||
{ runtime = "mediapipe", video = null, imageSize = null, enableLegs = true }: Partial<IPoseSolveOptions> = {}
|
||||
): TPose | undefined {
|
||||
if (!lm3d && !lm2d) {
|
||||
console.error("Need both World Pose and Pose Landmarks");
|
||||
return;
|
||||
}
|
||||
|
||||
// format and normalize values given by tfjs output
|
||||
if (video) {
|
||||
const videoEl = (typeof video === "string" ? document.querySelector(video) : video) as HTMLVideoElement;
|
||||
|
||||
imageSize = {
|
||||
width: videoEl.videoWidth,
|
||||
height: videoEl.videoHeight,
|
||||
};
|
||||
}
|
||||
if (runtime === "tfjs" && imageSize) {
|
||||
for (const e of lm3d) {
|
||||
e.visibility = e.score;
|
||||
}
|
||||
for (const e of lm2d) {
|
||||
e.x /= imageSize.width;
|
||||
e.y /= imageSize.height;
|
||||
e.z = 0;
|
||||
e.visibility = e.score;
|
||||
}
|
||||
}
|
||||
|
||||
const Arms = calcArms(lm3d);
|
||||
const Hips = calcHips(lm3d, lm2d);
|
||||
const Legs = enableLegs ? calcLegs(lm3d) : null;
|
||||
|
||||
//DETECT OFFSCREEN AND RESET VALUES TO DEFAULTS
|
||||
const rightHandOffscreen = lm3d[15].y > 0.1 || (lm3d[15].visibility ?? 0) < 0.23 || 0.995 < lm2d[15].y;
|
||||
const leftHandOffscreen = lm3d[16].y > 0.1 || (lm3d[16].visibility ?? 0) < 0.23 || 0.995 < lm2d[16].y;
|
||||
|
||||
const leftFootOffscreen = lm3d[23].y > 0.1 || (lm3d[23].visibility ?? 0) < 0.63 || Hips.Hips.position.z > -0.4;
|
||||
const rightFootOffscreen = lm3d[24].y > 0.1 || (lm3d[24].visibility ?? 0) < 0.63 || Hips.Hips.position.z > -0.4;
|
||||
|
||||
Arms.UpperArm.l = Arms.UpperArm.l.multiply(leftHandOffscreen ? 0 : 1);
|
||||
Arms.UpperArm.l.z = leftHandOffscreen ? RestingDefault.Pose.LeftUpperArm.z : Arms.UpperArm.l.z;
|
||||
Arms.UpperArm.r = Arms.UpperArm.r.multiply(rightHandOffscreen ? 0 : 1);
|
||||
Arms.UpperArm.r.z = rightHandOffscreen ? RestingDefault.Pose.RightUpperArm.z : Arms.UpperArm.r.z;
|
||||
|
||||
Arms.LowerArm.l = Arms.LowerArm.l.multiply(leftHandOffscreen ? 0 : 1);
|
||||
Arms.LowerArm.r = Arms.LowerArm.r.multiply(rightHandOffscreen ? 0 : 1);
|
||||
|
||||
Arms.Hand.l = Arms.Hand.l.multiply(leftHandOffscreen ? 0 : 1);
|
||||
Arms.Hand.r = Arms.Hand.r.multiply(rightHandOffscreen ? 0 : 1);
|
||||
|
||||
//skip calculations if disable legs
|
||||
if (Legs) {
|
||||
Legs.UpperLeg.l = Legs.UpperLeg.l.multiply(rightFootOffscreen ? 0 : 1);
|
||||
Legs.UpperLeg.r = Legs.UpperLeg.r.multiply(leftFootOffscreen ? 0 : 1);
|
||||
Legs.LowerLeg.l = Legs.LowerLeg.l.multiply(rightFootOffscreen ? 0 : 1);
|
||||
Legs.LowerLeg.r = Legs.LowerLeg.r.multiply(leftFootOffscreen ? 0 : 1);
|
||||
}
|
||||
|
||||
return {
|
||||
RightUpperArm: Arms.UpperArm.r,
|
||||
RightLowerArm: Arms.LowerArm.r,
|
||||
LeftUpperArm: Arms.UpperArm.l,
|
||||
LeftLowerArm: Arms.LowerArm.l,
|
||||
RightHand: Arms.Hand.r,
|
||||
LeftHand: Arms.Hand.l,
|
||||
RightUpperLeg: Legs ? Legs.UpperLeg.r : RestingDefault.Pose.RightUpperLeg,
|
||||
RightLowerLeg: Legs ? Legs.LowerLeg.r : RestingDefault.Pose.RightLowerLeg,
|
||||
LeftUpperLeg: Legs ? Legs.UpperLeg.l : RestingDefault.Pose.LeftUpperLeg,
|
||||
LeftLowerLeg: Legs ? Legs.LowerLeg.l : RestingDefault.Pose.LeftLowerLeg,
|
||||
Hips: Hips.Hips,
|
||||
Spine: Hips.Spine,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import Vector from "./utils/vector";
|
||||
import Euler from "./utils/euler";
|
||||
import { RIGHT, LEFT } from "./constants";
|
||||
export interface ISolveOptions {
|
||||
/**
|
||||
* Runtime for the solver.
|
||||
* @default "mediapipe"
|
||||
* @type {"tfjs" | "mediapipe"}
|
||||
*/
|
||||
runtime: "tfjs" | "mediapipe";
|
||||
/**
|
||||
* HTML Video element or selector for the video element.
|
||||
* @type {HTMLElement | string}
|
||||
*/
|
||||
video: null | HTMLVideoElement | string;
|
||||
/**
|
||||
* Set Manual Size
|
||||
* @type {{ width: number, height: number }}
|
||||
* @default null
|
||||
*/
|
||||
imageSize: null | { width: number; height: number };
|
||||
}
|
||||
|
||||
export interface IFaceSolveOptions extends ISolveOptions {
|
||||
/**
|
||||
* Smooth Blink Toggle
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
smoothBlink: boolean;
|
||||
/**
|
||||
* Blink settings
|
||||
*/
|
||||
blinkSettings: Array<number>;
|
||||
}
|
||||
|
||||
export interface IPoseSolveOptions extends ISolveOptions {
|
||||
/**
|
||||
* Toggle Calculation of legs
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
enableLegs: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The left or the right side
|
||||
*/
|
||||
export type Side = typeof RIGHT | typeof LEFT;
|
||||
|
||||
export type XYZ = Record<"x" | "y" | "z", number>;
|
||||
|
||||
export type LR<T = Vector> = Record<"l" | "r", T>;
|
||||
export type RotationOrder = "XYZ" | "YZX" | "ZXY" | "XZY" | "YXZ" | "ZYX";
|
||||
|
||||
export type EulerRotation = XYZ & { rotationOrder?: RotationOrder };
|
||||
|
||||
export type AxisMap = Record<"x" | "y" | "z", "x" | "y" | "z">;
|
||||
|
||||
export interface IHips {
|
||||
position: XYZ;
|
||||
rotation?: Vector;
|
||||
worldPosition?: XYZ;
|
||||
}
|
||||
|
||||
export type TPose = {
|
||||
RightUpperArm: Euler;
|
||||
RightLowerArm: Euler;
|
||||
LeftUpperArm: Euler;
|
||||
LeftLowerArm: Euler;
|
||||
RightHand: Vector;
|
||||
LeftHand: Vector;
|
||||
RightUpperLeg: Euler | XYZ;
|
||||
RightLowerLeg: Euler | XYZ;
|
||||
LeftUpperLeg: Euler | XYZ;
|
||||
LeftLowerLeg: Euler | XYZ;
|
||||
Hips: IHips;
|
||||
Spine: Vector | XYZ;
|
||||
};
|
||||
|
||||
export type HandKeys<S extends Side> = `${S}${
|
||||
| "Wrist"
|
||||
| "RingProximal"
|
||||
| "RingIntermediate"
|
||||
| "RingDistal"
|
||||
| "IndexProximal"
|
||||
| "IndexIntermediate"
|
||||
| "IndexDistal"
|
||||
| "MiddleProximal"
|
||||
| "MiddleIntermediate"
|
||||
| "MiddleDistal"
|
||||
| "ThumbProximal"
|
||||
| "ThumbIntermediate"
|
||||
| "ThumbDistal"
|
||||
| "LittleProximal"
|
||||
| "LittleIntermediate"
|
||||
| "LittleDistal"}`;
|
||||
export type THand<S extends Side> = Record<HandKeys<S>, XYZ>;
|
||||
export type THandUnsafe<S extends Side> = Record<HandKeys<S> | string, XYZ>;
|
||||
|
||||
export type TFace = {
|
||||
head: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
width: number;
|
||||
height: number;
|
||||
position: Vector | XYZ;
|
||||
normalized: Vector | XYZ;
|
||||
degrees: Vector | XYZ;
|
||||
};
|
||||
eye: {
|
||||
l: number;
|
||||
r: number;
|
||||
};
|
||||
brow: number;
|
||||
pupil: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
mouth: {
|
||||
x: number;
|
||||
y: number;
|
||||
shape: {
|
||||
A: number;
|
||||
E: number;
|
||||
I: number;
|
||||
O: number;
|
||||
U: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* TFJS 3D Pose Vector Type
|
||||
*/
|
||||
export type TFVectorPose = Array<{
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
score?: number;
|
||||
visibility?: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Array of results from TFJS or MediaPipe
|
||||
*/
|
||||
export type Results = Array<XYZ>;
|
||||
@@ -0,0 +1,5 @@
|
||||
export const RIGHT = "Right";
|
||||
export const LEFT = "Left";
|
||||
|
||||
export const PI = Math.PI;
|
||||
export const TWO_PI = Math.PI * 2;
|
||||
@@ -0,0 +1,6 @@
|
||||
export { PoseSolver as Pose } from "./PoseSolver";
|
||||
export { HandSolver as Hand } from "./HandSolver";
|
||||
export { FaceSolver as Face } from "./FaceSolver";
|
||||
export { default as Vector } from "./utils/vector";
|
||||
export * as Utils from "./utils/helpers";
|
||||
export * from "./Types";
|
||||
@@ -0,0 +1,33 @@
|
||||
import { XYZ, RotationOrder, EulerRotation } from "../Types";
|
||||
import { Vector } from "..";
|
||||
|
||||
/** Euler rotation class. */
|
||||
export default class Euler {
|
||||
public x: number;
|
||||
public y: number;
|
||||
public z: number;
|
||||
public rotationOrder?: RotationOrder;
|
||||
|
||||
constructor(a?: number | EulerRotation, b?: number, c?: number, rotationOrder?: RotationOrder) {
|
||||
if (!!a && typeof a === "object") {
|
||||
this.x = a.x ?? 0;
|
||||
this.y = a.y ?? 0;
|
||||
this.z = a.z ?? 0;
|
||||
this.rotationOrder = a.rotationOrder ?? "XYZ";
|
||||
return;
|
||||
}
|
||||
|
||||
this.x = a ?? 0;
|
||||
this.y = b ?? 0;
|
||||
this.z = c ?? 0;
|
||||
this.rotationOrder = rotationOrder ?? "XYZ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies a number to an Euler.
|
||||
* @param {number} a: Number to multiply
|
||||
*/
|
||||
multiply(v: number) {
|
||||
return new Euler(this.x * v, this.y * v, this.z * v, this.rotationOrder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Returns a clamped value between min and max values
|
||||
* @param {Number} val : transformed value
|
||||
* @param {Number} min : minimum value
|
||||
* @param {Number} max : maximum value
|
||||
*/
|
||||
export const clamp = (val: number, min: number, max: number) => {
|
||||
return Math.max(Math.min(val, max), min);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a remapped value between 0 and 1 using min and max values
|
||||
* @param {Number} value : transformed value
|
||||
* @param {Number} min : minimum value
|
||||
* @param {Number} max : maximum value
|
||||
*/
|
||||
export const remap = (val: number, min: number, max: number) => {
|
||||
//returns min to max -> 0 to 1
|
||||
return (clamp(val, min, max) - min) / (max - min);
|
||||
};
|
||||
|
||||
/** A set of default pose values in radians to serve as "rest" values */
|
||||
export const RestingDefault = {
|
||||
Face: {
|
||||
eye: {
|
||||
l: 1,
|
||||
r: 1,
|
||||
},
|
||||
mouth: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
shape: {
|
||||
A: 0,
|
||||
E: 0,
|
||||
I: 0,
|
||||
O: 0,
|
||||
U: 0,
|
||||
},
|
||||
},
|
||||
head: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
width: 0.3,
|
||||
height: 0.6,
|
||||
position: {
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
z: 0,
|
||||
},
|
||||
},
|
||||
brow: 0,
|
||||
pupil: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
Pose: {
|
||||
RightUpperArm: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -1.25,
|
||||
},
|
||||
LeftUpperArm: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 1.25,
|
||||
}, //y: 0 > -.5 // z: -.5>.5
|
||||
RightLowerArm: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
LeftLowerArm: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
}, //x: 0 > -4, z: 0 to -.9
|
||||
LeftUpperLeg: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
RightUpperLeg: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
RightLowerLeg: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
LeftLowerLeg: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
LeftHand: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
RightHand: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
Spine: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
Hips: {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
rotation: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
RightHand: {
|
||||
RightWrist: {
|
||||
x: -0.13,
|
||||
y: -0.07,
|
||||
z: -1.04,
|
||||
},
|
||||
RightRingProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.13,
|
||||
},
|
||||
RightRingIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.4,
|
||||
},
|
||||
RightRingDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.04,
|
||||
},
|
||||
RightIndexProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.24,
|
||||
},
|
||||
RightIndexIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.25,
|
||||
},
|
||||
RightIndexDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.06,
|
||||
},
|
||||
RightMiddleProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.09,
|
||||
},
|
||||
RightMiddleIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.44,
|
||||
},
|
||||
RightMiddleDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.06,
|
||||
},
|
||||
RightThumbProximal: {
|
||||
x: -0.23,
|
||||
y: -0.33,
|
||||
z: -0.12,
|
||||
},
|
||||
RightThumbIntermediate: {
|
||||
x: -0.2,
|
||||
y: -0.199,
|
||||
z: -0.0139,
|
||||
},
|
||||
RightThumbDistal: {
|
||||
x: -0.2,
|
||||
y: 0.002,
|
||||
z: 0.15,
|
||||
},
|
||||
RightLittleProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.09,
|
||||
},
|
||||
RightLittleIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.225,
|
||||
},
|
||||
RightLittleDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -0.1,
|
||||
},
|
||||
},
|
||||
LeftHand: {
|
||||
LeftWrist: {
|
||||
x: -0.13,
|
||||
y: -0.07,
|
||||
z: -1.04,
|
||||
},
|
||||
LeftRingProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.13,
|
||||
},
|
||||
LeftRingIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.4,
|
||||
},
|
||||
LeftRingDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.049,
|
||||
},
|
||||
LeftIndexProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.24,
|
||||
},
|
||||
LeftIndexIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.25,
|
||||
},
|
||||
LeftIndexDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.06,
|
||||
},
|
||||
LeftMiddleProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.09,
|
||||
},
|
||||
LeftMiddleIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.44,
|
||||
},
|
||||
LeftMiddleDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.066,
|
||||
},
|
||||
LeftThumbProximal: {
|
||||
x: -0.23,
|
||||
y: 0.33,
|
||||
z: 0.12,
|
||||
},
|
||||
LeftThumbIntermediate: {
|
||||
x: -0.2,
|
||||
y: 0.25,
|
||||
z: 0.05,
|
||||
},
|
||||
LeftThumbDistal: {
|
||||
x: -0.2,
|
||||
y: 0.17,
|
||||
z: -0.06,
|
||||
},
|
||||
LeftLittleProximal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.17,
|
||||
},
|
||||
LeftLittleIntermediate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.4,
|
||||
},
|
||||
LeftLittleDistal: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,474 @@
|
||||
import { XYZ, AxisMap, EulerRotation } from "../Types";
|
||||
import { PI, TWO_PI } from "./../constants";
|
||||
|
||||
/** Vector Math class. */
|
||||
export default class Vector {
|
||||
public x: number;
|
||||
public y: number;
|
||||
public z: number;
|
||||
|
||||
constructor(a?: number[] | XYZ | number | Vector | EulerRotation, b?: number, c?: number) {
|
||||
if (Array.isArray(a)) {
|
||||
this.x = a[0] ?? 0;
|
||||
this.y = a[1] ?? 0;
|
||||
this.z = a[2] ?? 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!a && typeof a === "object") {
|
||||
this.x = a.x ?? 0;
|
||||
this.y = a.y ?? 0;
|
||||
this.z = a.z ?? 0;
|
||||
return;
|
||||
}
|
||||
|
||||
this.x = a ?? 0;
|
||||
this.y = b ?? 0;
|
||||
this.z = c ?? 0;
|
||||
}
|
||||
|
||||
// Methods //
|
||||
/**
|
||||
* Returns the negative of this vector.
|
||||
*/
|
||||
negative() {
|
||||
return new Vector(-this.x, -this.y, -this.z);
|
||||
}
|
||||
/**
|
||||
* Add a vector or number to this vector.
|
||||
* @param {Vector | number} a: Vector or number to add
|
||||
* @returns {Vector} New vector
|
||||
*/
|
||||
add(v: Vector | number) {
|
||||
if (v instanceof Vector) return new Vector(this.x + v.x, this.y + v.y, this.z + v.z);
|
||||
else return new Vector(this.x + v, this.y + v, this.z + v);
|
||||
}
|
||||
/**
|
||||
* Substracts a vector or number from this vector.
|
||||
* @param {Vector | number} a: Vector or number to subtract
|
||||
* @returns {Vector} New vector
|
||||
*/
|
||||
subtract(v: Vector | number) {
|
||||
if (v instanceof Vector) return new Vector(this.x - v.x, this.y - v.y, this.z - v.z);
|
||||
else return new Vector(this.x - v, this.y - v, this.z - v);
|
||||
}
|
||||
/**
|
||||
* Multiplies a vector or a number to a vector.
|
||||
* @param {Vector | number} a: Vector or number to multiply
|
||||
* @param {Vector} b: Vector to multiply
|
||||
*/
|
||||
multiply(v: Vector | number) {
|
||||
if (v instanceof Vector) return new Vector(this.x * v.x, this.y * v.y, this.z * v.z);
|
||||
else return new Vector(this.x * v, this.y * v, this.z * v);
|
||||
}
|
||||
/**
|
||||
* Divide this vector by a vector or a number.
|
||||
* @param {Vector | number} a: Vector or number to divide
|
||||
* @returns {Vector} New vector
|
||||
*/
|
||||
divide(v: Vector | number) {
|
||||
if (v instanceof Vector) return new Vector(this.x / v.x, this.y / v.y, this.z / v.z);
|
||||
else return new Vector(this.x / v, this.y / v, this.z / v);
|
||||
}
|
||||
/**
|
||||
* Check if the given vector is equal to this vector.
|
||||
* @param {Vector} v: Vector to compare
|
||||
* @returns {boolean} True if equal
|
||||
*/
|
||||
equals(v: Vector) {
|
||||
return this.x == v.x && this.y == v.y && this.z == v.z;
|
||||
}
|
||||
/**
|
||||
* Returns the dot product of this vector and another vector.
|
||||
* @param {Vector} v: Vector to dot
|
||||
* @returns {number} Dot product
|
||||
*/
|
||||
dot(v: Vector) {
|
||||
return this.x * v.x + this.y * v.y + this.z * v.z;
|
||||
}
|
||||
/**
|
||||
* Cross product of two vectors.
|
||||
* @param {Vector} a: Vector to cross
|
||||
* @param {Vector} b: Vector to cross
|
||||
*/
|
||||
cross(v: Vector) {
|
||||
return new Vector(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x);
|
||||
}
|
||||
/**
|
||||
* Get the length of the Vector
|
||||
* @returns {number} Length
|
||||
*/
|
||||
length() {
|
||||
return Math.sqrt(this.dot(this));
|
||||
}
|
||||
/**
|
||||
* Find the distance between this and another vector.
|
||||
* @param {Vector} v: Vector to find distance to
|
||||
* @param {2 | 3} d: 2D or 3D distance
|
||||
* @returns {number} Distance
|
||||
*/
|
||||
distance(v: Vector, d: 2 | 3 = 3) {
|
||||
//2D distance
|
||||
if (d === 2) return Math.sqrt(Math.pow(this.x - v.x, 2) + Math.pow(this.y - v.y, 2));
|
||||
//3D distance
|
||||
else return Math.sqrt(Math.pow(this.x - v.x, 2) + Math.pow(this.y - v.y, 2) + Math.pow(this.z - v.z, 2));
|
||||
}
|
||||
/**
|
||||
* Lerp between this vector and another vector.
|
||||
* @param {Vector} v: Vector to lerp to
|
||||
* @param {number} fraction: Fraction to lerp
|
||||
* @returns {Vector}
|
||||
*/
|
||||
lerp(v: Vector, fraction: number) {
|
||||
return v.subtract(this).multiply(fraction).add(this);
|
||||
}
|
||||
/**
|
||||
* Returns the unit vector of this vector.
|
||||
* @returns {Vector} Unit vector
|
||||
*/
|
||||
unit() {
|
||||
return this.divide(this.length());
|
||||
}
|
||||
min() {
|
||||
return Math.min(Math.min(this.x, this.y), this.z);
|
||||
}
|
||||
max() {
|
||||
return Math.max(Math.max(this.x, this.y), this.z);
|
||||
}
|
||||
/**
|
||||
* To Angles
|
||||
* @param {AxisMap} [axisMap = {x: "x", y: "y", z: "z"}]
|
||||
* @returns {{ theta: number, phi: number }}
|
||||
*/
|
||||
toSphericalCoords(axisMap: AxisMap = { x: "x", y: "y", z: "z" }) {
|
||||
return {
|
||||
theta: Math.atan2(this[axisMap.y], this[axisMap.x]),
|
||||
phi: Math.acos(this[axisMap.z] / this.length()),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Returns the angle between this vector and vector a in radians.
|
||||
* @param {Vector} a: Vector
|
||||
* @returns {number}
|
||||
*/
|
||||
angleTo(a: Vector) {
|
||||
return Math.acos(this.dot(a) / (this.length() * a.length()));
|
||||
}
|
||||
/**
|
||||
* Array representation of the vector.
|
||||
* @param {number} n: Array length
|
||||
* @returns {number[]} Array
|
||||
* @example
|
||||
* new Vector(1, 2, 3).toArray(); // [1, 2, 3]
|
||||
*/
|
||||
toArray(n: number) {
|
||||
return [this.x, this.y, this.z].slice(0, n || 3);
|
||||
}
|
||||
/**
|
||||
* Clone the vector.
|
||||
* @returns {Vector} New vector
|
||||
*/
|
||||
clone() {
|
||||
return new Vector(this.x, this.y, this.z);
|
||||
}
|
||||
/**
|
||||
* Init this Vector with explicit values
|
||||
* @param {number} x: X value
|
||||
* @param {number} y: Y value
|
||||
* @param {number} z: Z value
|
||||
*/
|
||||
init(x: number, y: number, z: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
// static methods //
|
||||
static negative(a: Vector, b: Vector = new Vector()) {
|
||||
b.x = -a.x;
|
||||
b.y = -a.y;
|
||||
b.z = -a.z;
|
||||
return b;
|
||||
}
|
||||
static add(a: Vector, b: Vector | number, c: Vector = new Vector()) {
|
||||
if (b instanceof Vector) {
|
||||
c.x = a.x + b.x;
|
||||
c.y = a.y + b.y;
|
||||
c.z = a.z + b.z;
|
||||
} else {
|
||||
c.x = a.x + b;
|
||||
c.y = a.y + b;
|
||||
c.z = a.z + b;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
static subtract(a: Vector, b: Vector | number, c: Vector = new Vector()) {
|
||||
if (b instanceof Vector) {
|
||||
c.x = a.x - b.x;
|
||||
c.y = a.y - b.y;
|
||||
c.z = a.z - b.z;
|
||||
} else {
|
||||
c.x = a.x - b;
|
||||
c.y = a.y - b;
|
||||
c.z = a.z - b;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
static multiply(a: Vector, b: Vector | number, c: Vector = new Vector()) {
|
||||
if (b instanceof Vector) {
|
||||
c.x = a.x * b.x;
|
||||
c.y = a.y * b.y;
|
||||
c.z = a.z * b.z;
|
||||
} else {
|
||||
c.x = a.x * b;
|
||||
c.y = a.y * b;
|
||||
c.z = a.z * b;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
static divide(a: Vector, b: Vector | number, c: Vector = new Vector()) {
|
||||
if (b instanceof Vector) {
|
||||
c.x = a.x / b.x;
|
||||
c.y = a.y / b.y;
|
||||
c.z = a.z / b.z;
|
||||
} else {
|
||||
c.x = a.x / b;
|
||||
c.y = a.y / b;
|
||||
c.z = a.z / b;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
static cross(a: Vector, b: Vector, c: Vector = new Vector()) {
|
||||
c.x = a.y * b.z - a.z * b.y;
|
||||
c.y = a.z * b.x - a.x * b.z;
|
||||
c.z = a.x * b.y - a.y * b.x;
|
||||
return c;
|
||||
}
|
||||
static unit(a: Vector, b: Vector) {
|
||||
const length = a.length();
|
||||
b.x = a.x / length;
|
||||
b.y = a.y / length;
|
||||
b.z = a.z / length;
|
||||
return b;
|
||||
}
|
||||
/**
|
||||
* Create new vector from angles
|
||||
* @param {number} theta: Theta angle
|
||||
* @param {number} phi: Phi angle
|
||||
* @returns {Vector} New vector
|
||||
*/
|
||||
static fromAngles(theta: number, phi: number) {
|
||||
return new Vector(Math.cos(theta) * Math.cos(phi), Math.sin(phi), Math.sin(theta) * Math.cos(phi));
|
||||
}
|
||||
static randomDirection() {
|
||||
return Vector.fromAngles(Math.random() * TWO_PI, Math.asin(Math.random() * 2 - 1));
|
||||
}
|
||||
static min(a: Vector, b: Vector) {
|
||||
return new Vector(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z));
|
||||
}
|
||||
static max(a: Vector, b: Vector) {
|
||||
return new Vector(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z));
|
||||
}
|
||||
/**
|
||||
* Lerp between two vectors
|
||||
* @param {Vector} a: Vector a
|
||||
* @param {Vector} b: Vector b
|
||||
* @param {number} fraction: Fraction
|
||||
*/
|
||||
static lerp<T extends number | Vector>(a: T, b: T, fraction: number): T {
|
||||
if (b instanceof Vector) {
|
||||
return b.subtract(a).multiply(fraction).add(a) as unknown as T;
|
||||
} else {
|
||||
return (((b as number) - (a as number)) * fraction + (a as unknown as number)) as unknown as T;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a new vector from an Array
|
||||
* @param {number[]} array: Array
|
||||
* @returns {Vector} New vector
|
||||
*/
|
||||
static fromArray(a: Array<number> | XYZ) {
|
||||
if (Array.isArray(a)) {
|
||||
return new Vector(a[0], a[1], a[2]);
|
||||
}
|
||||
return new Vector(a.x, a.y, a.z);
|
||||
}
|
||||
/**
|
||||
* Angle between two vectors
|
||||
* @param {Vector} a: Vector a
|
||||
* @param {Vector} b: Vector b
|
||||
* @returns
|
||||
*/
|
||||
static angleBetween(a: Vector, b: Vector) {
|
||||
return a.angleTo(b);
|
||||
}
|
||||
static distance(a: Vector, b: Vector, d: number) {
|
||||
if (d === 2) return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
|
||||
else return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2) + Math.pow(a.z - b.z, 2));
|
||||
}
|
||||
static toDegrees(a: number) {
|
||||
return a * (180 / PI);
|
||||
}
|
||||
static normalizeAngle(radians: number) {
|
||||
let angle = radians % TWO_PI;
|
||||
angle = angle > PI ? angle - TWO_PI : angle < -PI ? TWO_PI + angle : angle;
|
||||
//returns normalized values to -1,1
|
||||
return angle / PI;
|
||||
}
|
||||
static normalizeRadians(radians: number) {
|
||||
if (radians >= PI / 2) {
|
||||
radians -= TWO_PI;
|
||||
}
|
||||
if (radians <= -PI / 2) {
|
||||
radians += TWO_PI;
|
||||
radians = PI - radians;
|
||||
}
|
||||
//returns normalized values to -1,1
|
||||
return radians / PI;
|
||||
}
|
||||
static find2DAngle(cx: number, cy: number, ex: number, ey: number) {
|
||||
const dy = ey - cy;
|
||||
const dx = ex - cx;
|
||||
const theta = Math.atan2(dy, dx);
|
||||
return theta;
|
||||
}
|
||||
/**
|
||||
* Find 3D rotation between two vectors
|
||||
* @param {Vector} a: First vector
|
||||
* @param {Vector} b: Second vector
|
||||
* @param {boolean} normalize: Normalize the result
|
||||
*/
|
||||
static findRotation(a: Vector | XYZ, b: Vector | XYZ, normalize = true) {
|
||||
if (normalize) {
|
||||
return new Vector(
|
||||
Vector.normalizeRadians(Vector.find2DAngle(a.z, a.x, b.z, b.x)),
|
||||
Vector.normalizeRadians(Vector.find2DAngle(a.z, a.y, b.z, b.y)),
|
||||
Vector.normalizeRadians(Vector.find2DAngle(a.x, a.y, b.x, b.y))
|
||||
);
|
||||
} else {
|
||||
return new Vector(
|
||||
Vector.find2DAngle(a.z, a.x, b.z, b.x),
|
||||
Vector.find2DAngle(a.z, a.y, b.z, b.y),
|
||||
Vector.find2DAngle(a.x, a.y, b.x, b.y)
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Find roll pitch yaw of plane formed by 3 points
|
||||
* @param {Vector} a: Vector
|
||||
* @param {Vector} b: Vector
|
||||
* @param {Vector} c: Vector
|
||||
*/
|
||||
static rollPitchYaw(a: Vector | XYZ, b: Vector | XYZ, c?: Vector) {
|
||||
if (!c) {
|
||||
return new Vector(
|
||||
Vector.normalizeAngle(Vector.find2DAngle(a.z, a.y, b.z, b.y)),
|
||||
Vector.normalizeAngle(Vector.find2DAngle(a.z, a.x, b.z, b.x)),
|
||||
Vector.normalizeAngle(Vector.find2DAngle(a.x, a.y, b.x, b.y))
|
||||
);
|
||||
}
|
||||
const qb = (b as Vector).subtract(a as Vector);
|
||||
const qc = c.subtract(a as Vector);
|
||||
const n = qb.cross(qc);
|
||||
|
||||
const unitZ = n.unit();
|
||||
const unitX = qb.unit();
|
||||
const unitY = unitZ.cross(unitX);
|
||||
|
||||
const beta = Math.asin(unitZ.x) || 0;
|
||||
const alpha = Math.atan2(-unitZ.y, unitZ.z) || 0;
|
||||
const gamma = Math.atan2(-unitY.x, unitX.x) || 0;
|
||||
|
||||
return new Vector(Vector.normalizeAngle(alpha), Vector.normalizeAngle(beta), Vector.normalizeAngle(gamma));
|
||||
}
|
||||
/**
|
||||
* Find angle between 3D Coordinates
|
||||
* @param {Vector | number} a: Vector or Number
|
||||
* @param {Vector | number} b: Vector or Number
|
||||
* @param {Vector | number} c: Vector or Number
|
||||
*/
|
||||
static angleBetween3DCoords(a: Vector | XYZ, b: Vector | XYZ, c: Vector | XYZ) {
|
||||
if (!(a instanceof Vector)) {
|
||||
a = new Vector(a);
|
||||
b = new Vector(b);
|
||||
c = new Vector(c);
|
||||
}
|
||||
// Calculate vector between points 1 and 2
|
||||
const v1 = (a as Vector).subtract(b as Vector);
|
||||
|
||||
// Calculate vector between points 2 and 3
|
||||
const v2 = (c as Vector).subtract(b as Vector);
|
||||
|
||||
// The dot product of vectors v1 & v2 is a function of the cosine of the
|
||||
// angle between them (it's scaled by the product of their magnitudes).
|
||||
const v1norm = v1.unit();
|
||||
const v2norm = v2.unit();
|
||||
|
||||
// Calculate the dot products of vectors v1 and v2
|
||||
const dotProducts = v1norm.dot(v2norm);
|
||||
|
||||
// Extract the angle from the dot products
|
||||
const angle = Math.acos(dotProducts);
|
||||
|
||||
// return single angle Normalized to 1
|
||||
return Vector.normalizeRadians(angle);
|
||||
}
|
||||
/**
|
||||
* Get normalized, spherical coordinates for the vector bc, relative to vector ab
|
||||
* @param {Vector | number} a: Vector or Number
|
||||
* @param {Vector | number} b: Vector or Number
|
||||
* @param {Vector | number} c: Vector or Number
|
||||
* @param {AxisMap} axisMap: Mapped axis to get the right spherical coords
|
||||
*/
|
||||
static getRelativeSphericalCoords(a: Vector | XYZ, b: Vector | XYZ, c: Vector | XYZ, axisMap: AxisMap) {
|
||||
if (!(a instanceof Vector)) {
|
||||
a = new Vector(a);
|
||||
b = new Vector(b);
|
||||
c = new Vector(c);
|
||||
}
|
||||
|
||||
// Calculate vector between points 1 and 2
|
||||
const v1 = (b as Vector).subtract(a as Vector);
|
||||
|
||||
// Calculate vector between points 2 and 3
|
||||
const v2 = (c as Vector).subtract(b as Vector);
|
||||
|
||||
const v1norm = v1.unit();
|
||||
const v2norm = v2.unit();
|
||||
|
||||
const { theta: theta1, phi: phi1 } = v1norm.toSphericalCoords(axisMap);
|
||||
const { theta: theta2, phi: phi2 } = v2norm.toSphericalCoords(axisMap);
|
||||
|
||||
const theta = theta1 - theta2;
|
||||
const phi = phi1 - phi2;
|
||||
|
||||
return {
|
||||
theta: Vector.normalizeAngle(theta),
|
||||
phi: Vector.normalizeAngle(phi),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get normalized, spherical coordinates for the vector bc
|
||||
* @param {Vector | number} a: Vector or Number
|
||||
* @param {Vector | number} b: Vector or Number
|
||||
* @param {AxisMap} axisMap: Mapped axis to get the right spherical coords
|
||||
*/
|
||||
static getSphericalCoords(a: Vector | XYZ, b: Vector | XYZ, axisMap: AxisMap = { x: "x", y: "y", z: "z" }) {
|
||||
if (!(a instanceof Vector)) {
|
||||
a = new Vector(a);
|
||||
b = new Vector(b);
|
||||
}
|
||||
|
||||
// Calculate vector between points 1 and 2
|
||||
const v1 = (b as Vector).subtract(a as Vector);
|
||||
|
||||
const v1norm = v1.unit();
|
||||
const { theta, phi } = v1norm.toSphericalCoords(axisMap);
|
||||
|
||||
return {
|
||||
theta: Vector.normalizeAngle(-theta),
|
||||
phi: Vector.normalizeAngle(PI / 2 - phi),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
const cloneDeep = require("lodash.clonedeep")
|
||||
import { calcLegs } from "../src/PoseSolver/calcLegs"
|
||||
import poseWorldLandmarks from "./poseWorlLandmarks"
|
||||
import { Results } from "../src/Types"
|
||||
import { offsets } from "../src/PoseSolver/calcLegs"
|
||||
|
||||
let worldLandmarks: Results
|
||||
const PI = Math.PI
|
||||
|
||||
describe("should resolve the correct upper leg rotation for", () => {
|
||||
beforeEach(() => {
|
||||
worldLandmarks = cloneDeep(poseWorldLandmarks)
|
||||
})
|
||||
|
||||
test("upper left leg, 90 degree forward, up", () => {
|
||||
worldLandmarks[23] = { x: -0.2, y: 0, z: 0 }
|
||||
worldLandmarks[24] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[26] = { x: 0, y: 0, z: -0.4 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.UpperLeg.l.x).toBeCloseTo(PI / 2)
|
||||
expect(result.Unscaled.UpperLeg.l.x).toBeCloseTo(0.5)
|
||||
|
||||
expect(result.UpperLeg.l.z).toBeCloseTo((0 - offsets.upperLeg.z))
|
||||
expect(result.Unscaled.UpperLeg.l.z).toBeCloseTo(0)
|
||||
})
|
||||
|
||||
test("upper left leg, straight down", () => {
|
||||
worldLandmarks[23] = { x: -0.2, y: 0, z: 0 }
|
||||
worldLandmarks[24] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[26] = { x: 0, y: 0.4, z: 0 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.UpperLeg.l.x).toBeCloseTo(0)
|
||||
expect(result.Unscaled.UpperLeg.l.z).toBeCloseTo(0)
|
||||
|
||||
expect(result.UpperLeg.l.z).toBeCloseTo((0 - offsets.upperLeg.z))
|
||||
expect(result.Unscaled.UpperLeg.l.x).toBeCloseTo(-0)
|
||||
})
|
||||
|
||||
test("upper left leg, 90 degree forward, up, 45 degree left", () => {
|
||||
worldLandmarks[23] = { x: -0.2, y: 0, z: 0 }
|
||||
worldLandmarks[24] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[26] = { x: -0.4, y: 0, z: -0.4 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.UpperLeg.l.x).toBeCloseTo(PI / 2)
|
||||
expect(result.Unscaled.UpperLeg.l.x).toBeCloseTo(0.5)
|
||||
|
||||
expect(result.UpperLeg.l.z).toBeCloseTo((-PI / 4 - offsets.upperLeg.z))
|
||||
expect(result.Unscaled.UpperLeg.l.z).toBeCloseTo(-0.25)
|
||||
})
|
||||
|
||||
test("upper right leg, 90 degree forward, up", () => {
|
||||
worldLandmarks[23] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[24] = { x: 0.2, y: 0, z: 0 }
|
||||
worldLandmarks[25] = { x: 0, y: 0, z: -0.4 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.UpperLeg.r.x).toBeCloseTo(PI / 2)
|
||||
expect(result.Unscaled.UpperLeg.r.x).toBeCloseTo(0.5)
|
||||
|
||||
expect(result.UpperLeg.r.z).toBeCloseTo(0 + offsets.upperLeg.z)
|
||||
expect(result.Unscaled.UpperLeg.r.z).toBeCloseTo(0)
|
||||
})
|
||||
|
||||
test("upper right leg, straight down", () => {
|
||||
worldLandmarks[23] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[24] = { x: 0.2, y: 0, z: 0 }
|
||||
worldLandmarks[25] = { x: 0, y: 0.4, z: 0 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.UpperLeg.r.x).toBeCloseTo(0)
|
||||
expect(result.Unscaled.UpperLeg.r.z).toBeCloseTo(0)
|
||||
|
||||
expect(result.UpperLeg.r.z).toBeCloseTo((0 + offsets.upperLeg.z))
|
||||
expect(result.Unscaled.UpperLeg.r.x).toBeCloseTo(0)
|
||||
})
|
||||
|
||||
test("upper right leg, 90 degree forward, up, 45 degree left", () => {
|
||||
worldLandmarks[23] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[24] = { x: 0.2, y: 0, z: 0 }
|
||||
worldLandmarks[25] = { x: 0.4, y: 0, z: -0.4 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.UpperLeg.r.x).toBeCloseTo(PI / 2)
|
||||
expect(result.Unscaled.UpperLeg.r.x).toBeCloseTo(0.5)
|
||||
|
||||
expect(result.UpperLeg.r.z).toBeCloseTo((PI / 4 + offsets.upperLeg.z))
|
||||
expect(result.Unscaled.UpperLeg.r.z).toBeCloseTo(0.25)
|
||||
})
|
||||
})
|
||||
|
||||
describe("should resolve the correct lower leg rotation for", () => {
|
||||
beforeEach(() => {
|
||||
worldLandmarks = cloneDeep(poseWorldLandmarks)
|
||||
})
|
||||
|
||||
test("lower left leg, 90 degree", () => {
|
||||
worldLandmarks[24] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[26] = { x: 0, y: 0, z: -0.4 }
|
||||
worldLandmarks[28] = { x: 0, y: 0.4, z: -0.4 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.LowerLeg.l.x).toBeCloseTo(-PI / 2)
|
||||
expect(result.Unscaled.LowerLeg.l.x).toBeCloseTo(-0.5)
|
||||
})
|
||||
|
||||
test("lower left leg, 45 degree", () => {
|
||||
worldLandmarks[24] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[26] = { x: 0, y: 0, z: -0.4 }
|
||||
worldLandmarks[28] = { x: 0, y: 0.4, z: -0.8 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.LowerLeg.l.x).toBeCloseTo(-PI / 4)
|
||||
expect(result.Unscaled.LowerLeg.l.x).toBeCloseTo(-0.25)
|
||||
})
|
||||
|
||||
test("lower right leg, 90 degree", () => {
|
||||
worldLandmarks[23] = { x: -0.2, y: 0, z: 0 }
|
||||
worldLandmarks[25] = { x: 0, y: 0, z: -0.4 }
|
||||
worldLandmarks[27] = { x: 0, y: 0.4, z: -0.4 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.LowerLeg.r.x).toBeCloseTo(-PI / 2)
|
||||
expect(result.Unscaled.LowerLeg.r.x).toBeCloseTo(-0.5)
|
||||
})
|
||||
|
||||
test("lower right leg, 45 degree", () => {
|
||||
worldLandmarks[23] = { x: 0, y: 0, z: 0 }
|
||||
worldLandmarks[25] = { x: 0, y: 0, z: -0.4 }
|
||||
worldLandmarks[27] = { x: 0, y: 0.4, z: -0.8 }
|
||||
|
||||
const result = calcLegs(worldLandmarks)
|
||||
|
||||
expect(result.LowerLeg.r.x).toBeCloseTo(-PI / 4)
|
||||
expect(result.Unscaled.LowerLeg.r.x).toBeCloseTo(-0.25)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
export default {
|
||||
0: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
1: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
2: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
3: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
4: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
5: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
6: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
7: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
8: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
9: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
10: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
11: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
12: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
13: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
14: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
15: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
16: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
17: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
18: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
19: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
20: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
21: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
22: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
23: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
24: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
25: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
26: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
27: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
28: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
29: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
30: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
31: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0},
|
||||
32: {x: 0.0, y: 0.0, z: 0.0, visibility: 1.0}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"ESNext.Array",
|
||||
"DOM"
|
||||
],
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"strictBindCallApply": true,
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"tests",
|
||||
"dist"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const path = require("path");
|
||||
const { defineConfig } = require("vite");
|
||||
import Banner from "vite-plugin-banner";
|
||||
import pkg from "./package.json";
|
||||
import friendlyTypeImports from "rollup-plugin-friendly-type-imports";
|
||||
import { resolve } from "path";
|
||||
module.exports = defineConfig({
|
||||
base: "./",
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "src/index.ts"),
|
||||
name: "Kalidokit",
|
||||
fileName: (format) => `kalidokit.${format}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
// make sure to externalize deps that shouldn't be bundled
|
||||
// into your library
|
||||
exports: "named",
|
||||
external: [],
|
||||
output: {
|
||||
// Provide global variables to use in the UMD build
|
||||
// for externalized deps
|
||||
globals: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
Banner(
|
||||
`/**\n * @${pkg.name} v${pkg.version}\n * ${pkg.description}\n * \n * @license\n * Copyright (c) ${pkg.year} ${pkg.author}\n * SPDX-License-Idntifier: ${pkg.license} \n * ${pkg.homepage}\n */`
|
||||
),
|
||||
friendlyTypeImports(),
|
||||
],
|
||||
});
|
||||
Generated
+3657
-5
File diff suppressed because it is too large.
Load diff
+2
-1
@@ -46,7 +46,7 @@
|
||||
"detect-gpu": "^5.0.38",
|
||||
"http-proxy-middleware": "^3.0.0",
|
||||
"isbot": "^4.1.0",
|
||||
"kalidokit": "^1.1.5",
|
||||
"kalidokit": "file:./kalidokit",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"posthog-js": "^1.141.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -64,6 +64,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.3.5",
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
||||
"@remix-run/dev": "^2.8.1",
|
||||
"@storybook/addon-actions": "^8.1.10",
|
||||
"@storybook/addon-essentials": "^8.1.10",
|
||||
|
||||
+3
-3
@@ -9,15 +9,15 @@
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ES2022"
|
||||
"ESNext"
|
||||
],
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2022",
|
||||
"target": "ESNext",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
@@ -2,12 +2,35 @@ import { vitePlugin as remix } from "@remix-run/dev";
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { netlifyPlugin } from "@netlify/remix-edge-adapter/plugin";
|
||||
import { viteCommonjs, esbuildCommonjs } from '@originjs/vite-plugin-commonjs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
//build: {
|
||||
//},
|
||||
//resolve: {
|
||||
// alias: {
|
||||
// 'kalidokit': `${path.resolve(__dirname, 'src')}/`,
|
||||
// },
|
||||
//},
|
||||
//rollupOptions: {
|
||||
// // make sure to externalize deps that shouldn't be bundled
|
||||
// // into your library
|
||||
// exports: "named",
|
||||
// external: [],
|
||||
// output: {
|
||||
// // Provide global variables to use in the UMD build
|
||||
// // for externalized deps
|
||||
// globals: {},
|
||||
// },
|
||||
//},
|
||||
plugins: [
|
||||
remix(),
|
||||
netlifyPlugin(),
|
||||
tsconfigPaths(),
|
||||
//viteCommonjs({
|
||||
// include: ["kalidokit"],
|
||||
//}),
|
||||
{
|
||||
name: "configure-response-headers",
|
||||
configureServer: (server) => {
|
||||
@@ -30,4 +53,21 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
//optimizeDeps: {
|
||||
// esbuildOptions: {
|
||||
// plugins: [
|
||||
// esbuildCommonjs(['kalidokit'])
|
||||
// ]
|
||||
// }
|
||||
//}
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
//'vue',
|
||||
//'vue-router',
|
||||
//'@vueuse/core',
|
||||
//'@vueuse/head',
|
||||
//'consola',
|
||||
//'kalidokit',
|
||||
],
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user