├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── api-extractor.json ├── deploy-avatar-viewer.sh ├── docs ├── index.md ├── three-avatar.addmirrorhud.md ├── three-avatar.addmirrorhudoptions.distancenonvr.md ├── three-avatar.addmirrorhudoptions.distancevr.md ├── three-avatar.addmirrorhudoptions.md ├── three-avatar.addmirrorhudoptions.xr.md ├── three-avatar.autowalker.md ├── three-avatar.autowalker.setup.md ├── three-avatar.autowalker.tick.md ├── three-avatar.avatar._constructor_.md ├── three-avatar.avatar.addextension.md ├── three-avatar.avatar.dispose.md ├── three-avatar.avatar.faceexpressionnames.md ├── three-avatar.avatar.facemesh.md ├── three-avatar.avatar.firstpersononlylayer.md ├── three-avatar.avatar.getheadheight.md ├── three-avatar.avatar.getsyncbonesdata.md ├── three-avatar.avatar.headbone.md ├── three-avatar.avatar.headboneoffset.md ├── three-avatar.avatar.headsync.md ├── three-avatar.avatar.height.md ├── three-avatar.avatar.iktargetleftarmbones.md ├── three-avatar.avatar.iktargetrightarmbones.md ├── three-avatar.avatar.invisiblefirstperson.md ├── three-avatar.avatar.isikmode.md ├── three-avatar.avatar.lipsync.md ├── three-avatar.avatar.md ├── three-avatar.avatar.object3d.md ├── three-avatar.avatar.playclip.md ├── three-avatar.avatar.removeextension.md ├── three-avatar.avatar.resetfaceexpression.md ├── three-avatar.avatar.setfaceexpression.md ├── three-avatar.avatar.setfirstpersonmode.md ├── three-avatar.avatar.setikmode.md ├── three-avatar.avatar.setsyncbonesdata.md ├── three-avatar.avatar.setthirdpersonmode.md ├── three-avatar.avatar.stopclip.md ├── three-avatar.avatar.synctargetbones.md ├── three-avatar.avatar.thirdpersononlylayer.md ├── three-avatar.avatar.tick.md ├── three-avatar.avatar.type.md ├── three-avatar.avatar.vrm.md ├── three-avatar.avatar.widthx.md ├── three-avatar.avatar.widthz.md ├── three-avatar.avataranimationdatasource.md ├── three-avatar.avatarextension.dispose.md ├── three-avatar.avatarextension.md ├── three-avatar.avatarextension.setup.md ├── three-avatar.avatarextension.tick.md ├── three-avatar.avatarik._constructor_.md ├── three-avatar.avatarik.dispose.md ├── three-avatar.avatarik.md ├── three-avatar.avatarik.tick.md ├── three-avatar.avatarmodel.md ├── three-avatar.avatarmodel.model.md ├── three-avatar.avatarmodel.vrm.md ├── three-avatar.avataroptions.animationintervalsec.md ├── three-avatar.avataroptions.firstpersononlylayer.md ├── three-avatar.avataroptions.md ├── three-avatar.avataroptions.thirdpersononlylayer.md ├── three-avatar.avatartype.md ├── three-avatar.avatartypereadyplayerme.md ├── three-avatar.avatartypevrmv0.md ├── three-avatar.avatartypevrmv1.md ├── three-avatar.blinker.md ├── three-avatar.blinker.setup.md ├── three-avatar.blinker.tick.md ├── three-avatar.createavatar.md ├── three-avatar.createavatarik.md ├── three-avatar.createavatarikoptions.intervalsec.md ├── three-avatar.createavatarikoptions.isdebug.md ├── three-avatar.createavatarikoptions.md ├── three-avatar.createavatarikoptions.rotationlimitset.md ├── three-avatar.createavatarikoptions.wristrotationoffsetset.md ├── three-avatar.createavataroptions.isinvisiblefirstperson.md ├── three-avatar.createavataroptions.islowspecmode.md ├── three-avatar.createavataroptions.md ├── three-avatar.decordersoptions.md ├── three-avatar.getdefaultrotationlimitset.md ├── three-avatar.getdefaultwristrotationoffsetset.md ├── three-avatar.getrenderinfo.md ├── three-avatar.iktargetbones.md ├── three-avatar.isandroid.md ├── three-avatar.isanimationdataloaded.md ├── three-avatar.isios.md ├── three-avatar.islowspecdevice.md ├── three-avatar.istouchdevice.md ├── three-avatar.lipsync.md ├── three-avatar.loadavatarmodel.md ├── three-avatar.md ├── three-avatar.preloadanimationdata.md ├── three-avatar.registersyncavatarheadandcamera.md ├── three-avatar.rotationlimit.md ├── three-avatar.rotationlimitset.md ├── three-avatar.setdefaultextensions.md ├── three-avatar.setnonvrcameramode.md ├── three-avatar.simpleboundingboxcollider._constructor_.md ├── three-avatar.simpleboundingboxcollider.md ├── three-avatar.simpleboundingboxcollider.moveto.md ├── three-avatar.simpleboundingboxcollider.setup.md ├── three-avatar.vector3tupple.md ├── three-avatar.vrhandgetter.md └── three-avatar.wristrotationoffsetset.md ├── example ├── asset │ ├── animation │ │ ├── Breakdance Uprock Var 1.fbx │ │ ├── idle.fbx │ │ └── walk.fbx │ └── avatar-example │ │ ├── rpm.glb │ │ ├── vrm-v0.vrm │ │ └── vrm-v1.vrm ├── avatar.html ├── index.html ├── main.js ├── player-controller.js ├── setup.js └── world.js ├── package.json ├── src ├── avatar-ik.ts ├── avatar.ts ├── collider │ └── simple-bounding-box-collider.ts ├── defaults.ts ├── ext │ ├── auto-walker.ts │ ├── avatar-extension.ts │ └── blinker.ts ├── external │ └── threelipsync-mod │ │ ├── LICENSE.md │ │ ├── README.md │ │ └── threelipsync.ts ├── index.ts ├── loader.ts ├── mixamo.ts ├── types.ts └── util.ts ├── tsconfig.json └── watch.sh /.eslintignore: -------------------------------------------------------------------------------- 1 | src/external/threelipsync-mod/* 2 | dist/* 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 4 | root: true, 5 | env: { 6 | browser: true, 7 | es2021: true, 8 | }, 9 | parserOptions: { 10 | ecmaVersion: "latest", 11 | sourceType: "module", 12 | }, 13 | plugins: ["eslint-plugin-tsdoc"], 14 | rules: { 15 | "no-unused-vars": "off", 16 | "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], 17 | "@typescript-eslint/ban-ts-comment": "off", 18 | "tsdoc/syntax": "warn", 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | *.clean 4 | .DS_Store 5 | cert/ 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Appland, Inc 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # three-avatar · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/VerseEngine/three-avatar/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/@verseengine%2Fthree-avatar.svg?style=flat)](https://www.npmjs.com/package/@verseengine%2Fthree-avatar) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/VerseEngine/three-avatar/pulls) 2 | 3 | Avatar system for three.js. 4 | 5 | ## Features 6 | * Loads [VRM](https://vrm.dev/)/[VRoid](https://vroid.com/), [Ready Player Me](https://readyplayer.me/) 7 | * Play [mixamo](https://www.mixamo.com/) animation 8 | * Head sync for VR 9 | * Replaceable collision detection 10 | * Replaceable lip sync 11 | * Replaceable IK (Inverse Kinematics) 12 | * Show hand mirror 13 | 14 | ![preview](https://user-images.githubusercontent.com/20784450/211959656-70b52dad-d58e-4b10-86ac-8c38a4de1948.gif) 15 | 16 | ## Example 17 | ```bash 18 | npm run example 19 | ``` 20 | 21 | ## Installation 22 | ### npm 23 | ```bash 24 | npm install @verseengine/three-move-controller @verseengine/three-touch-controller @verseengine/three-xr-controller @verseengine/three-avatar 25 | ``` 26 | 27 | ### CDN (ES Mobules) 28 | ```html 29 | 33 | 45 | ``` 46 | 47 | 48 | ## Usage 49 | ```javascript 50 | import * as THREE from "three"; 51 | import * as THREE_VRM from "@pixiv/three-vrm"; 52 | import { 53 | createAvatarIK, 54 | isAnimationDataLoaded, 55 | preLoadAnimationData, 56 | createAvatar, 57 | Lipsync, 58 | } from "three-avatar"; 59 | 60 | // Animation fbx files downloaded from mixamo.com 61 | const ANIMATION_MAP = { 62 | idle: "path/to/idle.fbx", 63 | walk: "path/to/walk.fbx", 64 | dance: "path/to/dance.fbx", 65 | }; 66 | 67 | async function loadAvatar(avatarURL) { 68 | ... 69 | if (!isAnimationDataLoaded()) { 70 | await preLoadAnimationData(ANIMATION_MAP); 71 | } 72 | 73 | const collisionObjects:THREE.Object3D[] = [wall0, wall1, ...]; 74 | const teleportTargetObjects:THREE.Object3D[] = [ground0, ...]; 75 | const collisionBoxes = []; 76 | [...collisionObjects, ...teleportTargetObjects].map((el) => { 77 | el.traverse((c) => { 78 | if (!c.isMesh) { 79 | return; 80 | } 81 | collisionBoxes.push(new THREE.Box3().setFromObject(c)); 82 | }); 83 | }); 84 | 85 | let resp = await fetch(avatarURL); 86 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 87 | const avatar = await createAvatar( 88 | avatarData, 89 | renderer, 90 | false, 91 | avatarContainer, 92 | { 93 | getCollisionBoxes: () => collisionBoxes, 94 | isInvisibleFirstPerson: true, 95 | } 96 | ); 97 | avatarContainer.add(avatar.object3D); 98 | 99 | const touchController = new TouchController(avatarContainer); 100 | } 101 | 102 | const clock = new THREE.Clock(); 103 | function animate() { 104 | _avatar?.tick(clock.getDelta()); 105 | renderer.render(scene, camera); 106 | } 107 | renderer.setAnimationLoop(animate); 108 | ``` 109 | 110 | # Reference 111 | 112 | ## API Reference 113 | [Link](docs/three-avatar.md) 114 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | /** 2 | * Config file for API Extractor. For more info, please visit: https://api-extractor.com 3 | */ 4 | { 5 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 6 | 7 | /** 8 | * Optionally specifies another JSON config file that this file extends from. This provides a way for 9 | * standard settings to be shared across multiple projects. 10 | * 11 | * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains 12 | * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be 13 | * resolved using NodeJS require(). 14 | * 15 | * SUPPORTED TOKENS: none 16 | * DEFAULT VALUE: "" 17 | */ 18 | // "extends": "./shared/api-extractor-base.json" 19 | // "extends": "my-package/include/api-extractor-base.json" 20 | 21 | /** 22 | * Determines the "" token that can be used with other config file settings. The project folder 23 | * typically contains the tsconfig.json and package.json config files, but the path is user-defined. 24 | * 25 | * The path is resolved relative to the folder of the config file that contains the setting. 26 | * 27 | * The default value for "projectFolder" is the token "", which means the folder is determined by traversing 28 | * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder 29 | * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error 30 | * will be reported. 31 | * 32 | * SUPPORTED TOKENS: 33 | * DEFAULT VALUE: "" 34 | */ 35 | // "projectFolder": "..", 36 | 37 | /** 38 | * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor 39 | * analyzes the symbols exported by this module. 40 | * 41 | * The file extension must be ".d.ts" and not ".ts". 42 | * 43 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 44 | * prepend a folder token such as "". 45 | * 46 | * SUPPORTED TOKENS: , , 47 | */ 48 | "mainEntryPointFilePath": "/dist/esm/index.d.ts", 49 | 50 | /** 51 | * A list of NPM package names whose exports should be treated as part of this package. 52 | * 53 | * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", 54 | * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part 55 | * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly 56 | * imports library2. To avoid this, we can specify: 57 | * 58 | * "bundledPackages": [ "library2" ], 59 | * 60 | * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been 61 | * local files for library1. 62 | */ 63 | "bundledPackages": [], 64 | 65 | /** 66 | * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files 67 | * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. 68 | * To use the OS's default newline kind, specify "os". 69 | * 70 | * DEFAULT VALUE: "crlf" 71 | */ 72 | // "newlineKind": "crlf", 73 | 74 | /** 75 | * Set to true when invoking API Extractor's test harness. When `testMode` is true, the `toolVersion` field in the 76 | * .api.json file is assigned an empty string to prevent spurious diffs in output files tracked for tests. 77 | * 78 | * DEFAULT VALUE: "false" 79 | */ 80 | // "testMode": false, 81 | 82 | /** 83 | * Specifies how API Extractor sorts members of an enum when generating the .api.json file. By default, the output 84 | * files will be sorted alphabetically, which is "by-name". To keep the ordering in the source code, specify 85 | * "preserve". 86 | * 87 | * DEFAULT VALUE: "by-name" 88 | */ 89 | // "enumMemberOrder": "by-name", 90 | 91 | /** 92 | * Determines how the TypeScript compiler engine will be invoked by API Extractor. 93 | */ 94 | "compiler": { 95 | /** 96 | * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. 97 | * 98 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 99 | * prepend a folder token such as "". 100 | * 101 | * Note: This setting will be ignored if "overrideTsconfig" is used. 102 | * 103 | * SUPPORTED TOKENS: , , 104 | * DEFAULT VALUE: "/tsconfig.json" 105 | */ 106 | // "tsconfigFilePath": "/tsconfig.json", 107 | /** 108 | * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. 109 | * The object must conform to the TypeScript tsconfig schema: 110 | * 111 | * http://json.schemastore.org/tsconfig 112 | * 113 | * If omitted, then the tsconfig.json file will be read from the "projectFolder". 114 | * 115 | * DEFAULT VALUE: no overrideTsconfig section 116 | */ 117 | // "overrideTsconfig": { 118 | // . . . 119 | // } 120 | /** 121 | * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended 122 | * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when 123 | * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses 124 | * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. 125 | * 126 | * DEFAULT VALUE: false 127 | */ 128 | // "skipLibCheck": true, 129 | }, 130 | 131 | /** 132 | * Configures how the API report file (*.api.md) will be generated. 133 | */ 134 | "apiReport": { 135 | /** 136 | * (REQUIRED) Whether to generate an API report. 137 | */ 138 | "enabled": true, 139 | 140 | /** 141 | * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce 142 | * a full file path. 143 | * 144 | * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". 145 | * 146 | * SUPPORTED TOKENS: , 147 | * DEFAULT VALUE: ".api.md" 148 | */ 149 | "reportFileName": ".api.md", 150 | 151 | /** 152 | * Specifies the folder where the API report file is written. The file name portion is determined by 153 | * the "reportFileName" setting. 154 | * 155 | * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, 156 | * e.g. for an API review. 157 | * 158 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 159 | * prepend a folder token such as "". 160 | * 161 | * SUPPORTED TOKENS: , , 162 | * DEFAULT VALUE: "/temp/" 163 | */ 164 | "reportFolder": "/dist/temp/", 165 | 166 | /** 167 | * Specifies the folder where the temporary report file is written. The file name portion is determined by 168 | * the "reportFileName" setting. 169 | * 170 | * After the temporary file is written to disk, it is compared with the file in the "reportFolder". 171 | * If they are different, a production build will fail. 172 | * 173 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 174 | * prepend a folder token such as "". 175 | * 176 | * SUPPORTED TOKENS: , , 177 | * DEFAULT VALUE: "/temp/" 178 | */ 179 | "reportTempFolder": "/dist/temp/" 180 | 181 | /** 182 | * Whether "forgotten exports" should be included in the API report file. Forgotten exports are declarations 183 | * flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to 184 | * learn more. 185 | * 186 | * DEFAULT VALUE: "false" 187 | */ 188 | // "includeForgottenExports": false 189 | }, 190 | 191 | /** 192 | * Configures how the doc model file (*.api.json) will be generated. 193 | */ 194 | "docModel": { 195 | /** 196 | * (REQUIRED) Whether to generate a doc model file. 197 | */ 198 | "enabled": true, 199 | 200 | /** 201 | * The output path for the doc model file. The file extension should be ".api.json". 202 | * 203 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 204 | * prepend a folder token such as "". 205 | * 206 | * SUPPORTED TOKENS: , , 207 | * DEFAULT VALUE: "/temp/.api.json" 208 | */ 209 | "apiJsonFilePath": "/dist/temp/.api.json" 210 | 211 | /** 212 | * Whether "forgotten exports" should be included in the doc model file. Forgotten exports are declarations 213 | * flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to 214 | * learn more. 215 | * 216 | * DEFAULT VALUE: "false" 217 | */ 218 | // "includeForgottenExports": false, 219 | 220 | /** 221 | * The base URL where the project's source code can be viewed on a website such as GitHub or 222 | * Azure DevOps. This URL path corresponds to the `` path on disk. 223 | * 224 | * This URL is concatenated with the file paths serialized to the doc model to produce URL file paths to individual API items. 225 | * For example, if the `projectFolderUrl` is "https://github.com/microsoft/rushstack/tree/main/apps/api-extractor" and an API 226 | * item's file path is "api/ExtractorConfig.ts", the full URL file path would be 227 | * "https://github.com/microsoft/rushstack/tree/main/apps/api-extractor/api/ExtractorConfig.js". 228 | * 229 | * Can be omitted if you don't need source code links in your API documentation reference. 230 | * 231 | * SUPPORTED TOKENS: none 232 | * DEFAULT VALUE: "" 233 | */ 234 | // "projectFolderUrl": "http://github.com/path/to/your/projectFolder" 235 | }, 236 | 237 | /** 238 | * Configures how the .d.ts rollup file will be generated. 239 | */ 240 | "dtsRollup": { 241 | /** 242 | * (REQUIRED) Whether to generate the .d.ts rollup file. 243 | */ 244 | "enabled": true 245 | 246 | /** 247 | * Specifies the output path for a .d.ts rollup file to be generated without any trimming. 248 | * This file will include all declarations that are exported by the main entry point. 249 | * 250 | * If the path is an empty string, then this file will not be written. 251 | * 252 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 253 | * prepend a folder token such as "". 254 | * 255 | * SUPPORTED TOKENS: , , 256 | * DEFAULT VALUE: "/dist/.d.ts" 257 | */ 258 | // "untrimmedFilePath": "/dist/.d.ts", 259 | 260 | /** 261 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release. 262 | * This file will include only declarations that are marked as "@public", "@beta", or "@alpha". 263 | * 264 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 265 | * prepend a folder token such as "". 266 | * 267 | * SUPPORTED TOKENS: , , 268 | * DEFAULT VALUE: "" 269 | */ 270 | // "alphaTrimmedFilePath": "/dist/-alpha.d.ts", 271 | 272 | /** 273 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. 274 | * This file will include only declarations that are marked as "@public" or "@beta". 275 | * 276 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 277 | * prepend a folder token such as "". 278 | * 279 | * SUPPORTED TOKENS: , , 280 | * DEFAULT VALUE: "" 281 | */ 282 | // "betaTrimmedFilePath": "/dist/-beta.d.ts", 283 | 284 | /** 285 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. 286 | * This file will include only declarations that are marked as "@public". 287 | * 288 | * If the path is an empty string, then this file will not be written. 289 | * 290 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 291 | * prepend a folder token such as "". 292 | * 293 | * SUPPORTED TOKENS: , , 294 | * DEFAULT VALUE: "" 295 | */ 296 | // "publicTrimmedFilePath": "/dist/-public.d.ts", 297 | 298 | /** 299 | * When a declaration is trimmed, by default it will be replaced by a code comment such as 300 | * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the 301 | * declaration completely. 302 | * 303 | * DEFAULT VALUE: false 304 | */ 305 | // "omitTrimmingComments": true 306 | }, 307 | 308 | /** 309 | * Configures how the tsdoc-metadata.json file will be generated. 310 | */ 311 | "tsdocMetadata": { 312 | /** 313 | * Whether to generate the tsdoc-metadata.json file. 314 | * 315 | * DEFAULT VALUE: true 316 | */ 317 | // "enabled": true, 318 | /** 319 | * Specifies where the TSDoc metadata file should be written. 320 | * 321 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 322 | * prepend a folder token such as "". 323 | * 324 | * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", 325 | * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup 326 | * falls back to "tsdoc-metadata.json" in the package folder. 327 | * 328 | * SUPPORTED TOKENS: , , 329 | * DEFAULT VALUE: "" 330 | */ 331 | // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" 332 | }, 333 | 334 | /** 335 | * Configures how API Extractor reports error and warning messages produced during analysis. 336 | * 337 | * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. 338 | */ 339 | "messages": { 340 | /** 341 | * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing 342 | * the input .d.ts files. 343 | * 344 | * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" 345 | * 346 | * DEFAULT VALUE: A single "default" entry with logLevel=warning. 347 | */ 348 | "compilerMessageReporting": { 349 | /** 350 | * Configures the default routing for messages that don't match an explicit rule in this table. 351 | */ 352 | "default": { 353 | /** 354 | * Specifies whether the message should be written to the the tool's output log. Note that 355 | * the "addToApiReportFile" property may supersede this option. 356 | * 357 | * Possible values: "error", "warning", "none" 358 | * 359 | * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail 360 | * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes 361 | * the "--local" option), the warning is displayed but the build will not fail. 362 | * 363 | * DEFAULT VALUE: "warning" 364 | */ 365 | "logLevel": "warning" 366 | 367 | /** 368 | * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), 369 | * then the message will be written inside that file; otherwise, the message is instead logged according to 370 | * the "logLevel" option. 371 | * 372 | * DEFAULT VALUE: false 373 | */ 374 | // "addToApiReportFile": false 375 | } 376 | 377 | // "TS2551": { 378 | // "logLevel": "warning", 379 | // "addToApiReportFile": true 380 | // }, 381 | // 382 | // . . . 383 | }, 384 | 385 | /** 386 | * Configures handling of messages reported by API Extractor during its analysis. 387 | * 388 | * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" 389 | * 390 | * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings 391 | */ 392 | "extractorMessageReporting": { 393 | "default": { 394 | "logLevel": "warning" 395 | // "addToApiReportFile": false 396 | }, 397 | "ae-missing-release-tag": { 398 | "logLevel": "none" 399 | } 400 | 401 | // "ae-extra-release-tag": { 402 | // "logLevel": "warning", 403 | // "addToApiReportFile": true 404 | // }, 405 | // 406 | // . . . 407 | }, 408 | 409 | /** 410 | * Configures handling of messages reported by the TSDoc parser when analyzing code comments. 411 | * 412 | * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" 413 | * 414 | * DEFAULT VALUE: A single "default" entry with logLevel=warning. 415 | */ 416 | "tsdocMessageReporting": { 417 | "default": { 418 | "logLevel": "warning" 419 | // "addToApiReportFile": false 420 | } 421 | 422 | // "tsdoc-link-tag-unescaped-text": { 423 | // "logLevel": "warning", 424 | // "addToApiReportFile": true 425 | // }, 426 | // 427 | // . . . 428 | } 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /deploy-avatar-viewer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | cd `/usr/bin/dirname $0` 4 | 5 | # _OPTS="--dryrun" 6 | _OPTS="" 7 | 8 | aws --profile metaverse-dev-deploy s3 sync ${_OPTS} \ 9 | --region ap-northeast-1 \ 10 | --delete \ 11 | --exclude "*.DS_Store" \ 12 | --exclude "temp/*" \ 13 | --exclude "*.clean" \ 14 | --cache-control "max-age=60"\ 15 | ./dist s3://static.verseengine.cloud/examples/avatar-viewer/dist 16 | 17 | aws --profile metaverse-dev-deploy s3 sync ${_OPTS} \ 18 | --region ap-northeast-1 \ 19 | --delete \ 20 | --exclude "*.DS_Store" \ 21 | --exclude "*.clean" \ 22 | --cache-control "max-age=31536000" \ 23 | ./example/asset/animation s3://static.verseengine.cloud/examples/avatar-viewer/app/asset/animation 24 | 25 | aws --profile metaverse-dev-deploy s3 cp \ 26 | example/avatar.html s3://static.verseengine.cloud/examples/avatar-viewer/app/index.html 27 | aws --profile metaverse-dev-deploy s3 cp \ 28 | example/setup.js s3://static.verseengine.cloud/examples/avatar-viewer/app/setup.js 29 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) 4 | 5 | ## API Reference 6 | 7 | ## Packages 8 | 9 | | Package | Description | 10 | | --- | --- | 11 | | [@verseengine/three-avatar](./three-avatar.md) | Avatar system | 12 | 13 | -------------------------------------------------------------------------------- /docs/three-avatar.addmirrorhud.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [addMirrorHUD](./three-avatar.addmirrorhud.md) 4 | 5 | ## addMirrorHUD() function 6 | 7 | Create a mirror in front of the avatar. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function addMirrorHUD(avatar: Avatar, container: THREE.Object3D, options?: AddMirrorHUDOptions): Reflector; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatar | [Avatar](./three-avatar.avatar.md) | | 20 | | container | THREE.Object3D | Where to ADD mirrors. | 21 | | options | [AddMirrorHUDOptions](./three-avatar.addmirrorhudoptions.md) | _(Optional)_ | 22 | 23 | **Returns:** 24 | 25 | Reflector 26 | 27 | -------------------------------------------------------------------------------- /docs/three-avatar.addmirrorhudoptions.distancenonvr.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AddMirrorHUDOptions](./three-avatar.addmirrorhudoptions.md) > [distanceNonVR](./three-avatar.addmirrorhudoptions.distancenonvr.md) 4 | 5 | ## AddMirrorHUDOptions.distanceNonVR property 6 | 7 | Distance between avatar and mirror (In non-VR mode). Default is 1.2. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | distanceNonVR?: number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.addmirrorhudoptions.distancevr.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AddMirrorHUDOptions](./three-avatar.addmirrorhudoptions.md) > [distanceVR](./three-avatar.addmirrorhudoptions.distancevr.md) 4 | 5 | ## AddMirrorHUDOptions.distanceVR property 6 | 7 | Distance between avatar and mirror (In VR mode). Default is 0.3. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | distanceVR?: number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.addmirrorhudoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AddMirrorHUDOptions](./three-avatar.addmirrorhudoptions.md) 4 | 5 | ## AddMirrorHUDOptions interface 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export interface AddMirrorHUDOptions 11 | ``` 12 | 13 | ## Properties 14 | 15 | | Property | Modifiers | Type | Description | 16 | | --- | --- | --- | --- | 17 | | [distanceNonVR?](./three-avatar.addmirrorhudoptions.distancenonvr.md) | | number | _(Optional)_ Distance between avatar and mirror (In non-VR mode). Default is 1.2. | 18 | | [distanceVR?](./three-avatar.addmirrorhudoptions.distancevr.md) | | number | _(Optional)_ Distance between avatar and mirror (In VR mode). Default is 0.3. | 19 | | [xr?](./three-avatar.addmirrorhudoptions.xr.md) | | THREE.WebXRManager | _(Optional)_ renderer.xr | 20 | 21 | -------------------------------------------------------------------------------- /docs/three-avatar.addmirrorhudoptions.xr.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AddMirrorHUDOptions](./three-avatar.addmirrorhudoptions.md) > [xr](./three-avatar.addmirrorhudoptions.xr.md) 4 | 5 | ## AddMirrorHUDOptions.xr property 6 | 7 | `renderer.xr` 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | xr?: THREE.WebXRManager; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.autowalker.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AutoWalker](./three-avatar.autowalker.md) 4 | 5 | ## AutoWalker class 6 | 7 | Avatar extension for automatic walking animation when moving. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare class AutoWalker implements AvatarExtension 13 | ``` 14 | **Implements:** [AvatarExtension](./three-avatar.avatarextension.md) 15 | 16 | ## Methods 17 | 18 | | Method | Modifiers | Description | 19 | | --- | --- | --- | 20 | | [setup(avatar)](./three-avatar.autowalker.setup.md) | | initialization | 21 | | [tick(dt)](./three-avatar.autowalker.tick.md) | | Processes called periodically | 22 | 23 | -------------------------------------------------------------------------------- /docs/three-avatar.autowalker.setup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AutoWalker](./three-avatar.autowalker.md) > [setup](./three-avatar.autowalker.setup.md) 4 | 5 | ## AutoWalker.setup() method 6 | 7 | initialization 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setup(avatar: Avatar): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatar | [Avatar](./three-avatar.avatar.md) | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.autowalker.tick.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AutoWalker](./three-avatar.autowalker.md) > [tick](./three-avatar.autowalker.tick.md) 4 | 5 | ## AutoWalker.tick() method 6 | 7 | Processes called periodically 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | tick(dt: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | dt | number | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar._constructor_.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [(constructor)](./three-avatar.avatar._constructor_.md) 4 | 5 | ## Avatar.(constructor) 6 | 7 | Constructs a new instance of the `Avatar` class 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | constructor(model: AvatarModel, options?: AvatarOptions); 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | model | [AvatarModel](./three-avatar.avatarmodel.md) | Avatar Model. | 20 | | options | [AvatarOptions](./three-avatar.avataroptions.md) | _(Optional)_ | 21 | 22 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.addextension.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [addExtension](./three-avatar.avatar.addextension.md) 4 | 5 | ## Avatar.addExtension() method 6 | 7 | Add an extension. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | addExtension(ext: AvatarExtension): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | ext | [AvatarExtension](./three-avatar.avatarextension.md) | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.dispose.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [dispose](./three-avatar.avatar.dispose.md) 4 | 5 | ## Avatar.dispose() method 6 | 7 | Releases all resources allocated by this instance. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | dispose(): void; 13 | ``` 14 | **Returns:** 15 | 16 | void 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.faceexpressionnames.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [faceExpressionNames](./three-avatar.avatar.faceexpressionnames.md) 4 | 5 | ## Avatar.faceExpressionNames property 6 | 7 | List of possible names for [Avatar.setFaceExpression()](./three-avatar.avatar.setfaceexpression.md). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get faceExpressionNames(): string[]; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.facemesh.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [faceMesh](./three-avatar.avatar.facemesh.md) 4 | 5 | ## Avatar.faceMesh property 6 | 7 | Mesh for changing facial expressions. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get faceMesh(): THREE.Mesh, THREE.Material | THREE.Material[]> | undefined; 13 | ``` 14 | 15 | ## Remarks 16 | 17 | For VRM, use `avatar.vrm.expressionManager` instead of this mesh. 18 | 19 | see [VRMExpressionManager](https://pixiv.github.io/three-vrm/packages/three-vrm/docs/classes/VRMExpressionManager.html) 20 | 21 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.firstpersononlylayer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [firstPersonOnlyLayer](./three-avatar.avatar.firstpersononlylayer.md) 4 | 5 | ## Avatar.firstPersonOnlyLayer property 6 | 7 | [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for first-person camera. (Layer numbers that you do not want displayed on mirrors, etc.) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get firstPersonOnlyLayer(): number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.getheadheight.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [getHeadHeight](./three-avatar.avatar.getheadheight.md) 4 | 5 | ## Avatar.getHeadHeight() method 6 | 7 | Head height. Use to adjust the height of the XR camera. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | getHeadHeight(): number; 13 | ``` 14 | **Returns:** 15 | 16 | number 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.getsyncbonesdata.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [getSyncBonesData](./three-avatar.avatar.getsyncbonesdata.md) 4 | 5 | ## Avatar.getSyncBonesData() method 6 | 7 | Get bones motion. Use to synchronize to another location. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | getSyncBonesData(): number[]; 13 | ``` 14 | **Returns:** 15 | 16 | number\[\] 17 | 18 | ## Example 19 | 20 | 21 | ```ts 22 | import { encode, decode } from "@msgpack/msgpack"; 23 | 24 | ... 25 | 26 | function startSenderLoop() { 27 | setInterval(() => { 28 | const data = player.avatar.getSyncBonesData(); 29 | send(encode(data)); 30 | }, 100); 31 | } 32 | 33 | ... 34 | 35 | function onReceive(data) { 36 | person0.avatar.setSyncBonesData(decode(data)); 37 | } 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.headbone.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [headBone](./three-avatar.avatar.headbone.md) 4 | 5 | ## Avatar.headBone property 6 | 7 | Head bone 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get headBone(): THREE.Bone; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.headboneoffset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [headBoneOffset](./three-avatar.avatar.headboneoffset.md) 4 | 5 | ## Avatar.headBoneOffset property 6 | 7 | Position offset from the head bone. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get headBoneOffset(): THREE.Vector3; 13 | ``` 14 | 15 | ## Example 16 | 17 | 18 | ```ts 19 | camera.position.copy( 20 | new THREE.Vector3(0, avatar.getHeadHeight(), 0).add( 21 | avatar.headBoneOffset 22 | ) 23 | ); 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.headsync.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [headSync](./three-avatar.avatar.headsync.md) 4 | 5 | ## Avatar.headSync() method 6 | 7 | Follow the avatar's head (neck) movements to the XR headset. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | headSync(rotation: THREE.Euler): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | rotation | THREE.Euler | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | ## Example 26 | 27 | 28 | ```ts 29 | renderer.setAnimationLoop(() => { 30 | ... 31 | if (renderer.xr?.enabled) { 32 | avatar.headSync(camera.rotation); 33 | } 34 | }); 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.height.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [height](./three-avatar.avatar.height.md) 4 | 5 | ## Avatar.height property 6 | 7 | Height 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get height(): number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.iktargetleftarmbones.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [ikTargetLeftArmBones](./three-avatar.avatar.iktargetleftarmbones.md) 4 | 5 | ## Avatar.ikTargetLeftArmBones property 6 | 7 | IK target bones of left arm. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get ikTargetLeftArmBones(): IKTargetBones; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.iktargetrightarmbones.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [ikTargetRightArmBones](./three-avatar.avatar.iktargetrightarmbones.md) 4 | 5 | ## Avatar.ikTargetRightArmBones property 6 | 7 | IK target bones of right arm. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get ikTargetRightArmBones(): IKTargetBones; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.invisiblefirstperson.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [invisibleFirstPerson](./three-avatar.avatar.invisiblefirstperson.md) 4 | 5 | ## Avatar.invisibleFirstPerson() method 6 | 7 | Hide avatar when in 1st person view. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | invisibleFirstPerson(): void; 13 | ``` 14 | **Returns:** 15 | 16 | void 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.isikmode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [isIKMode](./three-avatar.avatar.isikmode.md) 4 | 5 | ## Avatar.isIKMode property 6 | 7 | Get IK(Inverse Kinematics) mode or not. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get isIKMode(): boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.lipsync.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [lipSync](./three-avatar.avatar.lipsync.md) 4 | 5 | ## Avatar.lipSync() method 6 | 7 | Synchronize lip movements with the voice. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | lipSync(kiss: number, lipsClosed: number, jaw: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | kiss | number | | 20 | | lipsClosed | number | | 21 | | jaw | number | | 22 | 23 | **Returns:** 24 | 25 | void 26 | 27 | ## Remarks 28 | 29 | see [Web-based live speech-driven lip-sync](https://repositori.upf.edu/bitstream/handle/10230/28139/llorach_VSG16_web.pdf) 30 | 31 | summary 32 | 33 | ``` 34 | Our method generates 35 | reliable speech animation based on live speech using three blend 36 | shapes and no training, and it only needs manual adjustment of 37 | three parameters for each speaker (sensitivity, smoothness and 38 | vocal tract length). 39 | ``` 40 | 41 | ## Example 42 | 43 | 44 | ```ts 45 | import { 46 | createAvatar, 47 | addMirrorHUD, 48 | ... 49 | Lipsync, 50 | } from "three-avatar"; 51 | 52 | ... 53 | 54 | const ms = await navigator.mediaDevices.getUserMedia({ 55 | audio: true, 56 | }); 57 | const lipSync = new Lipsync(THREE.AudioContext.getContext(), ms); 58 | 59 | ... 60 | 61 | renderer.setAnimationLoop(() => { 62 | ... 63 | avatar.lipSync(...lipSync.update()); 64 | }); 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) 4 | 5 | ## Avatar class 6 | 7 | Avatar 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare class Avatar 13 | ``` 14 | 15 | ## Example 16 | 17 | 18 | ```ts 19 | let resp = await fetch(url); 20 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 21 | const model = await loadAvatarModel(avatarData, renderer, false); 22 | const avatar = new Avatar(model); 23 | playerObj.add(avatar.object3D); 24 | 25 | ... 26 | 27 | const clock = new THREE.Clock(); 28 | renderer.setAnimationLoop(() => { 29 | ... 30 | const dt = clock.getDelta(); 31 | avatar.tick(dt); 32 | }); 33 | ``` 34 | 35 | ## Constructors 36 | 37 | | Constructor | Modifiers | Description | 38 | | --- | --- | --- | 39 | | [(constructor)(model, options)](./three-avatar.avatar._constructor_.md) | | Constructs a new instance of the Avatar class | 40 | 41 | ## Properties 42 | 43 | | Property | Modifiers | Type | Description | 44 | | --- | --- | --- | --- | 45 | | [faceExpressionNames](./three-avatar.avatar.faceexpressionnames.md) | readonly | string\[\] | List of possible names for [Avatar.setFaceExpression()](./three-avatar.avatar.setfaceexpression.md). | 46 | | [faceMesh](./three-avatar.avatar.facemesh.md) | readonly | THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes>, THREE.Material \| THREE.Material\[\]> \| undefined | Mesh for changing facial expressions. | 47 | | [firstPersonOnlyLayer](./three-avatar.avatar.firstpersononlylayer.md) | readonly | number | [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for first-person camera. (Layer numbers that you do not want displayed on mirrors, etc.) | 48 | | [headBone](./three-avatar.avatar.headbone.md) | readonly | THREE.Bone | Head bone | 49 | | [headBoneOffset](./three-avatar.avatar.headboneoffset.md) | readonly | THREE.Vector3 | Position offset from the head bone. | 50 | | [height](./three-avatar.avatar.height.md) | readonly | number | Height | 51 | | [ikTargetLeftArmBones](./three-avatar.avatar.iktargetleftarmbones.md) | readonly | [IKTargetBones](./three-avatar.iktargetbones.md) | IK target bones of left arm. | 52 | | [ikTargetRightArmBones](./three-avatar.avatar.iktargetrightarmbones.md) | readonly | [IKTargetBones](./three-avatar.iktargetbones.md) | IK target bones of right arm. | 53 | | [isIKMode](./three-avatar.avatar.isikmode.md) | readonly | boolean | Get IK(Inverse Kinematics) mode or not. | 54 | | [object3D](./three-avatar.avatar.object3d.md) | readonly | THREE.Group | Object3D of this avatar. | 55 | | [syncTargetBones](./three-avatar.avatar.synctargetbones.md) | readonly | THREE.Bone\[\] | Bones to be synchronized. | 56 | | [thirdPersonOnlyLayer](./three-avatar.avatar.thirdpersononlylayer.md) | readonly | number | [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for 3rd-person camera. | 57 | | [type](./three-avatar.avatar.type.md) | readonly | [AvatarType](./three-avatar.avatartype.md) | Avatar Data Type | 58 | | [vrm](./three-avatar.avatar.vrm.md) | readonly | THREE\_VRM.VRM \| undefined | VRM Data | 59 | | [widthX](./three-avatar.avatar.widthx.md) | readonly | number | Width along x-axis | 60 | | [widthZ](./three-avatar.avatar.widthz.md) | readonly | number | Width along z-axis | 61 | 62 | ## Methods 63 | 64 | | Method | Modifiers | Description | 65 | | --- | --- | --- | 66 | | [addExtension(ext)](./three-avatar.avatar.addextension.md) | | Add an extension. | 67 | | [dispose()](./three-avatar.avatar.dispose.md) | | Releases all resources allocated by this instance. | 68 | | [getHeadHeight()](./three-avatar.avatar.getheadheight.md) | | Head height. Use to adjust the height of the XR camera. | 69 | | [getSyncBonesData()](./three-avatar.avatar.getsyncbonesdata.md) | | Get bones motion. Use to synchronize to another location. | 70 | | [headSync(rotation)](./three-avatar.avatar.headsync.md) | | Follow the avatar's head (neck) movements to the XR headset. | 71 | | [invisibleFirstPerson()](./three-avatar.avatar.invisiblefirstperson.md) | | Hide avatar when in 1st person view. | 72 | | [lipSync(kiss, lipsClosed, jaw)](./three-avatar.avatar.lipsync.md) | | Synchronize lip movements with the voice. | 73 | | [playClip(name)](./three-avatar.avatar.playclip.md) | |

Play an animation clip. idle animation is playing by default.

Must be loaded beforehand by executing [preLoadAnimationData()](./three-avatar.preloadanimationdata.md).

| 74 | | [removeExtension(ext)](./three-avatar.avatar.removeextension.md) | | Remove an extension. | 75 | | [resetFaceExpression()](./three-avatar.avatar.resetfaceexpression.md) | | Reset face expression to default. | 76 | | [setFaceExpression(name, value)](./three-avatar.avatar.setfaceexpression.md) | | Set face expression. | 77 | | [setFirstPersonMode(cameras)](./three-avatar.avatar.setfirstpersonmode.md) | | Switching the camera display to a first-person view. | 78 | | [setIKMode(v)](./three-avatar.avatar.setikmode.md) | | Set to IK mode. | 79 | | [setSyncBonesData(syncBonesData)](./three-avatar.avatar.setsyncbonesdata.md) | | Set bones motion. Synchronize data obtained with [Avatar.getSyncBonesData()](./three-avatar.avatar.getsyncbonesdata.md) at another location. | 80 | | [setThirdPersonMode(cameras)](./three-avatar.avatar.setthirdpersonmode.md) | | Switching the camera display to third-person view. | 81 | | [stopClip()](./three-avatar.avatar.stopclip.md) | | Stop an animation clip. | 82 | | [tick(deltaTime)](./three-avatar.avatar.tick.md) | | Processes called periodically | 83 | 84 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.object3d.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [object3D](./three-avatar.avatar.object3d.md) 4 | 5 | ## Avatar.object3D property 6 | 7 | Object3D of this avatar. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get object3D(): THREE.Group; 13 | ``` 14 | 15 | ## Example 16 | 17 | 18 | ```ts 19 | player.add(avatar.object3D); 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.playclip.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [playClip](./three-avatar.avatar.playclip.md) 4 | 5 | ## Avatar.playClip() method 6 | 7 | Play an animation clip. idle animation is playing by default. 8 | 9 | Must be loaded beforehand by executing [preLoadAnimationData()](./three-avatar.preloadanimationdata.md). 10 | 11 | **Signature:** 12 | 13 | ```typescript 14 | playClip(name: string): void; 15 | ``` 16 | 17 | ## Parameters 18 | 19 | | Parameter | Type | Description | 20 | | --- | --- | --- | 21 | | name | string | [AvatarAnimationDataSource](./three-avatar.avataranimationdatasource.md)'s key. | 22 | 23 | **Returns:** 24 | 25 | void 26 | 27 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.removeextension.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [removeExtension](./three-avatar.avatar.removeextension.md) 4 | 5 | ## Avatar.removeExtension() method 6 | 7 | Remove an extension. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | removeExtension(ext: AvatarExtension): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | ext | [AvatarExtension](./three-avatar.avatarextension.md) | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.resetfaceexpression.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [resetFaceExpression](./three-avatar.avatar.resetfaceexpression.md) 4 | 5 | ## Avatar.resetFaceExpression() method 6 | 7 | Reset face expression to default. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | resetFaceExpression(): void; 13 | ``` 14 | **Returns:** 15 | 16 | void 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.setfaceexpression.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [setFaceExpression](./three-avatar.avatar.setfaceexpression.md) 4 | 5 | ## Avatar.setFaceExpression() method 6 | 7 | Set face expression. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setFaceExpression(name: string, value?: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | name | string | One of the values get by [Avatar.faceExpressionNames](./three-avatar.avatar.faceexpressionnames.md). | 20 | | value | number | _(Optional)_ 0.0 to 1.0 | 21 | 22 | **Returns:** 23 | 24 | void 25 | 26 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.setfirstpersonmode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [setFirstPersonMode](./three-avatar.avatar.setfirstpersonmode.md) 4 | 5 | ## Avatar.setFirstPersonMode() method 6 | 7 | Switching the camera display to a first-person view. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setFirstPersonMode(cameras: Array): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | cameras | Array<THREE.Camera> | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.setikmode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [setIKMode](./three-avatar.avatar.setikmode.md) 4 | 5 | ## Avatar.setIKMode() method 6 | 7 | Set to IK mode. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setIKMode(v: boolean): Promise; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | v | boolean | | 20 | 21 | **Returns:** 22 | 23 | Promise<void> 24 | 25 | ## Remarks 26 | 27 | From the movement of the animation clip, the Exclude IK target bones from the motion of an animation clip. 28 | 29 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.setsyncbonesdata.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [setSyncBonesData](./three-avatar.avatar.setsyncbonesdata.md) 4 | 5 | ## Avatar.setSyncBonesData() method 6 | 7 | Set bones motion. Synchronize data obtained with [Avatar.getSyncBonesData()](./three-avatar.avatar.getsyncbonesdata.md) at another location. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setSyncBonesData(syncBonesData: number[]): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | syncBonesData | number\[\] | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.setthirdpersonmode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [setThirdPersonMode](./three-avatar.avatar.setthirdpersonmode.md) 4 | 5 | ## Avatar.setThirdPersonMode() method 6 | 7 | Switching the camera display to third-person view. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setThirdPersonMode(cameras: Array): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | cameras | Array<THREE.Camera> | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.stopclip.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [stopClip](./three-avatar.avatar.stopclip.md) 4 | 5 | ## Avatar.stopClip() method 6 | 7 | Stop an animation clip. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | stopClip(): void; 13 | ``` 14 | **Returns:** 15 | 16 | void 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.synctargetbones.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [syncTargetBones](./three-avatar.avatar.synctargetbones.md) 4 | 5 | ## Avatar.syncTargetBones property 6 | 7 | Bones to be synchronized. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get syncTargetBones(): THREE.Bone[]; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.thirdpersononlylayer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [thirdPersonOnlyLayer](./three-avatar.avatar.thirdpersononlylayer.md) 4 | 5 | ## Avatar.thirdPersonOnlyLayer property 6 | 7 | [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for 3rd-person camera. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get thirdPersonOnlyLayer(): number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.tick.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [tick](./three-avatar.avatar.tick.md) 4 | 5 | ## Avatar.tick() method 6 | 7 | Processes called periodically 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | tick(deltaTime: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | deltaTime | number | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | ## Example 26 | 27 | 28 | ```ts 29 | const clock = new THREE.Clock(); 30 | renderer.setAnimationLoop(() => { 31 | const dt = clock.getDelta(); 32 | avatar.tick(dt); 33 | }); 34 | ``` 35 | or 36 | 37 | ```ts 38 | const clock = new THREE.Clock(); 39 | setInterval(() => { 40 | const dt = clock.getDelta(); 41 | avatar.tick(dt); 42 | }, anything); 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.type.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [type](./three-avatar.avatar.type.md) 4 | 5 | ## Avatar.type property 6 | 7 | Avatar Data Type 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get type(): AvatarType; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.vrm.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [vrm](./three-avatar.avatar.vrm.md) 4 | 5 | ## Avatar.vrm property 6 | 7 | VRM Data 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get vrm(): THREE_VRM.VRM | undefined; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.widthx.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [widthX](./three-avatar.avatar.widthx.md) 4 | 5 | ## Avatar.widthX property 6 | 7 | Width along x-axis 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get widthX(): number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatar.widthz.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Avatar](./three-avatar.avatar.md) > [widthZ](./three-avatar.avatar.widthz.md) 4 | 5 | ## Avatar.widthZ property 6 | 7 | Width along z-axis 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | get widthZ(): number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avataranimationdatasource.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarAnimationDataSource](./three-avatar.avataranimationdatasource.md) 4 | 5 | ## AvatarAnimationDataSource type 6 | 7 | [mixamo](https://www.mixamo.com/) animation file URLs 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type AvatarAnimationDataSource = { 13 | walk: string; 14 | idle: string; 15 | [key: string]: string; 16 | }; 17 | ``` 18 | 19 | ## Example 20 | 21 | 22 | ```ts 23 | const ANIMATION_MAP = { 24 | idle: "asset/animation/idle.fbx", 25 | walk: "asset/animation/walk.fbx", 26 | dance: "asset/animation/dance.fbx", 27 | }; 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarextension.dispose.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarExtension](./three-avatar.avatarextension.md) > [dispose](./three-avatar.avatarextension.dispose.md) 4 | 5 | ## AvatarExtension.dispose() method 6 | 7 | Releases all resources allocated by this instance. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | dispose?(): void; 13 | ``` 14 | **Returns:** 15 | 16 | void 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarextension.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarExtension](./three-avatar.avatarextension.md) 4 | 5 | ## AvatarExtension interface 6 | 7 | Interface to extend [Avatar](./three-avatar.avatar.md). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export interface AvatarExtension 13 | ``` 14 | 15 | ## Methods 16 | 17 | | Method | Description | 18 | | --- | --- | 19 | | [dispose()?](./three-avatar.avatarextension.dispose.md) | _(Optional)_ Releases all resources allocated by this instance. | 20 | | [setup(avatar)](./three-avatar.avatarextension.setup.md) | initialization | 21 | | [tick(deltaTime)](./three-avatar.avatarextension.tick.md) | Processes called periodically | 22 | 23 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarextension.setup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarExtension](./three-avatar.avatarextension.md) > [setup](./three-avatar.avatarextension.setup.md) 4 | 5 | ## AvatarExtension.setup() method 6 | 7 | initialization 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setup(avatar: Avatar): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatar | [Avatar](./three-avatar.avatar.md) | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarextension.tick.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarExtension](./three-avatar.avatarextension.md) > [tick](./three-avatar.avatarextension.tick.md) 4 | 5 | ## AvatarExtension.tick() method 6 | 7 | Processes called periodically 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | tick(deltaTime: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | deltaTime | number | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarik._constructor_.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarIK](./three-avatar.avatarik.md) > [(constructor)](./three-avatar.avatarik._constructor_.md) 4 | 5 | ## AvatarIK.(constructor) 6 | 7 | i 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | constructor(target: THREE.Object3D, maybeRightArmBones: IKTargetBones, maybeLeftArmBones: IKTargetBones, rotationLimitSet: RotationLimitSet, vrHandGetter: VRHandGetter, wristRotationOffsetSet: WristRotationOffsetSet, options?: { 13 | isDebug?: boolean; 14 | intervalSec?: number; 15 | }); 16 | ``` 17 | 18 | ## Parameters 19 | 20 | | Parameter | Type | Description | 21 | | --- | --- | --- | 22 | | target | THREE.Object3D | Avatar object. Can be an object unrelated to the [Avatar](./three-avatar.avatar.md) class. | 23 | | maybeRightArmBones | [IKTargetBones](./three-avatar.iktargetbones.md) | Bones moved by IK. If it contains undefined, it is disabled. | 24 | | maybeLeftArmBones | [IKTargetBones](./three-avatar.iktargetbones.md) | Bones moved by IK. If it contains undefined, it is disabled. | 25 | | rotationLimitSet | [RotationLimitSet](./three-avatar.rotationlimitset.md) | Limit the range of rotation of joints. | 26 | | vrHandGetter | [VRHandGetter](./three-avatar.vrhandgetter.md) | Get XR controller objects. | 27 | | wristRotationOffsetSet | [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md) | Offset of rotation angle between the XR controller and the avatar's wrist. | 28 | | options | { isDebug?: boolean; intervalSec?: number; } | _(Optional)_ Processing frequency of tick(). Default is 1 / 60 (30fps). | 29 | 30 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarik.dispose.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarIK](./three-avatar.avatarik.md) > [dispose](./three-avatar.avatarik.dispose.md) 4 | 5 | ## AvatarIK.dispose() method 6 | 7 | Releases all resources allocated by this instance. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | dispose(): void; 13 | ``` 14 | **Returns:** 15 | 16 | void 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarik.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarIK](./three-avatar.avatarik.md) 4 | 5 | ## AvatarIK class 6 | 7 | IK (Inverse Kinematics) to move the avatar's arms in sync with the XR controller's movements. (Experimental Features) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare class AvatarIK 13 | ``` 14 | 15 | ## Example 16 | 17 | [createAvatarIK()](./three-avatar.createavatarik.md) 18 | 19 | ## Constructors 20 | 21 | | Constructor | Modifiers | Description | 22 | | --- | --- | --- | 23 | | [(constructor)(target, maybeRightArmBones, maybeLeftArmBones, rotationLimitSet, vrHandGetter, wristRotationOffsetSet, options)](./three-avatar.avatarik._constructor_.md) | | i | 24 | 25 | ## Methods 26 | 27 | | Method | Modifiers | Description | 28 | | --- | --- | --- | 29 | | [dispose()](./three-avatar.avatarik.dispose.md) | | Releases all resources allocated by this instance. | 30 | | [tick(deltaTime)](./three-avatar.avatarik.tick.md) | | Processes called periodically | 31 | 32 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarik.tick.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarIK](./three-avatar.avatarik.md) > [tick](./three-avatar.avatarik.tick.md) 4 | 5 | ## AvatarIK.tick() method 6 | 7 | Processes called periodically 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | tick(deltaTime: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | deltaTime | number | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | ## Example 26 | 27 | 28 | ```ts 29 | const clock = new THREE.Clock(); 30 | renderer.setAnimationLoop(() => { 31 | const dt = clock.getDelta(); 32 | avatarIK.tick(dt); 33 | }); 34 | ``` 35 | or 36 | 37 | ```ts 38 | const clock = new THREE.Clock(); 39 | setInterval(() => { 40 | const dt = clock.getDelta(); 41 | avatarIK.tick(dt); 42 | }, anything); 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarmodel.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarModel](./three-avatar.avatarmodel.md) 4 | 5 | ## AvatarModel interface 6 | 7 | Avatar model. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export interface AvatarModel 13 | ``` 14 | 15 | ## Properties 16 | 17 | | Property | Modifiers | Type | Description | 18 | | --- | --- | --- | --- | 19 | | [model](./three-avatar.avatarmodel.model.md) | | THREE.Group | | 20 | | [vrm?](./three-avatar.avatarmodel.vrm.md) | | THREE\_VRM.VRM | _(Optional)_ | 21 | 22 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarmodel.model.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarModel](./three-avatar.avatarmodel.md) > [model](./three-avatar.avatarmodel.model.md) 4 | 5 | ## AvatarModel.model property 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | model: THREE.Group; 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/three-avatar.avatarmodel.vrm.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarModel](./three-avatar.avatarmodel.md) > [vrm](./three-avatar.avatarmodel.vrm.md) 4 | 5 | ## AvatarModel.vrm property 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | vrm?: THREE_VRM.VRM; 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/three-avatar.avataroptions.animationintervalsec.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarOptions](./three-avatar.avataroptions.md) > [animationIntervalSec](./three-avatar.avataroptions.animationintervalsec.md) 4 | 5 | ## AvatarOptions.animationIntervalSec property 6 | 7 | Processing frequency of THREE.AnimationMixer.update(). Default is 1 / 60 (60fps). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | animationIntervalSec?: number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avataroptions.firstpersononlylayer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarOptions](./three-avatar.avataroptions.md) > [firstPersonOnlyLayer](./three-avatar.avataroptions.firstpersononlylayer.md) 4 | 5 | ## AvatarOptions.firstPersonOnlyLayer property 6 | 7 | [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for first-person camera. (Layer numbers that you do not want displayed on mirrors, etc.) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | firstPersonOnlyLayer?: number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avataroptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarOptions](./three-avatar.avataroptions.md) 4 | 5 | ## AvatarOptions interface 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export interface AvatarOptions 11 | ``` 12 | 13 | ## Properties 14 | 15 | | Property | Modifiers | Type | Description | 16 | | --- | --- | --- | --- | 17 | | [animationIntervalSec?](./three-avatar.avataroptions.animationintervalsec.md) | | number | _(Optional)_ Processing frequency of THREE.AnimationMixer.update(). Default is 1 / 60 (60fps). | 18 | | [firstPersonOnlyLayer?](./three-avatar.avataroptions.firstpersononlylayer.md) | | number | _(Optional)_ [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for first-person camera. (Layer numbers that you do not want displayed on mirrors, etc.) | 19 | | [thirdPersonOnlyLayer?](./three-avatar.avataroptions.thirdpersononlylayer.md) | | number | _(Optional)_ [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for 3rd-person camera. | 20 | 21 | -------------------------------------------------------------------------------- /docs/three-avatar.avataroptions.thirdpersononlylayer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarOptions](./three-avatar.avataroptions.md) > [thirdPersonOnlyLayer](./three-avatar.avataroptions.thirdpersononlylayer.md) 4 | 5 | ## AvatarOptions.thirdPersonOnlyLayer property 6 | 7 | [Layer number](https://threejs.org/docs/#api/en/core/Layers) to be displayed only for 3rd-person camera. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | thirdPersonOnlyLayer?: number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatartype.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarType](./three-avatar.avatartype.md) 4 | 5 | ## AvatarType type 6 | 7 | Avatar Data Types 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type AvatarType = "VrmV0" | "VrmV1" | "ReadyPlayerMe"; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatartypereadyplayerme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarTypeReadyPlayerMe](./three-avatar.avatartypereadyplayerme.md) 4 | 5 | ## AvatarTypeReadyPlayerMe variable 6 | 7 | [Ready Player Me](https://readyplayer.me/) \| Avatar Data Types 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | AvatarTypeReadyPlayerMe: AvatarType 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatartypevrmv0.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarTypeVrmV0](./three-avatar.avatartypevrmv0.md) 4 | 5 | ## AvatarTypeVrmV0 variable 6 | 7 | [VRM](https://vrm.dev/en/) version less than 1 \| Avatar Data Types 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | AvatarTypeVrmV0: AvatarType 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.avatartypevrmv1.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [AvatarTypeVrmV1](./three-avatar.avatartypevrmv1.md) 4 | 5 | ## AvatarTypeVrmV1 variable 6 | 7 | [VRM](https://vrm.dev/en/) version 1.x \| Avatar Data Types 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | AvatarTypeVrmV1: AvatarType 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.blinker.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Blinker](./three-avatar.blinker.md) 4 | 5 | ## Blinker class 6 | 7 | Avatar extension to implement blink. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare class Blinker implements AvatarExtension 13 | ``` 14 | **Implements:** [AvatarExtension](./three-avatar.avatarextension.md) 15 | 16 | ## Methods 17 | 18 | | Method | Modifiers | Description | 19 | | --- | --- | --- | 20 | | [setup(avatar)](./three-avatar.blinker.setup.md) | | initialization | 21 | | [tick(dt)](./three-avatar.blinker.tick.md) | | Processes called periodically | 22 | 23 | -------------------------------------------------------------------------------- /docs/three-avatar.blinker.setup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Blinker](./three-avatar.blinker.md) > [setup](./three-avatar.blinker.setup.md) 4 | 5 | ## Blinker.setup() method 6 | 7 | initialization 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | setup(avatar: Avatar): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatar | [Avatar](./three-avatar.avatar.md) | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.blinker.tick.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Blinker](./three-avatar.blinker.md) > [tick](./three-avatar.blinker.tick.md) 4 | 5 | ## Blinker.tick() method 6 | 7 | Processes called periodically 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | tick(dt: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | dt | number | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [createAvatar](./three-avatar.createavatar.md) 4 | 5 | ## createAvatar() function 6 | 7 | Create [Avatar](./three-avatar.avatar.md) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function createAvatar(avatarData: Uint8Array, renderer: THREE.WebGLRenderer, frustumCulled?: boolean, options?: CreateAvatarOptions): Promise; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatarData | Uint8Array | Data from gltf or vrm files. | 20 | | renderer | THREE.WebGLRenderer | | 21 | | frustumCulled | boolean | _(Optional)_ [Object3D.frustumCulled](https://threejs.org/docs/?q=Mesh#api/en/core/Object3D.frustumCulled) applied recursively. | 22 | | options | [CreateAvatarOptions](./three-avatar.createavataroptions.md) | _(Optional)_ | 23 | 24 | **Returns:** 25 | 26 | Promise<[Avatar](./three-avatar.avatar.md)> 27 | 28 | ## Example 29 | 30 | 31 | ```ts 32 | 33 | let resp = await fetch(url); 34 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 35 | const avatar = createAvatar(avatarData, renderer, false, { 36 | isInvisibleFirstPerson: true, 37 | isLowSpecMode: maybeLowSpecDevice, 38 | }); 39 | playerObj.add(avatar.object3D); 40 | 41 | ... 42 | 43 | const clock = new THREE.Clock(); 44 | renderer.setAnimationLoop(() => { 45 | ... 46 | const dt = clock.getDelta(); 47 | avatar.tick(dt); 48 | }); 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatarik.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [createAvatarIK](./three-avatar.createavatarik.md) 4 | 5 | ## createAvatarIK() function 6 | 7 | Create [AvatarIK](./three-avatar.avatarik.md). (Experimental Features) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function createAvatarIK(avatar: Avatar, handGetter: VRHandGetter, option?: CreateAvatarIKOptions): AvatarIK; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatar | [Avatar](./three-avatar.avatar.md) | | 20 | | handGetter | [VRHandGetter](./three-avatar.vrhandgetter.md) | Get XR controller objects. | 21 | | option | [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) | _(Optional)_ | 22 | 23 | **Returns:** 24 | 25 | [AvatarIK](./three-avatar.avatarik.md) 26 | 27 | ## Example 28 | 29 | 30 | ```ts 31 | 32 | ... 33 | 34 | onVR: async () => { 35 | await avatar.setIKMode(true); 36 | avatarIK = createAvatarIK( 37 | avatar, 38 | { 39 | left: () => leftXRController, 40 | right: () => rightXRController, 41 | }, 42 | ); 43 | }, 44 | onNonVR: async () => { 45 | await avatar.setIKMode(false); 46 | avatarIK?.dispose(); 47 | } 48 | 49 | ... 50 | 51 | 52 | const clock = new THREE.Clock(); 53 | renderer.setAnimationLoop(() => { 54 | ... 55 | const dt = clock.getDelta(); 56 | avatar.tick(dt); 57 | avatarIK?.tick(dt); 58 | }); 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatarikoptions.intervalsec.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) > [intervalSec](./three-avatar.createavatarikoptions.intervalsec.md) 4 | 5 | ## CreateAvatarIKOptions.intervalSec property 6 | 7 | Processing frequency of tick(). Default is 1 / 60 (30fps). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | intervalSec?: number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatarikoptions.isdebug.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) > [isDebug](./three-avatar.createavatarikoptions.isdebug.md) 4 | 5 | ## CreateAvatarIKOptions.isDebug property 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | isDebug?: boolean; 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatarikoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) 4 | 5 | ## CreateAvatarIKOptions interface 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export interface CreateAvatarIKOptions 11 | ``` 12 | 13 | ## Properties 14 | 15 | | Property | Modifiers | Type | Description | 16 | | --- | --- | --- | --- | 17 | | [intervalSec?](./three-avatar.createavatarikoptions.intervalsec.md) | | number | _(Optional)_ Processing frequency of tick(). Default is 1 / 60 (30fps). | 18 | | [isDebug?](./three-avatar.createavatarikoptions.isdebug.md) | | boolean | _(Optional)_ | 19 | | [rotationLimitSet](./three-avatar.createavatarikoptions.rotationlimitset.md) | | [RotationLimitSet](./three-avatar.rotationlimitset.md) | Limit the range of rotation of joints. Default is [getDefaultRotationLimitSet()](./three-avatar.getdefaultrotationlimitset.md). | 20 | | [wristRotationOffsetSet](./three-avatar.createavatarikoptions.wristrotationoffsetset.md) | | [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md) | Offset of rotation angle between the XR controller and the avatar's wrist. Default is [getDefaultWristRotationOffsetSet()](./three-avatar.getdefaultwristrotationoffsetset.md). | 21 | 22 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatarikoptions.rotationlimitset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) > [rotationLimitSet](./three-avatar.createavatarikoptions.rotationlimitset.md) 4 | 5 | ## CreateAvatarIKOptions.rotationLimitSet property 6 | 7 | Limit the range of rotation of joints. Default is [getDefaultRotationLimitSet()](./three-avatar.getdefaultrotationlimitset.md). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | rotationLimitSet: RotationLimitSet; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.createavatarikoptions.wristrotationoffsetset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) > [wristRotationOffsetSet](./three-avatar.createavatarikoptions.wristrotationoffsetset.md) 4 | 5 | ## CreateAvatarIKOptions.wristRotationOffsetSet property 6 | 7 | Offset of rotation angle between the XR controller and the avatar's wrist. Default is [getDefaultWristRotationOffsetSet()](./three-avatar.getdefaultwristrotationoffsetset.md). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | wristRotationOffsetSet: WristRotationOffsetSet; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.createavataroptions.isinvisiblefirstperson.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarOptions](./three-avatar.createavataroptions.md) > [isInvisibleFirstPerson](./three-avatar.createavataroptions.isinvisiblefirstperson.md) 4 | 5 | ## CreateAvatarOptions.isInvisibleFirstPerson property 6 | 7 | Not displayed with a first-person camera. ( But it will be shown in mirrors, etc.). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | isInvisibleFirstPerson?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.createavataroptions.islowspecmode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarOptions](./three-avatar.createavataroptions.md) > [isLowSpecMode](./three-avatar.createavataroptions.islowspecmode.md) 4 | 5 | ## CreateAvatarOptions.isLowSpecMode property 6 | 7 | Reduces resource consumption by omitting some processes. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | isLowSpecMode?: boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.createavataroptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [CreateAvatarOptions](./three-avatar.createavataroptions.md) 4 | 5 | ## CreateAvatarOptions interface 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export interface CreateAvatarOptions extends DecordersOptions, AvatarOptions 11 | ``` 12 | **Extends:** [DecordersOptions](./three-avatar.decordersoptions.md), [AvatarOptions](./three-avatar.avataroptions.md) 13 | 14 | ## Properties 15 | 16 | | Property | Modifiers | Type | Description | 17 | | --- | --- | --- | --- | 18 | | [isInvisibleFirstPerson?](./three-avatar.createavataroptions.isinvisiblefirstperson.md) | | boolean | _(Optional)_ Not displayed with a first-person camera. ( But it will be shown in mirrors, etc.). | 19 | | [isLowSpecMode?](./three-avatar.createavataroptions.islowspecmode.md) | | boolean | _(Optional)_ Reduces resource consumption by omitting some processes. | 20 | 21 | -------------------------------------------------------------------------------- /docs/three-avatar.decordersoptions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [DecordersOptions](./three-avatar.decordersoptions.md) 4 | 5 | ## DecordersOptions type 6 | 7 | URL of the library required if GLTF data is compressed. If omitted, decompress with default values. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type DecordersOptions = { 13 | dracoDecoderPath?: string; 14 | basisTranscoderPath?: string; 15 | meshoptDecoderPath?: string; 16 | }; 17 | ``` 18 | 19 | ## Remarks 20 | 21 | [DRACOLoader](https://threejs.org/docs/#examples/en/loaders/DRACOLoader), [KTX2Loader](https://threejs.org/docs/?q=KTX2Loader#examples/en/loaders/KTX2Loader), [GLTFLoader](https://threejs.org/docs/?q=gltfl#examples/en/loaders/GLTFLoader) 22 | 23 | -------------------------------------------------------------------------------- /docs/three-avatar.getdefaultrotationlimitset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [getDefaultRotationLimitSet](./three-avatar.getdefaultrotationlimitset.md) 4 | 5 | ## getDefaultRotationLimitSet() function 6 | 7 | Default value of [RotationLimitSet](./three-avatar.rotationlimitset.md). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function getDefaultRotationLimitSet(t: AvatarType): RotationLimitSet; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | t | [AvatarType](./three-avatar.avatartype.md) | | 20 | 21 | **Returns:** 22 | 23 | [RotationLimitSet](./three-avatar.rotationlimitset.md) 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.getdefaultwristrotationoffsetset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [getDefaultWristRotationOffsetSet](./three-avatar.getdefaultwristrotationoffsetset.md) 4 | 5 | ## getDefaultWristRotationOffsetSet() function 6 | 7 | Default value of [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function getDefaultWristRotationOffsetSet(t: AvatarType): WristRotationOffsetSet; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | t | [AvatarType](./three-avatar.avatartype.md) | | 20 | 21 | **Returns:** 22 | 23 | [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md) 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.getrenderinfo.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [getRenderInfo](./three-avatar.getrenderinfo.md) 4 | 5 | ## getRenderInfo() function 6 | 7 | Get GPU information. See also [WEBGL\_debug\_renderer\_info](https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_debug_renderer_info) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function getRenderInfo(): { 13 | vendor: string; 14 | renderer: string; 15 | } | undefined; 16 | ``` 17 | **Returns:** 18 | 19 | { vendor: string; renderer: string; } \| undefined 20 | 21 | -------------------------------------------------------------------------------- /docs/three-avatar.iktargetbones.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [IKTargetBones](./three-avatar.iktargetbones.md) 4 | 5 | ## IKTargetBones type 6 | 7 | Shoulder, Elbow, Wrist 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type IKTargetBones = [ 13 | THREE.Bone | undefined, 14 | THREE.Bone | undefined, 15 | THREE.Bone | undefined 16 | ]; 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/three-avatar.isandroid.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [isAndroid](./three-avatar.isandroid.md) 4 | 5 | ## isAndroid() function 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export declare function isAndroid(): boolean; 11 | ``` 12 | **Returns:** 13 | 14 | boolean 15 | 16 | -------------------------------------------------------------------------------- /docs/three-avatar.isanimationdataloaded.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [isAnimationDataLoaded](./three-avatar.isanimationdataloaded.md) 4 | 5 | ## isAnimationDataLoaded() function 6 | 7 | Whether animation data is pre-loaded or not 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function isAnimationDataLoaded(): boolean; 13 | ``` 14 | **Returns:** 15 | 16 | boolean 17 | 18 | -------------------------------------------------------------------------------- /docs/three-avatar.isios.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [isIOS](./three-avatar.isios.md) 4 | 5 | ## isIOS() function 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export declare function isIOS(): boolean; 11 | ``` 12 | **Returns:** 13 | 14 | boolean 15 | 16 | -------------------------------------------------------------------------------- /docs/three-avatar.islowspecdevice.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [isLowSpecDevice](./three-avatar.islowspecdevice.md) 4 | 5 | ## isLowSpecDevice() function 6 | 7 | Whether the device does not have a GPU capable of processing enough. (Experimental Features) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function isLowSpecDevice(): boolean; 13 | ``` 14 | **Returns:** 15 | 16 | boolean 17 | 18 | ## Remarks 19 | 20 | Intended to be used to adjust texture size, processing load, etc. The current implementation is a very tentative decision and needs to be improved. 21 | 22 | -------------------------------------------------------------------------------- /docs/three-avatar.istouchdevice.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [isTouchDevice](./three-avatar.istouchdevice.md) 4 | 5 | ## isTouchDevice() function 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | export declare function isTouchDevice(): boolean; 11 | ``` 12 | **Returns:** 13 | 14 | boolean 15 | 16 | -------------------------------------------------------------------------------- /docs/three-avatar.lipsync.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Lipsync](./three-avatar.lipsync.md) 4 | 5 | ## Lipsync() function 6 | 7 | Slightly customized [threelipsync module](https://github.com/gerardllorach/threelipsync). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function Lipsync(context: AudioContext, ms: MediaStream, threshold?: number, smoothness?: number, pitch?: number): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | context | AudioContext | | 20 | | ms | MediaStream | | 21 | | threshold | number | _(Optional)_ | 22 | | smoothness | number | _(Optional)_ | 23 | | pitch | number | _(Optional)_ | 24 | 25 | **Returns:** 26 | 27 | void 28 | 29 | ## Remarks 30 | 31 | 32 | ``` 33 | --------------------- THREELIPSYNC MODULE -------------------- 34 | Computes the values of THREE blend shapes (kiss, lips closed and mouth open/jaw) 35 | To do so, it computes the energy of THREE frequency bands in real time. 36 | he webpage needs to be https in order to get the microphone. If using external 37 | audio files from URL, they need to be from a https origin. 38 | 39 | Author: Gerard Llorach 40 | Paper: G. Llorach, A. Evans, J. Blat, G. Grimm, V. Hohmann. Web-based live speech-driven 41 | lip-sync, Proceedings of VS-Games 2016, September, Barcelona 42 | Date: Nov 2016 43 | https://repositori.upf.edu/bitstream/handle/10230/28139/llorach_VSG16_web.pdf 44 | License: MIT 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /docs/three-avatar.loadavatarmodel.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [loadAvatarModel](./three-avatar.loadavatarmodel.md) 4 | 5 | ## loadAvatarModel() function 6 | 7 | Load avatar file data. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function loadAvatarModel(avatarData: Uint8Array, renderer: THREE.WebGLRenderer, frustumCulled?: boolean, options?: DecordersOptions): Promise; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatarData | Uint8Array | Data from gltf or vrm files. | 20 | | renderer | THREE.WebGLRenderer | | 21 | | frustumCulled | boolean | _(Optional)_ [Object3D.frustumCulled](https://threejs.org/docs/?q=Mesh#api/en/core/Object3D.frustumCulled) applied recursively. | 22 | | options | [DecordersOptions](./three-avatar.decordersoptions.md) | _(Optional)_ | 23 | 24 | **Returns:** 25 | 26 | Promise<[AvatarModel](./three-avatar.avatarmodel.md)> 27 | 28 | ## Example 29 | 30 | 31 | ```ts 32 | let resp = await fetch(url); 33 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 34 | const model = await loadAvatarModel(avatarData, renderer, false); 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /docs/three-avatar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) 4 | 5 | ## three-avatar package 6 | 7 | Avatar system 8 | 9 | ## Classes 10 | 11 | | Class | Description | 12 | | --- | --- | 13 | | [AutoWalker](./three-avatar.autowalker.md) | Avatar extension for automatic walking animation when moving. | 14 | | [Avatar](./three-avatar.avatar.md) | Avatar | 15 | | [AvatarIK](./three-avatar.avatarik.md) | IK (Inverse Kinematics) to move the avatar's arms in sync with the XR controller's movements. (Experimental Features) | 16 | | [Blinker](./three-avatar.blinker.md) | Avatar extension to implement blink. | 17 | | [SimpleBoundingBoxCollider](./three-avatar.simpleboundingboxcollider.md) | Avatar extension to determine collision using [BoundingBox](https://threejs.org/docs/?q=Box3#api/en/math/Box3.setFromObject). | 18 | 19 | ## Functions 20 | 21 | | Function | Description | 22 | | --- | --- | 23 | | [addMirrorHUD(avatar, container, options)](./three-avatar.addmirrorhud.md) | Create a mirror in front of the avatar. | 24 | | [createAvatar(avatarData, renderer, frustumCulled, options)](./three-avatar.createavatar.md) | Create [Avatar](./three-avatar.avatar.md) | 25 | | [createAvatarIK(avatar, handGetter, option)](./three-avatar.createavatarik.md) | Create [AvatarIK](./three-avatar.avatarik.md). (Experimental Features) | 26 | | [getDefaultRotationLimitSet(t)](./three-avatar.getdefaultrotationlimitset.md) | Default value of [RotationLimitSet](./three-avatar.rotationlimitset.md). | 27 | | [getDefaultWristRotationOffsetSet(t)](./three-avatar.getdefaultwristrotationoffsetset.md) | Default value of [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md). | 28 | | [getRenderInfo()](./three-avatar.getrenderinfo.md) | Get GPU information. See also [WEBGL\_debug\_renderer\_info](https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_debug_renderer_info) | 29 | | [isAndroid()](./three-avatar.isandroid.md) | | 30 | | [isAnimationDataLoaded()](./three-avatar.isanimationdataloaded.md) | Whether animation data is pre-loaded or not | 31 | | [isIOS()](./three-avatar.isios.md) | | 32 | | [isLowSpecDevice()](./three-avatar.islowspecdevice.md) | Whether the device does not have a GPU capable of processing enough. (Experimental Features) | 33 | | [isTouchDevice()](./three-avatar.istouchdevice.md) | | 34 | | [Lipsync(context, ms, threshold, smoothness, pitch)](./three-avatar.lipsync.md) | Slightly customized [threelipsync module](https://github.com/gerardllorach/threelipsync). | 35 | | [loadAvatarModel(avatarData, renderer, frustumCulled, options)](./three-avatar.loadavatarmodel.md) | Load avatar file data. | 36 | | [preLoadAnimationData(source, fetchFunc)](./three-avatar.preloadanimationdata.md) | Pre-load animation data. | 37 | | [registerSyncAvatarHeadAndCamera(xr, nonVrCamera, head, headOffset, getAvatar, options)](./three-avatar.registersyncavatarheadandcamera.md) | Automatically adjust camera when switching between VR and non-VR modes. | 38 | | [setDefaultExtensions(avatar)](./three-avatar.setdefaultextensions.md) | Set up default extensions for [Avatar](./three-avatar.avatar.md) | 39 | | [setNonVRCameraMode(camera, cameraOffset, avatar, isFirstPerson)](./three-avatar.setnonvrcameramode.md) | Adjust the camera for non-VR mode. | 40 | 41 | ## Interfaces 42 | 43 | | Interface | Description | 44 | | --- | --- | 45 | | [AddMirrorHUDOptions](./three-avatar.addmirrorhudoptions.md) | | 46 | | [AvatarExtension](./three-avatar.avatarextension.md) | Interface to extend [Avatar](./three-avatar.avatar.md). | 47 | | [AvatarModel](./three-avatar.avatarmodel.md) | Avatar model. | 48 | | [AvatarOptions](./three-avatar.avataroptions.md) | | 49 | | [CreateAvatarIKOptions](./three-avatar.createavatarikoptions.md) | | 50 | | [CreateAvatarOptions](./three-avatar.createavataroptions.md) | | 51 | 52 | ## Variables 53 | 54 | | Variable | Description | 55 | | --- | --- | 56 | | [AvatarTypeReadyPlayerMe](./three-avatar.avatartypereadyplayerme.md) | [Ready Player Me](https://readyplayer.me/) \| Avatar Data Types | 57 | | [AvatarTypeVrmV0](./three-avatar.avatartypevrmv0.md) | [VRM](https://vrm.dev/en/) version less than 1 \| Avatar Data Types | 58 | | [AvatarTypeVrmV1](./three-avatar.avatartypevrmv1.md) | [VRM](https://vrm.dev/en/) version 1.x \| Avatar Data Types | 59 | 60 | ## Type Aliases 61 | 62 | | Type Alias | Description | 63 | | --- | --- | 64 | | [AvatarAnimationDataSource](./three-avatar.avataranimationdatasource.md) | [mixamo](https://www.mixamo.com/) animation file URLs | 65 | | [AvatarType](./three-avatar.avatartype.md) | Avatar Data Types | 66 | | [DecordersOptions](./three-avatar.decordersoptions.md) | URL of the library required if GLTF data is compressed. If omitted, decompress with default values. | 67 | | [IKTargetBones](./three-avatar.iktargetbones.md) | Shoulder, Elbow, Wrist | 68 | | [RotationLimit](./three-avatar.rotationlimit.md) | Limit the range of rotation of joint. | 69 | | [RotationLimitSet](./three-avatar.rotationlimitset.md) | Limit the range of rotation of joints. | 70 | | [Vector3Tupple](./three-avatar.vector3tupple.md) | x,y,z | 71 | | [VRHandGetter](./three-avatar.vrhandgetter.md) | Get XR controller objects. Use to get the position of the IK hand. | 72 | | [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md) | Offset of rotation angle between the XR controller and the avatar's wrist. | 73 | 74 | -------------------------------------------------------------------------------- /docs/three-avatar.preloadanimationdata.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [preLoadAnimationData](./three-avatar.preloadanimationdata.md) 4 | 5 | ## preLoadAnimationData() function 6 | 7 | Pre-load animation data. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function preLoadAnimationData(source: AvatarAnimationDataSource, fetchFunc?: (url: string) => Promise): Promise; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | source | [AvatarAnimationDataSource](./three-avatar.avataranimationdatasource.md) | | 20 | | fetchFunc | (url: string) => Promise<Response> | _(Optional)_ | 21 | 22 | **Returns:** 23 | 24 | Promise<void> 25 | 26 | ## Example 27 | 28 | 29 | ```ts 30 | // Animation fbx files downloaded from mixamo.com 31 | const ANIMATION_MAP = { 32 | idle: "path/to/idle.fbx", 33 | walk: "path/to/walk.fbx", 34 | dance: "path/to/dance.fbx", 35 | ... 36 | }; 37 | if (!isAnimationDataLoaded()) { 38 | await preLoadAnimationData(ANIMATION_MAP); 39 | } 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /docs/three-avatar.registersyncavatarheadandcamera.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [registerSyncAvatarHeadAndCamera](./three-avatar.registersyncavatarheadandcamera.md) 4 | 5 | ## registerSyncAvatarHeadAndCamera() function 6 | 7 | Automatically adjust camera when switching between VR and non-VR modes. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function registerSyncAvatarHeadAndCamera(xr: THREE.WebXRManager, nonVrCamera: THREE.PerspectiveCamera, head: THREE.Object3D, headOffset: THREE.Object3D, getAvatar: () => Avatar | undefined, options: { 13 | onVR: () => Promise | void; 14 | onNonVR: () => Promise | void; 15 | }): Promise; 16 | ``` 17 | 18 | ## Parameters 19 | 20 | | Parameter | Type | Description | 21 | | --- | --- | --- | 22 | | xr | THREE.WebXRManager | | 23 | | nonVrCamera | THREE.PerspectiveCamera | | 24 | | head | THREE.Object3D | | 25 | | headOffset | THREE.Object3D | | 26 | | getAvatar | () => [Avatar](./three-avatar.avatar.md) \| undefined | If the avatar is changed after calling registerSyncAvatarHeadAndCamera, it will continue to work correctly. | 27 | | options | { onVR: () => Promise<void> \| void; onNonVR: () => Promise<void> \| void; } | | 28 | 29 | **Returns:** 30 | 31 | Promise<void> 32 | 33 | ## Example 34 | 35 | 36 | ```ts 37 | registerSyncAvatarHeadAndCamera( 38 | renderer.xr, 39 | camera, 40 | camera, 41 | camera.parent, 42 | () => _avatar, 43 | { 44 | onVR: async () => { 45 | playerController.isVR = true; 46 | }, 47 | onNonVR: async () => { 48 | playerController.isVR = false; 49 | setNonVRCameraMode(camera, camera.parent, avatar, isFPS); 50 | }, 51 | } 52 | ); 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /docs/three-avatar.rotationlimit.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [RotationLimit](./three-avatar.rotationlimit.md) 4 | 5 | ## RotationLimit type 6 | 7 | Limit the range of rotation of joint. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type RotationLimit = { 13 | rotationMin?: Vector3Tupple; 14 | rotationMax?: Vector3Tupple; 15 | }; 16 | ``` 17 | **References:** [Vector3Tupple](./three-avatar.vector3tupple.md) 18 | 19 | -------------------------------------------------------------------------------- /docs/three-avatar.rotationlimitset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [RotationLimitSet](./three-avatar.rotationlimitset.md) 4 | 5 | ## RotationLimitSet type 6 | 7 | Limit the range of rotation of joints. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type RotationLimitSet = { 13 | leftArm: [RotationLimit, RotationLimit]; 14 | rightArm: [RotationLimit, RotationLimit]; 15 | }; 16 | ``` 17 | **References:** [RotationLimit](./three-avatar.rotationlimit.md) 18 | 19 | -------------------------------------------------------------------------------- /docs/three-avatar.setdefaultextensions.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [setDefaultExtensions](./three-avatar.setdefaultextensions.md) 4 | 5 | ## setDefaultExtensions() function 6 | 7 | Set up default extensions for [Avatar](./three-avatar.avatar.md) 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function setDefaultExtensions(avatar: Avatar): void; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | avatar | [Avatar](./three-avatar.avatar.md) | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.setnonvrcameramode.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [setNonVRCameraMode](./three-avatar.setnonvrcameramode.md) 4 | 5 | ## setNonVRCameraMode() function 6 | 7 | Adjust the camera for non-VR mode. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare function setNonVRCameraMode(camera: THREE.Camera, cameraOffset: THREE.Object3D, avatar: Avatar, isFirstPerson: boolean): Promise; 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | camera | THREE.Camera | | 20 | | cameraOffset | THREE.Object3D | | 21 | | avatar | [Avatar](./three-avatar.avatar.md) | | 22 | | isFirstPerson | boolean | First person view or 3rd person view. | 23 | 24 | **Returns:** 25 | 26 | Promise<void> 27 | 28 | -------------------------------------------------------------------------------- /docs/three-avatar.simpleboundingboxcollider._constructor_.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [SimpleBoundingBoxCollider](./three-avatar.simpleboundingboxcollider.md) > [(constructor)](./three-avatar.simpleboundingboxcollider._constructor_.md) 4 | 5 | ## SimpleBoundingBoxCollider.(constructor) 6 | 7 | Constructs a new instance of the `SimpleBoundingBoxCollider` class 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | constructor(moveTarget: THREE.Object3D, getBoxes: () => THREE.Box3[] | undefined); 13 | ``` 14 | 15 | ## Parameters 16 | 17 | | Parameter | Type | Description | 18 | | --- | --- | --- | 19 | | moveTarget | THREE.Object3D | Objects to move. | 20 | | getBoxes | () => THREE.Box3\[\] \| undefined | Get a list of bounding boxes of collisionable objects. | 21 | 22 | -------------------------------------------------------------------------------- /docs/three-avatar.simpleboundingboxcollider.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [SimpleBoundingBoxCollider](./three-avatar.simpleboundingboxcollider.md) 4 | 5 | ## SimpleBoundingBoxCollider class 6 | 7 | Avatar extension to determine collision using [BoundingBox](https://threejs.org/docs/?q=Box3#api/en/math/Box3.setFromObject). 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export declare class SimpleBoundingBoxCollider 13 | ``` 14 | 15 | ## Constructors 16 | 17 | | Constructor | Modifiers | Description | 18 | | --- | --- | --- | 19 | | [(constructor)(moveTarget, getBoxes)](./three-avatar.simpleboundingboxcollider._constructor_.md) | | Constructs a new instance of the SimpleBoundingBoxCollider class | 20 | 21 | ## Methods 22 | 23 | | Method | Modifiers | Description | 24 | | --- | --- | --- | 25 | | [moveTo(x, y, z)](./three-avatar.simpleboundingboxcollider.moveto.md) | | | 26 | | [setup(avatar)](./three-avatar.simpleboundingboxcollider.setup.md) | | | 27 | 28 | -------------------------------------------------------------------------------- /docs/three-avatar.simpleboundingboxcollider.moveto.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [SimpleBoundingBoxCollider](./three-avatar.simpleboundingboxcollider.md) > [moveTo](./three-avatar.simpleboundingboxcollider.moveto.md) 4 | 5 | ## SimpleBoundingBoxCollider.moveTo() method 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | moveTo(x: number, y: number, z: number): void; 11 | ``` 12 | 13 | ## Parameters 14 | 15 | | Parameter | Type | Description | 16 | | --- | --- | --- | 17 | | x | number | | 18 | | y | number | | 19 | | z | number | | 20 | 21 | **Returns:** 22 | 23 | void 24 | 25 | -------------------------------------------------------------------------------- /docs/three-avatar.simpleboundingboxcollider.setup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [SimpleBoundingBoxCollider](./three-avatar.simpleboundingboxcollider.md) > [setup](./three-avatar.simpleboundingboxcollider.setup.md) 4 | 5 | ## SimpleBoundingBoxCollider.setup() method 6 | 7 | **Signature:** 8 | 9 | ```typescript 10 | setup(avatar: Avatar): void; 11 | ``` 12 | 13 | ## Parameters 14 | 15 | | Parameter | Type | Description | 16 | | --- | --- | --- | 17 | | avatar | [Avatar](./three-avatar.avatar.md) | | 18 | 19 | **Returns:** 20 | 21 | void 22 | 23 | -------------------------------------------------------------------------------- /docs/three-avatar.vector3tupple.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [Vector3Tupple](./three-avatar.vector3tupple.md) 4 | 5 | ## Vector3Tupple type 6 | 7 | x,y,z 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type Vector3Tupple = [number, number, number]; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/three-avatar.vrhandgetter.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [VRHandGetter](./three-avatar.vrhandgetter.md) 4 | 5 | ## VRHandGetter type 6 | 7 | Get XR controller objects. Use to get the position of the IK hand. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type VRHandGetter = { 13 | left?: () => THREE.Object3D | undefined; 14 | right?: () => THREE.Object3D | undefined; 15 | }; 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/three-avatar.wristrotationoffsetset.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Home](./index.md) > [@verseengine/three-avatar](./three-avatar.md) > [WristRotationOffsetSet](./three-avatar.wristrotationoffsetset.md) 4 | 5 | ## WristRotationOffsetSet type 6 | 7 | Offset of rotation angle between the XR controller and the avatar's wrist. 8 | 9 | **Signature:** 10 | 11 | ```typescript 12 | export type WristRotationOffsetSet = { 13 | left: Vector3Tupple; 14 | right: Vector3Tupple; 15 | }; 16 | ``` 17 | **References:** [Vector3Tupple](./three-avatar.vector3tupple.md) 18 | 19 | -------------------------------------------------------------------------------- /example/asset/animation/Breakdance Uprock Var 1.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerseEngine/three-avatar/f33eedd77b301a66bf134e4635b553dda2930175/example/asset/animation/Breakdance Uprock Var 1.fbx -------------------------------------------------------------------------------- /example/asset/animation/idle.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerseEngine/three-avatar/f33eedd77b301a66bf134e4635b553dda2930175/example/asset/animation/idle.fbx -------------------------------------------------------------------------------- /example/asset/animation/walk.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerseEngine/three-avatar/f33eedd77b301a66bf134e4635b553dda2930175/example/asset/animation/walk.fbx -------------------------------------------------------------------------------- /example/asset/avatar-example/rpm.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerseEngine/three-avatar/f33eedd77b301a66bf134e4635b553dda2930175/example/asset/avatar-example/rpm.glb -------------------------------------------------------------------------------- /example/asset/avatar-example/vrm-v0.vrm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerseEngine/three-avatar/f33eedd77b301a66bf134e4635b553dda2930175/example/asset/avatar-example/vrm-v0.vrm -------------------------------------------------------------------------------- /example/asset/avatar-example/vrm-v1.vrm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerseEngine/three-avatar/f33eedd77b301a66bf134e4635b553dda2930175/example/asset/avatar-example/vrm-v1.vrm -------------------------------------------------------------------------------- /example/avatar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 54 | 58 | 69 | 203 | 204 | 205 |
206 | 209 | 212 |
213 |
214 |
Drag avatar file here (glb, vrm).
215 |
216 | 217 | 218 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 31 | 35 | 52 | 132 | 133 | 134 |
135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
143 | 144 | 145 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { 3 | createAvatarIK, 4 | isAnimationDataLoaded, 5 | preLoadAnimationData, 6 | createAvatar, 7 | registerSyncAvatarHeadAndCamera, 8 | setNonVRCameraMode, 9 | addMirrorHUD, 10 | Lipsync, 11 | SimpleBoundingBoxCollider, 12 | } from "three-avatar"; 13 | import { setupScene, createTransformControls } from "./setup"; 14 | import { PlayerController } from "./player-controller"; 15 | 16 | let _avatar; 17 | let _avatarIK; 18 | let _collisionBoxes = []; 19 | let _collisionObjects = []; 20 | let _interactableObjects = []; 21 | let _teleportTargetObjects = []; 22 | let _isFPS = false; 23 | let _bc; 24 | 25 | export const main = (initialLoad /* :?()=>Promise*/) => { 26 | let lipSync; 27 | const ctx = setupScene((dt) => { 28 | _avatar?.tick(dt); 29 | _avatarIK?.tick(dt); 30 | 31 | if (ctx?.renderer.xr?.enabled) { 32 | _avatar?.headSync(ctx?.camera.rotation); 33 | } 34 | if (lipSync) { 35 | _avatar?.lipSync(...lipSync.update()); 36 | } 37 | playerController?.tick(dt); 38 | }, true); 39 | _teleportTargetObjects.push(ctx.ground); 40 | 41 | const { scene, cameraContainer, renderer, camera } = ctx; 42 | const playerObj = new THREE.Group(); 43 | playerObj.name = "playerObj"; 44 | playerObj.add(cameraContainer); 45 | scene.add(playerObj); 46 | 47 | _bc = new SimpleBoundingBoxCollider(playerObj, () => _collisionBoxes); 48 | 49 | const playerController = new PlayerController( 50 | playerObj, 51 | cameraContainer, 52 | cameraContainer, 53 | scene, 54 | renderer, 55 | camera, 56 | { 57 | moveTo: _bc.moveTo.bind(_bc), 58 | getCollisionObjects: () => _collisionObjects, 59 | getInteractableObjects: () => _interactableObjects, 60 | getTeleportTargetObjects: () => _teleportTargetObjects, 61 | } 62 | ); 63 | 64 | if (!initialLoad) { 65 | setupVR(ctx, playerController); 66 | } else { 67 | setTimeout(async () => { 68 | await initialLoad(); 69 | setupVR(ctx, playerController); 70 | }); 71 | } 72 | const setTestIKEnabled = setupTestIK(ctx, playerObj, playerController); 73 | let mirrorHUD; 74 | let lastUrl; 75 | let lastAnimationMap; 76 | let isLowSpecMode = false; 77 | return { 78 | scene, 79 | collisionObjects: _collisionObjects, 80 | interactableObjects: _interactableObjects, 81 | teleportTargetObjects: _teleportTargetObjects, 82 | getAvatar: () => _avatar, 83 | setLowSpecMode: (enabled) => { 84 | enabled = !!enabled; 85 | if (enabled === isLowSpecMode) { 86 | return; 87 | } 88 | isLowSpecMode = enabled; 89 | if (lastUrl) { 90 | return changeAvatar( 91 | camera, 92 | lastUrl, 93 | lastAnimationMap, 94 | playerObj, 95 | renderer, 96 | isLowSpecMode 97 | ); 98 | } 99 | }, 100 | changeAvatar: (url, animationMap) => { 101 | lastUrl = url; 102 | lastAnimationMap = animationMap; 103 | return changeAvatar( 104 | camera, 105 | url, 106 | animationMap, 107 | playerObj, 108 | renderer, 109 | isLowSpecMode 110 | ); 111 | }, 112 | setLipsyncEnabled: async (enabled) => { 113 | if (enabled) { 114 | const ms = await navigator.mediaDevices.getUserMedia({ 115 | audio: true, 116 | }); 117 | lipSync = new Lipsync(THREE.AudioContext.getContext(), ms); 118 | } else { 119 | lipSync = undefined; 120 | } 121 | }, 122 | setTestIKEnabled, 123 | setFPSMode: (enabled) => { 124 | _isFPS = enabled; 125 | updateNonVrCameraMode(_isFPS, _avatar, ctx.camera); 126 | }, 127 | showMirrorHUD: async (enabled) => { 128 | mirrorHUD?.removeFromParent(); 129 | mirrorHUD?.dispose(); 130 | if (enabled) { 131 | while (!_avatar) { 132 | await new Promise((resolve) => setTimeout(resolve)); 133 | } 134 | mirrorHUD = addMirrorHUD(_avatar, playerObj, { 135 | xr: renderer.xr, 136 | }); 137 | } else { 138 | mirrorHUD = undefined; 139 | } 140 | }, 141 | }; 142 | }; 143 | 144 | const changeAvatar = async ( 145 | camera, 146 | url, 147 | animationMap, 148 | playerObj, 149 | renderer, 150 | isLowSpecMode 151 | ) => { 152 | _avatar?.dispose(); 153 | 154 | if (!isAnimationDataLoaded()) { 155 | await preLoadAnimationData(animationMap); 156 | } 157 | 158 | let resp = await fetch(url); 159 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 160 | 161 | _avatar = await createAvatar(avatarData, renderer, false, { 162 | isInvisibleFirstPerson: true, 163 | isLowSpecMode, 164 | }); 165 | window._avatar = _avatar; 166 | _avatar.object3D.name = "myAvatar"; 167 | playerObj.add(_avatar.object3D); 168 | 169 | _bc.setup(_avatar); 170 | _collisionBoxes.length = 0; 171 | [..._collisionObjects, ..._teleportTargetObjects].map((el) => { 172 | el.traverse((c) => { 173 | if (!c.isMesh) { 174 | return; 175 | } 176 | _collisionBoxes.push(new THREE.Box3().setFromObject(c)); 177 | }); 178 | }); 179 | 180 | if (renderer.xr.isPresenting) { 181 | const getCameras = () => 182 | [camera, renderer.xr?.getCamera()].filter((v) => !!v); 183 | _avatar.setFirstPersonMode(getCameras()); 184 | } else { 185 | updateNonVrCameraMode(_isFPS, _avatar, camera); 186 | } 187 | }; 188 | 189 | const setupVR = ({ camera, renderer }, playerController) => { 190 | const getCameras = () => 191 | [camera, renderer.xr?.getCamera()].filter((v) => !!v); 192 | 193 | registerSyncAvatarHeadAndCamera( 194 | renderer.xr, 195 | camera, 196 | camera, 197 | camera.parent, 198 | () => _avatar, 199 | { 200 | onVR: async () => { 201 | playerController.isVR = true; 202 | await _avatar.setIKMode(true); 203 | _avatar.setFirstPersonMode(getCameras()); 204 | _avatarIK?.dispose(); 205 | _avatarIK = createAvatarIK( 206 | _avatar, 207 | { 208 | left: () => playerController.xrController.handHolder.leftHand, 209 | right: () => playerController.xrController.handHolder.rightHand, 210 | }, 211 | { isDebug: true } 212 | ); 213 | window._debugAik = _avatarIK; 214 | }, 215 | onNonVR: async () => { 216 | await _avatar.setIKMode(false); 217 | updateNonVrCameraMode(_isFPS, _avatar, camera); 218 | _avatarIK?.dispose(); 219 | _avatarIK = undefined; 220 | 221 | playerController.isVR = false; 222 | }, 223 | } 224 | ); 225 | }; 226 | const updateNonVrCameraMode = (isFPS, avatar, camera) => { 227 | setNonVRCameraMode(camera, camera.parent, avatar, isFPS); 228 | }; 229 | const setupTestIK = ( 230 | { renderer, camera, scene }, 231 | playerObj, 232 | playerController 233 | ) => { 234 | const createTarget = (pos) => { 235 | const tc = createTransformControls( 236 | camera, 237 | renderer.domElement, 238 | (enabled) => { 239 | playerController.enabled = !enabled; 240 | } 241 | ); 242 | scene.add(tc); 243 | const mesh = new THREE.Mesh( 244 | new THREE.BoxGeometry(0.01, 0.01, 0.01), 245 | new THREE.MeshNormalMaterial() 246 | ); 247 | mesh.position.set(...pos); 248 | playerObj.add(mesh); 249 | tc.attach(mesh); 250 | tc.visible = tc.enabled = mesh.visible = false; 251 | return { tc, mesh }; 252 | }; 253 | const targets = [ 254 | createTarget([0.26, 0.7, -0.26]), 255 | createTarget([-0.26, 0.7, -0.26]), 256 | ]; 257 | 258 | const f = async (enabled) => { 259 | targets.forEach((v) => { 260 | v.tc.visible = v.tc.enabled = v.mesh.visible = enabled; 261 | }); 262 | await _avatar.setIKMode(enabled); 263 | _avatarIK?.dispose(); 264 | if (enabled) { 265 | _avatarIK = createAvatarIK( 266 | _avatar, 267 | { right: () => targets[0].mesh, left: () => targets[1].mesh }, 268 | { isDebug: true } 269 | ); 270 | } else { 271 | _avatarIK = undefined; 272 | } 273 | window._debugAik = _avatarIK; 274 | }; 275 | return f; 276 | }; 277 | -------------------------------------------------------------------------------- /example/player-controller.js: -------------------------------------------------------------------------------- 1 | import { TouchController } from "@verseengine/three-touch-controller"; 2 | import { MoveController } from "@verseengine/three-move-controller"; 3 | import { DefaultXrControllerSet } from "@verseengine/three-xr-controller"; 4 | 5 | function isTouchDevice() { 6 | return "ontouchstart" in window || navigator.maxTouchPoints > 0; 7 | } 8 | 9 | export function isVRSupported() { 10 | return !!navigator.xr; 11 | } 12 | 13 | export class PlayerController { 14 | constructor( 15 | moveTarget /* :THREE.Object3D */, 16 | headRotationTarget /* :THREE.Object3D */, 17 | handContainer /* :THREE.Object3D */, 18 | scene /* :THREE.Scene */, 19 | renderer /* :THREE.WebGLRenderer */, 20 | camera /* :THREE.Camera */, 21 | controllerOptions 22 | ) { 23 | this._enabled = true; 24 | this._isVR = false; 25 | this.touchController = new TouchController(moveTarget, { 26 | moveTo: controllerOptions?.moveTo, 27 | }); 28 | this.moveController = new MoveController( 29 | moveTarget, 30 | moveTarget, 31 | headRotationTarget, 32 | { 33 | moveTo: controllerOptions?.moveTo, 34 | minVerticalRotation: 1.2, 35 | maxVerticalRotation: 2.2, 36 | } 37 | ); 38 | this.xrController = new DefaultXrControllerSet( 39 | renderer, 40 | camera, 41 | scene, 42 | handContainer, 43 | moveTarget, 44 | moveTarget, 45 | controllerOptions 46 | ); 47 | this.isVR = renderer.xr.isPresenting; 48 | } 49 | set isVR(v) { 50 | this._isVR = v; 51 | if (v) { 52 | this.touchController.enabled = false; 53 | this.moveController.enabled = false; 54 | if (this.xrController) { 55 | this.xrController.enabled = true; 56 | } 57 | } else { 58 | this.touchController.enabled = isTouchDevice(); 59 | this.moveController.enabled = !this.touchController.enabled; 60 | if (this.xrController) { 61 | this.xrController.enabled = false; 62 | } 63 | } 64 | } 65 | get isVR() { 66 | return this._isVR; 67 | } 68 | set enabled(v) { 69 | this._enabled = v; 70 | } 71 | get enabled() { 72 | return this._enabled; 73 | } 74 | tick(deltaTime /* : number // THREE.Clock.getDelta() */) { 75 | if (!this._enabled) { 76 | return; 77 | } 78 | this.touchController.tick(deltaTime); 79 | this.moveController.tick(deltaTime); 80 | this.xrController.tick(deltaTime); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /example/setup.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { Sky } from "three/examples/jsm/objects/Sky.js"; 3 | import { TransformControls } from "three/examples/jsm/controls/TransformControls.js"; 4 | import { VRButton } from "three/examples/jsm/webxr/VRButton.js"; 5 | import Stats from "three/examples/jsm/libs/stats.module.js"; 6 | 7 | export function setupScene( 8 | tickFunc /* ?: (deltaTime: number) => void */, 9 | withVR, 10 | withStats 11 | ) { 12 | console.log(getRenderInfo()); 13 | 14 | const renderer = new THREE.WebGLRenderer({ antialias: true }); 15 | renderer.outputEncoding = THREE.sRGBEncoding; 16 | /* renderer.toneMapping = THREE.ACESFilmicToneMapping; 17 | renderer.toneMappingExposure = 1; */ 18 | renderer.setSize(window.innerWidth, window.innerHeight); 19 | renderer.setPixelRatio(window.devicePixelRatio); 20 | document.body.appendChild(renderer.domElement); 21 | 22 | window.addEventListener("resize", () => { 23 | camera.aspect = window.innerWidth / window.innerHeight; 24 | camera.updateProjectionMatrix(); 25 | 26 | renderer.setSize(window.innerWidth, window.innerHeight); 27 | }); 28 | 29 | const camera = new THREE.PerspectiveCamera( 30 | 60, 31 | window.innerWidth / window.innerHeight, 32 | 0.1, 33 | 100 34 | ); 35 | camera.position.set(0.0, 1.5, 2.0); 36 | const cameraContainer = new THREE.Group(); 37 | cameraContainer.add(camera); 38 | 39 | const scene = new THREE.Scene(); 40 | 41 | { 42 | const light = new THREE.AmbientLight(0xffffff, 0.2); 43 | scene.add(light); 44 | staticize(light); 45 | } 46 | { 47 | const light = new THREE.DirectionalLight(0xffffff, 0.8); 48 | light.position.set(0, 10, -10).normalize(); 49 | scene.add(light); 50 | staticize(light); 51 | } 52 | { 53 | const sky = new Sky(); 54 | sky.name = "sky"; 55 | sky.scale.setScalar(450000); 56 | scene.add(sky); 57 | staticize(sky); 58 | 59 | const uniforms = sky.material.uniforms; 60 | const phi = THREE.MathUtils.degToRad(90 - 30); 61 | const theta = THREE.MathUtils.degToRad(180); 62 | 63 | const sun = new THREE.Vector3(); 64 | sun.setFromSphericalCoords(1, phi, theta); 65 | 66 | uniforms["sunPosition"].value.copy(sun); 67 | } 68 | let ground; 69 | { 70 | ground = new THREE.Mesh( 71 | new THREE.PlaneGeometry(50, 50, 1, 1), 72 | new THREE.MeshLambertMaterial({ 73 | color: 0x5e5e5e, 74 | }) 75 | ); 76 | ground.name = "ground"; 77 | ground.rotation.x = Math.PI / -2; 78 | scene.add(ground); 79 | staticize(ground); 80 | } 81 | { 82 | const gridHelper = new THREE.GridHelper(50, 50); 83 | scene.add(gridHelper); 84 | staticize(gridHelper); 85 | } 86 | 87 | let stats; 88 | if (withStats) { 89 | stats = createStats(); 90 | document.body.appendChild(stats.stats.dom); 91 | stats.object3D.position.set(-0.5, -0.3, -1.5); 92 | stats.object3D.scale.set(0.003, 0.003, 0.003); 93 | camera.add(stats.object3D); 94 | stats.object3D.visible = false; 95 | renderer.xr.addEventListener("sessionstart", () => { 96 | stats.object3D.visible = true; 97 | }); 98 | renderer.xr.addEventListener("sessionend", () => { 99 | stats.object3D.visible = false; 100 | }); 101 | } 102 | 103 | const clock = new THREE.Clock(); 104 | function animate() { 105 | if (withStats) { 106 | stats.stats.begin(); 107 | } 108 | 109 | const dt = clock.getDelta(); 110 | if (tickFunc) { 111 | tickFunc(dt); 112 | } 113 | 114 | renderer.render(scene, camera); 115 | 116 | if (withStats) { 117 | stats.stats.end(); 118 | } 119 | } 120 | renderer.setAnimationLoop(animate); 121 | 122 | let vrButton = undefined; 123 | if (withVR) { 124 | if ("xr" in navigator) { 125 | navigator.xr 126 | .isSessionSupported("immersive-vr") 127 | .then(function (supported) { 128 | if (supported) { 129 | renderer.xr.enabled = true; 130 | 131 | document.addEventListener("keydown", function (e) { 132 | if (e.key === "Escape") { 133 | if (renderer.xr.isPresenting) { 134 | renderer.xr.getSession()?.end(); 135 | } 136 | } 137 | }); 138 | vrButton = VRButton.createButton(renderer); 139 | document.body.appendChild(vrButton); 140 | } 141 | }); 142 | } else { 143 | if (window.isSecureContext === false) { 144 | console.warn("webxr needs https"); 145 | } else { 146 | console.warn("webxr not available"); 147 | } 148 | } 149 | } 150 | 151 | { 152 | // For Three.js Inspector (https://zz85.github.io/zz85-bookmarklets/threelabs.html) 153 | window.THREE = THREE; 154 | window._scene = scene; 155 | } 156 | 157 | const res = { 158 | camera, 159 | scene, 160 | renderer, 161 | cameraContainer, 162 | ground, 163 | stats, 164 | vrButton, 165 | }; 166 | window._debugCtx = res; // debug 167 | return res; 168 | } 169 | 170 | export function createBridge() { 171 | const res = new THREE.Group(); 172 | res.name = "bridge"; 173 | const material = new THREE.MeshStandardMaterial({ color: 0xffd479 }); 174 | 175 | let y = 0; 176 | let z = 0; 177 | for (let i = 0; i < 10; i++) { 178 | const m = new THREE.Mesh(new THREE.BoxGeometry(1, 0.2, 0.2), material); 179 | m.position.set(0, y, z); 180 | y += 0.2; 181 | z += 0.2; 182 | res.add(m); 183 | } 184 | z -= 0.1; 185 | { 186 | const m = new THREE.Mesh(new THREE.BoxGeometry(1, 0.2, 5), material); 187 | m.position.set(0, y, z + 2.5); 188 | res.add(m); 189 | z += 5; 190 | } 191 | y -= 0.2; 192 | z += 0.1; 193 | for (let i = 0; i < 10; i++) { 194 | const m = new THREE.Mesh(new THREE.BoxGeometry(1, 0.2, 0.2), material); 195 | m.position.set(0, y, z); 196 | y -= 0.2; 197 | z += 0.2; 198 | res.add(m); 199 | } 200 | 201 | return res; 202 | } 203 | 204 | export function createTransformControls( 205 | camera /* :THREE.Camera*/, 206 | domElement /* ?:HTMLElement*/, 207 | onToggleTransform /* ?:(enabled:boolean)=>void */ 208 | ) { 209 | const tc = new TransformControls(camera, domElement); 210 | tc.addEventListener("dragging-changed", (e) => { 211 | if (onToggleTransform) { 212 | onToggleTransform(e.value); 213 | } 214 | }); 215 | let downTm = 0; 216 | tc.addEventListener("mouseDown", (_e) => { 217 | downTm = new Date().getTime(); 218 | // 219 | }); 220 | tc.addEventListener("mouseUp", (_e) => { 221 | const now = new Date().getTime(); 222 | if (now - downTm > 300) { 223 | return; 224 | } 225 | const prev = tc.getMode(); 226 | if (prev === "translate") { 227 | tc.setMode("rotate"); 228 | } else { 229 | tc.setMode("translate"); 230 | } 231 | }); 232 | 233 | return tc; 234 | } 235 | 236 | function createStats() { 237 | const stats = new Stats(); 238 | const canvas = stats.dom.children[0]; 239 | const panel = new THREE.Mesh( 240 | new THREE.PlaneGeometry( 241 | canvas.width * window.devicePixelRatio, 242 | canvas.height * window.devicePixelRatio 243 | ), 244 | new THREE.MeshBasicMaterial() 245 | ); 246 | panel.name = "stats"; 247 | const textureLoader = new THREE.TextureLoader(); 248 | 249 | const updateMesh = () => { 250 | const img = canvas.toDataURL("image/png"); 251 | textureLoader.load(img, (v) => { 252 | panel.material.map?.dispose(); 253 | panel.material.map = v; 254 | panel.material.needsUpdate = true; 255 | }); 256 | }; 257 | setInterval(updateMesh, 1000); 258 | 259 | return { 260 | object3D: panel, 261 | stats, 262 | }; 263 | } 264 | 265 | export function getRenderInfo() { 266 | try { 267 | const canvas = document.createElement("canvas"); 268 | const gl = canvas.getContext("webgl2"); 269 | const ext = gl.getExtension("WEBGL_debug_renderer_info"); 270 | if (ext) { 271 | return { 272 | vendor: gl.getParameter(ext.UNMASKED_VENDOR_WEBGL), 273 | renderer: gl.getParameter(ext.UNMASKED_RENDERER_WEBGL), 274 | }; 275 | } 276 | } catch (ex) { 277 | console.warn(ex); 278 | } 279 | } 280 | 281 | function staticize(o) { 282 | o.matrixAutoUpdate = false; 283 | o.matrixWorldAutoUpdate = false; 284 | o.updateMatrix(); 285 | o.updateMatrixWorld(); 286 | } 287 | -------------------------------------------------------------------------------- /example/world.js: -------------------------------------------------------------------------------- 1 | import { createBridge } from "./setup"; 2 | import * as THREE from "three"; 3 | 4 | export function createWorldObjects({ 5 | scene, 6 | collisionObjects, 7 | _interactableObjects, 8 | teleportTargetObjects, 9 | }) { 10 | { 11 | const o = createBridge(); 12 | o.rotateY(180 * (Math.PI / 180)); 13 | o.position.set(1, 0.1, -1.5); 14 | scene.add(o); 15 | teleportTargetObjects.push(o); 16 | } 17 | { 18 | const o = createBridge(); 19 | o.rotateY(135 * (Math.PI / 180)); 20 | o.position.set(2, 0.1, -1); 21 | scene.add(o); 22 | teleportTargetObjects.push(o); 23 | } 24 | const wallMaterial = new THREE.MeshLambertMaterial({ 25 | color: 0x5e5e5e, 26 | side: THREE.DoubleSide, 27 | }); 28 | { 29 | const wall = new THREE.Mesh( 30 | new THREE.PlaneGeometry(10, 3, 1, 1), 31 | wallMaterial 32 | ); 33 | wall.position.set(-2, 0.5, 0); 34 | wall.rotation.y = Math.PI / 2; 35 | scene.add(wall); 36 | collisionObjects.push(wall); 37 | } 38 | { 39 | const wall = new THREE.Mesh( 40 | new THREE.PlaneGeometry(1, 3, 1, 1), 41 | wallMaterial 42 | ); 43 | wall.position.set(-0.5, 0.5, -3); 44 | scene.add(wall); 45 | collisionObjects.push(wall); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@verseengine/three-avatar", 3 | "version": "1.0.2", 4 | "description": "Avatar system for three.js", 5 | "author": "Appland, Inc", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "module": "dist/esm/index.js", 9 | "types": "dist/esm/index.d.ts", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/VerseEngine/three-avatar" 13 | }, 14 | "homepage": "https://verseengine.cloud/", 15 | "keywords": [ 16 | "vr", 17 | "3d", 18 | "metaverse" 19 | ], 20 | "scripts": { 21 | "example": "npm run build && npx http-server -p 8080", 22 | "example-ssl": "npm run build && npx http-server -c-1 --ssl --key ./cert/localhost+2-key.pem --cert ./cert/localhost+2.pem -p 8080", 23 | "clean": "rimraf dist *.clean **/*.clean", 24 | "prepare": "npm run build", 25 | "prebuild": "rimraf dist", 26 | "build": "run-p build:*", 27 | "build:common": "esbuild --format=iife --sourcemap src/index.ts --tsconfig=tsconfig.json --bundle --packages=external --outfile=dist/index.js", 28 | "build:esm": "esbuild --format=esm --sourcemap src/index.ts --tsconfig=tsconfig.json --bundle --packages=external --outfile=dist/esm/index.js", 29 | "build:types": "tsc --emitDeclarationOnly --declaration --declarationDir dist/esm", 30 | "postbuild": "npx api-extractor run --local --verbose && npx api-documenter markdown -i dist/temp/ -o ./docs", 31 | "lint": "tsc --noEmit && npx eslint .", 32 | "check-update": "npx npm-check-updates" 33 | }, 34 | "files": [ 35 | "dist" 36 | ], 37 | "devDependencies": { 38 | "@microsoft/api-documenter": "^7.21.5", 39 | "@microsoft/api-extractor": "^7.34.4", 40 | "@types/three": ">=0.146.0", 41 | "@typescript-eslint/eslint-plugin": "^5.52.0", 42 | "@typescript-eslint/parser": "^5.52.0", 43 | "@verseengine/three-move-controller": "^1", 44 | "@verseengine/three-touch-controller": "^1", 45 | "@verseengine/three-xr-controller": "^1", 46 | "esbuild": "^0.17.8", 47 | "eslint": "^8.34.0", 48 | "eslint-plugin-tsdoc": "^0.2.17", 49 | "npm-run-all": "^4.1.5", 50 | "rimraf": "^4.1.2", 51 | "typescript": "^4.9.5" 52 | }, 53 | "dependencies": { 54 | "@pixiv/three-vrm": "2", 55 | "three": "^0.153.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/avatar-ik.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import type { IKTargetBones } from "./types"; 3 | 4 | import { 5 | CCDIKSolver, 6 | CCDIKHelper, 7 | } from "three/examples/jsm/animation/CCDIKSolver.js"; 8 | 9 | const DEFAULT_INTERVAL_SEC = 1 / 60; // 60fps 10 | 11 | type IKTargetBonesStrict = [THREE.Bone, THREE.Bone, THREE.Bone]; 12 | function isNotIncludeUndefined( 13 | bones: IKTargetBones 14 | ): bones is IKTargetBonesStrict { 15 | return !bones.includes(undefined); 16 | } 17 | 18 | class Tmps { 19 | vec: THREE.Vector3; 20 | vec1: THREE.Vector3; 21 | // vec2: THREE.Vector3; 22 | quat: THREE.Quaternion; 23 | quat1: THREE.Quaternion; 24 | mat: THREE.Matrix4; 25 | 26 | constructor() { 27 | this.vec = new THREE.Vector3(); 28 | this.vec1 = new THREE.Vector3(); 29 | // this.vec2 = new THREE.Vector3(); 30 | this.quat = new THREE.Quaternion(); 31 | this.quat1 = new THREE.Quaternion(); 32 | this.mat = new THREE.Matrix4(); 33 | } 34 | } 35 | let _tmps: Tmps; 36 | 37 | type SkeletonMesh = THREE.Object3D & { skeleton?: THREE.Skeleton }; 38 | /** 39 | * Get XR controller objects. 40 | * Use to get the position of the IK hand. 41 | */ 42 | export type VRHandGetter = { 43 | left?: () => THREE.Object3D | undefined; 44 | right?: () => THREE.Object3D | undefined; 45 | }; 46 | 47 | /** 48 | * x,y,z 49 | */ 50 | export type Vector3Tupple = [number, number, number]; 51 | /** 52 | * Limit the range of rotation of joint. 53 | */ 54 | export type RotationLimit = { 55 | rotationMin?: Vector3Tupple; 56 | rotationMax?: Vector3Tupple; 57 | }; 58 | /** 59 | * Limit the range of rotation of joints. 60 | */ 61 | export type RotationLimitSet = { 62 | leftArm: [RotationLimit, RotationLimit]; 63 | rightArm: [RotationLimit, RotationLimit]; 64 | }; 65 | /** 66 | * Offset of rotation angle between the XR controller and the avatar's wrist. 67 | */ 68 | export type WristRotationOffsetSet = { 69 | left: Vector3Tupple; 70 | right: Vector3Tupple; 71 | }; 72 | 73 | /** 74 | * IK (Inverse Kinematics) to move the avatar's arms in sync with the XR controller's movements. 75 | * (Experimental Features) 76 | * 77 | * @example 78 | * {@link createAvatarIK} 79 | */ 80 | export class AvatarIK { 81 | private _solver?: CCDIKSolver; 82 | private _vrMappings: VRMapping[]; 83 | private _helper?: CCDIKHelper; 84 | private _intervalSec = 0; 85 | private _sec = 0; 86 | 87 | /**i 88 | * 89 | * @param target - Avatar object. Can be an object unrelated to the {@link Avatar} class. 90 | * @param maybeRightArmBones - Bones moved by IK. If it contains undefined, it is disabled. 91 | * @param maybeLeftArmBones - Bones moved by IK. If it contains undefined, it is disabled. 92 | * @param rotationLimitSet - Limit the range of rotation of joints. 93 | * @param vrHandGetter - Get XR controller objects. 94 | * @param wristRotationOffsetSet - Offset of rotation angle between the XR controller and the avatar's wrist. 95 | * @param options - Processing frequency of tick(). Default is 1 / 60 (30fps). 96 | */ 97 | constructor( 98 | target: THREE.Object3D, 99 | maybeRightArmBones: IKTargetBones, 100 | maybeLeftArmBones: IKTargetBones, 101 | rotationLimitSet: RotationLimitSet, 102 | vrHandGetter: VRHandGetter, 103 | wristRotationOffsetSet: WristRotationOffsetSet, 104 | options?: { 105 | isDebug?: boolean; 106 | intervalSec?: number; 107 | } 108 | ) { 109 | if (!_tmps) { 110 | _tmps = new Tmps(); 111 | } 112 | this._intervalSec = 113 | options?.intervalSec || options?.intervalSec === 0 114 | ? options.intervalSec 115 | : DEFAULT_INTERVAL_SEC; 116 | 117 | const person = target as SkeletonMesh; 118 | 119 | const leftArmBones = isNotIncludeUndefined(maybeLeftArmBones) 120 | ? maybeLeftArmBones 121 | : undefined; 122 | const rightArmBones = isNotIncludeUndefined(maybeRightArmBones) 123 | ? maybeRightArmBones 124 | : undefined; 125 | 126 | if (!person.skeleton) { 127 | const bones = [...(leftArmBones || []), ...(rightArmBones || [])]; 128 | person.skeleton = new THREE.Skeleton(bones); 129 | } 130 | 131 | const skeleton = person.skeleton; 132 | 133 | const iks = []; 134 | const vrMappings: VRMapping[] = []; 135 | if (leftArmBones) { 136 | const { ik, vrMapping } = createIKSettings( 137 | skeleton, 138 | person, 139 | "leftArmIK", 140 | leftArmBones, 141 | rotationLimitSet.leftArm, 142 | vrHandGetter.left, 143 | wristRotationOffsetSet.left 144 | ); 145 | iks.push(ik); 146 | vrMappings.push(vrMapping); 147 | } 148 | if (rightArmBones) { 149 | const { ik, vrMapping } = createIKSettings( 150 | skeleton, 151 | person, 152 | "rightArmIK", 153 | rightArmBones, 154 | rotationLimitSet.rightArm, 155 | vrHandGetter.right, 156 | wristRotationOffsetSet.right 157 | ); 158 | iks.push(ik); 159 | vrMappings.push(vrMapping); 160 | } 161 | 162 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 163 | this._solver = new CCDIKSolver(person as any, iks as any); 164 | if (options?.isDebug) { 165 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 166 | this._helper = new CCDIKHelper(person as any, iks as any, 0.01); 167 | let el = person; 168 | while (el.parent) { 169 | el = el.parent; 170 | } 171 | el.add(this._helper); 172 | } 173 | 174 | this._vrMappings = vrMappings; 175 | } 176 | /** 177 | * Processes called periodically 178 | * 179 | * @example 180 | * ```ts 181 | * const clock = new THREE.Clock(); 182 | * renderer.setAnimationLoop(() => { 183 | * const dt = clock.getDelta(); 184 | * avatarIK.tick(dt); 185 | * }); 186 | * ``` 187 | * or 188 | * ```ts 189 | * const clock = new THREE.Clock(); 190 | * setInterval(() => { 191 | * const dt = clock.getDelta(); 192 | * avatarIK.tick(dt); 193 | * }, anything); 194 | * ``` 195 | */ 196 | tick( 197 | deltaTime: number // THREE.Clock.getDelta() 198 | ) { 199 | this._sec += deltaTime; 200 | if (this._sec < this._intervalSec) { 201 | return; 202 | } 203 | this._sec = 0; 204 | 205 | if (this._solver) { 206 | for (let i = 0; i < this._vrMappings.length; i++) { 207 | const m = this._vrMappings[i]; 208 | const vrHandPos = m.getVrHand()?.position; 209 | if ( 210 | vrHandPos && 211 | !(vrHandPos.x === 0 && vrHandPos.y === 0 && vrHandPos.z === 0) 212 | ) { 213 | m.mapVRAvatar(); 214 | } else { 215 | m.reset(); 216 | } 217 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 218 | this._solver.updateOne((this._solver as any).iks[i]); 219 | } 220 | } 221 | } 222 | /** 223 | * Releases all resources allocated by this instance. 224 | */ 225 | dispose() { 226 | delete this._solver; 227 | if (this._helper) { 228 | this._helper.removeFromParent(); 229 | delete this._helper; 230 | } 231 | } 232 | } 233 | 234 | function findOrAddTargetBone( 235 | skeleton: THREE.Skeleton, 236 | person: THREE.Object3D, 237 | name: string, 238 | effectorBone: THREE.Bone 239 | ): THREE.Bone { 240 | const b = skeleton.bones.find((v) => v.name === name); 241 | if (b) { 242 | return b; 243 | } 244 | const targetBone = new THREE.Bone(); 245 | targetBone.name = name; 246 | skeleton.bones.push(targetBone); 247 | skeleton.boneInverses.push(new THREE.Matrix4()); 248 | 249 | skeleton.boneInverses[skeleton.boneInverses.length - 1] 250 | .copy(skeleton.bones[skeleton.bones.length - 1].matrixWorld) 251 | .invert(); 252 | person.add(targetBone); 253 | person.updateMatrixWorld(); 254 | const initPos = effectorBone.getWorldPosition(new THREE.Vector3()); 255 | targetBone.position.copy( 256 | initPos.applyMatrix4(person.matrixWorld.clone().invert()) 257 | ); 258 | return targetBone; 259 | } 260 | 261 | const limitToVec = (ar: Vector3Tupple | undefined): THREE.Vector3 | undefined => 262 | ar 263 | ? new THREE.Vector3().fromArray(ar.map(THREE.MathUtils.degToRad)) 264 | : undefined; 265 | 266 | function createIKSettings( 267 | skeleton: THREE.Skeleton, 268 | person: THREE.Object3D, 269 | name: string, 270 | bones: IKTargetBonesStrict, 271 | rotationLimits: [RotationLimit, RotationLimit], 272 | vrHandGetter: (() => THREE.Object3D | undefined) | undefined, 273 | rotationOffset: Vector3Tupple 274 | ) { 275 | const targetBone = findOrAddTargetBone(skeleton, person, name, bones[2]); 276 | const ik = { 277 | target: skeleton.bones.findIndex((v) => v === targetBone), 278 | effector: skeleton.bones.findIndex((v) => v === bones[2]), 279 | iteration: 1, 280 | minAngle: -0.2, 281 | maxAngle: 0.2, 282 | links: [ 283 | { 284 | index: skeleton.bones.findIndex((v) => v === bones[1]), 285 | rotationMin: limitToVec(rotationLimits[1].rotationMin), 286 | rotationMax: limitToVec(rotationLimits[1].rotationMax), 287 | }, 288 | { 289 | index: skeleton.bones.findIndex((v) => v === bones[0]), 290 | rotationMin: limitToVec(rotationLimits[0].rotationMin), 291 | rotationMax: limitToVec(rotationLimits[0].rotationMax), 292 | }, 293 | ], 294 | }; 295 | const vrMapping = new VRMapping( 296 | person, 297 | () => vrHandGetter?.(), 298 | targetBone, 299 | bones[2], 300 | bones[0], 301 | new THREE.Quaternion().setFromEuler( 302 | new THREE.Euler(...rotationOffset.map((v) => THREE.MathUtils.degToRad(v))) 303 | ) 304 | ); 305 | return { ik, vrMapping }; 306 | } 307 | 308 | class VRMapping { 309 | vrPerson: THREE.Object3D; 310 | getVrHand: () => THREE.Object3D | undefined; 311 | ikTarget: THREE.Object3D; 312 | ikTargetInitPos: THREE.Vector3; 313 | wristBone: THREE.Object3D; 314 | wristRotationOffset: THREE.Quaternion | undefined; 315 | wristBoneInitQuat: THREE.Quaternion; 316 | // maxDistanceSquared: number; 317 | maxShoulderToControllderDistanceSquared = 0; 318 | shoulderBone: THREE.Object3D; 319 | 320 | constructor( 321 | vrPerson: THREE.Object3D, 322 | getVrHand: () => THREE.Object3D | undefined, 323 | ikTarget: THREE.Object3D, 324 | wristBone: THREE.Object3D, 325 | shoulderBone: THREE.Object3D, 326 | wristRotationOffset?: THREE.Quaternion 327 | ) { 328 | this.vrPerson = vrPerson; 329 | this.getVrHand = getVrHand; 330 | this.ikTarget = ikTarget; 331 | this.ikTargetInitPos = new THREE.Vector3().copy(ikTarget.position); 332 | this.wristBone = wristBone; 333 | this.wristRotationOffset = wristRotationOffset; 334 | this.wristBoneInitQuat = new THREE.Quaternion().copy(wristBone.quaternion); 335 | this.shoulderBone = shoulderBone; 336 | /* this.maxDistanceSquared = shoulderBone 337 | .getWorldPosition(_tmps.vec) 338 | .distanceToSquared(wristBone.getWorldPosition(_tmps.vec1)); */ 339 | } 340 | 341 | mapVRAvatar() { 342 | const vrHand = this.getVrHand(); 343 | if (!vrHand) { 344 | return; 345 | } 346 | 347 | { 348 | /* { 349 | const v = this.shoulderBone 350 | .getWorldPosition(_tmps.vec) 351 | .distanceToSquared(this.wristBone.getWorldPosition(_tmps.vec1)); 352 | if (this.maxDistanceSquared < v) { 353 | this.maxDistanceSquared = v; 354 | } 355 | } 356 | const controllerPos = vrHand.getWorldPosition(_tmps.vec); 357 | const shoulderPos = this.shoulderBone.getWorldPosition(_tmps.vec1); 358 | const shoulderToControllderDistanceSquared = 359 | shoulderPos.distanceToSquared(controllerPos); 360 | if ( 361 | this.maxShoulderToControllderDistanceSquared < 362 | shoulderToControllderDistanceSquared 363 | ) { 364 | this.maxShoulderToControllderDistanceSquared = 365 | shoulderToControllderDistanceSquared; 366 | } 367 | if (shoulderToControllderDistanceSquared > this.maxDistanceSquared) { 368 | const alpha = Math.sqrt( 369 | this.maxDistanceSquared / this.maxShoulderToControllderDistanceSquared 370 | ); 371 | this.ikTarget.position.copy( 372 | shoulderPos.lerp(controllerPos, alpha).applyMatrix4( 373 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 374 | _tmps.mat.copy(this.ikTarget.parent!.matrixWorld).invert() 375 | ) 376 | ); 377 | } else { */ 378 | { 379 | const controllerPos = vrHand.getWorldPosition(_tmps.vec); 380 | this.ikTarget.position.copy( 381 | controllerPos.applyMatrix4( 382 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 383 | _tmps.mat.copy(this.ikTarget.parent!.matrixWorld).invert() 384 | ) 385 | ); 386 | } 387 | } 388 | 389 | { 390 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 391 | const parentWorldQuat = this.wristBone.parent!.getWorldQuaternion( 392 | _tmps.quat1 393 | ); 394 | // https://github.com/mrdoob/three.js/issues/13704 395 | const vrHandQuat = vrHand 396 | .getWorldQuaternion(_tmps.quat) 397 | .premultiply(parentWorldQuat.invert()); 398 | 399 | if (this.wristRotationOffset) { 400 | this.wristBone.quaternion.multiplyQuaternions( 401 | vrHandQuat, 402 | this.wristRotationOffset 403 | ); 404 | } else { 405 | this.wristBone.quaternion.copy(vrHandQuat); 406 | } 407 | } 408 | } 409 | reset() { 410 | this.ikTarget.position.copy(this.ikTargetInitPos); 411 | this.wristBone.quaternion.copy(this.wristBoneInitQuat); 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /src/collider/simple-bounding-box-collider.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import type { Avatar } from "../avatar"; 3 | 4 | class Tmps { 5 | vec: THREE.Vector3; 6 | vec1: THREE.Vector3; 7 | vec2: THREE.Vector3; 8 | box: THREE.Box3; 9 | box1: THREE.Box3; 10 | mat: THREE.Matrix4; 11 | hits: [THREE.Box3, number][]; 12 | constructor() { 13 | this.vec = new THREE.Vector3(); 14 | this.vec1 = new THREE.Vector3(); 15 | this.vec2 = new THREE.Vector3(); 16 | this.box = new THREE.Box3(); 17 | this.box1 = new THREE.Box3(); 18 | this.mat = new THREE.Matrix4(); 19 | this.hits = []; 20 | } 21 | } 22 | let _tmps: Tmps; 23 | 24 | /** 25 | * Avatar extension to determine collision using {@link https://threejs.org/docs/?q=Box3#api/en/math/Box3.setFromObject | BoundingBox}. 26 | */ 27 | export class SimpleBoundingBoxCollider { 28 | private _getBoxes: () => THREE.Box3[] | undefined; 29 | private _moveTarget: THREE.Object3D; 30 | private _isSetup = false; 31 | private _height = 0; 32 | private _halfWidthX = 0; 33 | private _halfWidthZ = 0; 34 | private _lastX?: number; 35 | private _lastZ?: number; 36 | 37 | /** 38 | * @param moveTarget - Objects to move. 39 | * @param getBoxes - Get a list of bounding boxes of collisionable objects. 40 | * @param options - Processing frequency of tick(). Default is 1 / 30 (30fps). 41 | */ 42 | constructor( 43 | moveTarget: THREE.Object3D, 44 | getBoxes: () => THREE.Box3[] | undefined 45 | ) { 46 | this._moveTarget = moveTarget; 47 | this._getBoxes = getBoxes; 48 | } 49 | setup(avatar: Avatar) { 50 | if (!_tmps) { 51 | _tmps = new Tmps(); 52 | } 53 | this._isSetup = true; 54 | this._height = avatar.height; 55 | this._halfWidthX = avatar.widthX / 2; 56 | this._halfWidthZ = avatar.widthZ / 2; 57 | } 58 | moveTo(x: number, y: number, z: number) { 59 | if (!this._isSetup) { 60 | return; 61 | } 62 | const [targetBox, targetBoxUpDown] = this._getTargetBox(x, y, z); 63 | const targetPos = _tmps.vec2.set(x, y, z); 64 | 65 | { 66 | const hits = _tmps.hits; 67 | hits.length = 0; 68 | for (const box of this._getBoxes() || []) { 69 | if (targetBoxUpDown.intersectsBox(box)) { 70 | if ( 71 | box.max.y >= targetBoxUpDown.min.y && 72 | box.max.y <= targetBoxUpDown.max.y 73 | ) { 74 | hits.push([box, targetBoxUpDown.max.y - box.max.y]); 75 | } else if ( 76 | targetBoxUpDown.max.y >= box.max.y && 77 | targetBoxUpDown.min.y <= box.max.y 78 | ) { 79 | hits.push([box, box.max.y - targetBoxUpDown.min.y]); 80 | } 81 | } 82 | } 83 | if (hits.length !== 0) { 84 | hits.sort((a, b) => a[1] - b[1]); 85 | // console.log(hits.map((v) => `${v[0].max.y}(${v[1]})`).join(", ")); 86 | if (hits[0][0].max.y !== targetBox.min.y) { 87 | const pos = this._moveTarget.getWorldPosition(_tmps.vec1); 88 | pos.y = hits[0][0].max.y; 89 | // console.log("move y", pos.y); 90 | targetPos.copy( 91 | pos.applyMatrix4( 92 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 93 | _tmps.mat.copy(this._moveTarget.parent!.matrixWorld).invert() 94 | ) 95 | ); 96 | targetBox.min.y = hits[0][0].max.y; 97 | targetBox.max.y = targetBox.min.y + this._height; 98 | } 99 | } else { 100 | return; 101 | } 102 | } 103 | for (const box of this._getBoxes() || []) { 104 | if (targetBox.intersectsBox(box)) { 105 | if (box.max.y === targetBox.min.y) { 106 | // ground 107 | } else { 108 | if (this._lastX !== undefined && this._lastZ !== undefined) { 109 | if ( 110 | this._lastX > targetPos.x && 111 | (box.max.x <= targetBox.min.x || 112 | Math.abs(targetBox.min.x - box.max.x) < 113 | Math.abs(targetBox.max.x - box.max.x)) 114 | ) { 115 | return; 116 | } else if ( 117 | this._lastX < targetPos.x && 118 | (box.min.x >= targetBox.max.x || 119 | Math.abs(targetBox.max.x - box.min.x) < 120 | Math.abs(targetBox.min.x - box.min.x)) 121 | ) { 122 | return; 123 | } 124 | 125 | if ( 126 | this._lastZ > targetPos.z && 127 | (box.max.z <= targetBox.min.z || 128 | Math.abs(targetBox.min.z - box.max.z) < 129 | Math.abs(targetBox.max.z - box.max.z)) 130 | ) { 131 | return; 132 | } else if ( 133 | this._lastZ < targetPos.z && 134 | (box.min.z >= targetBox.max.z || 135 | Math.abs(targetBox.max.z - box.min.z) < 136 | Math.abs(targetBox.min.z - box.min.z)) 137 | ) { 138 | return; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | this._moveTarget.position.copy(targetPos); 145 | this._lastX = targetPos.x; 146 | this._lastZ = targetPos.z; 147 | } 148 | private _getTargetBox( 149 | x: number, 150 | y: number, 151 | z: number 152 | ): [THREE.Box3, THREE.Box3] { 153 | const targetBox = _tmps.box; 154 | targetBox.min.set(x - this._halfWidthX, y, z - this._halfWidthZ); 155 | targetBox.max.set( 156 | x + this._halfWidthX, 157 | y + this._height, 158 | z + this._halfWidthZ 159 | ); 160 | const targetBoxUpDown = _tmps.box1.copy(targetBox); 161 | targetBoxUpDown.min.y = y - 0.5; 162 | targetBoxUpDown.max.y = y + 0.5; 163 | 164 | return [targetBox, targetBoxUpDown]; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/defaults.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AvatarType, 3 | AvatarTypeVrmV0, 4 | AvatarTypeVrmV1, 5 | AvatarTypeReadyPlayerMe, 6 | } from "./avatar"; 7 | import type { RotationLimitSet, WristRotationOffsetSet } from "./avatar-ik"; 8 | const RotationLimitSetVrmV1: RotationLimitSet = { 9 | leftArm: [ 10 | { 11 | rotationMin: [0, -120, -80], 12 | rotationMax: [0, 5, 80], 13 | }, 14 | { 15 | rotationMin: [0, -150, 0], 16 | rotationMax: [0, 0, 0], 17 | }, 18 | ], 19 | rightArm: [ 20 | { 21 | rotationMin: [0, -5, -80], 22 | rotationMax: [0, 120, 80], 23 | }, 24 | { 25 | rotationMin: [0, 0, 0], 26 | rotationMax: [0, 150, 0], 27 | }, 28 | ], 29 | }; 30 | const RotationLimitSetVrmV0: RotationLimitSet = { 31 | leftArm: [ 32 | { 33 | rotationMin: [0, -120, -80], 34 | rotationMax: [0, 5, 80], 35 | }, 36 | { 37 | rotationMin: [0, -150, 0], 38 | rotationMax: [0, 0, 0], 39 | }, 40 | ], 41 | rightArm: [ 42 | { 43 | rotationMin: [0, -5, -80], 44 | rotationMax: [0, 120, 80], 45 | }, 46 | { 47 | rotationMin: [0, 0, 0], 48 | rotationMax: [0, 150, 0], 49 | }, 50 | ], 51 | }; 52 | const RotationLimitSetRpm: RotationLimitSet = { 53 | leftArm: [ 54 | { 55 | rotationMin: [-80, 0, -5], 56 | rotationMax: [80, 0, 120], 57 | }, 58 | { 59 | rotationMin: [0, 0, 0], 60 | rotationMax: [0, 0, 150], 61 | }, 62 | ], 63 | rightArm: [ 64 | { 65 | rotationMin: [-80, 0, -120], 66 | rotationMax: [80, 0, 5], 67 | }, 68 | { 69 | rotationMin: [0, 0, -150], 70 | rotationMax: [0, 0, 0], 71 | }, 72 | ], 73 | }; 74 | 75 | export const RotationLimitHeadSync = { 76 | x: { min: -0.6, max: 0.6 }, 77 | y: { min: -0.6, max: 0.6 }, 78 | z: { min: 0, max: 0 }, 79 | }; 80 | 81 | /** 82 | * Default value of {@link RotationLimitSet}. 83 | */ 84 | export function getDefaultRotationLimitSet(t: AvatarType): RotationLimitSet { 85 | switch (t) { 86 | case AvatarTypeVrmV0: 87 | return RotationLimitSetVrmV0; 88 | case AvatarTypeVrmV1: 89 | return RotationLimitSetVrmV1; 90 | case AvatarTypeReadyPlayerMe: 91 | return RotationLimitSetRpm; 92 | } 93 | throw new Error(`unknown avatar type: ${t}`); 94 | } 95 | const WristRotationOffsetSetVrmV0: WristRotationOffsetSet = { 96 | left: [110, 0, 70], 97 | right: [110, 0, -70], 98 | }; 99 | const WristRotationOffsetSetVrmV1: WristRotationOffsetSet = { 100 | left: [-85, 28, 57], 101 | right: [-85, -28, -57], 102 | }; 103 | const WristRotationOffsetSetRpm: WristRotationOffsetSet = { 104 | left: [-90, 90, 20], 105 | right: [-90, -90, 20], 106 | }; 107 | 108 | /** 109 | * Default value of {@link WristRotationOffsetSet}. 110 | */ 111 | export function getDefaultWristRotationOffsetSet( 112 | t: AvatarType 113 | ): WristRotationOffsetSet { 114 | switch (t) { 115 | case AvatarTypeVrmV0: 116 | return WristRotationOffsetSetVrmV0; 117 | case AvatarTypeVrmV1: 118 | return WristRotationOffsetSetVrmV1; 119 | case AvatarTypeReadyPlayerMe: 120 | return WristRotationOffsetSetRpm; 121 | } 122 | throw new Error(`unknown avatar type: ${t}`); 123 | } 124 | -------------------------------------------------------------------------------- /src/ext/auto-walker.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import type { Avatar } from "../avatar"; 3 | import type { AvatarExtension } from "./avatar-extension"; 4 | 5 | /** 6 | * Avatar extension for automatic walking animation when moving. 7 | */ 8 | export class AutoWalker implements AvatarExtension { 9 | private _isWalking = false; 10 | private _walkCheckTimer = 0; 11 | private _avatar?: WeakRef; 12 | private _object3D?: THREE.Object3D; 13 | private _lastPosition?: THREE.Vector3; 14 | private _tmpVec?: THREE.Vector3; 15 | 16 | /** 17 | * {@inheritDoc AvatarExtension.setup} 18 | */ 19 | setup(avatar: Avatar) { 20 | this._avatar = new WeakRef(avatar); 21 | this._object3D = avatar.object3D; 22 | this._tmpVec = new THREE.Vector3(); 23 | } 24 | /** 25 | * {@inheritDoc AvatarExtension.tick} 26 | */ 27 | tick(dt: number) { 28 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 29 | const object3D = this._object3D; 30 | if (!object3D?.visible) { 31 | return; 32 | } 33 | 34 | const newPos = object3D.getWorldPosition(this._tmpVec!); 35 | if (!this._lastPosition) { 36 | this._lastPosition = new THREE.Vector3().copy(newPos); 37 | } else if ( 38 | Math.abs(this._lastPosition.x - newPos.x) > 0.05 || 39 | Math.abs(this._lastPosition.z - newPos.z) > 0.05 40 | ) { 41 | this._lastPosition.copy(newPos); 42 | if (!this._isWalking) { 43 | this._isWalking = true; 44 | this._avatar?.deref()?.playClip("walk"); 45 | } 46 | this._walkCheckTimer = 0; 47 | } else { 48 | if (this._isWalking) { 49 | this._walkCheckTimer += dt; 50 | if (this._walkCheckTimer > 0.3) { 51 | this._isWalking = false; 52 | this._avatar?.deref()?.playClip("idle"); 53 | } 54 | } 55 | } 56 | /* eslint-enable */ 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ext/avatar-extension.ts: -------------------------------------------------------------------------------- 1 | import type { Avatar } from "../avatar"; 2 | 3 | /** 4 | * Interface to extend {@link Avatar}. 5 | */ 6 | export interface AvatarExtension { 7 | /** 8 | * initialization 9 | */ 10 | setup(avatar: Avatar): void; 11 | /** 12 | * Processes called periodically 13 | */ 14 | tick(deltaTime: number): void; 15 | /** 16 | * Releases all resources allocated by this instance. 17 | */ 18 | dispose?(): void; 19 | } 20 | -------------------------------------------------------------------------------- /src/ext/blinker.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import type * as THREE_VRM from "@pixiv/three-vrm"; 3 | import type { Avatar } from "../avatar"; 4 | import type { AvatarExtension } from "./avatar-extension"; 5 | 6 | /** 7 | * Avatar extension to implement blink. 8 | */ 9 | export class Blinker implements AvatarExtension { 10 | private _faceMesh?: THREE.Mesh; 11 | private _vrm?: THREE_VRM.VRM; 12 | 13 | private _blinking = false; 14 | private _blinkProgress = 0; 15 | private _blinkTiming = 0; 16 | private _blinkSpeed = 20; 17 | private _blinkPrev = 0; 18 | 19 | /** 20 | * {@inheritDoc AvatarExtension.setup} 21 | */ 22 | setup(avatar: Avatar) { 23 | this._faceMesh = avatar.faceMesh; 24 | this._vrm = avatar.vrm; 25 | } 26 | 27 | /** 28 | * {@inheritDoc AvatarExtension.tick} 29 | */ 30 | tick(dt: number) { 31 | if (!this._blinking) { 32 | this._blinkTiming += dt; 33 | if (this._blinkTiming > 1) { 34 | this._blinkTiming = 0; 35 | if (Math.random() < 0.2) { 36 | this._blinking = true; 37 | this._blinkProgress = 0; 38 | } 39 | } 40 | } else { 41 | this._blinkProgress += dt * this._blinkSpeed; 42 | let i = Math.sin(this._blinkProgress); 43 | if (i < 0.1 && i < this._blinkPrev) { 44 | this._blinking = false; 45 | this._blinkTiming = -2; 46 | i = 0; 47 | } 48 | this._blinkPrev = i; 49 | if (this._vrm) { 50 | this._vrm.expressionManager?.setValue("blink", i); 51 | } else if (this._faceMesh) { 52 | const faceMesh = this._faceMesh; 53 | const idx = faceMesh.morphTargetDictionary?.eyesClosed; 54 | if (idx && faceMesh.morphTargetInfluences) { 55 | faceMesh.morphTargetInfluences[idx] = i; 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/external/threelipsync-mod/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Gerard Llorach Tó 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 | -------------------------------------------------------------------------------- /src/external/threelipsync-mod/README.md: -------------------------------------------------------------------------------- 1 | # ThreeLS - threelipsync 2 | 3 | Computes the weights of THREE blend shapes (kiss, lips closed and mouth open/jaw) from an audio stream in real-time. The algorithm calculates the energies of THREE frequency bands and maps them to the blend shapes with simple equations. 4 | 5 | To use the microphone call startMic(). To use an external audio file from an URL use startSample(URL). Remember that the webpage should be https in order to use the microphone. 6 | 7 | An explanation about the theory behind the algorithm can be found here: 8 | https://www.youtube.com/watch?v=89pBiGKXpZI 9 | 10 | Here is the package for Unity: 11 | https://doi.org/10.5281/zenodo.5765691 12 | 13 | Reference: 14 | Llorach, G., Evans, A., Blat, J., Grimm, G. and Hohmann, V., 2016, September. Web-based live speech-driven lip-sync. In 2016 8th International Conference on Games and Virtual Worlds for Serious Applications (VS-GAMES) (pp. 1-4). IEEE. 15 | -------------------------------------------------------------------------------- /src/external/threelipsync-mod/threelipsync.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | Lipsync.prototype.refFBins = [0, 500, 700, 3000, 6000]; 4 | /** 5 | * Slightly customized {@link https://github.com/gerardllorach/threelipsync | threelipsync module}. 6 | * 7 | * 8 | * @remarks 9 | * ``` 10 | --------------------- THREELIPSYNC MODULE -------------------- 11 | Computes the values of THREE blend shapes (kiss, lips closed and mouth open/jaw) 12 | To do so, it computes the energy of THREE frequency bands in real time. 13 | he webpage needs to be https in order to get the microphone. If using external 14 | audio files from URL, they need to be from a https origin. 15 | 16 | Author: Gerard Llorach 17 | Paper: G. Llorach, A. Evans, J. Blat, G. Grimm, V. Hohmann. Web-based live speech-driven 18 | lip-sync, Proceedings of VS-Games 2016, September, Barcelona 19 | Date: Nov 2016 20 | https://repositori.upf.edu/bitstream/handle/10230/28139/llorach_VSG16_web.pdf 21 | License: MIT 22 | * ``` 23 | */ 24 | export function Lipsync( 25 | context: AudioContext, 26 | ms: MediaStream, 27 | threshold?: number, 28 | smoothness?: number, 29 | pitch?: number 30 | ) { 31 | this.context = context; 32 | // Freq analysis bins, energy and lipsync vectors 33 | this.energy = [0, 0, 0, 0, 0, 0, 0, 0]; 34 | this.lipsyncBSW = [0, 0, 0]; 35 | 36 | // Lipsync parameters 37 | this.threshold = threshold || 0.45; 38 | this.smoothness = smoothness || 0.6; 39 | this.pitch = pitch || 1; 40 | // Change freq bins according to pitch 41 | this.defineFBins(this.pitch); 42 | 43 | // Initialize buffers 44 | this.init(); 45 | 46 | this.sample = this.context.createMediaStreamSource(ms); 47 | this.sample.connect(this.analyser); 48 | } 49 | 50 | // Define fBins 51 | Lipsync.prototype.defineFBins = function (pitch: number) { 52 | this.fBins = this.refFBins.map((v: number) => v * pitch); 53 | }; 54 | 55 | // Audio buffers and analysers 56 | Lipsync.prototype.init = function () { 57 | // Sound source 58 | this.sample = this.context.createBufferSource(); 59 | // Gain Node 60 | this.gainNode = this.context.createGain(); 61 | // Analyser 62 | this.analyser = this.context.createAnalyser(); 63 | // FFT size 64 | this.analyser.fftSize = 1024; 65 | // FFT smoothing 66 | this.analyser.smoothingTimeConstant = this.smoothness; 67 | 68 | // FFT buffer 69 | this.data = new Float32Array(this.analyser.frequencyBinCount); 70 | }; 71 | 72 | // Update lipsync weights 73 | Lipsync.prototype.update = function (): [number, number, number] { 74 | // Short-term power spectrum 75 | this.analyser.getFloatFrequencyData(this.data); 76 | // Analyze energies 77 | this.binAnalysis(); 78 | // Calculate lipsync blenshape weights 79 | this.lipAnalysis(); 80 | // Return weights 81 | return this.lipsyncBSW; 82 | }; 83 | // Analyze energies 84 | Lipsync.prototype.binAnalysis = function () { 85 | // Signal properties 86 | const nfft = this.analyser.frequencyBinCount; 87 | const fs = this.context.sampleRate; 88 | 89 | const fBins = this.fBins; 90 | const energy = this.energy; 91 | 92 | // Energy of bins 93 | for (let binInd = 0; binInd < fBins.length - 1; binInd++) { 94 | // Start and end of bin 95 | const indxIn = Math.round((fBins[binInd] * nfft) / (fs / 2)); 96 | const indxEnd = Math.round((fBins[binInd + 1] * nfft) / (fs / 2)); 97 | 98 | // Sum of freq values 99 | energy[binInd] = 0; 100 | for (let i = indxIn; i < indxEnd; i++) { 101 | // data goes from -25 to -160 approx 102 | // default threshold: 0.45 103 | let value = this.threshold + (this.data[i] + 20) / 140; 104 | // Zeroes negative values 105 | value = value > 0 ? value : 0; 106 | 107 | energy[binInd] += value; 108 | } 109 | // Divide by number of sumples 110 | energy[binInd] /= indxEnd - indxIn; 111 | } 112 | }; 113 | 114 | // Calculate lipsyncBSW 115 | Lipsync.prototype.lipAnalysis = function () { 116 | const energy = this.energy; 117 | 118 | if (energy !== undefined) { 119 | let value = 0; 120 | 121 | // Kiss blend shape 122 | // When there is energy in the 1 and 2 bin, blend shape is 0 123 | value = (0.5 - energy[2]) * 2; 124 | if (energy[1] < 0.2) value = value * (energy[1] * 5); 125 | value = Math.max(0, Math.min(value, 1)); // Clip 126 | this.lipsyncBSW[0] = value; 127 | 128 | // Lips closed blend shape 129 | value = energy[3] * 3; 130 | value = Math.max(0, Math.min(value, 1)); // Clip 131 | this.lipsyncBSW[1] = value; 132 | 133 | // Jaw blend shape 134 | value = energy[1] * 0.8 - energy[3] * 0.8; 135 | value = Math.max(0, Math.min(value, 1)); // Clip 136 | this.lipsyncBSW[2] = value; 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Avatar system 3 | * @packageDocumentation 4 | */ 5 | export { 6 | Avatar, 7 | AvatarOptions, 8 | AvatarType, 9 | AvatarTypeVrmV0, 10 | AvatarTypeVrmV1, 11 | AvatarTypeReadyPlayerMe, 12 | } from "./avatar"; 13 | export { 14 | registerSyncAvatarHeadAndCamera, 15 | getRenderInfo, 16 | setNonVRCameraMode, 17 | addMirrorHUD, 18 | AddMirrorHUDOptions, 19 | isLowSpecDevice, 20 | isAndroid, 21 | isIOS, 22 | isTouchDevice, 23 | } from "./util"; 24 | export type { AvatarModel, IKTargetBones } from "./types"; 25 | export { 26 | preLoadAnimationData, 27 | isAnimationDataLoaded, 28 | loadAvatarModel, 29 | AvatarAnimationDataSource, 30 | DecordersOptions, 31 | } from "./loader"; 32 | export { 33 | AvatarIK, 34 | Vector3Tupple, 35 | RotationLimit, 36 | RotationLimitSet, 37 | WristRotationOffsetSet, 38 | VRHandGetter, 39 | } from "./avatar-ik"; 40 | export { 41 | getDefaultRotationLimitSet, 42 | getDefaultWristRotationOffsetSet, 43 | } from "./defaults"; 44 | 45 | export type { AvatarExtension } from "./ext/avatar-extension"; 46 | export { Blinker } from "./ext/blinker"; 47 | export { AutoWalker } from "./ext/auto-walker"; 48 | export { SimpleBoundingBoxCollider } from "./collider/simple-bounding-box-collider"; 49 | 50 | // @ts-ignore 51 | export { Lipsync } from "./external/threelipsync-mod/threelipsync"; 52 | 53 | import type * as THREE from "three"; 54 | import { Avatar, AvatarOptions } from "./avatar"; 55 | import { loadAvatarModel, DecordersOptions } from "./loader"; 56 | import { Blinker } from "./ext/blinker"; 57 | import { AutoWalker } from "./ext/auto-walker"; 58 | import { 59 | AvatarIK, 60 | VRHandGetter, 61 | RotationLimitSet, 62 | WristRotationOffsetSet, 63 | } from "./avatar-ik"; 64 | import { 65 | getDefaultRotationLimitSet, 66 | getDefaultWristRotationOffsetSet, 67 | } from "./defaults"; 68 | 69 | export interface CreateAvatarOptions extends DecordersOptions, AvatarOptions { 70 | /** 71 | * Not displayed with a first-person camera. ( But it will be shown in mirrors, etc.). 72 | */ 73 | isInvisibleFirstPerson?: boolean; 74 | /** 75 | * Reduces resource consumption by omitting some processes. 76 | */ 77 | isLowSpecMode?: boolean; 78 | } 79 | 80 | /** 81 | * Create {@link Avatar} 82 | * 83 | * @param avatarData - Data from gltf or vrm files. 84 | * @param frustumCulled - {@link https://threejs.org/docs/?q=Mesh#api/en/core/Object3D.frustumCulled | Object3D.frustumCulled } applied recursively. 85 | * 86 | * @example 87 | ```ts 88 | 89 | let resp = await fetch(url); 90 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 91 | const avatar = createAvatar(avatarData, renderer, false, { 92 | isInvisibleFirstPerson: true, 93 | isLowSpecMode: maybeLowSpecDevice, 94 | }); 95 | playerObj.add(avatar.object3D); 96 | 97 | ... 98 | 99 | const clock = new THREE.Clock(); 100 | renderer.setAnimationLoop(() => { 101 | ... 102 | const dt = clock.getDelta(); 103 | avatar.tick(dt); 104 | }); 105 | ``` 106 | */ 107 | export async function createAvatar( 108 | avatarData: Uint8Array, 109 | renderer: THREE.WebGLRenderer, 110 | frustumCulled?: boolean, 111 | options?: CreateAvatarOptions 112 | ): Promise { 113 | const model = await loadAvatarModel( 114 | avatarData, 115 | renderer, 116 | frustumCulled, 117 | options 118 | ); 119 | const res = new Avatar(model, options); 120 | if (options?.isInvisibleFirstPerson) { 121 | res.invisibleFirstPerson(); 122 | } 123 | res.object3D.updateMatrixWorld(); 124 | setDefaultExtensions(res); 125 | if (options?.isLowSpecMode) { 126 | if (res.vrm) { 127 | type unsafeVrm = { 128 | springBoneManager?: object; 129 | }; 130 | delete (res.vrm as unsafeVrm).springBoneManager; 131 | } 132 | } 133 | return res; 134 | } 135 | 136 | /** 137 | * Set up default extensions for {@link Avatar} 138 | * @param moveTarget - Objects to move. Specify the object that contains {@link Avatar.object3D}. 139 | */ 140 | export function setDefaultExtensions(avatar: Avatar): void { 141 | avatar.addExtension(new Blinker()); 142 | avatar.addExtension(new AutoWalker()); 143 | } 144 | 145 | export interface CreateAvatarIKOptions { 146 | /** 147 | * Limit the range of rotation of joints. Default is {@link getDefaultRotationLimitSet}. 148 | */ 149 | rotationLimitSet: RotationLimitSet; 150 | /** 151 | * Offset of rotation angle between the XR controller and the avatar's wrist. Default is {@link getDefaultWristRotationOffsetSet}. 152 | */ 153 | wristRotationOffsetSet: WristRotationOffsetSet; 154 | /** 155 | * Processing frequency of tick(). Default is 1 / 60 (30fps). 156 | */ 157 | intervalSec?: number; 158 | isDebug?: boolean; 159 | } 160 | 161 | /** 162 | * Create {@link AvatarIK}. 163 | * (Experimental Features) 164 | * 165 | * @param handGetter - Get XR controller objects. 166 | * 167 | * @example 168 | * ```ts 169 | 170 | ... 171 | 172 | onVR: async () => { 173 | await avatar.setIKMode(true); 174 | avatarIK = createAvatarIK( 175 | avatar, 176 | { 177 | left: () => leftXRController, 178 | right: () => rightXRController, 179 | }, 180 | ); 181 | }, 182 | onNonVR: async () => { 183 | await avatar.setIKMode(false); 184 | avatarIK?.dispose(); 185 | } 186 | 187 | ... 188 | 189 | 190 | const clock = new THREE.Clock(); 191 | renderer.setAnimationLoop(() => { 192 | ... 193 | const dt = clock.getDelta(); 194 | avatar.tick(dt); 195 | avatarIK?.tick(dt); 196 | }); 197 | * ``` 198 | */ 199 | export function createAvatarIK( 200 | avatar: Avatar, 201 | handGetter: VRHandGetter, 202 | option?: CreateAvatarIKOptions 203 | ) { 204 | return new AvatarIK( 205 | avatar.object3D, 206 | avatar.ikTargetRightArmBones, 207 | avatar.ikTargetLeftArmBones, 208 | option?.rotationLimitSet || getDefaultRotationLimitSet(avatar.type), 209 | handGetter, 210 | option?.wristRotationOffsetSet || 211 | getDefaultWristRotationOffsetSet(avatar.type), 212 | { 213 | isDebug: option?.isDebug || false, 214 | intervalSec: option?.intervalSec, 215 | } 216 | ); 217 | } 218 | -------------------------------------------------------------------------------- /src/loader.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import * as THREE_VRM from "@pixiv/three-vrm"; 3 | import { 4 | GLTFLoader, 5 | GLTFParser, 6 | } from "three/examples/jsm/loaders/GLTFLoader.js"; 7 | import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js"; 8 | import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; 9 | import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js"; 10 | import { convertForReadyPlayerMe, convertForVrm } from "./mixamo"; 11 | import { AvatarModel } from "./types"; 12 | // import { MeshoptDecoder } from "meshoptimizer"; 13 | 14 | // https://github.com/google/draco 15 | const DRACO_DECODER_PATH = 16 | "https://www.gstatic.com/draco/versioned/decoders/1.5.5/"; 17 | const BASIS_TRANSCODER_PATH = 18 | "https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/libs/basis/"; 19 | const MESHOPT_DECODER_PATH = 20 | "https://cdn.jsdelivr.net/npm/meshoptimizer@0.18.1/meshopt_decoder.js"; 21 | 22 | /** 23 | * URL of the library required if GLTF data is compressed. If omitted, decompress with default values. 24 | * 25 | * @remarks 26 | * {@link https://threejs.org/docs/#examples/en/loaders/DRACOLoader | DRACOLoader}, {@link https://threejs.org/docs/?q=KTX2Loader#examples/en/loaders/KTX2Loader | KTX2Loader}, {@link https://threejs.org/docs/?q=gltfl#examples/en/loaders/GLTFLoader | GLTFLoader} 27 | */ 28 | export type DecordersOptions = { 29 | dracoDecoderPath?: string; 30 | basisTranscoderPath?: string; 31 | meshoptDecoderPath?: string; 32 | }; 33 | 34 | type AvatarAnimationData = { 35 | walk: THREE.Group; 36 | idle: THREE.Group; 37 | [key: string]: THREE.Group; 38 | }; 39 | let avatarAnimationData: AvatarAnimationData | null = null; 40 | 41 | // export type AvatarDataType = "mixamo" | "readyplayerme" | "vrm"; 42 | // type FileType = "fbx" | "gltf"; 43 | 44 | /** 45 | * {@link https://www.mixamo.com/ | mixamo} animation file URLs 46 | * 47 | * @example 48 | * ```ts 49 | const ANIMATION_MAP = { 50 | idle: "asset/animation/idle.fbx", 51 | walk: "asset/animation/walk.fbx", 52 | dance: "asset/animation/dance.fbx", 53 | }; 54 | * ``` 55 | */ 56 | export type AvatarAnimationDataSource = { 57 | walk: string; 58 | idle: string; 59 | [key: string]: string; 60 | }; 61 | 62 | /** 63 | * Whether animation data is pre-loaded or not 64 | */ 65 | export function isAnimationDataLoaded() { 66 | return !!avatarAnimationData; 67 | } 68 | 69 | /** 70 | * Pre-load animation data. 71 | * 72 | * @example 73 | ```ts 74 | // Animation fbx files downloaded from mixamo.com 75 | const ANIMATION_MAP = { 76 | idle: "path/to/idle.fbx", 77 | walk: "path/to/walk.fbx", 78 | dance: "path/to/dance.fbx", 79 | ... 80 | }; 81 | if (!isAnimationDataLoaded()) { 82 | await preLoadAnimationData(ANIMATION_MAP); 83 | } 84 | ``` 85 | */ 86 | export async function preLoadAnimationData( 87 | source: AvatarAnimationDataSource, 88 | fetchFunc: (url: string) => Promise = fetch 89 | ) { 90 | const ps = Object.entries(source).map(([k, v]) => { 91 | return (async (k) => { 92 | const res = await (await fetchFunc(v)).arrayBuffer(); 93 | const asset = new FBXLoader().parse( 94 | res, 95 | "about:blank" // No additional data is needed. about:blank 96 | ); 97 | return [k, asset]; 98 | })(k); 99 | }); 100 | avatarAnimationData = Object.fromEntries(await Promise.all(ps)); 101 | } 102 | 103 | async function createGLTFLoader( 104 | renderer: THREE.WebGLRenderer, 105 | options?: DecordersOptions 106 | ) { 107 | const loader = new GLTFLoader(); 108 | 109 | const dracoLoader = new DRACOLoader(); 110 | dracoLoader.setDecoderPath(options?.dracoDecoderPath || DRACO_DECODER_PATH); 111 | loader.setDRACOLoader(dracoLoader); 112 | 113 | const ktx2Loader = new KTX2Loader(); 114 | ktx2Loader 115 | .setTranscoderPath(options?.basisTranscoderPath || BASIS_TRANSCODER_PATH) 116 | .detectSupport(renderer); 117 | loader.setKTX2Loader(ktx2Loader); 118 | 119 | const meshoptDecoder = await fetchScript( 120 | options?.meshoptDecoderPath || MESHOPT_DECODER_PATH 121 | ) 122 | .then(() => { 123 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 124 | return (window as any).MeshoptDecoder.ready; 125 | }) 126 | .then(() => { 127 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 128 | return (window as any).MeshoptDecoder; 129 | }); 130 | loader.setMeshoptDecoder(meshoptDecoder); 131 | 132 | loader.register((parser: GLTFParser) => { 133 | return new THREE_VRM.VRMLoaderPlugin(parser); 134 | }); 135 | return loader; 136 | } 137 | 138 | /** 139 | * Load avatar file data. 140 | * 141 | * @param avatarData - Data from gltf or vrm files. 142 | * @param frustumCulled - {@link https://threejs.org/docs/?q=Mesh#api/en/core/Object3D.frustumCulled | Object3D.frustumCulled } applied recursively. 143 | * 144 | * @example 145 | ```ts 146 | let resp = await fetch(url); 147 | const avatarData = new Uint8Array(await resp.arrayBuffer()); 148 | const model = await loadAvatarModel(avatarData, renderer, false); 149 | ``` 150 | */ 151 | export async function loadAvatarModel( 152 | avatarData: Uint8Array, 153 | renderer: THREE.WebGLRenderer, 154 | frustumCulled?: boolean, 155 | options?: DecordersOptions 156 | ): Promise { 157 | let model: THREE.Group | undefined; 158 | const gltfLoader = await createGLTFLoader(renderer, options); 159 | const gltf = await gltfLoader.parseAsync( 160 | avatarData.buffer, 161 | "about:blank" // Assume only avatars consisting of a single file. 162 | ); 163 | const vrm: THREE_VRM.VRM = gltf.userData.vrm; 164 | if (vrm) { 165 | THREE_VRM.VRMUtils.removeUnnecessaryVertices(gltf.scene); 166 | THREE_VRM.VRMUtils.removeUnnecessaryJoints(gltf.scene); 167 | THREE_VRM.VRMUtils.rotateVRM0(vrm); 168 | 169 | model = vrm.scene; 170 | } else { 171 | model = gltf.scene || gltf.scenes[0]; 172 | } 173 | model.animations = gltf.animations; 174 | 175 | if (!model) { 176 | throw new Error("invalid avatar data"); 177 | } 178 | 179 | if (frustumCulled !== undefined) { 180 | // Keep body parts from disappearing when approaching the camera. 181 | model.traverse((obj) => { 182 | if (obj.frustumCulled !== undefined) { 183 | obj.frustumCulled = frustumCulled; 184 | } 185 | }); 186 | } 187 | 188 | model.rotateY(180 * (Math.PI / 180)); 189 | 190 | if (!avatarAnimationData) { 191 | throw new Error("preLoadAnimationData is required"); 192 | } 193 | const animationData = avatarAnimationData; 194 | if (vrm) { 195 | model.animations = [ 196 | ...model.animations, 197 | ...Object.entries(animationData).map(([k, v]) => 198 | convertForVrm(v, vrm, k) 199 | ), 200 | ]; 201 | } else { 202 | model.animations = [ 203 | ...model.animations, 204 | ...Object.entries(animationData).map(([k, v]) => 205 | convertForReadyPlayerMe(v, model as THREE.Object3D, k) 206 | ), 207 | ]; 208 | } 209 | 210 | return { 211 | model, 212 | vrm, 213 | }; 214 | } 215 | 216 | function fetchScript(src: string) { 217 | return new Promise(function (resolve, reject) { 218 | const script = document.createElement("script"); 219 | document.body.appendChild(script); 220 | script.onload = resolve; 221 | script.onerror = reject; 222 | script.async = true; 223 | script.src = src; 224 | }); 225 | } 226 | 227 | /* function getFileType(data: Uint8Array): FileType | undefined { 228 | const hasPrefix = (v: Uint8Array, prefix: Uint8Array) => { 229 | const n = prefix.length; 230 | for (let i = 0; i < n; i++) { 231 | if (v[i] !== prefix[i]) { 232 | return false; 233 | } 234 | } 235 | return true; 236 | }; 237 | { 238 | const prefix = new TextEncoder().encode("glTF"); 239 | if (hasPrefix(data, prefix)) { 240 | return "gltf"; 241 | } 242 | } 243 | { 244 | const prefix = new TextEncoder().encode("Kaydara FBX Binary"); 245 | if (hasPrefix(data, prefix)) { 246 | return "fbx"; 247 | } 248 | } 249 | } */ 250 | -------------------------------------------------------------------------------- /src/mixamo.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import type * as THREE_VRM from "@pixiv/three-vrm"; 3 | 4 | export function convertForReadyPlayerMe( 5 | asset: THREE.Group, 6 | model: THREE.Object3D, 7 | name: string 8 | ) { 9 | const clip = THREE.AnimationClip.findByName( 10 | asset.animations, 11 | "mixamo.com" 12 | ).clone(); 13 | 14 | const getHipsPositionScale = (asset: THREE.Object3D) => { 15 | // Adjust with reference to hips height. 16 | const vec3 = new THREE.Vector3(); 17 | const motionHipsHeight = 18 | asset.getObjectByName("mixamorigHips")?.position?.y; 19 | if (motionHipsHeight === undefined) { 20 | throw new Error("mixamorigHips not found"); 21 | } 22 | const hipsY = model.getObjectByName("Hips")?.getWorldPosition(vec3)?.y; 23 | if (hipsY === undefined) { 24 | throw new Error("hip not found"); 25 | } 26 | const rootY = model.getWorldPosition(vec3).y; 27 | const hipsHeight = Math.abs(hipsY - rootY); 28 | return hipsHeight / motionHipsHeight; 29 | }; 30 | const hipsPositionScale = getHipsPositionScale(asset); 31 | 32 | const tracks: THREE.KeyframeTrack[] = []; 33 | clip.tracks.forEach((track) => { 34 | const trackSplitted = track.name.split("."); 35 | const mixamoRigName = trackSplitted[0]; 36 | const nodeName = mixamoRigName.replace("mixamorig", ""); 37 | 38 | const propertyName = trackSplitted[1]; 39 | if (track.ValueTypeName === "quaternion") { 40 | tracks.push( 41 | new THREE.QuaternionKeyframeTrack( 42 | `${nodeName}.${propertyName}`, 43 | track.times as any, // eslint-disable-line @typescript-eslint/no-explicit-any 44 | track.values as any // eslint-disable-line @typescript-eslint/no-explicit-any 45 | ) 46 | ); 47 | } else if (track.ValueTypeName === "vector") { 48 | const value = track.values.map((v, _i) => v * hipsPositionScale); 49 | tracks.push( 50 | new THREE.VectorKeyframeTrack( 51 | `${nodeName}.${propertyName}`, 52 | track.times as any, // eslint-disable-line @typescript-eslint/no-explicit-any 53 | value as any // eslint-disable-line @typescript-eslint/no-explicit-any 54 | ) 55 | ); 56 | } 57 | }); 58 | 59 | return new THREE.AnimationClip(name, clip.duration, tracks); 60 | } 61 | 62 | export function convertForVrm( 63 | asset: THREE.Group, 64 | vrm: THREE_VRM.VRM, 65 | name: string 66 | ) { 67 | // reference: https://github.com/pixiv/three-vrm/blob/dev/packages/three-vrm/examples/humanoidAnimation/loadMixamoAnimation.js 68 | const clip = THREE.AnimationClip.findByName( 69 | asset.animations, 70 | "mixamo.com" 71 | ).clone(); 72 | 73 | const tracks: THREE.KeyframeTrack[] = []; // KeyframeTracks compatible with VRM will be added here 74 | 75 | const restRotationInverse = new THREE.Quaternion(); 76 | const parentRestWorldRotation = new THREE.Quaternion(); 77 | const _quatA = new THREE.Quaternion(); 78 | const _vec3 = new THREE.Vector3(); 79 | 80 | // Adjust with reference to hips height. 81 | const motionHipsHeight = asset.getObjectByName("mixamorigHips")?.position?.y; 82 | if (motionHipsHeight === undefined) { 83 | throw new Error("mixamorigHips not found"); 84 | } 85 | const hipsY = vrm.humanoid 86 | .getNormalizedBoneNode("hips") 87 | ?.getWorldPosition(_vec3)?.y; 88 | if (hipsY === undefined) { 89 | throw new Error("hip not found"); 90 | } 91 | const rootY = vrm.scene.getWorldPosition(_vec3).y; 92 | const hipsHeight = Math.abs(hipsY - rootY); 93 | const hipsPositionScale = hipsHeight / motionHipsHeight; 94 | 95 | clip.tracks.forEach((track) => { 96 | // Convert each tracks for VRM use, and push to `tracks` 97 | const trackSplitted = track.name.split("."); 98 | const mixamoRigName = trackSplitted[0]; 99 | const boneName = mixamoVRMRigMap[mixamoRigName]; 100 | const nodeName = vrm.humanoid?.getNormalizedBoneNode(boneName)?.name; 101 | const mixamoRigNode = asset.getObjectByName(mixamoRigName); 102 | 103 | if (nodeName && mixamoRigNode) { 104 | const propertyName = trackSplitted[1]; 105 | 106 | // Store rotations of rest-pose. 107 | mixamoRigNode.getWorldQuaternion(restRotationInverse).invert(); 108 | if (!mixamoRigNode.parent) { 109 | throw new Error("mixamoRigNode.parent is null"); 110 | } 111 | mixamoRigNode.parent.getWorldQuaternion(parentRestWorldRotation); 112 | 113 | if (track.ValueTypeName === "quaternion") { 114 | // Retarget rotation of mixamoRig to NormalizedBone. 115 | for (let i = 0; i < track.values.length; i += 4) { 116 | const flatQuaternion = track.values.slice(i, i + 4); 117 | 118 | _quatA.fromArray(flatQuaternion); 119 | 120 | // 親のレスト時ワールド回転 * トラックの回転 * レスト時ワールド回転の逆 121 | _quatA 122 | .premultiply(parentRestWorldRotation) 123 | .multiply(restRotationInverse); 124 | 125 | _quatA.toArray(flatQuaternion); 126 | 127 | flatQuaternion.forEach((v, index) => { 128 | track.values[index + i] = v; 129 | }); 130 | } 131 | 132 | tracks.push( 133 | new THREE.QuaternionKeyframeTrack( 134 | `${nodeName}.${propertyName}`, 135 | track.times as any, // eslint-disable-line @typescript-eslint/no-explicit-any 136 | track.values.map((v, i) => 137 | vrm.meta?.metaVersion === "0" && i % 2 === 0 ? -v : v 138 | ) as any // eslint-disable-line @typescript-eslint/no-explicit-any 139 | ) 140 | ); 141 | } else if (track.ValueTypeName === "vector") { 142 | const value = track.values.map( 143 | (v, i) => 144 | (vrm.meta?.metaVersion === "0" && i % 3 !== 1 ? -v : v) * 145 | hipsPositionScale 146 | ); 147 | tracks.push( 148 | new THREE.VectorKeyframeTrack( 149 | `${nodeName}.${propertyName}`, 150 | track.times as any, // eslint-disable-line @typescript-eslint/no-explicit-any 151 | value as any // eslint-disable-line @typescript-eslint/no-explicit-any 152 | ) 153 | ); 154 | } 155 | } 156 | }); 157 | 158 | return new THREE.AnimationClip(name, clip.duration, tracks); 159 | } 160 | 161 | /** 162 | * A map from Mixamo rig name to VRM Humanoid bone name 163 | */ 164 | const mixamoVRMRigMap: { [key: string]: THREE_VRM.VRMHumanBoneName } = { 165 | mixamorigHips: "hips", 166 | mixamorigSpine: "spine", 167 | mixamorigSpine1: "chest", 168 | mixamorigSpine2: "upperChest", 169 | mixamorigNeck: "neck", 170 | mixamorigHead: "head", 171 | mixamorigLeftShoulder: "leftShoulder", 172 | mixamorigLeftArm: "leftUpperArm", 173 | mixamorigLeftForeArm: "leftLowerArm", 174 | mixamorigLeftHand: "leftHand", 175 | mixamorigLeftHandThumb1: "leftThumbMetacarpal", 176 | mixamorigLeftHandThumb2: "leftThumbProximal", 177 | mixamorigLeftHandThumb3: "leftThumbDistal", 178 | mixamorigLeftHandIndex1: "leftIndexProximal", 179 | mixamorigLeftHandIndex2: "leftIndexIntermediate", 180 | mixamorigLeftHandIndex3: "leftIndexDistal", 181 | mixamorigLeftHandMiddle1: "leftMiddleProximal", 182 | mixamorigLeftHandMiddle2: "leftMiddleIntermediate", 183 | mixamorigLeftHandMiddle3: "leftMiddleDistal", 184 | mixamorigLeftHandRing1: "leftRingProximal", 185 | mixamorigLeftHandRing2: "leftRingIntermediate", 186 | mixamorigLeftHandRing3: "leftRingDistal", 187 | mixamorigLeftHandPinky1: "leftLittleProximal", 188 | mixamorigLeftHandPinky2: "leftLittleIntermediate", 189 | mixamorigLeftHandPinky3: "leftLittleDistal", 190 | mixamorigRightShoulder: "rightShoulder", 191 | mixamorigRightArm: "rightUpperArm", 192 | mixamorigRightForeArm: "rightLowerArm", 193 | mixamorigRightHand: "rightHand", 194 | mixamorigRightHandPinky1: "rightLittleProximal", 195 | mixamorigRightHandPinky2: "rightLittleIntermediate", 196 | mixamorigRightHandPinky3: "rightLittleDistal", 197 | mixamorigRightHandRing1: "rightRingProximal", 198 | mixamorigRightHandRing2: "rightRingIntermediate", 199 | mixamorigRightHandRing3: "rightRingDistal", 200 | mixamorigRightHandMiddle1: "rightMiddleProximal", 201 | mixamorigRightHandMiddle2: "rightMiddleIntermediate", 202 | mixamorigRightHandMiddle3: "rightMiddleDistal", 203 | mixamorigRightHandIndex1: "rightIndexProximal", 204 | mixamorigRightHandIndex2: "rightIndexIntermediate", 205 | mixamorigRightHandIndex3: "rightIndexDistal", 206 | mixamorigRightHandThumb1: "rightThumbMetacarpal", 207 | mixamorigRightHandThumb2: "rightThumbProximal", 208 | mixamorigRightHandThumb3: "rightThumbDistal", 209 | mixamorigLeftUpLeg: "leftUpperLeg", 210 | mixamorigLeftLeg: "leftLowerLeg", 211 | mixamorigLeftFoot: "leftFoot", 212 | mixamorigLeftToeBase: "leftToes", 213 | mixamorigRightUpLeg: "rightUpperLeg", 214 | mixamorigRightLeg: "rightLowerLeg", 215 | mixamorigRightFoot: "rightFoot", 216 | mixamorigRightToeBase: "rightToes", 217 | }; 218 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import * as THREE_VRM from "@pixiv/three-vrm"; 3 | /** 4 | * Avatar model. 5 | */ 6 | export interface AvatarModel { 7 | model: THREE.Group; 8 | vrm?: THREE_VRM.VRM; 9 | } 10 | 11 | /** 12 | * Shoulder, Elbow, Wrist 13 | */ 14 | export type IKTargetBones = [ 15 | THREE.Bone | undefined, 16 | THREE.Bone | undefined, 17 | THREE.Bone | undefined 18 | ]; 19 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import type { Avatar } from "./avatar"; 3 | import { Reflector } from "three/examples/jsm/objects/Reflector.js"; 4 | 5 | /** 6 | * Automatically adjust camera when switching between VR and non-VR modes. 7 | * 8 | * @param getAvatar - If the avatar is changed after calling registerSyncAvatarHeadAndCamera, it will continue to work correctly. 9 | * @example 10 | * ```ts 11 | registerSyncAvatarHeadAndCamera( 12 | renderer.xr, 13 | camera, 14 | camera, 15 | camera.parent, 16 | () => _avatar, 17 | { 18 | onVR: async () => { 19 | playerController.isVR = true; 20 | }, 21 | onNonVR: async () => { 22 | playerController.isVR = false; 23 | setNonVRCameraMode(camera, camera.parent, avatar, isFPS); 24 | }, 25 | } 26 | ); 27 | * ``` 28 | */ 29 | export async function registerSyncAvatarHeadAndCamera( 30 | xr: THREE.WebXRManager, 31 | nonVrCamera: THREE.PerspectiveCamera, 32 | head: THREE.Object3D, 33 | headOffset: THREE.Object3D, 34 | getAvatar: () => Avatar | undefined, 35 | options: { 36 | onVR: () => Promise | void; 37 | onNonVR: () => Promise | void; 38 | } 39 | ) { 40 | let positions: [THREE.Vector3, THREE.Vector3] | undefined; 41 | let rotation: THREE.Euler | undefined; 42 | 43 | const setupVR = async () => { 44 | positions = [head.position.clone(), headOffset.position.clone()]; 45 | rotation = headOffset.rotation.clone(); // save Non VR camera rotation 46 | headOffset.rotation.set(0, 0, 0); 47 | 48 | let avatar: Avatar | undefined; 49 | while (!(avatar = getAvatar())) { 50 | await new Promise((resolve) => setTimeout(resolve, 0)); 51 | } 52 | 53 | xr.updateCamera(nonVrCamera); 54 | // waitForVRCameraInitialized 55 | while (Math.round(xr.getCamera().position.y * 10) === 0) { 56 | await new Promise((resolve) => setTimeout(resolve, 0)); 57 | } 58 | 59 | headOffset.position.setY(avatar.getHeadHeight() - head.position.y); 60 | 61 | const p = options?.onVR?.(); 62 | if (p) { 63 | await p; 64 | } 65 | }; 66 | const setupNonVR = async () => { 67 | head.rotation.set(0, 0, 0); 68 | if (rotation) { 69 | headOffset.rotation.copy(rotation); // restore Non VR camera rotation 70 | } 71 | if (positions) { 72 | head.position.copy(positions[0]); 73 | headOffset.position.copy(positions[1]); 74 | } 75 | const p = options?.onNonVR?.(); 76 | if (p) { 77 | await p; 78 | } 79 | }; 80 | xr.addEventListener("sessionstart", setupVR); 81 | xr.addEventListener("sessionend", setupNonVR); 82 | 83 | if (xr.isPresenting) { 84 | setupVR(); 85 | } else { 86 | setupNonVR(); 87 | } 88 | } 89 | 90 | /** 91 | * Whether the device does not have a GPU capable of processing enough. 92 | * (Experimental Features) 93 | * 94 | * @remarks 95 | * Intended to be used to adjust texture size, processing load, etc. 96 | * The current implementation is a very tentative decision and needs to be improved. 97 | */ 98 | export function isLowSpecDevice(): boolean { 99 | const info = getRenderInfo(); 100 | if (!info) { 101 | return false; 102 | } 103 | if (info.vendor === "Qualcomm") { 104 | return true; 105 | } 106 | if (info.vendor.includes("Intel")) { 107 | if (info.renderer.includes("Iris")) { 108 | return true; 109 | } 110 | } 111 | if (isAndroid() || isIOS()) { 112 | return true; 113 | } 114 | return false; 115 | } 116 | export function isAndroid() { 117 | return navigator.userAgent.includes("Android"); 118 | } 119 | export function isIOS() { 120 | return navigator.userAgent.includes("Mac") && isTouchDevice(); 121 | } 122 | export function isTouchDevice() { 123 | return "ontouchstart" in window || navigator.maxTouchPoints > 0; 124 | } 125 | 126 | /** 127 | * Get GPU information. 128 | * See also {@link https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_debug_renderer_info | WEBGL_debug_renderer_info} 129 | */ 130 | export function getRenderInfo(): 131 | | { vendor: string; renderer: string } 132 | | undefined { 133 | try { 134 | const canvas = document.createElement("canvas"); 135 | const gl = canvas.getContext("webgl2"); 136 | if (!gl) { 137 | return; 138 | } 139 | const ext = gl.getExtension("WEBGL_debug_renderer_info"); 140 | if (ext) { 141 | return { 142 | vendor: gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) || "", 143 | renderer: gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || "", 144 | }; 145 | } 146 | } catch (ex) { 147 | console.warn(ex); 148 | } 149 | } 150 | 151 | /** 152 | * Adjust the camera for non-VR mode. 153 | * @param isFirstPerson - First person view or 3rd person view. 154 | */ 155 | export async function setNonVRCameraMode( 156 | camera: THREE.Camera, 157 | cameraOffset: THREE.Object3D, 158 | avatar: Avatar, 159 | isFirstPerson: boolean // first person view or third person view 160 | ) { 161 | if (isFirstPerson) { 162 | avatar.setFirstPersonMode([camera]); 163 | camera.position.set(0, avatar.getHeadHeight(), 0); 164 | cameraOffset.rotation.set(0, 0, 0); 165 | } else { 166 | avatar.setThirdPersonMode([camera]); 167 | camera.position.set(0.0, avatar.getHeadHeight() + 0.2, 2.0); 168 | cameraOffset.rotation.set(-0.2, 0, 0); 169 | } 170 | } 171 | 172 | const DEFAULT_MIRROR_DISTANCE_VR = 0.3; 173 | const DEFAULT_MIRROR_DISTANCE_NON_VR = 1.2; 174 | 175 | export interface AddMirrorHUDOptions { 176 | /** 177 | * `renderer.xr` 178 | */ 179 | xr?: THREE.WebXRManager; 180 | /** 181 | * Distance between avatar and mirror (In VR mode). Default is 0.3. 182 | */ 183 | distanceVR?: number; 184 | /** 185 | * Distance between avatar and mirror (In non-VR mode). Default is 1.2. 186 | */ 187 | distanceNonVR?: number; 188 | } 189 | 190 | /** 191 | * Create a mirror in front of the avatar. 192 | * 193 | * @param container - Where to ADD mirrors. 194 | */ 195 | export function addMirrorHUD( 196 | avatar: Avatar, 197 | container: THREE.Object3D, 198 | options?: AddMirrorHUDOptions 199 | ): Reflector { 200 | const distanceNonVR = 201 | options?.distanceNonVR || DEFAULT_MIRROR_DISTANCE_NON_VR; 202 | const distanceVR = options?.distanceVR || DEFAULT_MIRROR_DISTANCE_VR; 203 | const mirror = new Reflector(new THREE.PlaneGeometry(0.6, 1.2), { 204 | textureWidth: window.innerWidth * window.devicePixelRatio, 205 | textureHeight: window.innerHeight * window.devicePixelRatio, 206 | color: 0x777777, 207 | }); 208 | mirror.camera.layers.enableAll(); 209 | mirror.camera.layers.disable(avatar.firstPersonOnlyLayer); 210 | mirror.name = "mirrorHUD"; 211 | 212 | if (options?.xr) { 213 | const xr = options.xr; 214 | const ref = new WeakRef(mirror); 215 | const update = () => { 216 | ref 217 | .deref() 218 | ?.position?.set( 219 | 0, 220 | avatar.getHeadHeight(), 221 | (options?.xr?.isPresenting ? distanceVR : distanceNonVR) * -1 222 | ); 223 | }; 224 | xr.addEventListener("sessionstart", update); 225 | xr.addEventListener("sessionend", update); 226 | const dispose = mirror.dispose.bind(mirror); 227 | mirror.dispose = () => { 228 | dispose(); 229 | xr.removeEventListener("sessionstart", update); 230 | xr.removeEventListener("sessionend", update); 231 | }; 232 | } 233 | 234 | mirror.position.set( 235 | 0, 236 | avatar.getHeadHeight(), 237 | (options?.xr?.isPresenting ? distanceVR : distanceNonVR) * -1 238 | ); 239 | container.add(mirror); 240 | return mirror; 241 | } 242 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "moduleResolution": "node", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "sourceMap": true, 8 | "strict": true, 9 | "skipLibCheck": true 10 | }, 11 | "include": ["src"], 12 | "exclude": ["dist", "node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # set -euxo pipefail 3 | set -euo pipefail 4 | cd `/usr/bin/dirname $0` 5 | 6 | trap "final; exit 1" 2 7 | 8 | function final { 9 | echo "Ctrl+C pushed." 10 | } 11 | 12 | dir=$(greadlink -f .) 13 | 14 | while true; do 15 | set +e 16 | npm run build 17 | set -e 18 | 19 | out=$(fswatch \ 20 | --one-event \ 21 | --recursive \ 22 | --exclude='.*' \ 23 | --include=$dir'/src/.*\.ts$' \ 24 | --include=$dir'/src/.*\.css$' \ 25 | --include=$dir'/src/.*\.svg$' \ 26 | .) 27 | 28 | if [ -z "$out" ]; then 29 | break 30 | fi 31 | done 32 | --------------------------------------------------------------------------------