├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── package-lock.json ├── package.json ├── postprocessing.d.ts ├── public └── share.jpg ├── readme.md ├── settings.ts ├── src ├── FaceMeshFaceGeometry │ ├── face.js │ └── geometry.js ├── appState.ts ├── assets │ ├── eyes.jpg │ ├── eyes.png │ ├── eyes_displacement.png │ ├── eyes_inverted.jpg │ ├── eyes_inverted.png │ ├── eyes_mouth_inverted.jpg │ ├── eyes_mouth_inverted.png │ ├── face_edges.jpg │ ├── face_highlights.jpg │ ├── face_highlights.png │ ├── hearts.png │ ├── horns.jpg │ ├── horns_col.png │ ├── mesh_map.jpg │ └── mouth_bottom_inverted.png ├── components │ ├── App.tsx │ └── Thumbs.tsx ├── effects │ ├── ColorOverlayEffect │ │ ├── index.ts │ │ └── shader.frag │ ├── DisplacementEffect │ │ ├── index.ts │ │ └── shader.frag │ ├── DriftEffect │ │ ├── index.ts │ │ └── shader.frag │ ├── FaceDetailEffect │ │ ├── index.ts │ │ └── shader.frag │ ├── MeltEffect │ │ ├── index.ts │ │ └── shader.frag │ ├── SmokeEffect │ │ ├── index.ts │ │ └── shader.frag │ └── TunnelEffect │ │ ├── index.ts │ │ └── shader.frag ├── faceMesh.ts ├── global.d.ts ├── glsl │ ├── common.glsl │ ├── faceBulge │ │ ├── fragment.glsl │ │ └── vertex.glsl │ ├── horns │ │ └── fragment.glsl │ └── texDisplace │ │ ├── fragment.glsl │ │ └── vertex.glsl ├── hooks │ └── useWinSize.tsx ├── index.html ├── index.tsx ├── setup.tsx ├── sketches │ ├── Devil.js │ ├── Drift.js │ ├── Lumpy.js │ ├── Melt.js │ ├── Smoke.js │ ├── Tunnel.js │ └── index.ts ├── style.css ├── utils │ ├── clamp.ts │ ├── getResizeFactors.ts │ └── resizeTexture.ts └── webcam.tsx ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-transform-regenerator", 8 | "@babel/plugin-transform-runtime" 9 | ] 10 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "wesbos", 4 | "plugin:@typescript-eslint/eslint-recommended", 5 | "plugin:@typescript-eslint/recommended" 6 | ], 7 | "globals": { 8 | "chrome": true 9 | }, 10 | "rules": { 11 | "no-restricted-globals": 0, 12 | "react/prop-types": 0, 13 | "react/destructuring-assignment": 0, 14 | "no-plusplus": 0, 15 | "react/jsx-filename-extension": 0, 16 | "@typescript-eslint/no-explicit-any": 0, 17 | "@typescript-eslint/explicit-function-return-type": 0, 18 | "@typescript-eslint/no-var-requires": 0 19 | }, 20 | "settings": { 21 | "import/parsers": { 22 | "@typescript-eslint/parser": [".ts", ".tsx"] 23 | }, 24 | "import/resolver": { 25 | "node": { 26 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 27 | } 28 | } 29 | }, 30 | "parser": "@typescript-eslint/parser", 31 | "plugins": [ 32 | "@typescript-eslint/eslint-plugin" 33 | ] 34 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | test-video 4 | certs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alex Kempton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "face-filter-gallery", 3 | "devDependencies": { 4 | "@babel/core": "7.5.0", 5 | "@babel/plugin-transform-regenerator": "7.4.5", 6 | "@babel/plugin-transform-runtime": "7.5.0", 7 | "@babel/preset-env": "7.5.0", 8 | "@babel/preset-react": "7.0.0", 9 | "@babel/runtime": "7.5.0", 10 | "babel-eslint": "9.0.0", 11 | "babel-loader": "8.0.4", 12 | "clean-webpack-plugin": "^3.0.0", 13 | "copy-webpack-plugin": "^6.0.1", 14 | "css-loader": "^3.5.3", 15 | "eslint": "5.16.0", 16 | "eslint-config-airbnb": "17.1.1", 17 | "eslint-config-prettier": "4.3.0", 18 | "eslint-config-wesbos": "0.0.19", 19 | "eslint-plugin-html": "5.0.5", 20 | "eslint-plugin-import": "2.20.0", 21 | "eslint-plugin-jsx-a11y": "6.2.3", 22 | "eslint-plugin-prettier": "3.1.2", 23 | "eslint-plugin-react": "7.18.0", 24 | "eslint-plugin-react-hooks": "1.7.0", 25 | "file-loader": "^6.0.0", 26 | "gh-pages": "^3.1.0", 27 | "glslify-import": "^3.1.0", 28 | "glslify-loader": "^2.0.0", 29 | "html-webpack-plugin": "^4.3.0", 30 | "prettier": "^1.19.1", 31 | "raw-loader": "^4.0.1", 32 | "regenerator-runtime": "0.13.2", 33 | "style-loader": "^1.2.1", 34 | "terser-webpack-plugin": "1.2.3", 35 | "ts-loader": "6.2.1", 36 | "typescript": "3.7.5", 37 | "webpack": "4.41.6", 38 | "webpack-bundle-analyzer": "^4.4.0", 39 | "webpack-cli": "3.1.2", 40 | "webpack-dev-server": "3.10.3", 41 | "webpack-merge": "^5.7.3" 42 | }, 43 | "dependencies": { 44 | "@srmagura/use-event-listener": "^0.2.1", 45 | "@tensorflow-models/facemesh": "0.0.3", 46 | "@tensorflow/tfjs-converter": "^1.7.4", 47 | "@tensorflow/tfjs-core": "^1.7.4", 48 | "@types/react": "16.9.19", 49 | "@types/react-dom": "16.9.5", 50 | "@types/stats": "^0.16.30", 51 | "@types/styled-components": "4.4.2", 52 | "@types/webpack-env": "1.15.1", 53 | "@typescript-eslint/eslint-plugin": "2.19.2", 54 | "@typescript-eslint/parser": "2.19.2", 55 | "flickity": "^2.2.1", 56 | "fscreen": "^1.2.0", 57 | "glsl-worley": "^1.0.2", 58 | "postprocessing": "^6.13.5", 59 | "prop-types": "15.7.2", 60 | "react": "16.8.6", 61 | "react-dom": "16.8.6", 62 | "react-flickity-component": "^3.5.0", 63 | "stats.js": "^0.17.0", 64 | "styled-components": "5.0.0", 65 | "three": "^0.116.1" 66 | }, 67 | "glslify": { 68 | "transform": [ 69 | "glslify-import" 70 | ] 71 | }, 72 | "scripts": { 73 | "build": "webpack --config ./webpack.prod.js", 74 | "dev": "webpack-dev-server --config ./webpack.dev.js", 75 | "deploy": "gh-pages -d dist" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /public/share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/public/share.jpg -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Instatrip 2 | A demo showcasing what's possible with face filters on the web. Built completely with free and open-source libraries. These ones in particular: 3 | 4 | - [three.js](https://github.com/mrdoob/three.js/) 5 | - [postprocessing](https://github.com/vanruesc/postprocessing) 6 | - [FaceMeshFaceGeometry](https://github.com/spite/FaceMeshFaceGeometry) 7 | - [MediaPipe Facemesh](https://github.com/tensorflow/tfjs-models/tree/master/facemesh) 8 | 9 | ## Dev notes 10 | These are more for myself, but might be useful for anyone who wants to fork and have a play! 11 | 12 | ### Setup 13 | * Clone the repo 14 | * Install dependencies with `npm install` 15 | * Make sure you have a test video in place (see below) 16 | * Make sure you have SSL certificates in place (or bypass them, see below) 17 | 18 | ### Scripts 19 | * `npm run dev`: Start development server 20 | * `npm run build`: Package up the app (will appear in `dist`) 21 | * `npm run deploy`: Uses [gh-pages](https://github.com/tschaub/gh-pages) to automatically deploy to Github Pages. 22 | 23 | ### JS vs TS 24 | This project is mostly Typescript, but the sketch files are Javascript. This is just to allow for quick and creative development for the fun parts of the code. :) 25 | 26 | ### Test videos 27 | Rather than constantly pulling faces on a webcam while developing, you can choose to load in a video instead. These are not included in the repo and must be added to a directory in the root named `test-video`. You'll need to reference this video `settings.ts` (e.g. `fakeCam: myCoolTestVideo.mp4`). Set `fakeCam` to `false` if you don't want to use the test video. Please note, that **things break if there isn't some sort of video to use** (even if you've disabled it). 28 | 29 | ### Testing iOS / SSL Certificates 30 | The dev server needs to run as HTTPS for webcam access. Unfortunately iOS is quite fussy with this and just simply enabling `https: true` for Webpack dev server isn't enough. Therefore, when testing iOS, you'll need to make some changes to the setup. Details are mentioned in this [Github comment](https://github.com/webpack/webpack-dev-server/issues/1796#issuecomment-497687804). 31 | 32 | - Use `inline: false` in the `devServer` webpack settings (`webpack.dev.js`). 33 | - Make sure you have SSL certificates generated 34 | 35 | If you're not worried about iOS, just set `https` to `true` in `webpack.dev.js`, rather than referencing the certificates. 36 | -------------------------------------------------------------------------------- /settings.ts: -------------------------------------------------------------------------------- 1 | interface DevModeInterface { 2 | fakeCam: string | false; 3 | fps: boolean; 4 | } 5 | 6 | export const devMode: DevModeInterface = { 7 | fakeCam: false, 8 | fps: false, 9 | }; 10 | -------------------------------------------------------------------------------- /src/FaceMeshFaceGeometry/face.js: -------------------------------------------------------------------------------- 1 | // Original FaceMeshFaceGeometry by Jaume Sanchez 2 | // https://github.com/spite/FaceMeshFaceGeometry 3 | 4 | import { 5 | BufferGeometry, 6 | BufferAttribute, 7 | Vector3, 8 | Triangle, 9 | Matrix4, 10 | } from 'three'; 11 | import { 12 | FACES as indices, 13 | MOUTH_FACES as mouthIndices, 14 | UVS as texCoords, 15 | } from './geometry.js'; 16 | 17 | class FaceMeshFaceGeometry extends BufferGeometry { 18 | constructor(options = {}) { 19 | super(); 20 | 21 | this.useVideoTexture = options.useVideoTexture || false; 22 | this.normalizeCoords = options.normalizeCoords || false; 23 | this.flipped = false; 24 | this.positions = new Float32Array(468 * 3); 25 | this.uvs = new Float32Array(468 * 2); 26 | this.videoUvs = new Float32Array(468 * 2); 27 | this.setAttribute('position', new BufferAttribute(this.positions, 3)); 28 | this.setAttribute('uv', new BufferAttribute(this.uvs, 2)); 29 | this.setAttribute('videoUv', new BufferAttribute(this.videoUvs, 2)); 30 | this.setUvs(); 31 | this.setIndex([...indices, ...mouthIndices]); 32 | this.computeVertexNormals(); 33 | this.applyMatrix4(new Matrix4().makeScale(10, 10, 10)); 34 | this.p0 = new Vector3(); 35 | this.p1 = new Vector3(); 36 | this.p2 = new Vector3(); 37 | this.triangle = new Triangle(); 38 | } 39 | 40 | setUvs() { 41 | for (let j = 0; j < 468; j++) { 42 | this.uvs[j * 2] = this.flipped ? 1 - texCoords[j][0] : texCoords[j][0]; 43 | this.uvs[j * 2 + 1] = 1 - texCoords[j][1]; 44 | } 45 | this.getAttribute('uv').needsUpdate = true; 46 | } 47 | 48 | setVideoUvs() { 49 | let ptr = 0; 50 | for (let j = 0; j < 468 * 2; j += 2) { 51 | this.videoUvs[j] = this.flipped 52 | ? this.positions[ptr] / this.w + 0.5 53 | : 1 - (this.positions[ptr] / this.w + 0.5); 54 | this.videoUvs[j + 1] = this.positions[ptr + 1] / this.h + 0.5; 55 | ptr += 3; 56 | } 57 | this.getAttribute('videoUv').needsUpdate = true; 58 | } 59 | 60 | setSize(w, h) { 61 | this.w = w; 62 | this.h = h; 63 | } 64 | 65 | update(face, cameraFlipped) { 66 | let ptr = 0; 67 | for (const p of face.scaledMesh) { 68 | this.positions[ptr] = cameraFlipped 69 | ? p[0] + 0.5 * this.w 70 | : p[0] - 0.5 * this.w; 71 | this.positions[ptr + 1] = this.h - p[1] - 0.5 * this.h; 72 | this.positions[ptr + 2] = -p[2]; 73 | ptr += 3; 74 | } 75 | 76 | // Mod of Spite's original approach, we generate video UVs in a different attribute 77 | if (this.useVideoTexture) { 78 | this.setVideoUvs(); 79 | if (this.normalizeCoords) { 80 | ptr = 0; 81 | const ar = this.h / this.w; 82 | const scale = 2 * Math.sqrt(this.w / 1000); 83 | for (const p of face.scaledMesh) { 84 | this.positions[ptr] = scale * (p[0] / this.w + 0.5); 85 | this.positions[ptr + 1] = scale * (-p[1] / this.h + 0.5) * ar; 86 | this.positions[ptr + 2] = scale * (-p[2] / 500); 87 | ptr += 3; 88 | } 89 | } 90 | } 91 | 92 | if (cameraFlipped !== this.flipped) { 93 | // this.flipped = cameraFlipped; 94 | this.setUvs(); 95 | } 96 | 97 | this.attributes.position.needsUpdate = true; 98 | this.computeVertexNormals(); 99 | } 100 | 101 | track(id0, id1, id2) { 102 | const points = this.positions; 103 | this.p0.set(points[id0 * 3], points[id0 * 3 + 1], points[id0 * 3 + 2]); 104 | this.p1.set(points[id1 * 3], points[id1 * 3 + 1], points[id1 * 3 + 2]); 105 | this.p2.set(points[id2 * 3], points[id2 * 3 + 1], points[id2 * 3 + 2]); 106 | this.triangle.set(this.p0, this.p1, this.p2); 107 | const center = new Vector3(); 108 | this.triangle.getMidpoint(center); 109 | const normal = new Vector3(); 110 | this.triangle.getNormal(normal); 111 | const matrix = new Matrix4(); 112 | const x = this.p1 113 | .clone() 114 | .sub(this.p2) 115 | .normalize(); 116 | const y = this.p1 117 | .clone() 118 | .sub(this.p0) 119 | .normalize(); 120 | const z = new Vector3().crossVectors(x, y); 121 | const y2 = new Vector3().crossVectors(x, z).normalize(); 122 | const z2 = new Vector3().crossVectors(x, y2).normalize(); 123 | matrix.makeBasis(x, y2, z2); 124 | return { position: center, normal, rotation: matrix }; 125 | } 126 | } 127 | 128 | export { FaceMeshFaceGeometry }; 129 | -------------------------------------------------------------------------------- /src/FaceMeshFaceGeometry/geometry.js: -------------------------------------------------------------------------------- 1 | export const FACES = [ 2 | 255, 3 | 448, 4 | 339, 5 | 249, 6 | 339, 7 | 390, 8 | 353, 9 | 372, 10 | 265, 11 | 288, 12 | 401, 13 | 361, 14 | 363, 15 | 420, 16 | 360, 17 | 456, 18 | 437, 19 | 420, 20 | 440, 21 | 360, 22 | 344, 23 | 399, 24 | 343, 25 | 437, 26 | 412, 27 | 357, 28 | 343, 29 | 465, 30 | 453, 31 | 357, 32 | 464, 33 | 341, 34 | 453, 35 | 433, 36 | 411, 37 | 376, 38 | 392, 39 | 250, 40 | 309, 41 | 435, 42 | 376, 43 | 401, 44 | 460, 45 | 290, 46 | 305, 47 | 290, 48 | 289, 49 | 305, 50 | 250, 51 | 459, 52 | 309, 53 | 445, 54 | 467, 55 | 260, 56 | 444, 57 | 260, 58 | 259, 59 | 443, 60 | 259, 61 | 257, 62 | 442, 63 | 257, 64 | 258, 65 | 441, 66 | 258, 67 | 286, 68 | 414, 69 | 441, 70 | 286, 71 | 463, 72 | 413, 73 | 414, 74 | 452, 75 | 341, 76 | 256, 77 | 451, 78 | 256, 79 | 252, 80 | 450, 81 | 252, 82 | 253, 83 | 449, 84 | 253, 85 | 254, 86 | 446, 87 | 255, 88 | 359, 89 | 448, 90 | 254, 91 | 339, 92 | 455, 93 | 305, 94 | 289, 95 | 370, 96 | 2, 97 | 94, 98 | 326, 99 | 462, 100 | 328, 101 | 439, 102 | 289, 103 | 392, 104 | 367, 105 | 433, 106 | 435, 107 | 358, 108 | 294, 109 | 331, 110 | 262, 111 | 421, 112 | 418, 113 | 4, 114 | 274, 115 | 1, 116 | 401, 117 | 352, 118 | 366, 119 | 457, 120 | 309, 121 | 459, 122 | 383, 123 | 264, 124 | 372, 125 | 411, 126 | 434, 127 | 427, 128 | 410, 129 | 432, 130 | 287, 131 | 409, 132 | 287, 133 | 291, 134 | 408, 135 | 291, 136 | 306, 137 | 407, 138 | 306, 139 | 292, 140 | 361, 141 | 366, 142 | 323, 143 | 406, 144 | 421, 145 | 313, 146 | 405, 147 | 313, 148 | 314, 149 | 404, 150 | 314, 151 | 315, 152 | 403, 153 | 315, 154 | 316, 155 | 402, 156 | 316, 157 | 317, 158 | 457, 159 | 354, 160 | 274, 161 | 262, 162 | 396, 163 | 428, 164 | 394, 165 | 434, 166 | 364, 167 | 461, 168 | 459, 169 | 458, 170 | 438, 171 | 440, 172 | 344, 173 | 326, 174 | 391, 175 | 393, 176 | 437, 177 | 429, 178 | 420, 179 | 391, 180 | 426, 181 | 322, 182 | 425, 183 | 330, 184 | 280, 185 | 381, 186 | 385, 187 | 380, 188 | 340, 189 | 446, 190 | 265, 191 | 301, 192 | 293, 193 | 298, 194 | 13, 195 | 268, 196 | 312, 197 | 387, 198 | 374, 199 | 386, 200 | 12, 201 | 302, 202 | 268, 203 | 267, 204 | 164, 205 | 393, 206 | 461, 207 | 370, 208 | 354, 209 | 393, 210 | 2, 211 | 326, 212 | 388, 213 | 373, 214 | 387, 215 | 322, 216 | 436, 217 | 410, 218 | 419, 219 | 412, 220 | 399, 221 | 379, 222 | 395, 223 | 394, 224 | 386, 225 | 380, 226 | 385, 227 | 389, 228 | 301, 229 | 251, 230 | 465, 231 | 351, 232 | 417, 233 | 368, 234 | 300, 235 | 301, 236 | 274, 237 | 440, 238 | 457, 239 | 197, 240 | 248, 241 | 195, 242 | 295, 243 | 441, 244 | 285, 245 | 370, 246 | 19, 247 | 354, 248 | 282, 249 | 442, 250 | 295, 251 | 280, 252 | 346, 253 | 352, 254 | 466, 255 | 390, 256 | 388, 257 | 372, 258 | 340, 259 | 265, 260 | 355, 261 | 358, 262 | 429, 263 | 415, 264 | 292, 265 | 308, 266 | 455, 267 | 278, 268 | 294, 269 | 151, 270 | 338, 271 | 337, 272 | 299, 273 | 336, 274 | 337, 275 | 454, 276 | 264, 277 | 356, 278 | 346, 279 | 449, 280 | 448, 281 | 296, 282 | 282, 283 | 295, 284 | 339, 285 | 373, 286 | 390, 287 | 445, 288 | 283, 289 | 276, 290 | 268, 291 | 311, 292 | 312, 293 | 199, 294 | 396, 295 | 175, 296 | 291, 297 | 273, 298 | 375, 299 | 281, 300 | 195, 301 | 248, 302 | 311, 303 | 272, 304 | 310, 305 | 439, 306 | 344, 307 | 278, 308 | 397, 309 | 435, 310 | 288, 311 | 365, 312 | 367, 313 | 397, 314 | 325, 315 | 318, 316 | 324, 317 | 419, 318 | 6, 319 | 351, 320 | 403, 321 | 318, 322 | 319, 323 | 252, 324 | 374, 325 | 253, 326 | 350, 327 | 329, 328 | 277, 329 | 9, 330 | 285, 331 | 8, 332 | 316, 333 | 14, 334 | 317, 335 | 279, 336 | 358, 337 | 331, 338 | 315, 339 | 15, 340 | 316, 341 | 447, 342 | 323, 343 | 366, 344 | 333, 345 | 293, 346 | 334, 347 | 349, 348 | 330, 349 | 329, 350 | 404, 351 | 319, 352 | 320, 353 | 336, 354 | 295, 355 | 285, 356 | 320, 357 | 325, 358 | 307, 359 | 411, 360 | 352, 361 | 376, 362 | 168, 363 | 351, 364 | 6, 365 | 299, 366 | 334, 367 | 296, 368 | 400, 369 | 395, 370 | 378, 371 | 431, 372 | 394, 373 | 395, 374 | 408, 375 | 272, 376 | 304, 377 | 427, 378 | 432, 379 | 436, 380 | 304, 381 | 271, 382 | 303, 383 | 294, 384 | 279, 385 | 331, 386 | 460, 387 | 294, 388 | 327, 389 | 371, 390 | 423, 391 | 358, 392 | 302, 393 | 271, 394 | 268, 395 | 345, 396 | 366, 397 | 352, 398 | 307, 399 | 292, 400 | 306, 401 | 424, 402 | 273, 403 | 422, 404 | 276, 405 | 342, 406 | 445, 407 | 430, 408 | 424, 409 | 422, 410 | 257, 411 | 387, 412 | 386, 413 | 342, 414 | 265, 415 | 446, 416 | 384, 417 | 258, 418 | 385, 419 | 328, 420 | 250, 421 | 290, 422 | 422, 423 | 287, 424 | 432, 425 | 329, 426 | 266, 427 | 371, 428 | 428, 429 | 200, 430 | 421, 431 | 393, 432 | 269, 433 | 267, 434 | 252, 435 | 381, 436 | 380, 437 | 425, 438 | 436, 439 | 426, 440 | 281, 441 | 456, 442 | 363, 443 | 19, 444 | 274, 445 | 354, 446 | 340, 447 | 352, 448 | 346, 449 | 447, 450 | 372, 451 | 264, 452 | 293, 453 | 276, 454 | 283, 455 | 325, 456 | 308, 457 | 292, 458 | 383, 459 | 276, 460 | 300, 461 | 462, 462 | 458, 463 | 250, 464 | 282, 465 | 293, 466 | 283, 467 | 467, 468 | 446, 469 | 359, 470 | 349, 471 | 450, 472 | 348, 473 | 321, 474 | 273, 475 | 335, 476 | 337, 477 | 297, 478 | 299, 479 | 369, 480 | 431, 481 | 395, 482 | 363, 483 | 275, 484 | 281, 485 | 283, 486 | 443, 487 | 282, 488 | 437, 489 | 277, 490 | 355, 491 | 379, 492 | 364, 493 | 365, 494 | 360, 495 | 429, 496 | 279, 497 | 256, 498 | 382, 499 | 381, 500 | 438, 501 | 392, 502 | 309, 503 | 329, 504 | 355, 505 | 277, 506 | 328, 507 | 327, 508 | 326, 509 | 285, 510 | 413, 511 | 417, 512 | 261, 513 | 346, 514 | 448, 515 | 8, 516 | 417, 517 | 168, 518 | 333, 519 | 284, 520 | 298, 521 | 419, 522 | 456, 523 | 248, 524 | 258, 525 | 386, 526 | 385, 527 | 464, 528 | 417, 529 | 413, 530 | 269, 531 | 322, 532 | 270, 533 | 369, 534 | 377, 535 | 396, 536 | 266, 537 | 426, 538 | 423, 539 | 17, 540 | 315, 541 | 314, 542 | 405, 543 | 320, 544 | 321, 545 | 406, 546 | 321, 547 | 335, 548 | 18, 549 | 421, 550 | 200, 551 | 411, 552 | 425, 553 | 280, 554 | 321, 555 | 307, 556 | 375, 557 | 253, 558 | 373, 559 | 254, 560 | 4, 561 | 281, 562 | 275, 563 | 298, 564 | 251, 565 | 301, 566 | 327, 567 | 423, 568 | 391, 569 | 416, 570 | 364, 571 | 434, 572 | 418, 573 | 335, 574 | 424, 575 | 260, 576 | 387, 577 | 259, 578 | 375, 579 | 306, 580 | 291, 581 | 17, 582 | 313, 583 | 18, 584 | 434, 585 | 422, 586 | 432, 587 | 347, 588 | 450, 589 | 449, 590 | 410, 591 | 270, 592 | 322, 593 | 407, 594 | 310, 595 | 272, 596 | 409, 597 | 304, 598 | 270, 599 | 431, 600 | 418, 601 | 424, 602 | 360, 603 | 278, 604 | 344, 605 | 337, 606 | 9, 607 | 151, 608 | 270, 609 | 303, 610 | 269, 611 | 330, 612 | 347, 613 | 280, 614 | 382, 615 | 384, 616 | 381, 617 | 377, 618 | 175, 619 | 396, 620 | 297, 621 | 333, 622 | 299, 623 | 357, 624 | 452, 625 | 350, 626 | 277, 627 | 357, 628 | 350, 629 | 269, 630 | 302, 631 | 267, 632 | 349, 633 | 452, 634 | 451, 635 | 267, 636 | 11, 637 | 0, 638 | 368, 639 | 356, 640 | 264, 641 | 25, 642 | 228, 643 | 31, 644 | 7, 645 | 110, 646 | 25, 647 | 124, 648 | 143, 649 | 156, 650 | 58, 651 | 177, 652 | 215, 653 | 134, 654 | 198, 655 | 236, 656 | 236, 657 | 217, 658 | 174, 659 | 220, 660 | 131, 661 | 134, 662 | 174, 663 | 114, 664 | 188, 665 | 188, 666 | 128, 667 | 245, 668 | 245, 669 | 233, 670 | 244, 671 | 244, 672 | 112, 673 | 243, 674 | 213, 675 | 187, 676 | 192, 677 | 166, 678 | 20, 679 | 60, 680 | 215, 681 | 147, 682 | 213, 683 | 240, 684 | 60, 685 | 99, 686 | 225, 687 | 247, 688 | 113, 689 | 224, 690 | 30, 691 | 225, 692 | 223, 693 | 29, 694 | 224, 695 | 222, 696 | 27, 697 | 223, 698 | 221, 699 | 28, 700 | 222, 701 | 190, 702 | 221, 703 | 189, 704 | 243, 705 | 189, 706 | 244, 707 | 232, 708 | 112, 709 | 233, 710 | 231, 711 | 26, 712 | 232, 713 | 230, 714 | 22, 715 | 231, 716 | 229, 717 | 23, 718 | 230, 719 | 226, 720 | 25, 721 | 31, 722 | 228, 723 | 24, 724 | 229, 725 | 235, 726 | 75, 727 | 240, 728 | 141, 729 | 2, 730 | 97, 731 | 97, 732 | 242, 733 | 141, 734 | 219, 735 | 59, 736 | 235, 737 | 138, 738 | 213, 739 | 192, 740 | 129, 741 | 64, 742 | 98, 743 | 32, 744 | 201, 745 | 208, 746 | 4, 747 | 44, 748 | 45, 749 | 177, 750 | 123, 751 | 147, 752 | 237, 753 | 79, 754 | 218, 755 | 156, 756 | 34, 757 | 139, 758 | 187, 759 | 214, 760 | 192, 761 | 186, 762 | 212, 763 | 216, 764 | 185, 765 | 57, 766 | 186, 767 | 184, 768 | 61, 769 | 185, 770 | 183, 771 | 76, 772 | 184, 773 | 132, 774 | 137, 775 | 177, 776 | 182, 777 | 201, 778 | 194, 779 | 181, 780 | 83, 781 | 182, 782 | 180, 783 | 84, 784 | 181, 785 | 179, 786 | 85, 787 | 180, 788 | 178, 789 | 86, 790 | 179, 791 | 237, 792 | 125, 793 | 241, 794 | 32, 795 | 171, 796 | 140, 797 | 169, 798 | 214, 799 | 210, 800 | 241, 801 | 239, 802 | 237, 803 | 218, 804 | 220, 805 | 237, 806 | 97, 807 | 165, 808 | 98, 809 | 217, 810 | 209, 811 | 126, 812 | 165, 813 | 206, 814 | 203, 815 | 205, 816 | 101, 817 | 36, 818 | 154, 819 | 158, 820 | 157, 821 | 111, 822 | 226, 823 | 31, 824 | 71, 825 | 63, 826 | 70, 827 | 13, 828 | 38, 829 | 12, 830 | 160, 831 | 145, 832 | 144, 833 | 12, 834 | 72, 835 | 11, 836 | 37, 837 | 164, 838 | 0, 839 | 241, 840 | 141, 841 | 242, 842 | 167, 843 | 2, 844 | 164, 845 | 161, 846 | 144, 847 | 163, 848 | 92, 849 | 216, 850 | 206, 851 | 196, 852 | 188, 853 | 122, 854 | 150, 855 | 170, 856 | 149, 857 | 159, 858 | 153, 859 | 145, 860 | 162, 861 | 71, 862 | 139, 863 | 245, 864 | 122, 865 | 188, 866 | 139, 867 | 70, 868 | 156, 869 | 44, 870 | 220, 871 | 45, 872 | 197, 873 | 3, 874 | 196, 875 | 65, 876 | 221, 877 | 222, 878 | 141, 879 | 19, 880 | 94, 881 | 52, 882 | 222, 883 | 223, 884 | 50, 885 | 117, 886 | 118, 887 | 246, 888 | 163, 889 | 7, 890 | 143, 891 | 111, 892 | 116, 893 | 126, 894 | 129, 895 | 142, 896 | 191, 897 | 62, 898 | 183, 899 | 235, 900 | 48, 901 | 219, 902 | 151, 903 | 109, 904 | 10, 905 | 69, 906 | 107, 907 | 66, 908 | 234, 909 | 34, 910 | 227, 911 | 117, 912 | 229, 913 | 118, 914 | 66, 915 | 52, 916 | 105, 917 | 110, 918 | 144, 919 | 24, 920 | 225, 921 | 53, 922 | 224, 923 | 38, 924 | 81, 925 | 41, 926 | 199, 927 | 171, 928 | 208, 929 | 61, 930 | 43, 931 | 57, 932 | 51, 933 | 195, 934 | 5, 935 | 81, 936 | 42, 937 | 41, 938 | 219, 939 | 115, 940 | 218, 941 | 172, 942 | 215, 943 | 138, 944 | 136, 945 | 138, 946 | 135, 947 | 96, 948 | 88, 949 | 89, 950 | 196, 951 | 6, 952 | 197, 953 | 179, 954 | 88, 955 | 178, 956 | 22, 957 | 145, 958 | 153, 959 | 121, 960 | 100, 961 | 120, 962 | 9, 963 | 55, 964 | 107, 965 | 86, 966 | 14, 967 | 15, 968 | 49, 969 | 129, 970 | 209, 971 | 85, 972 | 15, 973 | 16, 974 | 227, 975 | 93, 976 | 234, 977 | 104, 978 | 63, 979 | 68, 980 | 120, 981 | 101, 982 | 119, 983 | 180, 984 | 89, 985 | 179, 986 | 107, 987 | 65, 988 | 66, 989 | 90, 990 | 96, 991 | 89, 992 | 187, 993 | 123, 994 | 50, 995 | 168, 996 | 122, 997 | 193, 998 | 69, 999 | 105, 1000 | 104, 1001 | 176, 1002 | 170, 1003 | 140, 1004 | 211, 1005 | 169, 1006 | 210, 1007 | 184, 1008 | 42, 1009 | 183, 1010 | 207, 1011 | 212, 1012 | 214, 1013 | 74, 1014 | 41, 1015 | 42, 1016 | 64, 1017 | 49, 1018 | 48, 1019 | 240, 1020 | 64, 1021 | 235, 1022 | 142, 1023 | 203, 1024 | 36, 1025 | 72, 1026 | 41, 1027 | 73, 1028 | 116, 1029 | 137, 1030 | 227, 1031 | 77, 1032 | 62, 1033 | 96, 1034 | 204, 1035 | 43, 1036 | 106, 1037 | 46, 1038 | 113, 1039 | 124, 1040 | 210, 1041 | 204, 1042 | 211, 1043 | 27, 1044 | 160, 1045 | 29, 1046 | 113, 1047 | 35, 1048 | 124, 1049 | 157, 1050 | 28, 1051 | 56, 1052 | 99, 1053 | 20, 1054 | 242, 1055 | 202, 1056 | 57, 1057 | 43, 1058 | 100, 1059 | 36, 1060 | 101, 1061 | 208, 1062 | 200, 1063 | 199, 1064 | 167, 1065 | 39, 1066 | 165, 1067 | 22, 1068 | 154, 1069 | 26, 1070 | 205, 1071 | 216, 1072 | 207, 1073 | 51, 1074 | 236, 1075 | 3, 1076 | 19, 1077 | 44, 1078 | 1, 1079 | 111, 1080 | 123, 1081 | 116, 1082 | 227, 1083 | 143, 1084 | 116, 1085 | 63, 1086 | 46, 1087 | 70, 1088 | 96, 1089 | 78, 1090 | 95, 1091 | 156, 1092 | 46, 1093 | 124, 1094 | 242, 1095 | 238, 1096 | 241, 1097 | 52, 1098 | 63, 1099 | 105, 1100 | 247, 1101 | 226, 1102 | 113, 1103 | 120, 1104 | 230, 1105 | 231, 1106 | 91, 1107 | 43, 1108 | 146, 1109 | 108, 1110 | 67, 1111 | 109, 1112 | 140, 1113 | 211, 1114 | 32, 1115 | 134, 1116 | 45, 1117 | 220, 1118 | 53, 1119 | 223, 1120 | 224, 1121 | 217, 1122 | 47, 1123 | 114, 1124 | 150, 1125 | 135, 1126 | 169, 1127 | 131, 1128 | 209, 1129 | 198, 1130 | 26, 1131 | 155, 1132 | 112, 1133 | 218, 1134 | 166, 1135 | 219, 1136 | 100, 1137 | 126, 1138 | 142, 1139 | 99, 1140 | 98, 1141 | 240, 1142 | 55, 1143 | 189, 1144 | 221, 1145 | 31, 1146 | 117, 1147 | 111, 1148 | 8, 1149 | 193, 1150 | 55, 1151 | 104, 1152 | 54, 1153 | 103, 1154 | 196, 1155 | 236, 1156 | 174, 1157 | 161, 1158 | 247, 1159 | 30, 1160 | 28, 1161 | 159, 1162 | 27, 1163 | 244, 1164 | 193, 1165 | 245, 1166 | 39, 1167 | 92, 1168 | 165, 1169 | 140, 1170 | 148, 1171 | 176, 1172 | 36, 1173 | 206, 1174 | 205, 1175 | 17, 1176 | 85, 1177 | 16, 1178 | 181, 1179 | 90, 1180 | 180, 1181 | 182, 1182 | 91, 1183 | 181, 1184 | 18, 1185 | 201, 1186 | 83, 1187 | 187, 1188 | 205, 1189 | 207, 1190 | 91, 1191 | 77, 1192 | 90, 1193 | 23, 1194 | 144, 1195 | 145, 1196 | 4, 1197 | 51, 1198 | 5, 1199 | 68, 1200 | 21, 1201 | 54, 1202 | 98, 1203 | 203, 1204 | 129, 1205 | 192, 1206 | 135, 1207 | 138, 1208 | 194, 1209 | 106, 1210 | 182, 1211 | 173, 1212 | 56, 1213 | 190, 1214 | 30, 1215 | 160, 1216 | 161, 1217 | 146, 1218 | 76, 1219 | 77, 1220 | 17, 1221 | 83, 1222 | 84, 1223 | 214, 1224 | 202, 1225 | 210, 1226 | 118, 1227 | 230, 1228 | 119, 1229 | 186, 1230 | 40, 1231 | 185, 1232 | 183, 1233 | 80, 1234 | 191, 1235 | 185, 1236 | 74, 1237 | 184, 1238 | 211, 1239 | 194, 1240 | 32, 1241 | 131, 1242 | 48, 1243 | 49, 1244 | 108, 1245 | 9, 1246 | 107, 1247 | 40, 1248 | 73, 1249 | 74, 1250 | 101, 1251 | 118, 1252 | 119, 1253 | 155, 1254 | 157, 1255 | 173, 1256 | 148, 1257 | 175, 1258 | 152, 1259 | 67, 1260 | 104, 1261 | 103, 1262 | 128, 1263 | 232, 1264 | 233, 1265 | 47, 1266 | 128, 1267 | 114, 1268 | 39, 1269 | 72, 1270 | 73, 1271 | 120, 1272 | 232, 1273 | 121, 1274 | 37, 1275 | 11, 1276 | 72, 1277 | 139, 1278 | 127, 1279 | 162, 1280 | 79, 1281 | 239, 1282 | 238, 1283 | 166, 1284 | 60, 1285 | 75, 1286 | 260, 1287 | 467, 1288 | 466, 1289 | 255, 1290 | 249, 1291 | 263, 1292 | 467, 1293 | 359, 1294 | 263, 1295 | 341, 1296 | 463, 1297 | 362, 1298 | 414, 1299 | 398, 1300 | 362, 1301 | 286, 1302 | 384, 1303 | 398, 1304 | 25, 1305 | 130, 1306 | 33, 1307 | 247, 1308 | 246, 1309 | 33, 1310 | 112, 1311 | 155, 1312 | 133, 1313 | 190, 1314 | 243, 1315 | 133, 1316 | 255, 1317 | 261, 1318 | 448, 1319 | 249, 1320 | 255, 1321 | 339, 1322 | 353, 1323 | 383, 1324 | 372, 1325 | 288, 1326 | 435, 1327 | 401, 1328 | 363, 1329 | 456, 1330 | 420, 1331 | 456, 1332 | 399, 1333 | 437, 1334 | 440, 1335 | 363, 1336 | 360, 1337 | 399, 1338 | 412, 1339 | 343, 1340 | 412, 1341 | 465, 1342 | 357, 1343 | 465, 1344 | 464, 1345 | 453, 1346 | 464, 1347 | 463, 1348 | 341, 1349 | 433, 1350 | 416, 1351 | 411, 1352 | 392, 1353 | 290, 1354 | 250, 1355 | 435, 1356 | 433, 1357 | 376, 1358 | 460, 1359 | 328, 1360 | 290, 1361 | 290, 1362 | 392, 1363 | 289, 1364 | 250, 1365 | 458, 1366 | 459, 1367 | 445, 1368 | 342, 1369 | 467, 1370 | 444, 1371 | 445, 1372 | 260, 1373 | 443, 1374 | 444, 1375 | 259, 1376 | 442, 1377 | 443, 1378 | 257, 1379 | 441, 1380 | 442, 1381 | 258, 1382 | 414, 1383 | 413, 1384 | 441, 1385 | 463, 1386 | 464, 1387 | 413, 1388 | 452, 1389 | 453, 1390 | 341, 1391 | 451, 1392 | 452, 1393 | 256, 1394 | 450, 1395 | 451, 1396 | 252, 1397 | 449, 1398 | 450, 1399 | 253, 1400 | 446, 1401 | 261, 1402 | 255, 1403 | 448, 1404 | 449, 1405 | 254, 1406 | 455, 1407 | 460, 1408 | 305, 1409 | 370, 1410 | 326, 1411 | 2, 1412 | 326, 1413 | 370, 1414 | 462, 1415 | 439, 1416 | 455, 1417 | 289, 1418 | 367, 1419 | 416, 1420 | 433, 1421 | 358, 1422 | 327, 1423 | 294, 1424 | 262, 1425 | 428, 1426 | 421, 1427 | 4, 1428 | 275, 1429 | 274, 1430 | 401, 1431 | 376, 1432 | 352, 1433 | 457, 1434 | 438, 1435 | 309, 1436 | 383, 1437 | 368, 1438 | 264, 1439 | 411, 1440 | 416, 1441 | 434, 1442 | 410, 1443 | 436, 1444 | 432, 1445 | 409, 1446 | 410, 1447 | 287, 1448 | 408, 1449 | 409, 1450 | 291, 1451 | 407, 1452 | 408, 1453 | 306, 1454 | 361, 1455 | 401, 1456 | 366, 1457 | 406, 1458 | 418, 1459 | 421, 1460 | 405, 1461 | 406, 1462 | 313, 1463 | 404, 1464 | 405, 1465 | 314, 1466 | 403, 1467 | 404, 1468 | 315, 1469 | 402, 1470 | 403, 1471 | 316, 1472 | 457, 1473 | 461, 1474 | 354, 1475 | 262, 1476 | 369, 1477 | 396, 1478 | 362, 1479 | 463, 1480 | 414, 1481 | 394, 1482 | 430, 1483 | 434, 1484 | 461, 1485 | 457, 1486 | 459, 1487 | 362, 1488 | 382, 1489 | 341, 1490 | 438, 1491 | 457, 1492 | 440, 1493 | 326, 1494 | 327, 1495 | 391, 1496 | 437, 1497 | 355, 1498 | 429, 1499 | 391, 1500 | 423, 1501 | 426, 1502 | 425, 1503 | 266, 1504 | 330, 1505 | 381, 1506 | 384, 1507 | 385, 1508 | 340, 1509 | 261, 1510 | 446, 1511 | 301, 1512 | 300, 1513 | 293, 1514 | 13, 1515 | 12, 1516 | 268, 1517 | 387, 1518 | 373, 1519 | 374, 1520 | 12, 1521 | 11, 1522 | 302, 1523 | 267, 1524 | 0, 1525 | 164, 1526 | 461, 1527 | 462, 1528 | 370, 1529 | 393, 1530 | 164, 1531 | 2, 1532 | 388, 1533 | 390, 1534 | 373, 1535 | 322, 1536 | 426, 1537 | 436, 1538 | 419, 1539 | 351, 1540 | 412, 1541 | 379, 1542 | 378, 1543 | 395, 1544 | 386, 1545 | 374, 1546 | 380, 1547 | 389, 1548 | 368, 1549 | 301, 1550 | 466, 1551 | 467, 1552 | 263, 1553 | 465, 1554 | 412, 1555 | 351, 1556 | 368, 1557 | 383, 1558 | 300, 1559 | 274, 1560 | 275, 1561 | 440, 1562 | 359, 1563 | 255, 1564 | 263, 1565 | 197, 1566 | 419, 1567 | 248, 1568 | 295, 1569 | 442, 1570 | 441, 1571 | 370, 1572 | 94, 1573 | 19, 1574 | 282, 1575 | 443, 1576 | 442, 1577 | 280, 1578 | 347, 1579 | 346, 1580 | 466, 1581 | 249, 1582 | 390, 1583 | 372, 1584 | 345, 1585 | 340, 1586 | 355, 1587 | 371, 1588 | 358, 1589 | 415, 1590 | 407, 1591 | 292, 1592 | 455, 1593 | 439, 1594 | 278, 1595 | 151, 1596 | 10, 1597 | 338, 1598 | 299, 1599 | 296, 1600 | 336, 1601 | 454, 1602 | 447, 1603 | 264, 1604 | 346, 1605 | 347, 1606 | 449, 1607 | 296, 1608 | 334, 1609 | 282, 1610 | 249, 1611 | 466, 1612 | 263, 1613 | 339, 1614 | 254, 1615 | 373, 1616 | 445, 1617 | 444, 1618 | 283, 1619 | 268, 1620 | 271, 1621 | 311, 1622 | 199, 1623 | 428, 1624 | 396, 1625 | 291, 1626 | 287, 1627 | 273, 1628 | 281, 1629 | 5, 1630 | 195, 1631 | 311, 1632 | 271, 1633 | 272, 1634 | 439, 1635 | 438, 1636 | 344, 1637 | 397, 1638 | 367, 1639 | 435, 1640 | 365, 1641 | 364, 1642 | 367, 1643 | 325, 1644 | 319, 1645 | 318, 1646 | 419, 1647 | 197, 1648 | 6, 1649 | 403, 1650 | 402, 1651 | 318, 1652 | 252, 1653 | 380, 1654 | 374, 1655 | 350, 1656 | 349, 1657 | 329, 1658 | 9, 1659 | 336, 1660 | 285, 1661 | 316, 1662 | 15, 1663 | 14, 1664 | 279, 1665 | 429, 1666 | 358, 1667 | 315, 1668 | 16, 1669 | 15, 1670 | 447, 1671 | 454, 1672 | 323, 1673 | 333, 1674 | 298, 1675 | 293, 1676 | 349, 1677 | 348, 1678 | 330, 1679 | 404, 1680 | 403, 1681 | 319, 1682 | 336, 1683 | 296, 1684 | 295, 1685 | 320, 1686 | 319, 1687 | 325, 1688 | 411, 1689 | 280, 1690 | 352, 1691 | 168, 1692 | 417, 1693 | 351, 1694 | 299, 1695 | 333, 1696 | 334, 1697 | 400, 1698 | 369, 1699 | 395, 1700 | 431, 1701 | 430, 1702 | 394, 1703 | 408, 1704 | 407, 1705 | 272, 1706 | 427, 1707 | 434, 1708 | 432, 1709 | 304, 1710 | 272, 1711 | 271, 1712 | 294, 1713 | 278, 1714 | 279, 1715 | 460, 1716 | 455, 1717 | 294, 1718 | 371, 1719 | 266, 1720 | 423, 1721 | 302, 1722 | 303, 1723 | 271, 1724 | 345, 1725 | 447, 1726 | 366, 1727 | 307, 1728 | 325, 1729 | 292, 1730 | 424, 1731 | 335, 1732 | 273, 1733 | 276, 1734 | 353, 1735 | 342, 1736 | 430, 1737 | 431, 1738 | 424, 1739 | 257, 1740 | 259, 1741 | 387, 1742 | 342, 1743 | 353, 1744 | 265, 1745 | 384, 1746 | 286, 1747 | 258, 1748 | 328, 1749 | 462, 1750 | 250, 1751 | 422, 1752 | 273, 1753 | 287, 1754 | 329, 1755 | 330, 1756 | 266, 1757 | 428, 1758 | 199, 1759 | 200, 1760 | 393, 1761 | 391, 1762 | 269, 1763 | 252, 1764 | 256, 1765 | 381, 1766 | 425, 1767 | 427, 1768 | 436, 1769 | 281, 1770 | 248, 1771 | 456, 1772 | 19, 1773 | 1, 1774 | 274, 1775 | 340, 1776 | 345, 1777 | 352, 1778 | 362, 1779 | 398, 1780 | 382, 1781 | 447, 1782 | 345, 1783 | 372, 1784 | 293, 1785 | 300, 1786 | 276, 1787 | 325, 1788 | 324, 1789 | 308, 1790 | 383, 1791 | 353, 1792 | 276, 1793 | 462, 1794 | 461, 1795 | 458, 1796 | 282, 1797 | 334, 1798 | 293, 1799 | 467, 1800 | 342, 1801 | 446, 1802 | 349, 1803 | 451, 1804 | 450, 1805 | 321, 1806 | 375, 1807 | 273, 1808 | 337, 1809 | 338, 1810 | 297, 1811 | 369, 1812 | 262, 1813 | 431, 1814 | 363, 1815 | 440, 1816 | 275, 1817 | 283, 1818 | 444, 1819 | 443, 1820 | 437, 1821 | 343, 1822 | 277, 1823 | 379, 1824 | 394, 1825 | 364, 1826 | 360, 1827 | 420, 1828 | 429, 1829 | 256, 1830 | 341, 1831 | 382, 1832 | 438, 1833 | 439, 1834 | 392, 1835 | 329, 1836 | 371, 1837 | 355, 1838 | 328, 1839 | 460, 1840 | 327, 1841 | 285, 1842 | 441, 1843 | 413, 1844 | 261, 1845 | 340, 1846 | 346, 1847 | 8, 1848 | 285, 1849 | 417, 1850 | 333, 1851 | 332, 1852 | 284, 1853 | 419, 1854 | 399, 1855 | 456, 1856 | 388, 1857 | 260, 1858 | 466, 1859 | 258, 1860 | 257, 1861 | 386, 1862 | 464, 1863 | 465, 1864 | 417, 1865 | 269, 1866 | 391, 1867 | 322, 1868 | 369, 1869 | 400, 1870 | 377, 1871 | 266, 1872 | 425, 1873 | 426, 1874 | 17, 1875 | 16, 1876 | 315, 1877 | 405, 1878 | 404, 1879 | 320, 1880 | 406, 1881 | 405, 1882 | 321, 1883 | 18, 1884 | 313, 1885 | 421, 1886 | 411, 1887 | 427, 1888 | 425, 1889 | 321, 1890 | 320, 1891 | 307, 1892 | 253, 1893 | 374, 1894 | 373, 1895 | 4, 1896 | 5, 1897 | 281, 1898 | 298, 1899 | 284, 1900 | 251, 1901 | 327, 1902 | 358, 1903 | 423, 1904 | 416, 1905 | 367, 1906 | 364, 1907 | 418, 1908 | 406, 1909 | 335, 1910 | 398, 1911 | 414, 1912 | 286, 1913 | 260, 1914 | 388, 1915 | 387, 1916 | 375, 1917 | 307, 1918 | 306, 1919 | 17, 1920 | 314, 1921 | 313, 1922 | 434, 1923 | 430, 1924 | 422, 1925 | 347, 1926 | 348, 1927 | 450, 1928 | 410, 1929 | 409, 1930 | 270, 1931 | 407, 1932 | 415, 1933 | 310, 1934 | 409, 1935 | 408, 1936 | 304, 1937 | 431, 1938 | 262, 1939 | 418, 1940 | 360, 1941 | 279, 1942 | 278, 1943 | 337, 1944 | 336, 1945 | 9, 1946 | 270, 1947 | 304, 1948 | 303, 1949 | 330, 1950 | 348, 1951 | 347, 1952 | 382, 1953 | 398, 1954 | 384, 1955 | 377, 1956 | 152, 1957 | 175, 1958 | 297, 1959 | 332, 1960 | 333, 1961 | 357, 1962 | 453, 1963 | 452, 1964 | 277, 1965 | 343, 1966 | 357, 1967 | 269, 1968 | 303, 1969 | 302, 1970 | 349, 1971 | 350, 1972 | 452, 1973 | 267, 1974 | 302, 1975 | 11, 1976 | 368, 1977 | 389, 1978 | 356, 1979 | 25, 1980 | 110, 1981 | 228, 1982 | 7, 1983 | 163, 1984 | 110, 1985 | 124, 1986 | 35, 1987 | 143, 1988 | 58, 1989 | 132, 1990 | 177, 1991 | 134, 1992 | 131, 1993 | 198, 1994 | 236, 1995 | 198, 1996 | 217, 1997 | 220, 1998 | 115, 1999 | 131, 2000 | 174, 2001 | 217, 2002 | 114, 2003 | 188, 2004 | 114, 2005 | 128, 2006 | 245, 2007 | 128, 2008 | 233, 2009 | 244, 2010 | 233, 2011 | 112, 2012 | 213, 2013 | 147, 2014 | 187, 2015 | 166, 2016 | 79, 2017 | 20, 2018 | 215, 2019 | 177, 2020 | 147, 2021 | 240, 2022 | 75, 2023 | 60, 2024 | 75, 2025 | 59, 2026 | 166, 2027 | 20, 2028 | 79, 2029 | 238, 2030 | 225, 2031 | 30, 2032 | 247, 2033 | 224, 2034 | 29, 2035 | 30, 2036 | 223, 2037 | 27, 2038 | 29, 2039 | 222, 2040 | 28, 2041 | 27, 2042 | 221, 2043 | 56, 2044 | 28, 2045 | 190, 2046 | 56, 2047 | 221, 2048 | 243, 2049 | 190, 2050 | 189, 2051 | 232, 2052 | 26, 2053 | 112, 2054 | 231, 2055 | 22, 2056 | 26, 2057 | 230, 2058 | 23, 2059 | 22, 2060 | 229, 2061 | 24, 2062 | 23, 2063 | 226, 2064 | 130, 2065 | 25, 2066 | 228, 2067 | 110, 2068 | 24, 2069 | 235, 2070 | 59, 2071 | 75, 2072 | 141, 2073 | 94, 2074 | 2, 2075 | 97, 2076 | 99, 2077 | 242, 2078 | 219, 2079 | 166, 2080 | 59, 2081 | 138, 2082 | 215, 2083 | 213, 2084 | 129, 2085 | 102, 2086 | 64, 2087 | 32, 2088 | 194, 2089 | 201, 2090 | 4, 2091 | 1, 2092 | 44, 2093 | 177, 2094 | 137, 2095 | 123, 2096 | 237, 2097 | 239, 2098 | 79, 2099 | 156, 2100 | 143, 2101 | 34, 2102 | 187, 2103 | 207, 2104 | 214, 2105 | 186, 2106 | 57, 2107 | 212, 2108 | 185, 2109 | 61, 2110 | 57, 2111 | 184, 2112 | 76, 2113 | 61, 2114 | 183, 2115 | 62, 2116 | 76, 2117 | 132, 2118 | 93, 2119 | 137, 2120 | 182, 2121 | 83, 2122 | 201, 2123 | 181, 2124 | 84, 2125 | 83, 2126 | 180, 2127 | 85, 2128 | 84, 2129 | 179, 2130 | 86, 2131 | 85, 2132 | 178, 2133 | 87, 2134 | 86, 2135 | 237, 2136 | 44, 2137 | 125, 2138 | 32, 2139 | 208, 2140 | 171, 2141 | 133, 2142 | 173, 2143 | 190, 2144 | 169, 2145 | 135, 2146 | 214, 2147 | 241, 2148 | 238, 2149 | 239, 2150 | 243, 2151 | 112, 2152 | 133, 2153 | 218, 2154 | 115, 2155 | 220, 2156 | 97, 2157 | 167, 2158 | 165, 2159 | 217, 2160 | 198, 2161 | 209, 2162 | 165, 2163 | 92, 2164 | 206, 2165 | 205, 2166 | 50, 2167 | 101, 2168 | 154, 2169 | 153, 2170 | 158, 2171 | 111, 2172 | 35, 2173 | 226, 2174 | 71, 2175 | 68, 2176 | 63, 2177 | 13, 2178 | 82, 2179 | 38, 2180 | 160, 2181 | 159, 2182 | 145, 2183 | 12, 2184 | 38, 2185 | 72, 2186 | 37, 2187 | 167, 2188 | 164, 2189 | 241, 2190 | 125, 2191 | 141, 2192 | 167, 2193 | 97, 2194 | 2, 2195 | 161, 2196 | 160, 2197 | 144, 2198 | 92, 2199 | 186, 2200 | 216, 2201 | 196, 2202 | 174, 2203 | 188, 2204 | 150, 2205 | 169, 2206 | 170, 2207 | 159, 2208 | 158, 2209 | 153, 2210 | 162, 2211 | 21, 2212 | 71, 2213 | 33, 2214 | 130, 2215 | 247, 2216 | 245, 2217 | 193, 2218 | 122, 2219 | 139, 2220 | 71, 2221 | 70, 2222 | 44, 2223 | 237, 2224 | 220, 2225 | 33, 2226 | 7, 2227 | 25, 2228 | 197, 2229 | 195, 2230 | 3, 2231 | 65, 2232 | 55, 2233 | 221, 2234 | 141, 2235 | 125, 2236 | 19, 2237 | 52, 2238 | 65, 2239 | 222, 2240 | 50, 2241 | 123, 2242 | 117, 2243 | 246, 2244 | 161, 2245 | 163, 2246 | 143, 2247 | 35, 2248 | 111, 2249 | 126, 2250 | 209, 2251 | 129, 2252 | 191, 2253 | 78, 2254 | 62, 2255 | 235, 2256 | 64, 2257 | 48, 2258 | 151, 2259 | 108, 2260 | 109, 2261 | 69, 2262 | 108, 2263 | 107, 2264 | 234, 2265 | 127, 2266 | 34, 2267 | 117, 2268 | 228, 2269 | 229, 2270 | 66, 2271 | 65, 2272 | 52, 2273 | 7, 2274 | 33, 2275 | 246, 2276 | 110, 2277 | 163, 2278 | 144, 2279 | 225, 2280 | 46, 2281 | 53, 2282 | 38, 2283 | 82, 2284 | 81, 2285 | 199, 2286 | 175, 2287 | 171, 2288 | 61, 2289 | 146, 2290 | 43, 2291 | 51, 2292 | 3, 2293 | 195, 2294 | 81, 2295 | 80, 2296 | 42, 2297 | 219, 2298 | 48, 2299 | 115, 2300 | 172, 2301 | 58, 2302 | 215, 2303 | 136, 2304 | 172, 2305 | 138, 2306 | 96, 2307 | 95, 2308 | 88, 2309 | 196, 2310 | 122, 2311 | 6, 2312 | 179, 2313 | 89, 2314 | 88, 2315 | 22, 2316 | 23, 2317 | 145, 2318 | 121, 2319 | 47, 2320 | 100, 2321 | 9, 2322 | 8, 2323 | 55, 2324 | 86, 2325 | 87, 2326 | 14, 2327 | 49, 2328 | 102, 2329 | 129, 2330 | 85, 2331 | 86, 2332 | 15, 2333 | 227, 2334 | 137, 2335 | 93, 2336 | 104, 2337 | 105, 2338 | 63, 2339 | 120, 2340 | 100, 2341 | 101, 2342 | 180, 2343 | 90, 2344 | 89, 2345 | 107, 2346 | 55, 2347 | 65, 2348 | 90, 2349 | 77, 2350 | 96, 2351 | 187, 2352 | 147, 2353 | 123, 2354 | 168, 2355 | 6, 2356 | 122, 2357 | 69, 2358 | 66, 2359 | 105, 2360 | 176, 2361 | 149, 2362 | 170, 2363 | 211, 2364 | 170, 2365 | 169, 2366 | 184, 2367 | 74, 2368 | 42, 2369 | 207, 2370 | 216, 2371 | 212, 2372 | 74, 2373 | 73, 2374 | 41, 2375 | 64, 2376 | 102, 2377 | 49, 2378 | 240, 2379 | 98, 2380 | 64, 2381 | 142, 2382 | 129, 2383 | 203, 2384 | 72, 2385 | 38, 2386 | 41, 2387 | 116, 2388 | 123, 2389 | 137, 2390 | 77, 2391 | 76, 2392 | 62, 2393 | 204, 2394 | 202, 2395 | 43, 2396 | 46, 2397 | 225, 2398 | 113, 2399 | 210, 2400 | 202, 2401 | 204, 2402 | 27, 2403 | 159, 2404 | 160, 2405 | 113, 2406 | 226, 2407 | 35, 2408 | 157, 2409 | 158, 2410 | 28, 2411 | 99, 2412 | 60, 2413 | 20, 2414 | 202, 2415 | 212, 2416 | 57, 2417 | 100, 2418 | 142, 2419 | 36, 2420 | 208, 2421 | 201, 2422 | 200, 2423 | 167, 2424 | 37, 2425 | 39, 2426 | 22, 2427 | 153, 2428 | 154, 2429 | 205, 2430 | 206, 2431 | 216, 2432 | 51, 2433 | 134, 2434 | 236, 2435 | 19, 2436 | 125, 2437 | 44, 2438 | 111, 2439 | 117, 2440 | 123, 2441 | 133, 2442 | 155, 2443 | 173, 2444 | 227, 2445 | 34, 2446 | 143, 2447 | 63, 2448 | 53, 2449 | 46, 2450 | 96, 2451 | 62, 2452 | 78, 2453 | 156, 2454 | 70, 2455 | 46, 2456 | 242, 2457 | 20, 2458 | 238, 2459 | 52, 2460 | 53, 2461 | 63, 2462 | 247, 2463 | 130, 2464 | 226, 2465 | 120, 2466 | 119, 2467 | 230, 2468 | 91, 2469 | 106, 2470 | 43, 2471 | 108, 2472 | 69, 2473 | 67, 2474 | 140, 2475 | 170, 2476 | 211, 2477 | 134, 2478 | 51, 2479 | 45, 2480 | 53, 2481 | 52, 2482 | 223, 2483 | 217, 2484 | 126, 2485 | 47, 2486 | 150, 2487 | 136, 2488 | 135, 2489 | 131, 2490 | 49, 2491 | 209, 2492 | 26, 2493 | 154, 2494 | 155, 2495 | 218, 2496 | 79, 2497 | 166, 2498 | 100, 2499 | 47, 2500 | 126, 2501 | 99, 2502 | 97, 2503 | 98, 2504 | 55, 2505 | 193, 2506 | 189, 2507 | 31, 2508 | 228, 2509 | 117, 2510 | 8, 2511 | 168, 2512 | 193, 2513 | 104, 2514 | 68, 2515 | 54, 2516 | 196, 2517 | 3, 2518 | 236, 2519 | 161, 2520 | 246, 2521 | 247, 2522 | 28, 2523 | 158, 2524 | 159, 2525 | 244, 2526 | 189, 2527 | 193, 2528 | 39, 2529 | 40, 2530 | 92, 2531 | 140, 2532 | 171, 2533 | 148, 2534 | 36, 2535 | 203, 2536 | 206, 2537 | 17, 2538 | 84, 2539 | 85, 2540 | 181, 2541 | 91, 2542 | 90, 2543 | 182, 2544 | 106, 2545 | 91, 2546 | 18, 2547 | 200, 2548 | 201, 2549 | 187, 2550 | 50, 2551 | 205, 2552 | 91, 2553 | 146, 2554 | 77, 2555 | 23, 2556 | 24, 2557 | 144, 2558 | 4, 2559 | 45, 2560 | 51, 2561 | 68, 2562 | 71, 2563 | 21, 2564 | 98, 2565 | 165, 2566 | 203, 2567 | 192, 2568 | 214, 2569 | 135, 2570 | 194, 2571 | 204, 2572 | 106, 2573 | 173, 2574 | 157, 2575 | 56, 2576 | 30, 2577 | 29, 2578 | 160, 2579 | 146, 2580 | 61, 2581 | 76, 2582 | 17, 2583 | 18, 2584 | 83, 2585 | 214, 2586 | 212, 2587 | 202, 2588 | 118, 2589 | 229, 2590 | 230, 2591 | 186, 2592 | 92, 2593 | 40, 2594 | 183, 2595 | 42, 2596 | 80, 2597 | 185, 2598 | 40, 2599 | 74, 2600 | 211, 2601 | 204, 2602 | 194, 2603 | 131, 2604 | 115, 2605 | 48, 2606 | 108, 2607 | 151, 2608 | 9, 2609 | 40, 2610 | 39, 2611 | 73, 2612 | 101, 2613 | 50, 2614 | 118, 2615 | 155, 2616 | 154, 2617 | 157, 2618 | 148, 2619 | 171, 2620 | 175, 2621 | 67, 2622 | 69, 2623 | 104, 2624 | 128, 2625 | 121, 2626 | 232, 2627 | 47, 2628 | 121, 2629 | 128, 2630 | 39, 2631 | 37, 2632 | 72, 2633 | 120, 2634 | 231, 2635 | 232, 2636 | 37, 2637 | 0, 2638 | 11, 2639 | 139, 2640 | 34, 2641 | 127, 2642 | ]; 2643 | 2644 | export const MOUTH_FACES = [ 2645 | 415, 2646 | 308, 2647 | 324, 2648 | 310, 2649 | 415, 2650 | 324, 2651 | 310, 2652 | 324, 2653 | 318, 2654 | 311, 2655 | 310, 2656 | 318, 2657 | 311, 2658 | 318, 2659 | 402, 2660 | 312, 2661 | 311, 2662 | 402, 2663 | 312, 2664 | 402, 2665 | 317, 2666 | 13, 2667 | 312, 2668 | 317, 2669 | 13, 2670 | 317, 2671 | 14, 2672 | 13, 2673 | 14, 2674 | 87, 2675 | 82, 2676 | 13, 2677 | 87, 2678 | 82, 2679 | 87, 2680 | 178, 2681 | 81, 2682 | 82, 2683 | 178, 2684 | 81, 2685 | 178, 2686 | 88, 2687 | 80, 2688 | 81, 2689 | 88, 2690 | 80, 2691 | 88, 2692 | 95, 2693 | 191, 2694 | 80, 2695 | 95, 2696 | 191, 2697 | 95, 2698 | 78, 2699 | ]; 2700 | 2701 | export const UVS = [ 2702 | [0.499976992607117, 0.652534008026123], 2703 | [0.500025987625122, 0.547487020492554], 2704 | [0.499974012374878, 0.602371990680695], 2705 | [0.482113003730774, 0.471979022026062], 2706 | [0.500150978565216, 0.527155995368958], 2707 | [0.499909996986389, 0.498252987861633], 2708 | [0.499523013830185, 0.40106201171875], 2709 | [0.289712011814117, 0.380764007568359], 2710 | [0.499954998493195, 0.312398016452789], 2711 | [0.499987006187439, 0.269918978214264], 2712 | [0.500023007392883, 0.107050001621246], 2713 | [0.500023007392883, 0.666234016418457], 2714 | [0.5000159740448, 0.679224014282227], 2715 | [0.500023007392883, 0.692348003387451], 2716 | [0.499976992607117, 0.695277988910675], 2717 | [0.499976992607117, 0.70593398809433], 2718 | [0.499976992607117, 0.719385027885437], 2719 | [0.499976992607117, 0.737019002437592], 2720 | [0.499967992305756, 0.781370997428894], 2721 | [0.499816000461578, 0.562981009483337], 2722 | [0.473773002624512, 0.573909997940063], 2723 | [0.104906998574734, 0.254140973091125], 2724 | [0.365929991006851, 0.409575998783112], 2725 | [0.338757991790771, 0.41302502155304], 2726 | [0.311120003461838, 0.409460008144379], 2727 | [0.274657994508743, 0.389131009578705], 2728 | [0.393361985683441, 0.403706014156342], 2729 | [0.345234006643295, 0.344011008739471], 2730 | [0.370094001293182, 0.346076011657715], 2731 | [0.319321990013123, 0.347265005111694], 2732 | [0.297903001308441, 0.353591024875641], 2733 | [0.24779200553894, 0.410809993743896], 2734 | [0.396889001131058, 0.842755019664764], 2735 | [0.280097991228104, 0.375599980354309], 2736 | [0.106310002505779, 0.399955987930298], 2737 | [0.2099249958992, 0.391353011131287], 2738 | [0.355807989835739, 0.534406006336212], 2739 | [0.471751004457474, 0.65040397644043], 2740 | [0.474155008792877, 0.680191993713379], 2741 | [0.439785003662109, 0.657229006290436], 2742 | [0.414617002010345, 0.66654098033905], 2743 | [0.450374007225037, 0.680860996246338], 2744 | [0.428770989179611, 0.682690978050232], 2745 | [0.374971002340317, 0.727805018424988], 2746 | [0.486716985702515, 0.547628998756409], 2747 | [0.485300987958908, 0.527395009994507], 2748 | [0.257764995098114, 0.314490020275116], 2749 | [0.401223003864288, 0.455172002315521], 2750 | [0.429818987846375, 0.548614978790283], 2751 | [0.421351999044418, 0.533740997314453], 2752 | [0.276895999908447, 0.532056987285614], 2753 | [0.483370006084442, 0.499586999416351], 2754 | [0.33721199631691, 0.282882988452911], 2755 | [0.296391993761063, 0.293242990970612], 2756 | [0.169294998049736, 0.193813979625702], 2757 | [0.447580009698868, 0.302609980106354], 2758 | [0.392390012741089, 0.353887975215912], 2759 | [0.354490011930466, 0.696784019470215], 2760 | [0.067304998636246, 0.730105042457581], 2761 | [0.442739009857178, 0.572826027870178], 2762 | [0.457098007202148, 0.584792017936707], 2763 | [0.381974011659622, 0.694710969924927], 2764 | [0.392388999462128, 0.694203019142151], 2765 | [0.277076005935669, 0.271932005882263], 2766 | [0.422551989555359, 0.563233017921448], 2767 | [0.385919004678726, 0.281364023685455], 2768 | [0.383103013038635, 0.255840003490448], 2769 | [0.331431001424789, 0.119714021682739], 2770 | [0.229923993349075, 0.232002973556519], 2771 | [0.364500999450684, 0.189113974571228], 2772 | [0.229622006416321, 0.299540996551514], 2773 | [0.173287004232407, 0.278747975826263], 2774 | [0.472878992557526, 0.666198015213013], 2775 | [0.446828007698059, 0.668527007102966], 2776 | [0.422762006521225, 0.673889994621277], 2777 | [0.445307999849319, 0.580065965652466], 2778 | [0.388103008270264, 0.693961024284363], 2779 | [0.403039008378983, 0.706539988517761], 2780 | [0.403629004955292, 0.693953037261963], 2781 | [0.460041999816895, 0.557139039039612], 2782 | [0.431158006191254, 0.692366003990173], 2783 | [0.452181994915009, 0.692366003990173], 2784 | [0.475387006998062, 0.692366003990173], 2785 | [0.465828001499176, 0.779190003871918], 2786 | [0.472328990697861, 0.736225962638855], 2787 | [0.473087012767792, 0.717857003211975], 2788 | [0.473122000694275, 0.704625964164734], 2789 | [0.473033010959625, 0.695277988910675], 2790 | [0.427942007780075, 0.695277988910675], 2791 | [0.426479011774063, 0.703539967536926], 2792 | [0.423162013292313, 0.711845993995667], 2793 | [0.4183090031147, 0.720062971115112], 2794 | [0.390094995498657, 0.639572978019714], 2795 | [0.013953999616206, 0.560034036636353], 2796 | [0.499913990497589, 0.58014702796936], 2797 | [0.413199990987778, 0.69539999961853], 2798 | [0.409626007080078, 0.701822996139526], 2799 | [0.468080013990402, 0.601534962654114], 2800 | [0.422728985548019, 0.585985004901886], 2801 | [0.463079988956451, 0.593783974647522], 2802 | [0.37211999297142, 0.47341400384903], 2803 | [0.334562003612518, 0.496073007583618], 2804 | [0.411671012639999, 0.546965003013611], 2805 | [0.242175996303558, 0.14767599105835], 2806 | [0.290776997804642, 0.201445996761322], 2807 | [0.327338010072708, 0.256527006626129], 2808 | [0.399509996175766, 0.748921036720276], 2809 | [0.441727995872498, 0.261676013469696], 2810 | [0.429764986038208, 0.187834024429321], 2811 | [0.412198007106781, 0.108901023864746], 2812 | [0.288955003023148, 0.398952007293701], 2813 | [0.218936994671822, 0.435410976409912], 2814 | [0.41278201341629, 0.398970007896423], 2815 | [0.257135003805161, 0.355440020561218], 2816 | [0.427684992551804, 0.437960982322693], 2817 | [0.448339998722076, 0.536936044692993], 2818 | [0.178560003638268, 0.45755398273468], 2819 | [0.247308000922203, 0.457193970680237], 2820 | [0.286267012357712, 0.467674970626831], 2821 | [0.332827985286713, 0.460712015628815], 2822 | [0.368755996227264, 0.447206974029541], 2823 | [0.398963987827301, 0.432654976844788], 2824 | [0.476410001516342, 0.405806005001068], 2825 | [0.189241006970406, 0.523923993110657], 2826 | [0.228962004184723, 0.348950982093811], 2827 | [0.490725994110107, 0.562400996685028], 2828 | [0.404670000076294, 0.485132992267609], 2829 | [0.019469000399113, 0.401564002037048], 2830 | [0.426243007183075, 0.420431017875671], 2831 | [0.396993011236191, 0.548797011375427], 2832 | [0.266469985246658, 0.376977026462555], 2833 | [0.439121007919312, 0.51895797252655], 2834 | [0.032313998788595, 0.644356966018677], 2835 | [0.419054001569748, 0.387154996395111], 2836 | [0.462783008813858, 0.505746960639954], 2837 | [0.238978996872902, 0.779744982719421], 2838 | [0.198220998048782, 0.831938028335571], 2839 | [0.107550002634525, 0.540755033493042], 2840 | [0.183610007166862, 0.740257024765015], 2841 | [0.134409993886948, 0.333683013916016], 2842 | [0.385764002799988, 0.883153975009918], 2843 | [0.490967005491257, 0.579378008842468], 2844 | [0.382384985685349, 0.508572995662689], 2845 | [0.174399003386497, 0.397670984268188], 2846 | [0.318785011768341, 0.39623498916626], 2847 | [0.343364000320435, 0.400596976280212], 2848 | [0.396100014448166, 0.710216999053955], 2849 | [0.187885001301765, 0.588537991046906], 2850 | [0.430987000465393, 0.944064974784851], 2851 | [0.318993002176285, 0.898285031318665], 2852 | [0.266247987747192, 0.869701027870178], 2853 | [0.500023007392883, 0.190576016902924], 2854 | [0.499976992607117, 0.954452991485596], 2855 | [0.366169989109039, 0.398822009563446], 2856 | [0.393207013607025, 0.39553701877594], 2857 | [0.410373002290726, 0.391080021858215], 2858 | [0.194993004202843, 0.342101991176605], 2859 | [0.388664990663528, 0.362284004688263], 2860 | [0.365961998701096, 0.355970978736877], 2861 | [0.343364000320435, 0.355356991291046], 2862 | [0.318785011768341, 0.35834002494812], 2863 | [0.301414996385574, 0.363156020641327], 2864 | [0.058132998645306, 0.319076001644135], 2865 | [0.301414996385574, 0.387449026107788], 2866 | [0.499987989664078, 0.618434011936188], 2867 | [0.415838003158569, 0.624195992946625], 2868 | [0.445681989192963, 0.566076993942261], 2869 | [0.465844005346298, 0.620640993118286], 2870 | [0.49992299079895, 0.351523995399475], 2871 | [0.288718998432159, 0.819945991039276], 2872 | [0.335278987884521, 0.852819979190826], 2873 | [0.440512001514435, 0.902418971061707], 2874 | [0.128294005990028, 0.791940987110138], 2875 | [0.408771991729736, 0.373893976211548], 2876 | [0.455606997013092, 0.451801002025604], 2877 | [0.499877005815506, 0.908990025520325], 2878 | [0.375436991453171, 0.924192011356354], 2879 | [0.11421000212431, 0.615022003650665], 2880 | [0.448662012815475, 0.695277988910675], 2881 | [0.4480200111866, 0.704632043838501], 2882 | [0.447111994028091, 0.715808033943176], 2883 | [0.444831997156143, 0.730794012546539], 2884 | [0.430011987686157, 0.766808986663818], 2885 | [0.406787008047104, 0.685672998428345], 2886 | [0.400738000869751, 0.681069016456604], 2887 | [0.392399996519089, 0.677703022956848], 2888 | [0.367855995893478, 0.663918972015381], 2889 | [0.247923001646996, 0.601333022117615], 2890 | [0.452769994735718, 0.420849978923798], 2891 | [0.43639200925827, 0.359887003898621], 2892 | [0.416164010763168, 0.368713974952698], 2893 | [0.413385987281799, 0.692366003990173], 2894 | [0.228018000721931, 0.683571994304657], 2895 | [0.468268007040024, 0.352671027183533], 2896 | [0.411361992359161, 0.804327011108398], 2897 | [0.499989002943039, 0.469825029373169], 2898 | [0.479153990745544, 0.442654013633728], 2899 | [0.499974012374878, 0.439637005329132], 2900 | [0.432112008333206, 0.493588984012604], 2901 | [0.499886006116867, 0.866917014122009], 2902 | [0.49991300702095, 0.821729004383087], 2903 | [0.456548988819122, 0.819200992584229], 2904 | [0.344549000263214, 0.745438992977142], 2905 | [0.37890899181366, 0.574010014533997], 2906 | [0.374292999505997, 0.780184984207153], 2907 | [0.319687992334366, 0.570737957954407], 2908 | [0.357154995203018, 0.604269981384277], 2909 | [0.295284003019333, 0.621580958366394], 2910 | [0.447750002145767, 0.862477004528046], 2911 | [0.410986006259918, 0.508723020553589], 2912 | [0.31395098567009, 0.775308012962341], 2913 | [0.354128003120422, 0.812552988529205], 2914 | [0.324548006057739, 0.703992962837219], 2915 | [0.189096003770828, 0.646299958229065], 2916 | [0.279776990413666, 0.71465802192688], 2917 | [0.1338230073452, 0.682700991630554], 2918 | [0.336768001317978, 0.644733011722565], 2919 | [0.429883986711502, 0.466521978378296], 2920 | [0.455527991056442, 0.548622965812683], 2921 | [0.437114000320435, 0.558896005153656], 2922 | [0.467287987470627, 0.529924988746643], 2923 | [0.414712011814117, 0.335219979286194], 2924 | [0.37704598903656, 0.322777986526489], 2925 | [0.344107985496521, 0.320150971412659], 2926 | [0.312875986099243, 0.32233202457428], 2927 | [0.283526003360748, 0.333190023899078], 2928 | [0.241245999932289, 0.382785975933075], 2929 | [0.102986000478268, 0.468762993812561], 2930 | [0.267612010240555, 0.424560010433197], 2931 | [0.297879010438919, 0.433175981044769], 2932 | [0.333433985710144, 0.433878004550934], 2933 | [0.366427004337311, 0.426115989685059], 2934 | [0.396012008190155, 0.416696012020111], 2935 | [0.420121014118195, 0.41022801399231], 2936 | [0.007561000064015, 0.480777025222778], 2937 | [0.432949006557465, 0.569517970085144], 2938 | [0.458638995885849, 0.479089021682739], 2939 | [0.473466008901596, 0.545744001865387], 2940 | [0.476087987422943, 0.563830018043518], 2941 | [0.468472003936768, 0.555056989192963], 2942 | [0.433990985155106, 0.582361996173859], 2943 | [0.483518004417419, 0.562983989715576], 2944 | [0.482482999563217, 0.57784903049469], 2945 | [0.42645001411438, 0.389798998832703], 2946 | [0.438998997211456, 0.39649498462677], 2947 | [0.450067013502121, 0.400434017181396], 2948 | [0.289712011814117, 0.368252992630005], 2949 | [0.276670008897781, 0.363372981548309], 2950 | [0.517862021923065, 0.471948027610779], 2951 | [0.710287988185883, 0.380764007568359], 2952 | [0.526226997375488, 0.573909997940063], 2953 | [0.895093023777008, 0.254140973091125], 2954 | [0.634069979190826, 0.409575998783112], 2955 | [0.661242008209229, 0.41302502155304], 2956 | [0.688880026340485, 0.409460008144379], 2957 | [0.725341975688934, 0.389131009578705], 2958 | [0.606630027294159, 0.40370500087738], 2959 | [0.654766023159027, 0.344011008739471], 2960 | [0.629905998706818, 0.346076011657715], 2961 | [0.680678009986877, 0.347265005111694], 2962 | [0.702096998691559, 0.353591024875641], 2963 | [0.75221198797226, 0.410804986953735], 2964 | [0.602918028831482, 0.842862963676453], 2965 | [0.719901978969574, 0.375599980354309], 2966 | [0.893692970275879, 0.399959981441498], 2967 | [0.790081977844238, 0.391354024410248], 2968 | [0.643998026847839, 0.534487962722778], 2969 | [0.528249025344849, 0.65040397644043], 2970 | [0.525849997997284, 0.680191040039062], 2971 | [0.560214996337891, 0.657229006290436], 2972 | [0.585384011268616, 0.66654098033905], 2973 | [0.549625992774963, 0.680860996246338], 2974 | [0.57122802734375, 0.682691991329193], 2975 | [0.624852001667023, 0.72809898853302], 2976 | [0.513050019741058, 0.547281980514526], 2977 | [0.51509702205658, 0.527251958847046], 2978 | [0.742246985435486, 0.314507007598877], 2979 | [0.598631024360657, 0.454979002475739], 2980 | [0.570338010787964, 0.548575043678284], 2981 | [0.578631997108459, 0.533622980117798], 2982 | [0.723087012767792, 0.532054007053375], 2983 | [0.516445994377136, 0.499638974666595], 2984 | [0.662801027297974, 0.282917976379395], 2985 | [0.70362401008606, 0.293271005153656], 2986 | [0.830704987049103, 0.193813979625702], 2987 | [0.552385985851288, 0.302568018436432], 2988 | [0.607609987258911, 0.353887975215912], 2989 | [0.645429015159607, 0.696707010269165], 2990 | [0.932694971561432, 0.730105042457581], 2991 | [0.557260990142822, 0.572826027870178], 2992 | [0.542901992797852, 0.584792017936707], 2993 | [0.6180260181427, 0.694710969924927], 2994 | [0.607590973377228, 0.694203019142151], 2995 | [0.722943007946014, 0.271963000297546], 2996 | [0.577413976192474, 0.563166975975037], 2997 | [0.614082992076874, 0.281386971473694], 2998 | [0.616907000541687, 0.255886018276215], 2999 | [0.668509006500244, 0.119913995265961], 3000 | [0.770092010498047, 0.232020974159241], 3001 | [0.635536015033722, 0.189248979091644], 3002 | [0.77039098739624, 0.299556016921997], 3003 | [0.826722025871277, 0.278755009174347], 3004 | [0.527121007442474, 0.666198015213013], 3005 | [0.553171992301941, 0.668527007102966], 3006 | [0.577238023281097, 0.673889994621277], 3007 | [0.554691970348358, 0.580065965652466], 3008 | [0.611896991729736, 0.693961024284363], 3009 | [0.59696102142334, 0.706539988517761], 3010 | [0.596370995044708, 0.693953037261963], 3011 | [0.539958000183105, 0.557139039039612], 3012 | [0.568841993808746, 0.692366003990173], 3013 | [0.547818005084991, 0.692366003990173], 3014 | [0.52461302280426, 0.692366003990173], 3015 | [0.534089982509613, 0.779141008853912], 3016 | [0.527670979499817, 0.736225962638855], 3017 | [0.526912987232208, 0.717857003211975], 3018 | [0.526877999305725, 0.704625964164734], 3019 | [0.526966989040375, 0.695277988910675], 3020 | [0.572058022022247, 0.695277988910675], 3021 | [0.573521018028259, 0.703539967536926], 3022 | [0.57683801651001, 0.711845993995667], 3023 | [0.581691026687622, 0.720062971115112], 3024 | [0.609944999217987, 0.639909982681274], 3025 | [0.986046016216278, 0.560034036636353], 3026 | [0.5867999792099, 0.69539999961853], 3027 | [0.590372025966644, 0.701822996139526], 3028 | [0.531915009021759, 0.601536989212036], 3029 | [0.577268004417419, 0.585934996604919], 3030 | [0.536915004253387, 0.593786001205444], 3031 | [0.627542972564697, 0.473352015018463], 3032 | [0.665585994720459, 0.495950996875763], 3033 | [0.588353991508484, 0.546862006187439], 3034 | [0.757824003696442, 0.14767599105835], 3035 | [0.709249973297119, 0.201507985591888], 3036 | [0.672684013843536, 0.256581008434296], 3037 | [0.600408971309662, 0.74900496006012], 3038 | [0.55826598405838, 0.261672019958496], 3039 | [0.570303976535797, 0.187870979309082], 3040 | [0.588165998458862, 0.109044015407562], 3041 | [0.711045026779175, 0.398952007293701], 3042 | [0.781069993972778, 0.435405015945435], 3043 | [0.587247014045715, 0.398931980133057], 3044 | [0.742869973182678, 0.355445981025696], 3045 | [0.572156012058258, 0.437651991844177], 3046 | [0.55186802148819, 0.536570012569427], 3047 | [0.821442008018494, 0.457556009292603], 3048 | [0.752701997756958, 0.457181990146637], 3049 | [0.71375697851181, 0.467626988887787], 3050 | [0.66711300611496, 0.460672974586487], 3051 | [0.631101012229919, 0.447153985500336], 3052 | [0.6008620262146, 0.432473003864288], 3053 | [0.523481011390686, 0.405627012252808], 3054 | [0.810747981071472, 0.523926019668579], 3055 | [0.771045982837677, 0.348959028720856], 3056 | [0.509127020835876, 0.562718033790588], 3057 | [0.595292985439301, 0.485023975372314], 3058 | [0.980530977249146, 0.401564002037048], 3059 | [0.573499977588654, 0.420000016689301], 3060 | [0.602994978427887, 0.548687994480133], 3061 | [0.733529984951019, 0.376977026462555], 3062 | [0.560611009597778, 0.519016981124878], 3063 | [0.967685997486115, 0.644356966018677], 3064 | [0.580985009670258, 0.387160003185272], 3065 | [0.537728011608124, 0.505385041236877], 3066 | [0.760966002941132, 0.779752969741821], 3067 | [0.801778972148895, 0.831938028335571], 3068 | [0.892440974712372, 0.54076099395752], 3069 | [0.816350996494293, 0.740260004997253], 3070 | [0.865594983100891, 0.333687007427216], 3071 | [0.614073991775513, 0.883246004581451], 3072 | [0.508952975273132, 0.579437971115112], 3073 | [0.617941975593567, 0.508316040039062], 3074 | [0.825608015060425, 0.397674977779388], 3075 | [0.681214988231659, 0.39623498916626], 3076 | [0.656635999679565, 0.400596976280212], 3077 | [0.603900015354156, 0.710216999053955], 3078 | [0.81208598613739, 0.588539004325867], 3079 | [0.56801301240921, 0.944564998149872], 3080 | [0.681007981300354, 0.898285031318665], 3081 | [0.733752012252808, 0.869701027870178], 3082 | [0.633830010890961, 0.398822009563446], 3083 | [0.606792986392975, 0.39553701877594], 3084 | [0.589659988880157, 0.391062021255493], 3085 | [0.805015981197357, 0.342108011245728], 3086 | [0.611334979534149, 0.362284004688263], 3087 | [0.634037971496582, 0.355970978736877], 3088 | [0.656635999679565, 0.355356991291046], 3089 | [0.681214988231659, 0.35834002494812], 3090 | [0.698584973812103, 0.363156020641327], 3091 | [0.941866993904114, 0.319076001644135], 3092 | [0.698584973812103, 0.387449026107788], 3093 | [0.584177017211914, 0.624107003211975], 3094 | [0.554318010807037, 0.566076993942261], 3095 | [0.534153997898102, 0.62064003944397], 3096 | [0.711217999458313, 0.819975018501282], 3097 | [0.664629995822906, 0.852871000766754], 3098 | [0.559099972248077, 0.902631998062134], 3099 | [0.871706008911133, 0.791940987110138], 3100 | [0.591234028339386, 0.373893976211548], 3101 | [0.544341027736664, 0.451583981513977], 3102 | [0.624562978744507, 0.924192011356354], 3103 | [0.88577002286911, 0.615028977394104], 3104 | [0.551338016986847, 0.695277988910675], 3105 | [0.551980018615723, 0.704632043838501], 3106 | [0.552887976169586, 0.715808033943176], 3107 | [0.555167973041534, 0.730794012546539], 3108 | [0.569944024085999, 0.767035007476807], 3109 | [0.593203008174896, 0.685675978660583], 3110 | [0.599261999130249, 0.681069016456604], 3111 | [0.607599973678589, 0.677703022956848], 3112 | [0.631937980651855, 0.663500010967255], 3113 | [0.752032995223999, 0.601315021514893], 3114 | [0.547226011753082, 0.420395016670227], 3115 | [0.563543975353241, 0.359827995300293], 3116 | [0.583841025829315, 0.368713974952698], 3117 | [0.586614012718201, 0.692366003990173], 3118 | [0.771915018558502, 0.683578014373779], 3119 | [0.531597018241882, 0.352482974529266], 3120 | [0.588370978832245, 0.804440975189209], 3121 | [0.52079701423645, 0.442565023899078], 3122 | [0.567984998226166, 0.493479013442993], 3123 | [0.543282985687256, 0.819254994392395], 3124 | [0.655317008495331, 0.745514988899231], 3125 | [0.621008992195129, 0.574018001556396], 3126 | [0.625559985637665, 0.78031200170517], 3127 | [0.680198013782501, 0.570719003677368], 3128 | [0.64276397228241, 0.604337990283966], 3129 | [0.704662978649139, 0.621529996395111], 3130 | [0.552012026309967, 0.862591981887817], 3131 | [0.589071989059448, 0.508637011051178], 3132 | [0.685944974422455, 0.775357007980347], 3133 | [0.645735025405884, 0.812640011310577], 3134 | [0.675342977046967, 0.703978002071381], 3135 | [0.810858011245728, 0.646304965019226], 3136 | [0.72012197971344, 0.714666962623596], 3137 | [0.866151988506317, 0.682704985141754], 3138 | [0.663187026977539, 0.644596993923187], 3139 | [0.570082008838654, 0.466325998306274], 3140 | [0.544561982154846, 0.548375964164734], 3141 | [0.562758982181549, 0.558784961700439], 3142 | [0.531987011432648, 0.530140042304993], 3143 | [0.585271000862122, 0.335177004337311], 3144 | [0.622952997684479, 0.32277899980545], 3145 | [0.655896008014679, 0.320163011550903], 3146 | [0.687132000923157, 0.322345972061157], 3147 | [0.716481983661652, 0.333200991153717], 3148 | [0.758756995201111, 0.382786989212036], 3149 | [0.897013008594513, 0.468769013881683], 3150 | [0.732392013072968, 0.424547016620636], 3151 | [0.70211398601532, 0.433162987232208], 3152 | [0.66652500629425, 0.433866024017334], 3153 | [0.633504986763, 0.426087975502014], 3154 | [0.603875994682312, 0.416586995124817], 3155 | [0.579657971858978, 0.409945011138916], 3156 | [0.992439985275269, 0.480777025222778], 3157 | [0.567192018032074, 0.569419980049133], 3158 | [0.54136598110199, 0.478899002075195], 3159 | [0.526564002037048, 0.546118021011353], 3160 | [0.523913025856018, 0.563830018043518], 3161 | [0.531529009342194, 0.555056989192963], 3162 | [0.566035985946655, 0.582329034805298], 3163 | [0.51631098985672, 0.563053965568542], 3164 | [0.5174720287323, 0.577877044677734], 3165 | [0.573594987392426, 0.389806985855103], 3166 | [0.560697972774506, 0.395331978797913], 3167 | [0.549755990505219, 0.399751007556915], 3168 | [0.710287988185883, 0.368252992630005], 3169 | [0.723330020904541, 0.363372981548309], 3170 | ]; 3171 | -------------------------------------------------------------------------------- /src/appState.ts: -------------------------------------------------------------------------------- 1 | export const appState = { 2 | camOn: false, 3 | faceMeshModelLoaded: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/assets/eyes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes.jpg -------------------------------------------------------------------------------- /src/assets/eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes.png -------------------------------------------------------------------------------- /src/assets/eyes_displacement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes_displacement.png -------------------------------------------------------------------------------- /src/assets/eyes_inverted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes_inverted.jpg -------------------------------------------------------------------------------- /src/assets/eyes_inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes_inverted.png -------------------------------------------------------------------------------- /src/assets/eyes_mouth_inverted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes_mouth_inverted.jpg -------------------------------------------------------------------------------- /src/assets/eyes_mouth_inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/eyes_mouth_inverted.png -------------------------------------------------------------------------------- /src/assets/face_edges.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/face_edges.jpg -------------------------------------------------------------------------------- /src/assets/face_highlights.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/face_highlights.jpg -------------------------------------------------------------------------------- /src/assets/face_highlights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/face_highlights.png -------------------------------------------------------------------------------- /src/assets/hearts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/hearts.png -------------------------------------------------------------------------------- /src/assets/horns.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/horns.jpg -------------------------------------------------------------------------------- /src/assets/horns_col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/horns_col.png -------------------------------------------------------------------------------- /src/assets/mesh_map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/mesh_map.jpg -------------------------------------------------------------------------------- /src/assets/mouth_bottom_inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funwithtriangles/instatrip/08c8bdfbb7c00d8e9068aafd4d4db418822d54e7/src/assets/mouth_bottom_inverted.png -------------------------------------------------------------------------------- /src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import styled from 'styled-components'; 3 | import { 4 | renderer, 5 | composer, 6 | scene, 7 | startAnimation, 8 | renderPass, 9 | showIntroBlock, 10 | } from '../setup'; 11 | import { Thumbs } from './Thumbs'; 12 | import { sketches, SketchInterface } from '../sketches'; 13 | 14 | const Wrapper = styled.div` 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | `; 21 | 22 | const CanvasContainer = styled.div` 23 | canvas { 24 | position: fixed; 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | } 30 | `; 31 | 32 | const Message = styled.div` 33 | position: fixed; 34 | bottom: 10rem; 35 | width: 100%; 36 | color: white; 37 | text-align: center; 38 | font-weight: bold; 39 | text-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 40 | opacity: 0; 41 | transition: 1s; 42 | 43 | &.show { 44 | opacity: 1; 45 | } 46 | `; 47 | 48 | const HelpIcon = styled.div` 49 | position: fixed; 50 | top: 1rem; 51 | left: 1rem; 52 | z-index: 10; 53 | background: rgba(255, 255, 255, 0.5); 54 | border: 1px solid white; 55 | border-radius: 999px; 56 | width: 2rem; 57 | height: 2rem; 58 | display: flex; 59 | text-align: center; 60 | justify-content: center; 61 | align-items: center; 62 | color: #fa00ff; 63 | `; 64 | 65 | const div = document.createElement('div'); 66 | 67 | let to: number; 68 | 69 | export default function App() { 70 | const containerRef = useRef(div); 71 | const currentSketch = useRef(); 72 | 73 | const [sketchIndex, setSketchIndex] = useState(0); 74 | const [messageText, setMessageText] = useState(''); 75 | const [textVisible, setTextVisible] = useState(false); 76 | 77 | // Will only fire once 78 | useEffect(() => { 79 | containerRef.current.appendChild(renderer.domElement); 80 | 81 | startAnimation(info => { 82 | if ( 83 | currentSketch && 84 | currentSketch.current && 85 | currentSketch.current.update 86 | ) { 87 | currentSketch.current.update(info); 88 | } 89 | }); 90 | }, []); 91 | 92 | // Fires every time we change sketch 93 | useEffect(() => { 94 | renderPass.renderToScreen = false; 95 | renderPass.clear = true; 96 | composer.reset(); 97 | while (scene.children.length > 0) { 98 | scene.remove(scene.children[0]); 99 | } 100 | currentSketch.current = new sketches[sketchIndex].Module({ 101 | composer, 102 | scene, 103 | }); 104 | 105 | // Set text if set in sketch 106 | setMessageText(currentSketch.current.messageText || ''); 107 | setTextVisible(false); 108 | 109 | // Make text appear after 1.5 seconds 110 | clearTimeout(to); 111 | to = setTimeout(() => { 112 | setTextVisible(true); 113 | }, 1500); 114 | 115 | // Provide method to sketch to hide text when necessary 116 | currentSketch.current.hideText = () => { 117 | setTextVisible(false); 118 | }; 119 | }, [sketchIndex]); 120 | 121 | return ( 122 | 123 | 👽 124 | 125 | {messageText && ( 126 | 127 | {messageText} 128 | 129 | )} 130 | 131 | 132 | ); 133 | } 134 | -------------------------------------------------------------------------------- /src/components/Thumbs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react'; 2 | import styled from 'styled-components'; 3 | import Flickity from 'react-flickity-component'; 4 | import { sketches } from '../sketches'; 5 | 6 | const Nav = styled.div` 7 | position: absolute; 8 | bottom: 0.5rem; 9 | width: 100%; 10 | `; 11 | 12 | const Item = styled.div` 13 | background: rgba(255, 255, 255, 0.7); 14 | border: 2px solid white; 15 | border-radius: 999px; 16 | width: 6rem; 17 | height: 6rem; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | font-size: 4rem; 22 | margin: 0.5rem; 23 | transition: 0.2s; 24 | transform: scale(0.7); 25 | 26 | &.is-selected { 27 | transform: scale(1); 28 | } 29 | `; 30 | 31 | type ThumbsProps = { 32 | setSketchIndex: (index: number) => void; 33 | sketchIndex: number; 34 | }; 35 | 36 | export function Thumbs({ setSketchIndex, sketchIndex }: ThumbsProps) { 37 | const flickityRef = useRef(); 38 | const [isDragging, setIsDragging] = useState(false); 39 | 40 | useEffect(() => { 41 | if (flickityRef.current !== undefined) { 42 | flickityRef.current.on('settle', (index: number) => { 43 | setSketchIndex(index); 44 | setIsDragging(false); 45 | }); 46 | 47 | flickityRef.current.on('dragStart', () => { 48 | setIsDragging(true); 49 | }); 50 | } else { 51 | console.error('Flickity ref not available'); 52 | } 53 | }, [setSketchIndex]); 54 | 55 | useEffect(() => { 56 | if (flickityRef.current !== undefined) { 57 | flickityRef.current.select(sketchIndex); 58 | } 59 | }, [sketchIndex]); 60 | 61 | const onItemClick = (index: number) => { 62 | if (!isDragging) { 63 | setSketchIndex(index); 64 | } 65 | }; 66 | 67 | return ( 68 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/effects/ColorOverlayEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Effect, BlendFunction } from 'postprocessing'; 2 | 3 | import fragment from './shader.frag'; 4 | 5 | export class ColorOverlayEffect extends Effect { 6 | constructor() { 7 | super('FaceDetailEffect', fragment, { 8 | blendFunction: BlendFunction.ALPHA, 9 | uniforms: new Map([]), 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/effects/ColorOverlayEffect/shader.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D camTexture; 2 | uniform sampler2D maskTexture; 3 | 4 | void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { 5 | outputColor = vec4(0., inputColor.r, 0., 1.); 6 | } -------------------------------------------------------------------------------- /src/effects/DisplacementEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Effect, BlendFunction } from 'postprocessing'; 2 | import { Uniform, Texture } from 'three'; 3 | 4 | import fragment from './shader.frag'; 5 | 6 | interface DisplacementEffectProps { 7 | displacementTex: Texture; 8 | } 9 | 10 | export class DisplacementEffect extends Effect { 11 | constructor({ displacementTex }: DisplacementEffectProps) { 12 | super('MeltEffect', fragment, { 13 | blendFunction: BlendFunction.NORMAL, 14 | uniforms: new Map([['displacementTex', new Uniform(displacementTex)]]), 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/effects/DisplacementEffect/shader.frag: -------------------------------------------------------------------------------- 1 | #pragma glslify: import('../../glsl/common.glsl') 2 | 3 | uniform sampler2D displacementTex; 4 | 5 | void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { 6 | vec4 disp = texture2D(displacementTex, uv); 7 | 8 | // Remap the space to -1. to 1. 9 | vec2 st = uv - vec2(0.5); 10 | st.x *= resolution.x/resolution.y; 11 | 12 | st /= 1. + disp.a * 0.5; 13 | 14 | st.y += 0.02 * disp.a; 15 | 16 | if (disp.r <.5 ) { 17 | st.x -= 0.01 * disp.a; 18 | } else { 19 | st.x += 0.01 * disp.a; 20 | } 21 | 22 | 23 | // // Remap back to UV coords 24 | st.x /= resolution.x/resolution.y; 25 | st += vec2(0.5); 26 | 27 | vec4 col = texture2D(inputBuffer, st); 28 | 29 | outputColor = col; 30 | 31 | // debug displacement over face 32 | // outputColor = vec4(1.0) * disp + vec4(1.0 - disp.a) * col; 33 | } -------------------------------------------------------------------------------- /src/effects/DriftEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Effect, BlendFunction } from 'postprocessing'; 2 | import { Uniform, Texture } from 'three'; 3 | 4 | import fragment from './shader.frag'; 5 | 6 | interface DriftEffectProps { 7 | prevFrameTex: Texture; 8 | featuresTex: Texture; 9 | headTex: Texture; 10 | driftAmp: number; 11 | maskAmp: number; 12 | frame: number; 13 | } 14 | 15 | export class DriftEffect extends Effect { 16 | constructor({ 17 | prevFrameTex, 18 | featuresTex, 19 | driftAmp, 20 | maskAmp, 21 | frame, 22 | }: DriftEffectProps) { 23 | super('DriftEffect', fragment, { 24 | blendFunction: BlendFunction.NORMAL, 25 | uniforms: new Map([ 26 | ['prevFrameTex', new Uniform(prevFrameTex)], 27 | ['featuresTex', new Uniform(featuresTex)], 28 | ['driftAmp', new Uniform(driftAmp)], 29 | ['maskAmp', new Uniform(maskAmp)], 30 | ['frame', new Uniform(frame)], 31 | ]), 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/effects/DriftEffect/shader.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D prevFrameTex; 2 | uniform sampler2D featuresTex; 3 | uniform float driftAmp; 4 | uniform int frame; 5 | uniform float maskAmp; 6 | 7 | #pragma glslify: import('../../glsl/common.glsl') 8 | 9 | float crush(float val, float c) { 10 | return floor(val / c) * c; 11 | } 12 | 13 | void mainImage(const in vec4 headCol, const in vec2 uv, out vec4 outputColor) { 14 | // Remap the space to -1. to 1. 15 | vec2 st = uv - vec2(0.5); 16 | st.x *= resolution.x/resolution.y; 17 | 18 | // generate noise 19 | float noise0 = snoise(vec3(st, time * 0.01) * 5.); 20 | // Bitcrushing the angle to make it more jagged 21 | float angle0 = noise0 * PI * 2.; 22 | 23 | // Remap back to UV coords 24 | st.x /= resolution.x/resolution.y; 25 | st += vec2(0.5); 26 | 27 | vec2 vel = vec2( 28 | sin(angle0), 29 | cos(angle0) 30 | ); 31 | 32 | st += vel * .0003; 33 | 34 | vec4 col = texture2D(prevFrameTex, st); 35 | 36 | 37 | // Mix prev frame and clean face based on inputs and noise 38 | float maskNoise = clamp(headCol.r + maskAmp * 2., 0., 1.); 39 | float driftVal = mix(0.9, 1., driftAmp); // The feedback biting range is narrow 40 | col = mix(headCol, col, driftVal * maskNoise); 41 | 42 | vec3 newCol = rgb2hsl(col.rgb); 43 | 44 | // Adjust hue 45 | newCol.r -= 0.001; 46 | 47 | col = vec4(hsl2rgb(newCol.rgb), col.a); 48 | 49 | // Do the feedback only after 1st frame has passed 50 | if (frame < 2) { 51 | outputColor = headCol; 52 | } else { 53 | // Alpha blend prev frame with current 54 | outputColor = vec4(1.0) * col + vec4(1.0 - col.a) * headCol; 55 | // outputColor = headCol * vec4(vec3(maskNoise), 1.); // Debug: noise mask 56 | } 57 | } -------------------------------------------------------------------------------- /src/effects/FaceDetailEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Uniform, Texture } from 'three'; 2 | import { Effect, BlendFunction } from 'postprocessing'; 3 | 4 | import fragment from './shader.frag'; 5 | 6 | interface FaceDetailEffectProps { 7 | camTexture: Texture; 8 | maskTexture: Texture; 9 | invert: boolean; 10 | } 11 | 12 | export class FaceDetailEffect extends Effect { 13 | constructor({ camTexture, maskTexture, invert }: FaceDetailEffectProps) { 14 | super('FaceDetailEffect', fragment, { 15 | blendFunction: BlendFunction.ALPHA, 16 | uniforms: new Map([ 17 | ['camTexture', new Uniform(camTexture)], 18 | ['maskTexture', new Uniform(maskTexture)], 19 | ['invert', new Uniform(invert)], 20 | ]), 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/effects/FaceDetailEffect/shader.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D camTexture; 2 | uniform sampler2D maskTexture; 3 | uniform bool invert; 4 | 5 | void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { 6 | vec2 st = uv ; 7 | st.x = (st.x - 0.5) + 0.5; 8 | 9 | float mask = texture2D(maskTexture, uv).r; 10 | 11 | vec3 col = texture2D(camTexture, st).rgb; 12 | 13 | outputColor = vec4(col, mask); 14 | } -------------------------------------------------------------------------------- /src/effects/MeltEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Effect, BlendFunction } from 'postprocessing'; 2 | import { Uniform, Texture } from 'three'; 3 | 4 | import fragment from './shader.frag'; 5 | 6 | interface MeltEffectProps { 7 | prevFrameTex: Texture; 8 | featuresTex: Texture; 9 | headTex: Texture; 10 | noiseStength: number; 11 | featuresStrength: number; 12 | frame: number; 13 | } 14 | 15 | export class MeltEffect extends Effect { 16 | constructor({ 17 | prevFrameTex, 18 | featuresTex, 19 | noiseStength, 20 | featuresStrength, 21 | frame, 22 | }: MeltEffectProps) { 23 | super('MeltEffect', fragment, { 24 | blendFunction: BlendFunction.NORMAL, 25 | uniforms: new Map([ 26 | ['prevFrameTex', new Uniform(prevFrameTex)], 27 | ['featuresTex', new Uniform(featuresTex)], 28 | ['noiseStrength', new Uniform(noiseStength)], 29 | ['featuresStrength', new Uniform(featuresStrength)], 30 | ['frame', new Uniform(frame)], 31 | ]), 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/effects/MeltEffect/shader.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D prevFrameTex; 2 | uniform sampler2D featuresTex; 3 | uniform float noiseStrength; 4 | uniform float featuresStrength; 5 | uniform int frame; 6 | 7 | #pragma glslify: import('../../glsl/common.glsl') 8 | 9 | #pragma glslify: worley2x2x2 = require(glsl-worley/worley2x2x2.glsl) 10 | 11 | float crush(float val, float c) { 12 | return floor(val / c) * c; 13 | } 14 | 15 | void mainImage(const in vec4 headCol, const in vec2 uv, out vec4 outputColor) { 16 | // Remap the space to -1. to 1. 17 | vec2 st = uv - vec2(0.5); 18 | st.x *= resolution.x/resolution.y; 19 | 20 | // generate noise 21 | vec2 F = worley2x2x2(vec3(st, time * 0.1) * 5., 1., true); 22 | float noise0 = F.y - F.x; 23 | // Bitcrushing the angle to make it more jagged 24 | float angle0 = crush(F.y * PI * 2., 1.); 25 | 26 | // scale based on input 27 | st *= 1. - (featuresStrength * 0.05); 28 | 29 | // rotate based on input 30 | st *= rotate2d(featuresStrength * -0.04); 31 | 32 | // Send face upwards and wiggle 33 | st.y -= 0.002; 34 | st.x += sin(angle0) * 0.01; 35 | 36 | // Remap back to UV coords 37 | st.x /= resolution.x/resolution.y; 38 | st += vec2(0.5); 39 | 40 | vec4 col = texture2D(prevFrameTex, st); 41 | vec4 featuresCol = texture2D(featuresTex, uv); 42 | 43 | float mixBoost = 1. + featuresStrength * 0.5; 44 | float crushAmp = .2; 45 | float crushedNoise = abs(crush(noise0, crushAmp)); 46 | float mixVal = crushedNoise * noiseStrength * mixBoost; 47 | 48 | // Mix prev frame and clean face based on various inputs and noise 49 | col = mix(col, headCol, mixVal + featuresCol * featuresStrength); 50 | 51 | vec3 newCol = rgb2hsl(col.rgb); 52 | 53 | // Adjust hue based on input 54 | newCol.r -= featuresStrength * 0.01; 55 | 56 | col = vec4(hsl2rgb(newCol.rgb), col.a); 57 | 58 | // Do the feedback only after 1st frame has passed 59 | if (frame < 2) { 60 | outputColor = headCol; 61 | } else { 62 | // Alpha blend prev frame with current 63 | outputColor = vec4(1.0) * col + vec4(1.0 - col.a) * headCol; 64 | 65 | // Debug: noise test 66 | // outputColor = vec4(crushedNoise * noiseStrength, 0., 0., 1.); 67 | } 68 | } -------------------------------------------------------------------------------- /src/effects/SmokeEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Effect, BlendFunction } from 'postprocessing'; 2 | import { Uniform, Texture, Vector3, Vector2 } from 'three'; 3 | 4 | import fragment from './shader.frag'; 5 | 6 | interface SmokeEffectProps { 7 | prevFrameTex: Texture; 8 | smokeColorHSL: Vector3; 9 | frame: number; 10 | smokeTextureAmp: number; 11 | smokeVelocity: Vector2; 12 | smokeDecay: number; 13 | smokeRot: number; 14 | noiseAmp: Vector2; 15 | } 16 | 17 | export class SmokeEffect extends Effect { 18 | constructor({ 19 | prevFrameTex, 20 | frame = 0, 21 | smokeColorHSL, 22 | smokeTextureAmp, 23 | smokeVelocity, 24 | smokeDecay, 25 | smokeRot, 26 | noiseAmp = new Vector2(1, 1), 27 | }: SmokeEffectProps) { 28 | super('SmokeEffect', fragment, { 29 | blendFunction: BlendFunction.NORMAL, 30 | uniforms: new Map([ 31 | ['prevFrameTex', new Uniform(prevFrameTex)], 32 | ['frame', new Uniform(frame)], 33 | ['smokeColorHSL', new Uniform(smokeColorHSL)], 34 | ['smokeTextureAmp', new Uniform(smokeTextureAmp)], 35 | ['smokeVelocity', new Uniform(smokeVelocity)], 36 | ['smokeDecay', new Uniform(smokeDecay)], 37 | ['smokeRot', new Uniform(smokeRot)], 38 | ['noiseAmp', new Uniform(noiseAmp)], 39 | ]), 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/effects/SmokeEffect/shader.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D prevFrameTex; 2 | uniform int frame; 3 | uniform vec3 smokeColorHSL; 4 | uniform float smokeTextureAmp; 5 | uniform vec2 smokeVelocity; 6 | uniform float smokeDecay; 7 | uniform float smokeRot; 8 | uniform vec2 noiseAmp; 9 | 10 | #pragma glslify: import('../../glsl/common.glsl') 11 | 12 | void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { 13 | // Remap the space to -1. to 1. 14 | vec2 st = uv - vec2(0.5); 15 | st.x *= resolution.x/resolution.y; 16 | 17 | // Send smoke in direction 18 | st -= smokeVelocity; 19 | 20 | // Generate noise at various scales and speeds 21 | float noise0base = snoise(vec3(st, time * 0.03) * 2.); 22 | float noise1base = snoise(vec3(st, time * 0.02) * 8.); 23 | float noise2base = snoise(vec3(st, time * 0.02) * 16.); 24 | 25 | // Convert noise into angle 26 | float angle0 = noise0base * PI * 2.; 27 | float angle1 = noise1base * PI * 2.; 28 | float angle2 = noise2base * PI * 2.; 29 | 30 | // Use angle to send smoke in random directions 31 | st.x += cos(angle0) * 0.003 * noiseAmp.x; 32 | st.y += sin(angle0) * 0.002 * noiseAmp.y; 33 | 34 | st.x += cos(angle1) * 0.003 * noiseAmp.x; 35 | st.y += sin(angle1) * 0.001 * noiseAmp.y; 36 | 37 | st.x += cos(angle2) * 0.005 * noiseAmp.x; 38 | st.y += sin(angle2) * 0.002 * noiseAmp.y; 39 | 40 | // rotate smoke a little 41 | st *= rotate2d(smokeRot); 42 | 43 | // Remap back to UV coords 44 | st.x /= resolution.x/resolution.y; 45 | st += vec2(0.5); 46 | 47 | // Add previous frame to this frame 48 | vec4 col = texture2D(prevFrameTex, st); 49 | outputColor = col + inputColor; 50 | 51 | // Adjust colour 52 | outputColor.rgb = hsl2rgb( 53 | smokeColorHSL + 54 | vec3(0., 0., noise1base * smokeTextureAmp) 55 | ); 56 | 57 | // Only set alpha after some time has passed, because prevFrameTex initiates unpopulated 58 | if (frame < 2) { 59 | outputColor.a = inputColor.a; 60 | } else { 61 | outputColor.a -= smokeDecay; 62 | } 63 | } -------------------------------------------------------------------------------- /src/effects/TunnelEffect/index.ts: -------------------------------------------------------------------------------- 1 | import { Effect, BlendFunction } from 'postprocessing'; 2 | import { Uniform, Texture } from 'three'; 3 | 4 | import fragment from './shader.frag'; 5 | 6 | interface TunnelEffectProps { 7 | prevFrameTex: Texture; 8 | } 9 | 10 | export class TunnelEffect extends Effect { 11 | constructor({ prevFrameTex }: TunnelEffectProps) { 12 | super('TunnelEffect', fragment, { 13 | blendFunction: BlendFunction.NORMAL, 14 | uniforms: new Map([['prevFrameTex', new Uniform(prevFrameTex)]]), 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/effects/TunnelEffect/shader.frag: -------------------------------------------------------------------------------- 1 | #pragma glslify: import('../../glsl/common.glsl') 2 | 3 | uniform sampler2D prevFrameTex; 4 | 5 | void mainImage(const in vec4 headCol, const in vec2 uv, out vec4 outputColor) { 6 | // Remap the space to -1. to 1. 7 | vec2 st = uv - vec2(0.5); 8 | st.x *= resolution.x/resolution.y; 9 | 10 | // scale 11 | st *= .95; 12 | 13 | // rotate based on input 14 | st *= rotate2d(sin(time * 0.6) * .1); 15 | 16 | // Remap back to UV coords 17 | st.x /= resolution.x/resolution.y; 18 | st += vec2(0.5); 19 | 20 | vec4 col = texture2D(prevFrameTex, st); 21 | 22 | vec3 newHeadCol = rgb2hsl(headCol.rgb); 23 | 24 | // Adjust hue each frame 25 | newHeadCol.r += mod(time * 0.3, 1.); 26 | // Boost saturation 27 | newHeadCol.g = min(newHeadCol.g + .5, 1.); 28 | 29 | newHeadCol = hsl2rgb(newHeadCol); 30 | 31 | outputColor = vec4(1.0) * vec4(newHeadCol, headCol.a) + vec4(1.0 - headCol.a) * col; 32 | } -------------------------------------------------------------------------------- /src/faceMesh.ts: -------------------------------------------------------------------------------- 1 | import * as facemesh from '@tensorflow-models/facemesh'; 2 | import { Vector3, Object3D, Matrix4 } from 'three'; 3 | import { appState } from './appState'; 4 | import { video } from './webcam'; 5 | 6 | import { FaceMeshFaceGeometry } from './FaceMeshFaceGeometry/face'; 7 | 8 | let faceMeshModel: facemesh.FaceMesh; 9 | export const faceGeometry = new FaceMeshFaceGeometry({ 10 | useVideoTexture: true, 11 | }); 12 | 13 | export const metrics = { 14 | mouthOpenness: 0, 15 | zed: 1, 16 | track: { 17 | position: new Vector3(), 18 | normal: new Vector3(), 19 | rotation: new Matrix4(), 20 | }, 21 | }; 22 | 23 | const lipTop = new Vector3(); 24 | const lipBot = new Vector3(); 25 | 26 | export const initFaceMesh = async (cb?: () => void) => { 27 | faceMeshModel = await facemesh.load({ 28 | maxFaces: 1, 29 | }); 30 | appState.faceMeshModelLoaded = true; 31 | 32 | if (cb) cb(); 33 | }; 34 | 35 | export const updateFaceMesh = async () => { 36 | const faces = await faceMeshModel.estimateFaces(video, false, true); 37 | if (faces.length) { 38 | const face = faces[0]; 39 | faceGeometry.update(face, true); 40 | 41 | /* eslint-disable */ 42 | // @ts-ignore 43 | const top = face.mesh[13]; 44 | // @ts-ignore 45 | const bot = face.mesh[14]; 46 | /* eslint-enable */ 47 | 48 | lipTop.set(top[0], top[1], top[2]); 49 | lipBot.set(top[0], bot[1], bot[2]); 50 | 51 | metrics.track = faceGeometry.track(5, 45, 275); 52 | metrics.mouthOpenness = lipBot.distanceTo(lipTop) / 25; 53 | metrics.zed = 2 + lipTop.z / 10; 54 | } 55 | }; 56 | 57 | export const trackFace = (object: Object3D) => { 58 | object.position.copy(metrics.track.position); 59 | object.rotation.setFromRotationMatrix(metrics.track.rotation); 60 | }; 61 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.frag'; 2 | -------------------------------------------------------------------------------- /src/glsl/common.glsl: -------------------------------------------------------------------------------- 1 | 2 | // Simplex 3D Noise 3 | // by Ian McEwan, Ashima Arts 4 | // 5 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} 6 | vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} 7 | 8 | float snoise(vec3 v){ 9 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 10 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 11 | 12 | // First corner 13 | vec3 i = floor(v + dot(v, C.yyy) ); 14 | vec3 x0 = v - i + dot(i, C.xxx) ; 15 | 16 | // Other corners 17 | vec3 g = step(x0.yzx, x0.xyz); 18 | vec3 l = 1.0 - g; 19 | vec3 i1 = min( g.xyz, l.zxy ); 20 | vec3 i2 = max( g.xyz, l.zxy ); 21 | 22 | // x0 = x0 - 0. + 0.0 * C 23 | vec3 x1 = x0 - i1 + 1.0 * C.xxx; 24 | vec3 x2 = x0 - i2 + 2.0 * C.xxx; 25 | vec3 x3 = x0 - 1. + 3.0 * C.xxx; 26 | 27 | // Permutations 28 | i = mod(i, 289.0 ); 29 | vec4 p = permute( permute( permute( 30 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 31 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 32 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 33 | 34 | // Gradients 35 | // ( N*N points uniformly over a square, mapped onto an octahedron.) 36 | float n_ = 1.0/7.0; // N=7 37 | vec3 ns = n_ * D.wyz - D.xzx; 38 | 39 | vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) 40 | 41 | vec4 x_ = floor(j * ns.z); 42 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 43 | 44 | vec4 x = x_ *ns.x + ns.yyyy; 45 | vec4 y = y_ *ns.x + ns.yyyy; 46 | vec4 h = 1.0 - abs(x) - abs(y); 47 | 48 | vec4 b0 = vec4( x.xy, y.xy ); 49 | vec4 b1 = vec4( x.zw, y.zw ); 50 | 51 | vec4 s0 = floor(b0)*2.0 + 1.0; 52 | vec4 s1 = floor(b1)*2.0 + 1.0; 53 | vec4 sh = -step(h, vec4(0.0)); 54 | 55 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 56 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 57 | 58 | vec3 p0 = vec3(a0.xy,h.x); 59 | vec3 p1 = vec3(a0.zw,h.y); 60 | vec3 p2 = vec3(a1.xy,h.z); 61 | vec3 p3 = vec3(a1.zw,h.w); 62 | 63 | //Normalise gradients 64 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 65 | p0 *= norm.x; 66 | p1 *= norm.y; 67 | p2 *= norm.z; 68 | p3 *= norm.w; 69 | 70 | // Mix final noise value 71 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 72 | m = m * m; 73 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 74 | dot(p2,x2), dot(p3,x3) ) ); 75 | } 76 | 77 | mat2 rotate2d(float _angle){ 78 | return mat2(cos(_angle),-sin(_angle), 79 | sin(_angle),cos(_angle)); 80 | } 81 | 82 | 83 | float hue2rgb(float f1, float f2, float hue) { 84 | if (hue < 0.0) 85 | hue += 1.0; 86 | else if (hue > 1.0) 87 | hue -= 1.0; 88 | float res; 89 | if ((6.0 * hue) < 1.0) 90 | res = f1 + (f2 - f1) * 6.0 * hue; 91 | else if ((2.0 * hue) < 1.0) 92 | res = f2; 93 | else if ((3.0 * hue) < 2.0) 94 | res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; 95 | else 96 | res = f1; 97 | return res; 98 | } 99 | 100 | vec3 hsl2rgb(vec3 hsl) { 101 | vec3 rgb; 102 | 103 | if (hsl.y == 0.0) { 104 | rgb = vec3(hsl.z); // Luminance 105 | } else { 106 | float f2; 107 | 108 | if (hsl.z < 0.5) 109 | f2 = hsl.z * (1.0 + hsl.y); 110 | else 111 | f2 = hsl.z + hsl.y - hsl.y * hsl.z; 112 | 113 | float f1 = 2.0 * hsl.z - f2; 114 | 115 | rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); 116 | rgb.g = hue2rgb(f1, f2, hsl.x); 117 | rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); 118 | } 119 | return rgb; 120 | } 121 | 122 | vec3 hsl2rgb(float h, float s, float l) { 123 | return hsl2rgb(vec3(h, s, l)); 124 | } 125 | 126 | vec3 rgb2hsl(vec3 color) { 127 | vec3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part) 128 | 129 | float fmin = min(min(color.r, color.g), color.b); //Min. value of RGB 130 | float fmax = max(max(color.r, color.g), color.b); //Max. value of RGB 131 | float delta = fmax - fmin; //Delta RGB value 132 | 133 | hsl.z = (fmax + fmin) / 2.0; // Luminance 134 | 135 | if (delta == 0.0) //This is a gray, no chroma... 136 | { 137 | hsl.x = 0.0; // Hue 138 | hsl.y = 0.0; // Saturation 139 | } else //Chromatic data... 140 | { 141 | if (hsl.z < 0.5) 142 | hsl.y = delta / (fmax + fmin); // Saturation 143 | else 144 | hsl.y = delta / (2.0 - fmax - fmin); // Saturation 145 | 146 | float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; 147 | float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; 148 | float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; 149 | 150 | if (color.r == fmax) 151 | hsl.x = deltaB - deltaG; // Hue 152 | else if (color.g == fmax) 153 | hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue 154 | else if (color.b == fmax) 155 | hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue 156 | 157 | if (hsl.x < 0.0) 158 | hsl.x += 1.0; // Hue 159 | else if (hsl.x > 1.0) 160 | hsl.x -= 1.0; // Hue 161 | } 162 | 163 | return hsl; 164 | } 165 | 166 | float rand(float n){return fract(sin(n) * 43758.5453123);} 167 | 168 | float noise(float p){ 169 | float fl = floor(p); 170 | float fc = fract(p); 171 | return mix(rand(fl), rand(fl + 1.0), fc); 172 | } -------------------------------------------------------------------------------- /src/glsl/faceBulge/fragment.glsl: -------------------------------------------------------------------------------- 1 | uniform float time; 2 | uniform sampler2D camTex; 3 | 4 | varying vec2 vVideoUv; 5 | 6 | void main( void ) { 7 | 8 | gl_FragColor = texture2D(camTex, vVideoUv); 9 | 10 | } -------------------------------------------------------------------------------- /src/glsl/faceBulge/vertex.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: import('../../glsl/common.glsl') 2 | 3 | uniform float time; 4 | uniform sampler2D faceHighlightsTex; 5 | uniform vec2 resolution; 6 | uniform vec3 masterNormal; 7 | uniform float baseDisplacement; 8 | uniform float animatedDisplacementAmp; 9 | uniform float animatedNormalAmp; 10 | attribute vec2 videoUv; 11 | 12 | varying vec2 vUv; 13 | varying vec2 vVideoUv; 14 | 15 | void main() 16 | { 17 | vUv = uv; 18 | vVideoUv = videoUv; 19 | 20 | vec4 edgeCol = texture2D(faceHighlightsTex, uv); 21 | 22 | // float displacement = (snoise(vec3(uv * 2., time)) + 0.5) * 100. * edgeCol.r; 23 | 24 | float displacement = 25 | ( 26 | baseDisplacement + 27 | (sin(uv.y * 5. + time * 10.) + 1.) * 28 | (cos(uv.x * 5. + time * 8.) + 1.) * 29 | animatedDisplacementAmp 30 | ) * edgeCol.r; 31 | 32 | vec3 modifiedNormal = normal + vec3( 33 | sin(time * 1.) - masterNormal.x * 6., 34 | cos(time * 1.5) + masterNormal.y * 6., 35 | 0. 36 | ) * animatedNormalAmp; 37 | 38 | vec3 newPosition = position + modifiedNormal * displacement; 39 | 40 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 41 | } -------------------------------------------------------------------------------- /src/glsl/horns/fragment.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: import('../../glsl/common.glsl') 2 | 3 | uniform float time; 4 | uniform sampler2D faceHighlightsTex; 5 | uniform sampler2D faceEdgeTex; 6 | uniform sampler2D overlayTex; 7 | uniform sampler2D camTex; 8 | 9 | varying vec2 vVideoUv; 10 | varying vec2 vUv; 11 | 12 | void main( void ) { 13 | 14 | vec4 headCol = texture2D(camTex, vVideoUv); 15 | vec4 edgesCol = texture2D(faceEdgeTex, vUv); 16 | vec4 overlayCol = texture2D(overlayTex, vUv); 17 | 18 | vec3 newHeadCol = rgb2hsl(headCol.rgb); 19 | 20 | // Adjust hue 21 | newHeadCol.r = 1.; 22 | // AdjustBoost saturation 23 | newHeadCol.g = min(headCol.g + .1, 1.); 24 | // AdjustBoost lum 25 | //newHeadCol.b += 0.2; 26 | 27 | newHeadCol = hsl2rgb(newHeadCol); 28 | 29 | newHeadCol = mix(headCol.rgb, newHeadCol, edgesCol.r); 30 | 31 | vec4 finalHeadCol = vec4(newHeadCol, 1.); 32 | 33 | gl_FragColor = mix(finalHeadCol, overlayCol, overlayCol.a); 34 | } -------------------------------------------------------------------------------- /src/glsl/texDisplace/fragment.glsl: -------------------------------------------------------------------------------- 1 | uniform float time; 2 | uniform sampler2D camTex; 3 | 4 | varying vec2 vVideoUv; 5 | 6 | void main( void ) { 7 | 8 | gl_FragColor = texture2D(camTex, vVideoUv); 9 | 10 | } -------------------------------------------------------------------------------- /src/glsl/texDisplace/vertex.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: import('../../glsl/common.glsl') 2 | 3 | const float TAU = 6.2831853071; 4 | 5 | uniform float time; 6 | uniform sampler2D dispTex; 7 | uniform vec2 resolution; 8 | uniform vec3 masterNormal; 9 | uniform float masterDisp; 10 | 11 | attribute vec2 videoUv; 12 | varying vec2 vVideoUv; 13 | 14 | void main() 15 | { 16 | vVideoUv = videoUv; 17 | 18 | // Get angle of displacement based on colour of texture 19 | vec4 dispCol = texture2D(dispTex, uv); 20 | float dispAngle = -dispCol.r * TAU; 21 | 22 | // Convert angle into X and Y 23 | vec3 dispDir = vec3( 24 | sin(dispAngle), 25 | cos(dispAngle), 26 | 1. 27 | ); 28 | 29 | // Animate bulge 30 | float noise = (sin(uv.x * 4. + time * 2.) + 1.) * .5; 31 | 32 | // Displace 33 | float displacement = (30. + 20. * noise) * dispCol.a * masterDisp; 34 | 35 | vec3 newPosition = position + dispDir * displacement; 36 | 37 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 38 | } -------------------------------------------------------------------------------- /src/hooks/useWinSize.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import useEventListener from '@srmagura/use-event-listener'; 3 | 4 | export const useWinSize = () => { 5 | const [winSize, setWinSize] = useState({ 6 | width: window.innerWidth, 7 | height: window.innerHeight, 8 | }); 9 | 10 | useEventListener('resize', () => { 11 | setWinSize({ width: window.innerWidth, height: window.innerHeight }); 12 | }); 13 | 14 | return winSize; 15 | }; 16 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | InstaTrip 8 | 9 | 10 | 11 | 12 | 13 | 102 | 103 | 104 | 105 |
106 |

👽 InstaTrip 👽

107 |

A demo showcasing what's possible with face filters on the web. Built completely with free and open-source libraries.

108 |

Find out more on Github. Created by Alex Kempton.

109 | 110 |
111 |
112 | 113 | 114 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | 4 | import App from './components/App'; 5 | import './style.css'; 6 | 7 | ReactDOM.render(, document.querySelector('#root')); 8 | 9 | if (module && module.hot) { 10 | module.hot.accept(); 11 | 12 | module.hot.addStatusHandler(status => { 13 | if (status === 'prepare') console.clear(); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/setup.tsx: -------------------------------------------------------------------------------- 1 | import fscreen from 'fscreen'; 2 | import { EffectComposer, TextureEffect, RenderPass } from 'postprocessing'; 3 | import Stats from 'stats.js'; 4 | import { 5 | OrthographicCamera, 6 | WebGLRenderer, 7 | Clock, 8 | PerspectiveCamera, 9 | Scene, 10 | } from 'three'; 11 | 12 | import { devMode } from '../settings'; 13 | import { camTextureFlipped, video } from './webcam'; 14 | import { getResizeFactors } from './utils/getResizeFactors'; 15 | import { faceGeometry, updateFaceMesh, initFaceMesh } from './faceMesh'; 16 | import { appState } from './appState'; 17 | 18 | export const renderer = new WebGLRenderer({ 19 | powerPreference: 'high-performance', 20 | antialias: false, 21 | stencil: true, 22 | depth: true, 23 | alpha: true, 24 | }); 25 | export const composer = new EffectComposer(renderer, { 26 | stencilBuffer: true, 27 | }); 28 | export const clock = new Clock(); 29 | export const stats = new Stats(); 30 | export const persCam = new PerspectiveCamera( 31 | 75, 32 | window.innerWidth / window.innerHeight, 33 | 0.1, 34 | 10000 35 | ); 36 | 37 | export const orthCam = new OrthographicCamera(1, 1, 1, 1, -1000, 1000); 38 | export const scene = new Scene(); 39 | 40 | export const webcamEffect = new TextureEffect({ 41 | texture: camTextureFlipped, 42 | }); 43 | webcamEffect.uvTransform = true; 44 | 45 | export const renderPass = new RenderPass(scene, orthCam); 46 | 47 | const resize = () => { 48 | composer.setSize(window.innerWidth, window.innerHeight, false); 49 | 50 | const { x, y } = getResizeFactors( 51 | window.innerWidth, 52 | window.innerHeight, 53 | video.videoWidth, 54 | video.videoHeight 55 | ); 56 | 57 | const w = video.videoWidth * x; 58 | const h = video.videoHeight * y; 59 | 60 | orthCam.left = -0.5 * w; 61 | orthCam.right = 0.5 * w; 62 | orthCam.top = 0.5 * h; 63 | orthCam.bottom = -0.5 * h; 64 | orthCam.updateProjectionMatrix(); 65 | 66 | faceGeometry.setSize(video.videoWidth, video.videoHeight); 67 | }; 68 | 69 | window.addEventListener('resize', resize); 70 | video.addEventListener('loadedmetadata', resize); 71 | 72 | if (devMode.fps) { 73 | document.body.appendChild(stats.dom); 74 | } 75 | 76 | export interface AnimationInfo { 77 | deltaFPS: number; 78 | elapsedS: number; 79 | } 80 | 81 | export const startAnimation = (cb?: (info: AnimationInfo) => void) => { 82 | const animate = async () => { 83 | window.requestAnimationFrame(animate); 84 | if (appState.camOn && appState.faceMeshModelLoaded) { 85 | stats.begin(); 86 | const delta = clock.getDelta(); 87 | const elapsedS = clock.getElapsedTime(); 88 | const deltaFPS = delta * 60; 89 | 90 | if (cb) { 91 | cb({ deltaFPS, elapsedS }); 92 | } 93 | 94 | updateFaceMesh(); 95 | 96 | composer.render(delta); 97 | 98 | stats.end(); 99 | } 100 | }; 101 | 102 | animate(); 103 | }; 104 | 105 | // for faster initial loads, the loading screen is not a React component 106 | // TODO: Maybe move over to React-Static 107 | const startButtonEl = document.querySelector('.start-button')!; 108 | const introBlock = document.querySelector('.intro-block')!; 109 | 110 | startButtonEl.textContent = 'Loading Model...'; 111 | 112 | resize(); 113 | initFaceMesh(() => { 114 | startButtonEl.classList.remove('loading'); 115 | startButtonEl.textContent = 'Start'; 116 | 117 | startButtonEl.addEventListener('click', () => { 118 | if (fscreen.fullscreenEnabled) { 119 | fscreen.requestFullscreen(document.body); 120 | } 121 | introBlock.classList.add('fade'); 122 | }); 123 | }); 124 | 125 | export const showIntroBlock = () => { 126 | if (fscreen.fullscreenEnabled) { 127 | fscreen.exitFullscreen(); 128 | } 129 | introBlock.classList.remove('fade'); 130 | }; 131 | -------------------------------------------------------------------------------- /src/sketches/Devil.js: -------------------------------------------------------------------------------- 1 | import { 2 | EffectPass, 3 | BlurPass, 4 | KernelSize, 5 | SavePass, 6 | TextureEffect, 7 | BlendFunction, 8 | RenderPass, 9 | } from 'postprocessing'; 10 | 11 | import { 12 | Mesh, 13 | MeshBasicMaterial, 14 | Scene, 15 | ShaderMaterial, 16 | TextureLoader, 17 | Vector2, 18 | Vector3, 19 | } from 'three'; 20 | import { orthCam, renderPass as eyesRenderPass, webcamEffect } from '../setup'; 21 | 22 | import { faceGeometry, metrics } from '../faceMesh'; 23 | import { SmokeEffect } from '../effects/SmokeEffect'; 24 | 25 | import eyesUrl from '../assets/eyes_inverted.png'; 26 | import mouthUrl from '../assets/mouth_bottom_inverted.png'; 27 | 28 | import hornsUrl from '../assets/horns.jpg'; 29 | import hornsColUrl from '../assets/horns_col.png'; 30 | import faceEdgeUrl from '../assets/face_edges.jpg'; 31 | 32 | import vert from '../glsl/faceBulge/vertex.glsl'; 33 | import frag from '../glsl/horns/fragment.glsl'; 34 | import { camTextureFlipped } from '../webcam'; 35 | 36 | const eyesTex = new TextureLoader().load(eyesUrl); 37 | const eyesMat = new MeshBasicMaterial({ 38 | map: eyesTex, 39 | transparent: true, 40 | }); 41 | 42 | const mouthTex = new TextureLoader().load(mouthUrl); 43 | const mouthMat = new MeshBasicMaterial({ 44 | map: mouthTex, 45 | transparent: true, 46 | }); 47 | 48 | const mouthScene = new Scene(); 49 | const mouthRenderPass = new RenderPass(mouthScene, orthCam); 50 | 51 | const hornsScene = new Scene(); 52 | const hornsRenderPass = new RenderPass(hornsScene, orthCam); 53 | 54 | const hornsTex = new TextureLoader().load(hornsUrl); 55 | const hornsColTex = new TextureLoader().load(hornsColUrl); 56 | const faceEdgeTex = new TextureLoader().load(faceEdgeUrl); 57 | 58 | export class Devil { 59 | constructor({ composer, scene: eyesScene }) { 60 | // Add mesh with horns displacement material 61 | this.hornsMat = new ShaderMaterial({ 62 | uniforms: { 63 | time: { value: 1.0 }, 64 | camTex: { value: camTextureFlipped }, 65 | faceHighlightsTex: { value: hornsTex }, 66 | overlayTex: { value: hornsColTex }, 67 | faceEdgeTex: { value: faceEdgeTex }, 68 | masterNormal: { value: new Vector3() }, 69 | baseDisplacement: { value: 300 }, 70 | animatedDisplacementAmp: { value: 0 }, 71 | animatedNormalAmp: { value: 0 }, 72 | }, 73 | vertexShader: vert, 74 | fragmentShader: frag, 75 | }); 76 | const hornsMesh = new Mesh(faceGeometry, this.hornsMat); 77 | hornsScene.add(hornsMesh); 78 | 79 | // Add mesh with eyes texture 80 | const eyesMesh = new Mesh(faceGeometry, eyesMat); 81 | eyesScene.add(eyesMesh); 82 | // Add mesh with mouth texture 83 | const mouthMesh = new Mesh(faceGeometry, mouthMat); 84 | mouthScene.add(mouthMesh); 85 | 86 | // Setup all the passes used below 87 | const saveEyesSmokePass = new SavePass(); 88 | const saveMouthSmokePass = new SavePass(); 89 | const saveHornsPass = new SavePass(); 90 | 91 | this.eyesSmokeEffect = new SmokeEffect({ 92 | prevFrameTex: saveEyesSmokePass.renderTarget.texture, 93 | smokeColorHSL: new Vector3(1, 1, 1), 94 | smokeTextureAmp: 0, 95 | smokeVelocity: new Vector2(0, 0.002), 96 | smokeDecay: 0.04, 97 | smokeRot: 0, 98 | }); 99 | 100 | this.mouthSmokeEffect = new SmokeEffect({ 101 | prevFrameTex: saveMouthSmokePass.renderTarget.texture, 102 | smokeColorHSL: new Vector3(1, 0.8, 0.2), 103 | smokeTextureAmp: 0.1, 104 | smokeVelocity: new Vector2(0, -0.005), 105 | noiseAmp: new Vector2(0.2, 0.5), 106 | smokeDecay: 0, 107 | smokeRot: 0, 108 | }); 109 | 110 | const eyesSmokeEffectPass = new EffectPass(null, this.eyesSmokeEffect); 111 | const mouthSmokeEffectPass = new EffectPass(null, this.mouthSmokeEffect); 112 | 113 | const hornsTexEffect = new TextureEffect({ 114 | texture: saveHornsPass.renderTarget.texture, 115 | blendFunction: BlendFunction.ALPHA, 116 | }); 117 | 118 | const smokeEyesTexEffect = new TextureEffect({ 119 | texture: saveEyesSmokePass.renderTarget.texture, 120 | blendFunction: BlendFunction.ALPHA, 121 | }); 122 | 123 | const smokeMouthTexEffect = new TextureEffect({ 124 | texture: saveMouthSmokePass.renderTarget.texture, 125 | blendFunction: BlendFunction.ALPHA, 126 | }); 127 | 128 | const compositeLayersPass = new EffectPass( 129 | null, 130 | webcamEffect, 131 | hornsTexEffect, 132 | smokeEyesTexEffect, 133 | smokeMouthTexEffect 134 | ); 135 | 136 | const blurPass = new BlurPass({ 137 | KernelSize: KernelSize.SMALL, 138 | }); 139 | blurPass.scale = 0.001; 140 | 141 | // Render face with horns (and skin) 142 | composer.addPass(hornsRenderPass); 143 | // Save frame to be composited later 144 | composer.addPass(saveHornsPass); 145 | 146 | // Render eyes 147 | composer.addPass(eyesRenderPass); 148 | // Add previous frame and manipulate for smoke effect 149 | composer.addPass(eyesSmokeEffectPass); 150 | // Blur each frame 151 | composer.addPass(blurPass); 152 | // Save frame to be fed into next frame 153 | composer.addPass(saveEyesSmokePass); 154 | 155 | // Render mouth 156 | composer.addPass(mouthRenderPass); 157 | // Add previous frame and manipulate for smoke effect 158 | composer.addPass(mouthSmokeEffectPass); 159 | // Save frame to be fed into next frame 160 | composer.addPass(saveMouthSmokePass); 161 | 162 | // Render webcam image and overlay smoke 163 | composer.addPass(compositeLayersPass); 164 | 165 | this.frame = 0; 166 | } 167 | 168 | update({ elapsedS }) { 169 | this.hornsMat.uniforms.time.value = elapsedS; 170 | 171 | this.hornsMat.uniforms.baseDisplacement.value = 172 | 100 * metrics.zed + metrics.mouthOpenness * 200; 173 | 174 | this.eyesSmokeEffect.uniforms.get('frame').value = this.frame; 175 | this.mouthSmokeEffect.uniforms.get('frame').value = this.frame; 176 | 177 | this.frame++; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/sketches/Drift.js: -------------------------------------------------------------------------------- 1 | import { 2 | EffectPass, 3 | SavePass, 4 | TextureEffect, 5 | BlendFunction, 6 | } from 'postprocessing'; 7 | 8 | import { Mesh, MeshBasicMaterial, TextureLoader } from 'three'; 9 | import { renderPass, webcamEffect } from '../setup'; 10 | 11 | import { faceGeometry, metrics } from '../faceMesh'; 12 | import { DriftEffect } from '../effects/DriftEffect'; 13 | 14 | import eyesMouthUrl from '../assets/eyes_mouth_inverted.png'; 15 | import { clamp } from '../utils/clamp'; 16 | 17 | export const easeFunc = t => t * t; 18 | 19 | const eyesMouthTex = new TextureLoader().load(eyesMouthUrl); 20 | const mat = new MeshBasicMaterial({ 21 | map: eyesMouthTex, 22 | transparent: true, 23 | }); 24 | 25 | export class Drift { 26 | constructor({ composer, scene }) { 27 | this.messageText = 'Hold still, let your mind drift...'; 28 | 29 | // Add mesh with head outline 30 | const mesh = new Mesh(faceGeometry, mat); 31 | scene.add(mesh); 32 | 33 | // Setup all the passes used below 34 | const camPass = new EffectPass(null, webcamEffect); 35 | 36 | const saveDriftPass = new SavePass(); 37 | const saveFeaturesPass = new SavePass(); 38 | 39 | this.driftEffect = new DriftEffect({ 40 | prevFrameTex: saveDriftPass.renderTarget.texture, 41 | featuresTex: saveFeaturesPass.renderTarget.texture, 42 | frame: 0, 43 | }); 44 | 45 | const driftEffectPass = new EffectPass(null, this.driftEffect); 46 | 47 | const driftTexEffect = new TextureEffect({ 48 | texture: saveDriftPass.renderTarget.texture, 49 | blendFunction: BlendFunction.ALPHA, 50 | }); 51 | 52 | const overlayDriftPass = new EffectPass(null, webcamEffect, driftTexEffect); 53 | 54 | composer.addPass(renderPass); 55 | composer.addPass(saveFeaturesPass); 56 | // Render camera 57 | composer.addPass(camPass); 58 | // Do melt effect 59 | composer.addPass(driftEffectPass); 60 | // Save frame to be fed into next frame 61 | composer.addPass(saveDriftPass); 62 | // Render webcam image and overlay melt 63 | composer.addPass(overlayDriftPass); 64 | 65 | this.frame = 0; 66 | this.checkTime = 0; 67 | this.prevLength = metrics.track.position.lengthSq(); 68 | this.motion = 1; 69 | this.drift = 0; 70 | this.mask = 1; 71 | } 72 | 73 | update({ elapsedS, deltaFPS }) { 74 | if (elapsedS - this.checkTime > 0.1) { 75 | // Roughly calculate motion every 0.1s 76 | // TODO: Distance will be greater if face is nearer to camera 77 | const currLength = metrics.track.position.lengthSq(); 78 | this.motion = 79 | Math.abs(currLength / this.prevLength - 1) + metrics.mouthOpenness; 80 | this.prevLength = currLength; 81 | this.checkTime = elapsedS; 82 | } 83 | 84 | let easedDrift; 85 | 86 | if (this.motion < 0.1) { 87 | this.drift = clamp(this.drift + 0.004 * deltaFPS, 0, 1); 88 | // Quickly hide the mask when we want to slowly melt 89 | this.mask = clamp(this.mask + 0.04 * deltaFPS, -1, 1); 90 | easedDrift = easeFunc(this.drift); 91 | 92 | if (this.drift > 0.8) { 93 | this.hideText(); 94 | } 95 | } else { 96 | // Slowly bring in the mask when we want to fade in the cam 97 | this.mask = clamp(this.mask - 0.02 * deltaFPS, -1, 1); 98 | this.drift = clamp(this.drift - 0.01 * deltaFPS, 0, 1); 99 | easedDrift = this.drift; 100 | } 101 | 102 | this.driftEffect.uniforms.get('driftAmp').value = easedDrift; 103 | this.driftEffect.uniforms.get('maskAmp').value = this.mask; 104 | this.driftEffect.uniforms.get('frame').value = this.frame; 105 | 106 | this.frame++; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/sketches/Lumpy.js: -------------------------------------------------------------------------------- 1 | import { EffectPass } from 'postprocessing'; 2 | 3 | import { Mesh, TextureLoader, ShaderMaterial, Vector3 } from 'three'; 4 | 5 | import { renderPass, webcamEffect } from '../setup'; 6 | 7 | import { faceGeometry, metrics } from '../faceMesh'; 8 | import { camTextureFlipped } from '../webcam'; 9 | 10 | import faceHighlightsUrl from '../assets/face_highlights.jpg'; 11 | 12 | import vert from '../glsl/faceBulge/vertex.glsl'; 13 | import frag from '../glsl/faceBulge/fragment.glsl'; 14 | 15 | const faceHighlightsTex = new TextureLoader().load(faceHighlightsUrl); 16 | 17 | export class Lumpy { 18 | constructor({ composer, scene }) { 19 | this.mat = new ShaderMaterial({ 20 | uniforms: { 21 | time: { value: 1.0 }, 22 | camTex: { value: camTextureFlipped }, 23 | faceHighlightsTex: { value: faceHighlightsTex }, 24 | masterNormal: { value: new Vector3() }, 25 | baseDisplacement: { value: 0 }, 26 | animatedDisplacementAmp: { value: 50 }, 27 | animatedNormalAmp: { value: 1 }, 28 | }, 29 | vertexShader: vert, 30 | fragmentShader: frag, 31 | }); 32 | 33 | // Add mesh with face as texture 34 | const mesh = new Mesh(faceGeometry, this.mat); 35 | scene.add(mesh); 36 | 37 | // Setup all the passes used below 38 | const camPass = new EffectPass(null, webcamEffect); 39 | 40 | camPass.renderToScreen = true; 41 | renderPass.renderToScreen = true; 42 | renderPass.clear = false; 43 | 44 | composer.addPass(camPass); 45 | composer.addPass(renderPass); 46 | } 47 | 48 | update({ elapsedS }) { 49 | this.mat.uniforms.time.value = elapsedS; 50 | this.mat.uniforms.animatedDisplacementAmp.value = 50 * metrics.zed; 51 | this.mat.uniforms.masterNormal.value.copy(metrics.track.normal); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/sketches/Melt.js: -------------------------------------------------------------------------------- 1 | import { 2 | EffectPass, 3 | SavePass, 4 | TextureEffect, 5 | BlendFunction, 6 | MaskPass, 7 | ClearMaskPass, 8 | } from 'postprocessing'; 9 | 10 | import { Mesh, MeshBasicMaterial, TextureLoader } from 'three'; 11 | import { orthCam, renderPass, webcamEffect } from '../setup'; 12 | 13 | import { faceGeometry, metrics } from '../faceMesh'; 14 | import { MeltEffect } from '../effects/MeltEffect'; 15 | 16 | import eyesMouthUrl from '../assets/eyes_mouth_inverted.png'; 17 | 18 | const eyesMouthTex = new TextureLoader().load(eyesMouthUrl); 19 | const mat = new MeshBasicMaterial({ 20 | map: eyesMouthTex, 21 | transparent: true, 22 | }); 23 | 24 | export class Melt { 25 | constructor({ composer, scene }) { 26 | // Add mesh with head outline 27 | const mesh = new Mesh(faceGeometry, mat); 28 | scene.add(mesh); 29 | 30 | // Setup all the passes used below 31 | const headMaskPass = new MaskPass(scene, orthCam); 32 | const clearMaskPass = new ClearMaskPass(); 33 | 34 | const camPass = new EffectPass(null, webcamEffect); 35 | 36 | const saveMeltPass = new SavePass(); 37 | const saveFeaturesPass = new SavePass(); 38 | 39 | this.meltEffect = new MeltEffect({ 40 | prevFrameTex: saveMeltPass.renderTarget.texture, 41 | featuresTex: saveFeaturesPass.renderTarget.texture, 42 | frame: 0, 43 | }); 44 | 45 | const meltEffectPass = new EffectPass(null, this.meltEffect); 46 | 47 | const meltTexEffect = new TextureEffect({ 48 | texture: saveMeltPass.renderTarget.texture, 49 | blendFunction: BlendFunction.ALPHA, 50 | }); 51 | 52 | const overlayMeltPass = new EffectPass(null, webcamEffect, meltTexEffect); 53 | 54 | composer.addPass(renderPass); 55 | composer.addPass(saveFeaturesPass); 56 | // Mask head 57 | composer.addPass(headMaskPass); 58 | // Render camera 59 | composer.addPass(camPass); 60 | // Clear mask 61 | composer.addPass(clearMaskPass); 62 | // Do melt effect 63 | composer.addPass(meltEffectPass); 64 | // Save frame to be fed into next frame 65 | composer.addPass(saveMeltPass); 66 | // Render webcam image and overlay melt 67 | composer.addPass(overlayMeltPass); 68 | 69 | this.frame = 0; 70 | } 71 | 72 | update() { 73 | this.meltEffect.uniforms.get('frame').value = this.frame; 74 | 75 | this.meltEffect.uniforms.get('noiseStrength').value = 76 | 5 + metrics.mouthOpenness * 10; 77 | 78 | this.meltEffect.uniforms.get('featuresStrength').value = 79 | metrics.mouthOpenness * 2; 80 | 81 | this.frame++; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/sketches/Smoke.js: -------------------------------------------------------------------------------- 1 | import { 2 | EffectPass, 3 | BlurPass, 4 | KernelSize, 5 | SavePass, 6 | TextureEffect, 7 | BlendFunction, 8 | } from 'postprocessing'; 9 | 10 | import { 11 | Mesh, 12 | MeshBasicMaterial, 13 | TextureLoader, 14 | Vector3, 15 | Vector2, 16 | } from 'three'; 17 | import { renderPass, webcamEffect } from '../setup'; 18 | 19 | import { faceGeometry } from '../faceMesh'; 20 | import { SmokeEffect } from '../effects/SmokeEffect'; 21 | 22 | import eyesMouthUrl from '../assets/eyes_mouth_inverted.png'; 23 | 24 | const eyesMouthTex = new TextureLoader().load(eyesMouthUrl); 25 | const mat = new MeshBasicMaterial({ 26 | map: eyesMouthTex, 27 | transparent: true, 28 | }); 29 | 30 | export class Smoke { 31 | constructor({ composer, scene }) { 32 | // Add mesh with eyes/mouth/nostrils texture 33 | const mesh = new Mesh(faceGeometry, mat); 34 | scene.add(mesh); 35 | 36 | // Setup all the passes used below 37 | const saveSmokePass = new SavePass(); 38 | 39 | this.smokeEffect = new SmokeEffect({ 40 | prevFrameTex: saveSmokePass.renderTarget.texture, 41 | frame: 0, 42 | smokeColorHSL: new Vector3(0.95, 0.9, 0.6), 43 | smokeTextureAmp: 0.1, 44 | smokeVelocity: new Vector2(0, 0.004), 45 | smokeDecay: 0.001, 46 | smokeRot: 0.01, 47 | }); 48 | 49 | const smokeEffectPass = new EffectPass(null, this.smokeEffect); 50 | 51 | const smokeTexEffect = new TextureEffect({ 52 | texture: saveSmokePass.renderTarget.texture, 53 | blendFunction: BlendFunction.ALPHA, 54 | }); 55 | 56 | const overlaySmokePass = new EffectPass(null, webcamEffect, smokeTexEffect); 57 | 58 | const blurPass = new BlurPass({ 59 | KernelSize: KernelSize.SMALL, 60 | }); 61 | blurPass.scale = 0.2; 62 | 63 | // Render eyes, mouth, nostrils 64 | composer.addPass(renderPass); 65 | // Add previous frame and manipulate for smoke effect 66 | composer.addPass(smokeEffectPass); 67 | // Blur each frame 68 | composer.addPass(blurPass); 69 | // Save frame to be fed into next frame 70 | composer.addPass(saveSmokePass); 71 | // Render webcam image and overlay smoke 72 | composer.addPass(overlaySmokePass); 73 | 74 | this.frame = 0; 75 | } 76 | 77 | update() { 78 | this.smokeEffect.uniforms.get('frame').value = this.frame; 79 | 80 | this.frame++; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/sketches/Tunnel.js: -------------------------------------------------------------------------------- 1 | import { EffectPass, SavePass } from 'postprocessing'; 2 | 3 | import { Mesh, TextureLoader, ShaderMaterial } from 'three'; 4 | 5 | import { renderPass } from '../setup'; 6 | 7 | import { faceGeometry, metrics } from '../faceMesh'; 8 | import { camTextureFlipped } from '../webcam'; 9 | 10 | import { TunnelEffect } from '../effects/TunnelEffect'; 11 | 12 | import eyesDisplacementUrl from '../assets/eyes_displacement.png'; 13 | 14 | import vert from '../glsl/texDisplace/vertex.glsl'; 15 | import frag from '../glsl/texDisplace/fragment.glsl'; 16 | 17 | const dispTex = new TextureLoader().load(eyesDisplacementUrl); 18 | 19 | export class Tunnel { 20 | constructor({ composer, scene }) { 21 | this.mat = new ShaderMaterial({ 22 | uniforms: { 23 | time: { value: 1.0 }, 24 | camTex: { value: camTextureFlipped }, 25 | dispTex: { value: dispTex }, 26 | masterDisp: { value: 1 }, 27 | }, 28 | 29 | vertexShader: vert, 30 | fragmentShader: frag, 31 | // wireframe: true, 32 | }); 33 | 34 | // Add mesh with face as texture 35 | const mesh = new Mesh(faceGeometry, this.mat); 36 | scene.add(mesh); 37 | 38 | // Setup all the passes used below 39 | const saveFacePass = new SavePass(); 40 | const saveAllPass = new SavePass(); 41 | const finalSavePass = new SavePass(); 42 | 43 | const tunnelEffect = new TunnelEffect({ 44 | prevFrameTex: saveAllPass.renderTarget.texture, 45 | }); 46 | 47 | const tunnelPass = new EffectPass(null, tunnelEffect); 48 | 49 | finalSavePass.renderToScreen = true; 50 | 51 | composer.addPass(renderPass); 52 | composer.addPass(saveFacePass); 53 | composer.addPass(tunnelPass); 54 | composer.addPass(saveAllPass); 55 | composer.addPass(finalSavePass); 56 | } 57 | 58 | update({ elapsedS }) { 59 | this.mat.uniforms.time.value = elapsedS; 60 | this.mat.uniforms.masterDisp.value = metrics.zed / metrics.zed / 2.5; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/sketches/index.ts: -------------------------------------------------------------------------------- 1 | import { EffectComposer } from 'postprocessing'; 2 | import { Scene } from 'three'; 3 | 4 | import { AnimationInfo } from '../setup'; 5 | import { Smoke } from './Smoke'; 6 | import { Melt } from './Melt'; 7 | import { Tunnel } from './Tunnel'; 8 | import { Lumpy } from './Lumpy'; 9 | import { Drift } from './Drift'; 10 | import { Devil } from './Devil'; 11 | 12 | interface SketchConstructorArgs { 13 | composer: EffectComposer; 14 | scene: Scene; 15 | } 16 | 17 | export interface SketchConstructor { 18 | new (arg0: SketchConstructorArgs): SketchInterface; 19 | } 20 | 21 | export interface SketchInterface { 22 | update?(arg0: AnimationInfo): void; 23 | messageText?: string; 24 | hideText?: () => void; 25 | } 26 | 27 | export interface SketchItem { 28 | Module: SketchConstructor; 29 | icon: string; 30 | } 31 | 32 | export const sketches: SketchItem[] = [ 33 | { 34 | Module: Smoke, 35 | icon: '🚬', 36 | }, 37 | { 38 | Module: Melt, 39 | icon: '💊', 40 | }, 41 | { 42 | Module: Tunnel, 43 | icon: '🌈', 44 | }, 45 | { 46 | Module: Lumpy, 47 | icon: '🥴', 48 | }, 49 | { 50 | Module: Drift, 51 | icon: '🧘🏻', 52 | }, 53 | { 54 | Module: Devil, 55 | icon: '👹', 56 | }, 57 | ]; 58 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /*! Flickity v2.1.2 2 | https://flickity.metafizzy.co 3 | ---------------------------------------------- */ 4 | .flickity-enabled { 5 | position: relative; 6 | } 7 | 8 | .flickity-enabled:focus { 9 | outline: none; 10 | } 11 | 12 | .flickity-viewport { 13 | position: relative; 14 | height: 100%; 15 | overflow: hidden; 16 | } 17 | 18 | .flickity-slider { 19 | position: absolute; 20 | width: 100%; 21 | height: 100%; 22 | } 23 | 24 | /* draggable */ 25 | 26 | .flickity-enabled.is-draggable { 27 | -webkit-tap-highlight-color: transparent; 28 | tap-highlight-color: transparent; 29 | -webkit-user-select: none; 30 | -moz-user-select: none; 31 | -ms-user-select: none; 32 | user-select: none; 33 | } 34 | 35 | .flickity-enabled.is-draggable .flickity-viewport { 36 | cursor: move; 37 | cursor: -webkit-grab; 38 | cursor: grab; 39 | } 40 | 41 | .flickity-enabled.is-draggable .flickity-viewport.is-pointer-down { 42 | cursor: -webkit-grabbing; 43 | cursor: grabbing; 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/clamp.ts: -------------------------------------------------------------------------------- 1 | export const clamp = (value: number, min: number, max: number) => 2 | Math.max(min, Math.min(value, max)); 3 | -------------------------------------------------------------------------------- /src/utils/getResizeFactors.ts: -------------------------------------------------------------------------------- 1 | export const getResizeFactors = ( 2 | dWidth: number, 3 | dHeight: number, 4 | sWidth: number, 5 | sHeight: number 6 | ) => { 7 | let x; 8 | let y; 9 | 10 | if (sWidth / sHeight > dWidth / dHeight) { 11 | // Source has wider aspect ratio than destination 12 | x = (dWidth / sWidth) * (sHeight / dHeight); 13 | y = 1; 14 | } else { 15 | // Source has narrower aspect ratio than destination 16 | x = 1; 17 | y = (sWidth / dWidth) * (dHeight / sHeight); 18 | } 19 | 20 | return { x, y }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/resizeTexture.ts: -------------------------------------------------------------------------------- 1 | import { VideoTexture } from 'three'; 2 | import { getResizeFactors } from './getResizeFactors'; 3 | 4 | export const resizeTexture = ( 5 | texture: VideoTexture, 6 | dWidth: number, 7 | dHeight: number, 8 | sWidth: number, 9 | sHeight: number 10 | ) => { 11 | texture.center.set(0.5, 0.5); 12 | 13 | const { x, y } = getResizeFactors(dWidth, dHeight, sWidth, sHeight); 14 | 15 | texture.repeat.x = x; 16 | texture.repeat.y = y; 17 | }; 18 | -------------------------------------------------------------------------------- /src/webcam.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { resizeTexture } from './utils/resizeTexture'; 3 | import { devMode } from '../settings'; 4 | import { appState } from './appState'; 5 | 6 | export const video = document.createElement('video'); 7 | export const camTexture = new THREE.VideoTexture(video); 8 | export const camTextureFlipped = new THREE.VideoTexture(video); 9 | 10 | // bunch of settings to make sure things work in iOS 11 | video.loop = true; 12 | video.muted = true; 13 | video.setAttribute('playsinline', 'true'); 14 | video.setAttribute('preload', 'auto'); 15 | video.autoplay = true; 16 | 17 | if (devMode.fakeCam) { 18 | video.src = devMode.fakeCam; 19 | video.play(); 20 | appState.camOn = true; 21 | } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { 22 | const constraints = { 23 | video: { facingMode: 'user' }, 24 | }; 25 | 26 | navigator.mediaDevices 27 | .getUserMedia(constraints) 28 | .then(stream => { 29 | video.srcObject = stream; 30 | video.play(); 31 | appState.camOn = true; 32 | }) 33 | .catch(function(error) { 34 | alert(`Unable to access webcam: ${error.message}`); 35 | console.log(error); 36 | }); 37 | } else { 38 | alert('No webcam detected!'); 39 | } 40 | 41 | const resize = () => { 42 | resizeTexture( 43 | camTexture, 44 | window.innerWidth, 45 | window.innerHeight, 46 | video.videoWidth, 47 | video.videoHeight 48 | ); 49 | 50 | resizeTexture( 51 | camTextureFlipped, 52 | window.innerWidth, 53 | window.innerHeight, 54 | video.videoWidth, 55 | video.videoHeight 56 | ); 57 | 58 | if (camTextureFlipped.repeat.x > 0) { 59 | camTextureFlipped.repeat.x *= -1; // Mirror cam 60 | } 61 | }; 62 | 63 | window.addEventListener('resize', resize); 64 | video.addEventListener('loadedmetadata', resize); 65 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "outDir": "dist/", 5 | "noImplicitAny": true, 6 | "removeComments": true, 7 | "preserveConstEnums": true, 8 | "sourceMap": true, 9 | "target": "ESNext", 10 | "allowJs": true, 11 | "checkJs": false, 12 | "jsx": "react", 13 | "pretty": true, 14 | "skipLibCheck": false, 15 | "strict": true, 16 | "moduleResolution": "node", 17 | "esModuleInterop": true, 18 | "lib": ["dom", "dom.iterable", "ESNext"], 19 | "allowSyntheticDefaultImports": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "resolveJsonModule": true, 22 | "isolatedModules": true 23 | }, 24 | "include": ["src", "postprocessing.d.ts"], 25 | "exclude": ["node_modules", "**/*.spec.ts"] 26 | } -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: ['./src/index.tsx'], 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.(js)$/, 10 | exclude: /node_modules/, 11 | use: ['babel-loader'], 12 | }, 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.css$/i, 20 | use: ['style-loader', 'css-loader'], 21 | }, 22 | { 23 | test: /\.(png|jpg|gif)$/i, 24 | use: ['file-loader'], 25 | }, 26 | { 27 | test: /\.(glsl|frag|vert)$/, 28 | use: ['raw-loader', 'glslify-loader'], 29 | }, 30 | ], 31 | }, 32 | resolve: { 33 | extensions: ['.tsx', '.ts', '.js'], 34 | }, 35 | plugins: [ 36 | new HtmlWebpackPlugin({ 37 | template: './src/index.html', 38 | minify: { 39 | minifyCSS: true, 40 | collapseWhitespace: true, 41 | keepClosingSlash: true, 42 | removeComments: true, 43 | removeRedundantAttributes: true, 44 | removeScriptTypeAttributes: true, 45 | removeStyleLinkTypeAttributes: true, 46 | useShortDoctype: true 47 | } 48 | }), 49 | new CleanWebpackPlugin() 50 | ], 51 | }; 52 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const { merge } = require('webpack-merge'); 5 | const commonConfig = require('./webpack.common'); 6 | 7 | module.exports = merge(commonConfig, { 8 | mode: 'development', 9 | watch: true, 10 | devtool: 'inline-source-map', 11 | plugins: [ 12 | new CopyWebpackPlugin({ 13 | patterns: [{ from: 'test-video' }], 14 | }), 15 | ], 16 | devServer: { 17 | contentBase: path.resolve(__dirname, 'dist'), 18 | port: 8080, 19 | // inline: false, // Uncomment when testing iOS 20 | https: { 21 | key: fs.readFileSync('./certs/key.pem'), 22 | cert: fs.readFileSync('./certs/cert.pem'), 23 | }, 24 | host: '0.0.0.0', 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | // const TerserPlugin = require('terser-webpack-plugin'); 2 | const { merge } = require('webpack-merge'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 5 | const commonConfig = require('./webpack.common'); 6 | 7 | module.exports = merge(commonConfig, { 8 | mode: 'production', 9 | output: { 10 | path: `${__dirname}/dist`, 11 | publicPath: '/instatrip/', 12 | filename: 'app.min.js', 13 | }, 14 | plugins: [ 15 | new CopyWebpackPlugin({ 16 | patterns: [{ from: 'public' }], 17 | }), 18 | // Useful for analysing bundle, keep commented out otherwise (breaks netlify build) 19 | // new BundleAnalyzerPlugin(), 20 | ], 21 | }); 22 | --------------------------------------------------------------------------------