diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f17867 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..7420c0b --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,31 @@ +extends: + - eslint:recommended + - plugin:@typescript-eslint/eslint-recommended + - plugin:@typescript-eslint/recommended + - plugin:@typescript-eslint/recommended-requiring-type-checking + - plugin:prettier/recommended + - prettier/@typescript-eslint +plugins: + - '@typescript-eslint' +parser: '@typescript-eslint/parser' +parserOptions: + sourceType: module + ecmaVersion: 2020 + project: ./tsconfig.json +rules: + prettier/prettier: + - error + - singleQuote: true + '@typescript-eslint/camelcase': warn + '@typescript-eslint/no-use-before-define': off + no-empty-function: off + '@typescript-eslint/no-empty-function': + - error + - allow: + - constructors + '@typescript-eslint/no-namespace': warn + 'no-fallthrough': warn + '@typescript-eslint/unbound-method': off + 'no-inner-declarations': off + '@typescript-eslint/class-name-casing': warn + 'prefer-const': warn diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9313e9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Package files +node_modules/ +# Build files +dist/ +# Other files +.vs/ +.idea/ +*.iml +.DS_Store +# Exclude some VSCode setting files. +.vscode/* +!/.vscode/extensions.json +!/.vscode/tasks.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..fda5ad5 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "editorconfig.editorconfig" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7cd3fff --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,39 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "install", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "build", + "group": "build", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "test", + "group": "test", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "lint", + "group": "test", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "lint:fix", + "group": "test", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "clear", + "problemMatcher": [] + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..64c9eac --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + + +## [4-r.1] - 2020-01-30 + +### Added + +* Add `.editorconfig`, `.gitattributes` and `.gitignore`. +* Add document `README.md` and `CHANGELOG.md`. +* Add `package.json` for development and build. +* Add Prettier and ESLint for format and check code quolity. + +### Changed + +* Move source files to `/src` directory. +* Reformat code using Prettier and ESLint. + + +[4-r.1]: https://github.com/Live2D/CubismWebFramework/compare/ce2585a919ac6e99f64dd468933772c6f1abbcc7...4-r.1 diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d056d8d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,39 @@ +## Definitions + +### Live2D Cubism Components + +Cubism Web Framework is included in Live2D Cubism Components. + +Cubism Web Framework は Live2D Cubism Components に含まれます。 + + +## Cubism SDK Release License + +*All business* users must obtain a Cubism SDK Release License. "Business" means an entity with the annual gross revenue more than ten million (10,000,000) JPY for the most recent fiscal year. + +* [Cubism SDK Release License](https://www.live2d.com/en/download/cubism-sdk/release-license/) + +直近会計年度の売上高が 1000 万円以上の事業者様がご利用になる場合は、Cubism SDK リリースライセンス(出版許諾契約)に同意していただく必要がございます。 + +* [Cubism SDK リリースライセンス](https://www.live2d.com/ja/download/cubism-sdk/release-license/) + + +## Live2D Open Software License + +Live2D Cubism Components is available under Live2D Open Software License. + +* [Live2D Open Software License](https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html) +* [Live2D Open Software 使用許諾契約書](https://www.live2d.com/eula/live2d-open-software-license-agreement_jp.html) + + +## Live2D Proprietary Software License + +Live2D Cubism Core is available under Live2D Proprietary Software License. + +* [Live2D Proprietary Software License Agreement](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html) +* [Live2D Proprietary Software 使用許諾契約書](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_jp.html) + + +--- + +Please contact us from [here](https://www.live2d.jp/contact/) for more license information. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a95d6fe --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# Cubism Web Framework + +Live2D Cubism 4 Editor で出力したモデルをアプリケーションで利用するためのフレームワークです。 + +モデルを表示、操作するための各種機能を提供します。 +モデルをロードするには Live2D Cubism Core ライブラリと組み合わせて使用します。 + +ビルドを行うことで、ブラウザで利用可能な JavaScript ライブラリとして利用することができます。 + + +## ライセンス + +本 SDK を使用する前に、[ライセンス](LICENSE.md)をご確認ください。 + + +## 開発環境 + +### Node.js + +* 13.7.0 +* 12.14.1 +* 10.18.1 + +### TypeScript + +3.7.5 + + +## 開発環境構築 + +1. [Node.js] と [Visual Studio Code] をインストールします +1. Visual Studio Code で本プロジェクトを開き、推奨拡張機能をインストールします + * 拡張機能タブから `@recommended` と入力することで確認できます +1. コマンドパレット(*View > Command Palette...*)で `>Tasks: Run Task` を入力してタスク一覧を表示します +1. `npm: install` を選択して依存パッケージのダウンロードを行います + +コマンドパレットのタスク一覧から各種コマンドを実行することができます。 + +NOTE: デバック用の設定は、`.vscode/tasks.json` に記述しています。 + +## タスク一覧 + +### `npm: build` + +ソースファイルのビルドを行い、`dist` ディレクトリに出力します。 + +`tsconfig.json` を編集することで設定内容を変更できます。 + +### `npm: test` + +TypeScript の型チェックテストを行います。 + +`tsconfig.json` を編集することで設定内容を変更できます。 + +### `npm: lint` + +`src` ディレクトリ内の TypeScript ファイルの静的解析を行います。 + +`.eslintrc.yml` を編集することで設定内容を変更できます。 + +### `npm: lint:fix` + +`src` ディレクトリ内の TypeScript ファイルの静的解析及び自動修正を行います。 + +`.eslintrc.yml` を編集することで設定内容を変更できます。 + +### `npm: clean` + +ビルド成果物ディレクトリ(`dist`)を削除します。 + + +## コンポーネント + +### effect + +自動まばたきやリップシンクなど、モデルに対してモーション情報をエフェクト的に付加する機能を提供します。 + +### id + +モデルに設定されたパラメータ名・パーツ名・Drawable名を独自の型で管理する機能を提供します。 + +### math + +行列計算やベクトル計算など、モデルの操作や描画に必要な算術演算の機能を提供します。 + +### model + +モデルを取り扱うための各種機能(生成、更新、破棄)を提供します。 + +### motion + +モデルにモーションデータを適用するための各種機能(モーション再生、パラメータブレンド)を提供します。 + +### physics + +モデルに物理演算による変形操作を適用するための機能を提供します。 + +### rendering + +モデルを描画するためのグラフィックス命令を実装したレンダラを提供します。 + +### type + +フレームワーク内で使用する型定義を提供します。 + +### utils + +JSONパーサーやログ出力などのユーティリティ機能を提供します。 + + +## Live2D Cubism Core for Web + +当リポジトリには Cubism Core for Web は同梱されていません。 + +[Cubism SDK for Web] からダウンロードしてください。 + +[Cubism SDK for Web]: https://www.live2d.com/download/cubism-sdk/download-web/ + + +## サンプル + +標準的なアプリケーションの実装例は [CubismWebSamples] を参照ください。 + +[CubismWebSamples]: https://github.com/Live2D/CubismWebSamples + + +## マニュアル + +[Cubism SDK Manual](https://docs.live2d.com/cubism-sdk-manual/top/) + + +## 変更履歴 + +当リポジトリの変更履歴については [CHANGELOG.md](CHANGELOG.md) を参照ください。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fc24da4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1155 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.18.0.tgz", + "integrity": "sha512-kuO8WQjV+RCZvAXVRJfXWiJ8iYEtfHlKgcqqqXg9uUkIolEHuUaMmm8/lcO4xwCOtaw6mY0gStn2Lg4/eUXXYQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.18.0", + "eslint-utils": "^1.4.3", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.18.0.tgz", + "integrity": "sha512-J6MopKPHuJYmQUkANLip7g9I82ZLe1naCbxZZW3O2sIxTiq/9YYoOELEKY7oPg0hJ0V/AQ225h2z0Yp+RRMXhw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.18.0", + "eslint-scope": "^5.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.18.0.tgz", + "integrity": "sha512-SJJPxFMEYEWkM6pGfcnjLU+NJIPo+Ko1QrCBL+i0+zV30ggLD90huEmMMhKLHBpESWy9lVEeWlQibweNQzyc+A==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.18.0", + "@typescript-eslint/typescript-estree": "2.18.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.18.0.tgz", + "integrity": "sha512-gVHylf7FDb8VSi2ypFuEL3hOtoC4HkZZ5dOjXvVjoyKdRrvXAOPSzpNRnKMfaUUEiSLP8UF9j9X9EDLxC0lfZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + } + }, + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", + "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz", + "integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.1.tgz", + "integrity": "sha512-IQ4ikL8SjBiEDZfk+DFVwqRK8md24RWMEJkdSlgNLkyyAImcjf8SWvU1qFMDOb4igBClbTQ/ugPqXcRwdFTxZw==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6635103 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "scripts": { + "build": "tsc", + "test": "tsc --noEmit", + "lint": "eslint src -f codeframe --ext .ts", + "lint:fix": "eslint src -f codeframe --ext .ts --fix", + "clean": "rimraf dist" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.18.0", + "@typescript-eslint/parser": "^2.18.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-prettier": "^3.1.2", + "prettier": "^1.19.1", + "rimraf": "^3.0.1", + "typescript": "^3.7.5" + } +} diff --git a/src/cubismdefaultparameterid.ts b/src/cubismdefaultparameterid.ts new file mode 100644 index 0000000..4d3168f --- /dev/null +++ b/src/cubismdefaultparameterid.ts @@ -0,0 +1,65 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * @brief パラメータIDのデフォルト値を保持する定数
+ * デフォルト値の仕様は以下のマニュアルに基づく
+ * https://docs.live2d.com/cubism-editor-manual/standard-parametor-list/ + */ +export namespace Live2DCubismFramework { + // パーツID + export const HitAreaPrefix = 'HitArea'; + export const HitAreaHead = 'Head'; + export const HitAreaBody = 'Body'; + export const PartsIdCore = 'Parts01Core'; + export const PartsArmPrefix = 'Parts01Arm_'; + export const PartsArmLPrefix = 'Parts01ArmL_'; + export const PartsArmRPrefix = 'Parts01ArmR_'; + + // パラメータID + export const ParamAngleX = 'ParamAngleX'; + export const ParamAngleY = 'ParamAngleY'; + export const ParamAngleZ = 'ParamAngleZ'; + export const ParamEyeLOpen = 'ParamEyeLOpen'; + export const ParamEyeLSmile = 'ParamEyeLSmile'; + export const ParamEyeROpen = 'ParamEyeROpen'; + export const ParamEyeRSmile = 'ParamEyeRSmile'; + export const ParamEyeBallX = 'ParamEyeBallX'; + export const ParamEyeBallY = 'ParamEyeBallY'; + export const ParamEyeBallForm = 'ParamEyeBallForm'; + export const ParamBrowLY = 'ParamBrowLY'; + export const ParamBrowRY = 'ParamBrowRY'; + export const ParamBrowLX = 'ParamBrowLX'; + export const ParamBrowRX = 'ParamBrowRX'; + export const ParamBrowLAngle = 'ParamBrowLAngle'; + export const ParamBrowRAngle = 'ParamBrowRAngle'; + export const ParamBrowLForm = 'ParamBrowLForm'; + export const ParamBrowRForm = 'ParamBrowRForm'; + export const ParamMouthForm = 'ParamMouthForm'; + export const ParamMouthOpenY = 'ParamMouthOpenY'; + export const ParamCheek = 'ParamCheek'; + export const ParamBodyAngleX = 'ParamBodyAngleX'; + export const ParamBodyAngleY = 'ParamBodyAngleY'; + export const ParamBodyAngleZ = 'ParamBodyAngleZ'; + export const ParamBreath = 'ParamBreath'; + export const ParamArmLA = 'ParamArmLA'; + export const ParamArmRA = 'ParamArmRA'; + export const ParamArmLB = 'ParamArmLB'; + export const ParamArmRB = 'ParamArmRB'; + export const ParamHandL = 'ParamHandL'; + export const ParamHandR = 'ParamHandR'; + export const ParamHairFront = 'ParamHairFront'; + export const ParamHairSide = 'ParamHairSide'; + export const ParamHairBack = 'ParamHairBack'; + export const ParamHairFluffy = 'ParamHairFluffy'; + export const ParamShoulderY = 'ParamShoulderY'; + export const ParamBustX = 'ParamBustX'; + export const ParamBustY = 'ParamBustY'; + export const ParamBaseX = 'ParamBaseX'; + export const ParamBaseY = 'ParamBaseY'; + export const ParamNONE = 'NONE:'; +} diff --git a/src/cubismframeworkconfig.ts b/src/cubismframeworkconfig.ts new file mode 100644 index 0000000..3ff7d46 --- /dev/null +++ b/src/cubismframeworkconfig.ts @@ -0,0 +1,32 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +//======================================================== +// ログ出力関数の設定 +//======================================================== + +//---------- ログ出力レベル 選択項目 定義 ---------- +// 詳細ログ出力設定 +export const CSM_LOG_LEVEL_VERBOSE = 0; +// デバッグログ出力設定 +export const CSM_LOG_LEVEL_DEBUG = 1; +// Infoログ出力設定 +export const CSM_LOG_LEVEL_INFO = 2; +// 警告ログ出力設定 +export const CSM_LOG_LEVEL_WARNING = 3; +// エラーログ出力設定 +export const CSM_LOG_LEVEL_ERROR = 4; +// ログ出力オフ設定 +export const CSM_LOG_LEVEL_OFF = 5; + +/** + * ログ出力レベル設定。 + * + * 強制的にログ出力レベルを変える時に定義を有効にする。 + * CSM_LOG_LEVEL_VERBOSE ~ CSM_LOG_LEVEL_OFF を選択する。 + */ +export const CSM_LOG_LEVEL: number = CSM_LOG_LEVEL_VERBOSE; diff --git a/src/cubismmodelsettingjson.ts b/src/cubismmodelsettingjson.ts new file mode 100644 index 0000000..40c5715 --- /dev/null +++ b/src/cubismmodelsettingjson.ts @@ -0,0 +1,842 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismframework } from './live2dcubismframework'; +import { Live2DCubismFramework as icubismmodelsetting } from './icubismmodelsetting'; +import { Live2DCubismFramework as cubismid } from './id/cubismid'; +import { Live2DCubismFramework as cubismjson } from './utils/cubismjson'; +import { Live2DCubismFramework as csmmap } from './type/csmmap'; +import { Live2DCubismFramework as csmvector } from './type/csmvector'; +import csmVector = csmvector.csmVector; +import csmMap = csmmap.csmMap; +import iterator = csmmap.iterator; +import CubismFramework = cubismframework.CubismFramework; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismJson = cubismjson.CubismJson; +import Value = cubismjson.Value; +import ICubismModelSetting = icubismmodelsetting.ICubismModelSetting; + +export namespace Live2DCubismFramework { + /** + * Model3Jsonのキー文字列 + */ + + // JSON Keys + const Version = 'Version'; + const FileReferences = 'FileReferences'; + const Groups = 'Groups'; + const Layout = 'Layout'; + const HitAreas = 'HitAreas'; + + const Moc = 'Moc'; + const Textures = 'Textures'; + const Physics = 'Physics'; + const Pose = 'Pose'; + const Expressions = 'Expressions'; + const Motions = 'Motions'; + + const UserData = 'UserData'; + const Name = 'Name'; + const FilePath = 'File'; + const Id = 'Id'; + const Ids = 'Ids'; + const Target = 'Target'; + + // Motions + const Idle = 'Idle'; + const TapBody = 'TapBody'; + const PinchIn = 'PinchIn'; + const PinchOut = 'PinchOut'; + const Shake = 'Shake'; + const FlickHead = 'FlickHead'; + const Parameter = 'Parameter'; + + const SoundPath = 'Sound'; + const FadeInTime = 'FadeInTime'; + const FadeOutTime = 'FadeOutTime'; + + // Layout + const CenterX = 'CenterX'; + const CenterY = 'CenterY'; + const X = 'X'; + const Y = 'Y'; + const Width = 'Width'; + const Height = 'Height'; + + const LipSync = 'LipSync'; + const EyeBlink = 'EyeBlink'; + + const InitParameter = 'init_param'; + const InitPartsVisible = 'init_parts_visible'; + const Val = 'val'; + + enum FrequestNode { + FrequestNode_Groups, // getRoot().getValueByString(Groups) + FrequestNode_Moc, // getRoot().getValueByString(FileReferences).getValueByString(Moc) + FrequestNode_Motions, // getRoot().getValueByString(FileReferences).getValueByString(Motions) + FrequestNode_Expressions, // getRoot().getValueByString(FileReferences).getValueByString(Expressions) + FrequestNode_Textures, // getRoot().getValueByString(FileReferences).getValueByString(Textures) + FrequestNode_Physics, // getRoot().getValueByString(FileReferences).getValueByString(Physics) + FrequestNode_Pose, // getRoot().getValueByString(FileReferences).getValueByString(Pose) + FrequestNode_HitAreas // getRoot().getValueByString(HitAreas) + } + + /** + * Model3Jsonパーサー + * + * model3.jsonファイルをパースして値を取得する + */ + export class CubismModelSettingJson extends ICubismModelSetting { + /** + * 引数付きコンストラクタ + * + * @param buffer Model3Jsonをバイト配列として読み込んだデータバッファ + * @param size Model3Jsonのデータサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + super(); + this._json = CubismJson.create(buffer, size); + + if (this._json) { + this._jsonValue = new csmVector(); + + // 順番はenum FrequestNodeと一致させる + this._jsonValue.pushBack(this._json.getRoot().getValueByString(Groups)); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Moc) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Motions) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Expressions) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Textures) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Physics) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Pose) + ); + this._jsonValue.pushBack( + this._json.getRoot().getValueByString(HitAreas) + ); + } + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + + this._jsonValue = null; + } + + /** + * CubismJsonオブジェクトを取得する + * + * @return CubismJson + */ + public GetJson(): CubismJson { + return this._json; + } + + /** + * Mocファイルの名前を取得する + * @return Mocファイルの名前 + */ + public getModelFileName(): string { + if (!this.isExistModelFile()) { + return ''; + } + return this._jsonValue.at(FrequestNode.FrequestNode_Moc).getRawString(); + } + + /** + * モデルが使用するテクスチャの数を取得する + * テクスチャの数 + */ + public getTextureCount(): number { + if (!this.isExistTextureFiles()) { + return 0; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_Textures).getSize(); + } + + /** + * テクスチャが配置されたディレクトリの名前を取得する + * @return テクスチャが配置されたディレクトリの名前 + */ + public getTextureDirectory(): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Textures) + .getRawString(); + } + + /** + * モデルが使用するテクスチャの名前を取得する + * @param index 配列のインデックス値 + * @return テクスチャの名前 + */ + public getTextureFileName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Textures) + .getValueByIndex(index) + .getRawString(); + } + + /** + * モデルに設定された当たり判定の数を取得する + * @return モデルに設定された当たり判定の数 + */ + public getHitAreasCount(): number { + if (!this.isExistHitAreas()) { + return 0; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_HitAreas).getSize(); + } + + /** + * 当たり判定に設定されたIDを取得する + * + * @param index 配列のindex + * @return 当たり判定に設定されたID + */ + public getHitAreaId(index: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._jsonValue + .at(FrequestNode.FrequestNode_HitAreas) + .getValueByIndex(index) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 当たり判定に設定された名前を取得する + * @param index 配列のインデックス値 + * @return 当たり判定に設定された名前 + */ + public getHitAreaName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_HitAreas) + .getValueByIndex(index) + .getValueByString(Name) + .getRawString(); + } + + /** + * 物理演算設定ファイルの名前を取得する + * @return 物理演算設定ファイルの名前 + */ + public getPhysicsFileName(): string { + if (!this.isExistPhysicsFile()) { + return ''; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Physics) + .getRawString(); + } + + /** + * パーツ切り替え設定ファイルの名前を取得する + * @return パーツ切り替え設定ファイルの名前 + */ + public getPoseFileName(): string { + if (!this.isExistPoseFile()) { + return ''; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_Pose).getRawString(); + } + + /** + * 表情設定ファイルの数を取得する + * @return 表情設定ファイルの数 + */ + public getExpressionCount(): number { + if (!this.isExistExpressionFile()) { + return 0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Expressions) + .getSize(); + } + + /** + * 表情設定ファイルを識別する名前(別名)を取得する + * @param index 配列のインデックス値 + * @return 表情の名前 + */ + public getExpressionName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Expressions) + .getValueByIndex(index) + .getValueByString(Name) + .getRawString(); + } + + /** + * 表情設定ファイルの名前を取得する + * @param index 配列のインデックス値 + * @return 表情設定ファイルの名前 + */ + public getExpressionFileName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Expressions) + .getValueByIndex(index) + .getValueByString(FilePath) + .getRawString(); + } + + /** + * モーショングループの数を取得する + * @return モーショングループの数 + */ + public getMotionGroupCount(): number { + if (!this.isExistMotionGroups()) { + return 0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getKeys() + .getSize(); + } + + /** + * モーショングループの名前を取得する + * @param index 配列のインデックス値 + * @return モーショングループの名前 + */ + public getMotionGroupName(index: number): string { + if (!this.isExistMotionGroups()) { + return null; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getKeys() + .at(index); + } + + /** + * モーショングループに含まれるモーションの数を取得する + * @param groupName モーショングループの名前 + * @return モーショングループの数 + */ + public getMotionCount(groupName: string): number { + if (!this.isExistMotionGroupName(groupName)) { + return 0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getSize(); + } + + /** + * グループ名とインデックス値からモーションファイル名を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return モーションファイルの名前 + */ + public getMotionFileName(groupName: string, index: number): string { + if (!this.isExistMotionGroupName(groupName)) { + return ''; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FilePath) + .getRawString(); + } + + /** + * モーションに対応するサウンドファイルの名前を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return サウンドファイルの名前 + */ + public getMotionSoundFileName(groupName: string, index: number): string { + if (!this.isExistMotionSoundFile(groupName, index)) { + return ''; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(SoundPath) + .getRawString(); + } + + /** + * モーション開始時のフェードイン処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードイン処理時間[秒] + */ + public getMotionFadeInTimeValue(groupName: string, index: number): number { + if (!this.isExistMotionFadeIn(groupName, index)) { + return -1.0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーション終了時のフェードアウト処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードアウト処理時間[秒] + */ + public getMotionFadeOutTimeValue(groupName: string, index: number): number { + if (!this.isExistMotionFadeOut(groupName, index)) { + return -1.0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * ユーザーデータのファイル名を取得する + * @return ユーザーデータのファイル名 + */ + public getUserDataFile(): string { + if (!this.isExistUserDataFile()) { + return ''; + } + + return this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(UserData) + .getRawString(); + } + + /** + * レイアウト情報を取得する + * @param outLayoutMap csmMapクラスのインスタンス + * @return true レイアウト情報が存在する + * @return false レイアウト情報が存在しない + */ + public getLayoutMap(outLayoutMap: csmMap): boolean { + // 存在しない要素にアクセスするとエラーになるためValueがnullの場合はnullを代入する + const map: csmMap = this._json + .getRoot() + .getValueByString(Layout) + .getMap(); + + if (map == null) { + return false; + } + + let ret = false; + + for ( + const ite: iterator = map.begin(); + ite.notEqual(map.end()); + ite.preIncrement() + ) { + outLayoutMap.setValue(ite.ptr().first, ite.ptr().second.toFloat()); + ret = true; + } + + return ret; + } + + /** + * 目パチに関連付けられたパラメータの数を取得する + * @return 目パチに関連付けられたパラメータの数 + */ + public getEyeBlinkParameterCount(): number { + if (!this.isExistEyeBlinkParameters()) { + return 0; + } + + let num = 0; + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == EyeBlink) { + num = refI + .getValueByString(Ids) + .getVector() + .getSize(); + break; + } + } + + return num; + } + + /** + * 目パチに関連付けられたパラメータのIDを取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public getEyeBlinkParameterId(index: number): CubismIdHandle { + if (!this.isExistEyeBlinkParameters()) { + return null; + } + + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == EyeBlink) { + return CubismFramework.getIdManager().getId( + refI + .getValueByString(Ids) + .getValueByIndex(index) + .getRawString() + ); + } + } + return null; + } + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @return リップシンクに関連付けられたパラメータの数 + */ + public getLipSyncParameterCount(): number { + if (!this.isExistLipSyncParameters()) { + return 0; + } + + let num = 0; + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == LipSync) { + num = refI + .getValueByString(Ids) + .getVector() + .getSize(); + break; + } + } + + return num; + } + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public getLipSyncParameterId(index: number): CubismIdHandle { + if (!this.isExistLipSyncParameters()) { + return null; + } + + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == LipSync) { + return CubismFramework.getIdManager().getId( + refI + .getValueByString(Ids) + .getValueByIndex(index) + .getRawString() + ); + } + } + return null; + } + + /** + * モデルファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistModelFile(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Moc); + return !node.isNull() && !node.isError(); + } + + /** + * テクスチャファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistTextureFiles(): boolean { + const node: Value = this._jsonValue.at( + FrequestNode.FrequestNode_Textures + ); + return !node.isNull() && !node.isError(); + } + + /** + * 当たり判定のキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistHitAreas(): boolean { + const node: Value = this._jsonValue.at( + FrequestNode.FrequestNode_HitAreas + ); + return !node.isNull() && !node.isError(); + } + + /** + * 物理演算ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistPhysicsFile(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Physics); + return !node.isNull() && !node.isError(); + } + + /** + * ポーズ設定ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistPoseFile(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Pose); + return !node.isNull() && !node.isError(); + } + + /** + * 表情設定ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistExpressionFile(): boolean { + const node: Value = this._jsonValue.at( + FrequestNode.FrequestNode_Expressions + ); + return !node.isNull() && !node.isError(); + } + + /** + * モーショングループのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionGroups(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Motions); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーショングループのキーが存在するかどうかを確認する + * @param groupName グループ名 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionGroupName(groupName: string): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するサウンドファイルのキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionSoundFile(groupName: string, index: number): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(SoundPath); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するフェードイン時間のキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionFadeIn(groupName: string, index: number): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeInTime); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するフェードアウト時間のキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionFadeOut(groupName: string, index: number): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeOutTime); + return !node.isNull() && !node.isError(); + } + + /** + * UserDataのファイル名が存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistUserDataFile(): boolean { + const node: Value = this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(UserData); + return !node.isNull() && !node.isError(); + } + + /** + * 目ぱちに対応付けられたパラメータが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistEyeBlinkParameters(): boolean { + if ( + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isNull() || + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isError() + ) { + return false; + } + + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + ++i + ) { + if ( + this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i) + .getValueByString(Name) + .getRawString() == EyeBlink + ) { + return true; + } + } + + return false; + } + + /** + * リップシンクに対応付けられたパラメータが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistLipSyncParameters(): boolean { + if ( + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isNull() || + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isError() + ) { + return false; + } + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + ++i + ) { + if ( + this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i) + .getValueByString(Name) + .getRawString() == LipSync + ) { + return true; + } + } + return false; + } + + private _json: CubismJson; + private _jsonValue: csmVector; + } +} diff --git a/src/effect/cubismbreath.ts b/src/effect/cubismbreath.ts new file mode 100644 index 0000000..91362b4 --- /dev/null +++ b/src/effect/cubismbreath.ts @@ -0,0 +1,124 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismModel = cubismmodel.CubismModel; +import csmVector = csmvector.csmVector; + +export namespace Live2DCubismFramework { + /** + * 呼吸機能 + * + * 呼吸機能を提供する。 + */ + export class CubismBreath { + /** + * インスタンスの作成 + */ + public static create(): CubismBreath { + return new CubismBreath(); + } + + /** + * インスタンスの破棄 + * @param instance 対象のCubismBreath + */ + public static delete(instance: CubismBreath): void { + if (instance != null) { + instance = null; + } + } + + /** + * 呼吸のパラメータの紐づけ + * @param breathParameters 呼吸を紐づけたいパラメータのリスト + */ + public setParameters( + breathParameters: csmVector + ): void { + this._breathParameters = breathParameters; + } + + /** + * 呼吸に紐づいているパラメータの取得 + * @return 呼吸に紐づいているパラメータのリスト + */ + public getParameters(): csmVector { + return this._breathParameters; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters( + model: CubismModel, + deltaTimeSeconds: number + ): void { + this._currentTime += deltaTimeSeconds; + + const t: number = this._currentTime * 2.0 * 3.14159; + + for (let i = 0; i < this._breathParameters.getSize(); ++i) { + const data: BreathParameterData = this._breathParameters.at(i); + + model.addParameterValueById( + data.parameterId, + data.offset + data.peak * Math.sin(t / data.cycle), + data.weight + ); + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._currentTime = 0.0; + } + + _breathParameters: csmVector; // 呼吸にひもづいているパラメータのリスト + _currentTime: number; // 積算時間[秒] + } + + /** + * 呼吸のパラメータ情報 + */ + export class BreathParameterData { + /** + * コンストラクタ + * @param parameterId 呼吸をひもづけるパラメータID + * @param offset 呼吸を正弦波としたときの、波のオフセット + * @param peak 呼吸を正弦波としたときの、波の高さ + * @param cycle 呼吸を正弦波としたときの、波の周期 + * @param weight パラメータへの重み + */ + constructor( + parameterId?: CubismIdHandle, + offset?: number, + peak?: number, + cycle?: number, + weight?: number + ) { + this.parameterId = parameterId == undefined ? null : parameterId; + this.offset = offset == undefined ? 0.0 : offset; + this.peak = peak == undefined ? 0.0 : peak; + this.cycle = cycle == undefined ? 0.0 : cycle; + this.weight = weight == undefined ? 0.0 : weight; + } + + parameterId: CubismIdHandle; // 呼吸をひもづけるパラメータID\ + offset: number; // 呼吸を正弦波としたときの、波のオフセット + peak: number; // 呼吸を正弦波としたときの、波の高さ + cycle: number; // 呼吸を正弦波としたときの、波の周期 + weight: number; // パラメータへの重み + } +} diff --git a/src/effect/cubismeyeblink.ts b/src/effect/cubismeyeblink.ts new file mode 100644 index 0000000..74774fd --- /dev/null +++ b/src/effect/cubismeyeblink.ts @@ -0,0 +1,232 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as icubismmodelsetting } from '../icubismmodelsetting'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import CubismModel = cubismmodel.CubismModel; +import CubismIdHandle = cubismid.CubismIdHandle; +import ICubismModelSetting = icubismmodelsetting.ICubismModelSetting; +import csmVector = csmvector.csmVector; + +export namespace Live2DCubismFramework { + /** + * 自動まばたき機能 + * + * 自動まばたき機能を提供する。 + */ + export class CubismEyeBlink { + /** + * インスタンスを作成する + * @param modelSetting モデルの設定情報 + * @return 作成されたインスタンス + * @note 引数がNULLの場合、パラメータIDが設定されていない空のインスタンスを作成する。 + */ + public static create( + modelSetting: ICubismModelSetting = null + ): CubismEyeBlink { + return new CubismEyeBlink(modelSetting); + } + + /** + * インスタンスの破棄 + * @param eyeBlink 対象のCubismEyeBlink + */ + public static delete(eyeBlink: CubismEyeBlink): void { + if (eyeBlink != null) { + eyeBlink = null; + } + } + + /** + * まばたきの間隔の設定 + * @param blinkingInterval まばたきの間隔の時間[秒] + */ + public setBlinkingInterval(blinkingInterval: number): void { + this._blinkingIntervalSeconds = blinkingInterval; + } + + /** + * まばたきのモーションの詳細設定 + * @param closing まぶたを閉じる動作の所要時間[秒] + * @param closed まぶたを閉じている動作の所要時間[秒] + * @param opening まぶたを開く動作の所要時間[秒] + */ + public setBlinkingSetting( + closing: number, + closed: number, + opening: number + ): void { + this._closingSeconds = closing; + this._closedSeconds = closed; + this._openingSeconds = opening; + } + + /** + * まばたきさせるパラメータIDのリストの設定 + * @param parameterIds パラメータのIDのリスト + */ + public setParameterIds(parameterIds: csmVector): void { + this._parameterIds = parameterIds; + } + + /** + * まばたきさせるパラメータIDのリストの取得 + * @return パラメータIDのリスト + */ + public getParameterIds(): csmVector { + return this._parameterIds; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters( + model: CubismModel, + deltaTimeSeconds: number + ): void { + this._userTimeSeconds += deltaTimeSeconds; + let parameterValue: number; + let t = 0.0; + + switch (this._blinkingState) { + case EyeState.EyeState_Closing: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._closingSeconds; + + if (t >= 1.0) { + t = 1.0; + this._blinkingState = EyeState.EyeState_Closed; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 1.0 - t; + + break; + case EyeState.EyeState_Closed: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._closedSeconds; + + if (t >= 1.0) { + this._blinkingState = EyeState.EyeState_Opening; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 0.0; + + break; + case EyeState.EyeState_Opening: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._openingSeconds; + + if (t >= 1.0) { + t = 1.0; + this._blinkingState = EyeState.EyeState_Interval; + this._nextBlinkingTime = this.determinNextBlinkingTiming(); + } + + parameterValue = t; + + break; + case EyeState.EyeState_Interval: + if (this._nextBlinkingTime < this._userTimeSeconds) { + this._blinkingState = EyeState.EyeState_Closing; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 1.0; + + break; + case EyeState.EyeState_First: + default: + this._blinkingState = EyeState.EyeState_Interval; + this._nextBlinkingTime = this.determinNextBlinkingTiming(); + + parameterValue = 1.0; + break; + } + + if (!CubismEyeBlink.CloseIfZero) { + parameterValue = -parameterValue; + } + + for (let i = 0; i < this._parameterIds.getSize(); ++i) { + model.setParameterValueById(this._parameterIds.at(i), parameterValue); + } + } + + /** + * コンストラクタ + * @param modelSetting モデルの設定情報 + */ + public constructor(modelSetting: ICubismModelSetting) { + this._blinkingState = EyeState.EyeState_First; + this._nextBlinkingTime = 0.0; + this._stateStartTimeSeconds = 0.0; + this._blinkingIntervalSeconds = 4.0; + this._closingSeconds = 0.1; + this._closedSeconds = 0.05; + this._openingSeconds = 0.15; + this._userTimeSeconds = 0.0; + this._parameterIds = new csmVector(); + + if (modelSetting == null) { + return; + } + + for (let i = 0; i < modelSetting.getEyeBlinkParameterCount(); ++i) { + this._parameterIds.pushBack(modelSetting.getEyeBlinkParameterId(i)); + } + } + + /** + * 次の瞬きのタイミングの決定 + * + * @return 次のまばたきを行う時刻[秒] + */ + public determinNextBlinkingTiming(): number { + const r: number = Math.random(); + return ( + this._userTimeSeconds + r * (2.0 * this._blinkingIntervalSeconds - 1.0) + ); + } + + _blinkingState: number; // 現在の状態 + _parameterIds: csmVector; // 操作対象のパラメータのIDのリスト + _nextBlinkingTime: number; // 次のまばたきの時刻[秒] + _stateStartTimeSeconds: number; // 現在の状態が開始した時刻[秒] + _blinkingIntervalSeconds: number; // まばたきの間隔[秒] + _closingSeconds: number; // まぶたを閉じる動作の所要時間[秒] + _closedSeconds: number; // まぶたを閉じている動作の所要時間[秒] + _openingSeconds: number; // まぶたを開く動作の所要時間[秒] + _userTimeSeconds: number; // デルタ時間の積算値[秒] + + /** + * IDで指定された目のパラメータが、0のときに閉じるなら true 、1の時に閉じるなら false 。 + */ + static readonly CloseIfZero: boolean = true; + } + + /** + * まばたきの状態 + * + * まばたきの状態を表す列挙型 + */ + export enum EyeState { + EyeState_First = 0, // 初期状態 + EyeState_Interval, // まばたきしていない状態 + EyeState_Closing, // まぶたが閉じていく途中の状態 + EyeState_Closed, // まぶたが閉じている状態 + EyeState_Opening // まぶたが開いていく途中の状態 + } +} diff --git a/src/effect/cubismpose.ts b/src/effect/cubismpose.ts new file mode 100644 index 0000000..5c47d18 --- /dev/null +++ b/src/effect/cubismpose.ts @@ -0,0 +1,405 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as cubismjson } from '../utils/cubismjson'; +import CubismIdHandle = cubismid.CubismIdHandle; +import csmVector = csmvector.csmVector; +import iterator = csmvector.iterator; +import CubismModel = cubismmodel.CubismModel; +import CubismFramework = cubismframework.CubismFramework; +import CubismJson = cubismjson.CubismJson; +import Value = cubismjson.Value; + +export namespace Live2DCubismFramework { + const Epsilon = 0.001; + const DefaultFadeInSeconds = 0.5; + + // Pose.jsonのタグ + const FadeIn = 'FadeInTime'; + const Link = 'Link'; + const Groups = 'Groups'; + const Id = 'Id'; + + /** + * パーツの不透明度の設定 + * + * パーツの不透明度の管理と設定を行う。 + */ + export class CubismPose { + /** + * インスタンスの作成 + * @param pose3json pose3.jsonのデータ + * @param size pose3.jsonのデータのサイズ[byte] + * @return 作成されたインスタンス + */ + public static create(pose3json: ArrayBuffer, size: number): CubismPose { + const ret: CubismPose = new CubismPose(); + const json: CubismJson = CubismJson.create(pose3json, size); + const root: Value = json.getRoot(); + + // フェード時間の指定 + if (!root.getValueByString(FadeIn).isNull()) { + ret._fadeTimeSeconds = root + .getValueByString(FadeIn) + .toFloat(DefaultFadeInSeconds); + + if (ret._fadeTimeSeconds <= 0.0) { + ret._fadeTimeSeconds = DefaultFadeInSeconds; + } + } + + // パーツグループ + const poseListInfo: Value = root.getValueByString(Groups); + const poseCount: number = poseListInfo.getSize(); + + for (let poseIndex = 0; poseIndex < poseCount; ++poseIndex) { + const idListInfo: Value = poseListInfo.getValueByIndex(poseIndex); + const idCount: number = idListInfo.getSize(); + let groupCount = 0; + + for (let groupIndex = 0; groupIndex < idCount; ++groupIndex) { + const partInfo: Value = idListInfo.getValueByIndex(groupIndex); + const partData: PartData = new PartData(); + const parameterId: CubismIdHandle = CubismFramework.getIdManager().getId( + partInfo.getValueByString(Id).getRawString() + ); + + partData.partId = parameterId; + + // リンクするパーツの設定 + if (!partInfo.getValueByString(Link).isNull()) { + const linkListInfo: Value = partInfo.getValueByString(Link); + const linkCount: number = linkListInfo.getSize(); + + for (let linkIndex = 0; linkIndex < linkCount; ++linkIndex) { + const linkPart: PartData = new PartData(); + const linkId: CubismIdHandle = CubismFramework.getIdManager().getId( + linkListInfo.getValueByIndex(linkIndex).getString() + ); + + linkPart.partId = linkId; + + partData.link.pushBack(linkPart); + } + } + + ret._partGroups.pushBack(partData.clone()); + + ++groupCount; + } + + ret._partGroupCounts.pushBack(groupCount); + } + + CubismJson.delete(json); + + return ret; + } + + /** + * インスタンスを破棄する + * @param pose 対象のCubismPose + */ + public static delete(pose: CubismPose): void { + if (pose != null) { + pose = null; + } + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters( + model: CubismModel, + deltaTimeSeconds: number + ): void { + // 前回のモデルと同じでない場合は初期化が必要 + if (model != this._lastModel) { + // パラメータインデックスの初期化 + this.reset(model); + } + + this._lastModel = model; + + // 設定から時間を変更すると、経過時間がマイナスになる事があるので、経過時間0として対応 + if (deltaTimeSeconds < 0.0) { + deltaTimeSeconds = 0.0; + } + + let beginIndex = 0; + + for (let i = 0; i < this._partGroupCounts.getSize(); i++) { + const partGroupCount: number = this._partGroupCounts.at(i); + + this.doFade(model, deltaTimeSeconds, beginIndex, partGroupCount); + + beginIndex += partGroupCount; + } + + this.copyPartOpacities(model); + } + + /** + * 表示を初期化 + * @param model 対象のモデル + * @note 不透明度の初期値が0でないパラメータは、不透明度を1に設定する + */ + public reset(model: CubismModel): void { + let beginIndex = 0; + + for (let i = 0; i < this._partGroupCounts.getSize(); ++i) { + const groupCount: number = this._partGroupCounts.at(i); + + for (let j: number = beginIndex; j < beginIndex + groupCount; ++j) { + this._partGroups.at(j).initialize(model); + + const partsIndex: number = this._partGroups.at(j).partIndex; + const paramIndex: number = this._partGroups.at(j).parameterIndex; + + if (partsIndex < 0) { + continue; + } + + model.setPartOpacityByIndex(partsIndex, j == beginIndex ? 1.0 : 0.0); + model.setParameterValueByIndex( + paramIndex, + j == beginIndex ? 1.0 : 0.0 + ); + + for (let k = 0; k < this._partGroups.at(j).link.getSize(); ++k) { + this._partGroups + .at(j) + .link.at(k) + .initialize(model); + } + } + + beginIndex += groupCount; + } + } + + /** + * パーツの不透明度をコピー + * + * @param model 対象のモデル + */ + public copyPartOpacities(model: CubismModel): void { + for ( + let groupIndex = 0; + groupIndex < this._partGroups.getSize(); + ++groupIndex + ) { + const partData: PartData = this._partGroups.at(groupIndex); + + if (partData.link.getSize() == 0) { + continue; // 連動するパラメータはない + } + + const partIndex: number = this._partGroups.at(groupIndex).partIndex; + const opacity: number = model.getPartOpacityByIndex(partIndex); + + for ( + let linkIndex = 0; + linkIndex < partData.link.getSize(); + ++linkIndex + ) { + const linkPart: PartData = partData.link.at(linkIndex); + const linkPartIndex: number = linkPart.partIndex; + + if (linkPartIndex < 0) { + continue; + } + + model.setPartOpacityByIndex(linkPartIndex, opacity); + } + } + } + + /** + * パーツのフェード操作を行う。 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + * @param beginIndex フェード操作を行うパーツグループの先頭インデックス + * @param partGroupCount フェード操作を行うパーツグループの個数 + */ + public doFade( + model: CubismModel, + deltaTimeSeconds: number, + beginIndex: number, + partGroupCount: number + ): void { + let visiblePartIndex = -1; + let newOpacity = 1.0; + + const phi = 0.5; + const backOpacityThreshold = 0.15; + + // 現在、表示状態になっているパーツを取得 + for (let i: number = beginIndex; i < beginIndex + partGroupCount; ++i) { + const partIndex: number = this._partGroups.at(i).partIndex; + const paramIndex: number = this._partGroups.at(i).parameterIndex; + + if (model.getParameterValueByIndex(paramIndex) > Epsilon) { + if (visiblePartIndex >= 0) { + break; + } + + visiblePartIndex = i; + newOpacity = model.getPartOpacityByIndex(partIndex); + + // 新しい不透明度を計算 + newOpacity += deltaTimeSeconds / this._fadeTimeSeconds; + + if (newOpacity > 1.0) { + newOpacity = 1.0; + } + } + } + + if (visiblePartIndex < 0) { + visiblePartIndex = 0; + newOpacity = 1.0; + } + + // 表示パーツ、非表示パーツの不透明度を設定する + for (let i: number = beginIndex; i < beginIndex + partGroupCount; ++i) { + const partsIndex: number = this._partGroups.at(i).partIndex; + + // 表示パーツの設定 + if (visiblePartIndex == i) { + model.setPartOpacityByIndex(partsIndex, newOpacity); // 先に設定 + } + // 非表示パーツの設定 + else { + let opacity: number = model.getPartOpacityByIndex(partsIndex); + let a1: number; // 計算によって求められる不透明度 + + if (newOpacity < phi) { + a1 = (newOpacity * (phi - 1)) / phi + 1.0; // (0,1),(phi,phi)を通る直線式 + } else { + a1 = ((1 - newOpacity) * phi) / (1.0 - phi); // (1,0),(phi,phi)を通る直線式 + } + + // 背景の見える割合を制限する場合 + const backOpacity: number = (1.0 - a1) * (1.0 - newOpacity); + + if (backOpacity > backOpacityThreshold) { + a1 = 1.0 - backOpacityThreshold / (1.0 - newOpacity); + } + + if (opacity > a1) { + opacity = a1; // 計算の不透明度よりも大きければ(濃ければ)不透明度を上げる + } + + model.setPartOpacityByIndex(partsIndex, opacity); + } + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._fadeTimeSeconds = DefaultFadeInSeconds; + this._lastModel = null; + this._partGroups = new csmVector(); + this._partGroupCounts = new csmVector(); + } + + _partGroups: csmVector; // パーツグループ + _partGroupCounts: csmVector; // それぞれのパーツグループの個数 + _fadeTimeSeconds: number; // フェード時間[秒] + _lastModel: CubismModel; // 前回操作したモデル + } + + /** + * パーツにまつわるデータを管理 + */ + export class PartData { + /** + * コンストラクタ + */ + constructor(v?: PartData) { + this.parameterIndex = 0; + this.partIndex = 0; + this.link = new csmVector(); + + if (v != undefined) { + this.partId = v.partId; + + for ( + const ite: iterator = v.link.begin(); + ite.notEqual(v.link.end()); + ite.preIncrement() + ) { + this.link.pushBack(ite.ptr().clone()); + } + } + } + + /** + * =演算子のオーバーロード + */ + public assignment(v: PartData): PartData { + this.partId = v.partId; + + for ( + const ite: iterator = v.link.begin(); + ite.notEqual(v.link.end()); + ite.preIncrement() + ) { + this.link.pushBack(ite.ptr().clone()); + } + + return this; + } + + /** + * 初期化 + * @param model 初期化に使用するモデル + */ + public initialize(model: CubismModel): void { + this.parameterIndex = model.getParameterIndex(this.partId); + this.partIndex = model.getPartIndex(this.partId); + + model.setParameterValueByIndex(this.parameterIndex, 1); + } + + /** + * オブジェクトのコピーを生成する + */ + public clone(): PartData { + const clonePartData: PartData = new PartData(); + + clonePartData.partId = this.partId; + clonePartData.parameterIndex = this.parameterIndex; + clonePartData.partIndex = this.partIndex; + clonePartData.link = new csmVector(); + + for ( + let ite: iterator = this.link.begin(); + ite.notEqual(this.link.end()); + ite.increment() + ) { + clonePartData.link.pushBack(ite.ptr().clone()); + } + + return clonePartData; + } + + partId: CubismIdHandle; // パーツID + parameterIndex: number; // パラメータのインデックス + partIndex: number; // パーツのインデックス + link: csmVector; // 連動するパラメータ + } +} diff --git a/src/icubismallcator.ts b/src/icubismallcator.ts new file mode 100644 index 0000000..52b7740 --- /dev/null +++ b/src/icubismallcator.ts @@ -0,0 +1,45 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + /** + * メモリアロケーションを抽象化したクラス + * + * メモリ確保・解放処理をプラットフォーム側で実装して + * フレームワークから呼び出すためのインターフェース + */ + export abstract class ICubismAllocator { + /** + * アラインメント制約なしのヒープ・メモリーを確保します + * + * @param size 確保するバイト数 + * @return 成功すると割り当てられたメモリのアドレス。そうでなければ'0'を返す + */ + public abstract allocate(size: number): any; + + /** + * アラインメント制約なしのヒープ・メモリーを解放します。 + * + * @param memory 解放するメモリのアドレス + */ + public abstract deallocate(memory: any): void; + + /** + * アラインメント制約有のヒープ・メモリーを確保します。 + * @param size 確保するバイト数 + * @param alignment メモリーブロックのアラインメント幅 + * @return 成功すると割り当てられたメモリのアドレス。そうでなければ'0'を返す + */ + public abstract allocateAligned(size: number, alignment: number): any; + + /** + * アラインメント制約ありのヒープ・メモリーを解放します。 + * @param alignedMemory 解放するメモリのアドレス + */ + public abstract deallocateAligned(alignedMemory: any): void; + } +} diff --git a/src/icubismmodelsetting.ts b/src/icubismmodelsetting.ts new file mode 100644 index 0000000..222b43c --- /dev/null +++ b/src/icubismmodelsetting.ts @@ -0,0 +1,199 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismid } from './id/cubismid'; +import { Live2DCubismFramework as csmmap } from './type/csmmap'; +import csmMap = csmmap.csmMap; +import CubismIdHandle = cubismid.CubismIdHandle; + +export namespace Live2DCubismFramework { + /** + * モデル設定情報を取り扱う関数を宣言した純粋仮想クラス。 + * + * このクラスを継承することで、モデル設定情報を取り扱うクラスになる。 + */ + export abstract class ICubismModelSetting { + /** + * Mocファイルの名前を取得する + * @return Mocファイルの名前 + */ + public abstract getModelFileName(): string; + + /** + * モデルが使用するテクスチャの数を取得する + * テクスチャの数 + */ + public abstract getTextureCount(): number; + + /** + * テクスチャが配置されたディレクトリの名前を取得する + * @return テクスチャが配置されたディレクトリの名前 + */ + public abstract getTextureDirectory(): string; + + /** + * モデルが使用するテクスチャの名前を取得する + * @param index 配列のインデックス値 + * @return テクスチャの名前 + */ + public abstract getTextureFileName(index: number): string; + + /** + * モデルに設定された当たり判定の数を取得する + * @return モデルに設定された当たり判定の数 + */ + public abstract getHitAreasCount(): number; + + /** + * 当たり判定に設定されたIDを取得する + * + * @param index 配列のindex + * @return 当たり判定に設定されたID + */ + public abstract getHitAreaId(index: number): CubismIdHandle; + + /** + * 当たり判定に設定された名前を取得する + * @param index 配列のインデックス値 + * @return 当たり判定に設定された名前 + */ + public abstract getHitAreaName(index: number): string; + + /** + * 物理演算設定ファイルの名前を取得する + * @return 物理演算設定ファイルの名前 + */ + public abstract getPhysicsFileName(): string; + + /** + * パーツ切り替え設定ファイルの名前を取得する + * @return パーツ切り替え設定ファイルの名前 + */ + public abstract getPoseFileName(): string; + + /** + * 表情設定ファイルの数を取得する + * @return 表情設定ファイルの数 + */ + public abstract getExpressionCount(): number; + + /** + * 表情設定ファイルを識別する名前(別名)を取得する + * @param index 配列のインデックス値 + * @return 表情の名前 + */ + public abstract getExpressionName(index: number): string; + + /** + * 表情設定ファイルの名前を取得する + * @param index 配列のインデックス値 + * @return 表情設定ファイルの名前 + */ + public abstract getExpressionFileName(index: number): string; + + /** + * モーショングループの数を取得する + * @return モーショングループの数 + */ + public abstract getMotionGroupCount(): number; + + /** + * モーショングループの名前を取得する + * @param index 配列のインデックス値 + * @return モーショングループの名前 + */ + public abstract getMotionGroupName(index: number): string; + + /** + * モーショングループに含まれるモーションの数を取得する + * @param groupName モーショングループの名前 + * @return モーショングループの数 + */ + public abstract getMotionCount(groupName: string): number; + + /** + * グループ名とインデックス値からモーションファイル名を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return モーションファイルの名前 + */ + public abstract getMotionFileName(groupName: string, index: number): string; + + /** + * モーションに対応するサウンドファイルの名前を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return サウンドファイルの名前 + */ + public abstract getMotionSoundFileName( + groupName: string, + index: number + ): string; + + /** + * モーション開始時のフェードイン処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードイン処理時間[秒] + */ + public abstract getMotionFadeInTimeValue( + groupName: string, + index: number + ): number; + + /** + * モーション終了時のフェードアウト処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードアウト処理時間[秒] + */ + public abstract getMotionFadeOutTimeValue( + groupName: string, + index: number + ): number; + + /** + * ユーザーデータのファイル名を取得する + * @return ユーザーデータのファイル名 + */ + public abstract getUserDataFile(): string; + + /** + * レイアウト情報を取得する + * @param outLayoutMap csmMapクラスのインスタンス + * @return true レイアウト情報が存在する + * @return false レイアウト情報が存在しない + */ + public abstract getLayoutMap(outLayoutMap: csmMap): boolean; + + /** + * 目パチに関連付けられたパラメータの数を取得する + * @return 目パチに関連付けられたパラメータの数 + */ + public abstract getEyeBlinkParameterCount(): number; + + /** + * 目パチに関連付けられたパラメータのIDを取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public abstract getEyeBlinkParameterId(index: number): CubismIdHandle; + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @return リップシンクに関連付けられたパラメータの数 + */ + public abstract getLipSyncParameterCount(): number; + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public abstract getLipSyncParameterId(index: number): CubismIdHandle; + } +} diff --git a/src/id/cubismid.ts b/src/id/cubismid.ts new file mode 100644 index 0000000..5c8c9eb --- /dev/null +++ b/src/id/cubismid.ts @@ -0,0 +1,73 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import csmString = csmstring.csmString; + +export namespace Live2DCubismFramework { + /** + * パラメータ名・パーツ名・Drawable名を保持 + * + * パラメータ名・パーツ名・Drawable名を保持するクラス。 + */ + export class CubismId { + /** + * ID名を取得する + */ + public getString(): csmString { + return this._id; + } + + /** + * コンストラクタ + */ + public constructor(id: string | csmString) { + if (typeof id === 'string') { + this._id = new csmString(id); + return; + } + + this._id = id; + } + + /** + * idを比較 + * @param c 比較するid + * @return 同じならばtrue,異なっていればfalseを返す + */ + public isEqual(c: string | csmString | CubismId): boolean { + if (typeof c === 'string') { + return this._id.isEqual(c); + } else if (c instanceof csmString) { + return this._id.isEqual(c.s); + } else if (c instanceof CubismId) { + return this._id.isEqual(c._id.s); + } + return false; + } + + /** + * idを比較 + * @param c 比較するid + * @return 同じならばtrue,異なっていればfalseを返す + */ + public isNotEqual(c: string | csmString | CubismId): boolean { + if (typeof c == 'string') { + return !this._id.isEqual(c); + } else if (c instanceof csmString) { + return !this._id.isEqual(c.s); + } else if (c instanceof CubismId) { + return !this._id.isEqual(c._id.s); + } + return false; + } + + private _id: csmString; // ID名 + } + + export declare type CubismIdHandle = CubismId; +} diff --git a/src/id/cubismidmanager.ts b/src/id/cubismidmanager.ts new file mode 100644 index 0000000..04d5c66 --- /dev/null +++ b/src/id/cubismidmanager.ts @@ -0,0 +1,118 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as cubismid } from './cubismid'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import csmString = csmstring.csmString; +import CubismId = cubismid.CubismId; +import csmVector = csmvector.csmVector; + +export namespace Live2DCubismFramework { + /** + * ID名の管理 + * + * ID名を管理する。 + */ + export class CubismIdManager { + /** + * コンストラクタ + */ + public constructor() { + this._ids = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._ids.getSize(); ++i) { + this._ids.set(i, void 0); + } + this._ids = null; + } + + /** + * ID名をリストから登録 + * + * @param ids ID名リスト + * @param count IDの個数 + */ + public registerIds(ids: string[] | csmString[]): void { + for (let i = 0; i < ids.length; i++) { + this.registerId(ids[i]); + } + } + + /** + * ID名を登録 + * + * @param id ID名 + */ + public registerId(id: string | csmString): CubismId { + let result: CubismId = null; + + if ('string' == typeof id) { + if ((result = this.findId(id)) != null) { + return result; + } + + result = new CubismId(id); + this._ids.pushBack(result); + } else { + return this.registerId(id.s); + } + + return result; + } + + /** + * ID名からIDを取得する + * + * @param id ID名 + */ + public getId(id: csmString | string): CubismId { + return this.registerId(id); + } + + /** + * ID名からIDの確認 + * + * @return true 存在する + * @return false 存在しない + */ + public isExist(id: csmString | string): boolean { + if ('string' == typeof id) { + return this.findId(id) != null; + } + return this.isExist(id.s); + } + + /** + * ID名からIDを検索する。 + * + * @param id ID名 + * @return 登録されているID。なければNULL。 + */ + private findId(id: string): CubismId { + for (let i = 0; i < this._ids.getSize(); ++i) { + if ( + this._ids + .at(i) + .getString() + .isEqual(id) + ) { + return this._ids.at(i); + } + } + + return null; + } + + private _ids: csmVector; // 登録されているIDのリスト + } +} diff --git a/src/live2dcubismframework.ts b/src/live2dcubismframework.ts new file mode 100644 index 0000000..a7a51b3 --- /dev/null +++ b/src/live2dcubismframework.ts @@ -0,0 +1,272 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismjson } from './utils/cubismjson'; +import { Live2DCubismFramework as cubismidmanager } from './id/cubismidmanager'; +import { Live2DCubismFramework as cubismrenderer } from './rendering/cubismrenderer'; +import { + CubismLogInfo, + CubismLogWarning, + CSM_ASSERT +} from './utils/cubismdebug'; +import Value = cubismjson.Value; +import CubismIdManager = cubismidmanager.CubismIdManager; +import CubismRenderer = cubismrenderer.CubismRenderer; + +export function strtod(s: string, endPtr: string[]): number { + let index = 0; + for (let i = 1; ; i++) { + const testC: string = s.slice(i - 1, i); + + // 指数・マイナスの可能性があるのでスキップする + if (testC == 'e' || testC == '-' || testC == 'E') { + continue; + } // 文字列の範囲を広げていく + + const test: string = s.substring(0, i); + const number = Number(test); + if (isNaN(number)) { + // 数値として認識できなくなったので終了 + break; + } // 最後に数値としてできたindexを格納しておく + + index = i; + } + let d = parseFloat(s); // パースした数値 + + if (isNaN(d)) { + // 数値として認識できなくなったので終了 + d = NaN; + } + + endPtr[0] = s.slice(index); // 後続の文字列 + return d; +} + +export namespace Live2DCubismFramework { + // ファイルスコープの変数を初期化 + + let s_isStarted = false; + let s_isInitialized = false; + let s_option: Option = null; + let s_cubismIdManager: CubismIdManager = null; + + /** + * Framework内で使う定数の宣言 + */ + export namespace Constant { + export const vertexOffset = 0; // メッシュ頂点のオフセット値 + export const vertexStep = 2; // メッシュ頂点のステップ値 + } + + export function csmDelete(address: T): void { + if (!address) { + return; + } + + address = void 0; + } + + /** + * Live2D Cubism SDK Original Workflow SDKのエントリポイント + * 利用開始時はCubismFramework.initialize()を呼び、CubismFramework.dispose()で終了する。 + */ + export class CubismFramework { + /** + * Cubism FrameworkのAPIを使用可能にする。 + * APIを実行する前に必ずこの関数を実行すること。 + * 一度準備が完了して以降は、再び実行しても内部処理がスキップされます。 + * + * @param option Optionクラスのインスタンス + * + * @return 準備処理が完了したらtrueが返ります。 + */ + public static startUp(option: Option = null): boolean { + if (s_isStarted) { + CubismLogInfo('CubismFramework.startUp() is already done.'); + return s_isStarted; + } + + s_option = option; + + if (s_option != null) { + Live2DCubismCore.Logging.csmSetLogFunction(s_option.logFunction); + } + + s_isStarted = true; + + // Live2D Cubism Coreバージョン情報を表示 + if (s_isStarted) { + const version: number = Live2DCubismCore.Version.csmGetVersion(); + const major: number = (version & 0xff000000) >> 24; + const minor: number = (version & 0x00ff0000) >> 16; + const patch: number = version & 0x0000ffff; + const versionNumber: number = version; + + CubismLogInfo( + `Live2D Cubism Core version: {0}.{1}.{2} ({3})`, + ('00' + major).slice(-2), + ('00' + minor).slice(-2), + ('0000' + patch).slice(-4), + versionNumber + ); + } + + CubismLogInfo('CubismFramework.startUp() is complete.'); + + return s_isStarted; + } + + /** + * StartUp()で初期化したCubismFrameworkの各パラメータをクリアします。 + * Dispose()したCubismFrameworkを再利用する際に利用してください。 + */ + public static cleanUp(): void { + s_isStarted = false; + s_isInitialized = false; + s_option = null; + s_cubismIdManager = null; + } + + /** + * Cubism Framework内のリソースを初期化してモデルを表示可能な状態にします。
+ * 再度Initialize()するには先にDispose()を実行する必要があります。 + */ + public static initialize(): void { + CSM_ASSERT(s_isStarted); + if (!s_isStarted) { + CubismLogWarning('CubismFramework is not started.'); + return; + } + + // --- s_isInitializedによる連続初期化ガード --- + // 連続してリソース確保が行われないようにする。 + // 再度Initialize()するには先にDispose()を実行する必要がある。 + if (s_isInitialized) { + CubismLogWarning( + 'CubismFramework.initialize() skipped, already initialized.' + ); + return; + } + + //---- static 初期化 ---- + Value.staticInitializeNotForClientCall(); + + s_cubismIdManager = new CubismIdManager(); + + s_isInitialized = true; + + CubismLogInfo('CubismFramework.initialize() is complete.'); + } + + /** + * Cubism Framework内の全てのリソースを解放します。 + * ただし、外部で確保されたリソースについては解放しません。 + * 外部で適切に破棄する必要があります。 + */ + public static dispose(): void { + CSM_ASSERT(s_isStarted); + if (!s_isStarted) { + CubismLogWarning('CubismFramework is not started.'); + return; + } + + // --- s_isInitializedによる未初期化解放ガード --- + // dispose()するには先にinitialize()を実行する必要がある。 + if (!s_isInitialized) { + // false...リソース未確保の場合 + CubismLogWarning('CubismFramework.dispose() skipped, not initialized.'); + return; + } + + Value.staticReleaseNotForClientCall(); + + s_cubismIdManager.release(); + s_cubismIdManager = null; + + // レンダラの静的リソース(シェーダプログラム他)を解放する + CubismRenderer.staticRelease(); + + s_isInitialized = false; + + CubismLogInfo('CubismFramework.dispose() is complete.'); + } + + /** + * Cubism FrameworkのAPIを使用する準備が完了したかどうか + * @return APIを使用する準備が完了していればtrueが返ります。 + */ + public static isStarted(): boolean { + return s_isStarted; + } + + /** + * Cubism Frameworkのリソース初期化がすでに行われているかどうか + * @return リソース確保が完了していればtrueが返ります + */ + public static isInitialized(): boolean { + return s_isInitialized; + } + + /** + * Core APIにバインドしたログ関数を実行する + * + * @praram message ログメッセージ + */ + public static coreLogFunction(message: string): void { + // Return if logging not possible. + if (!Live2DCubismCore.Logging.csmGetLogFunction()) { + return; + } + + Live2DCubismCore.Logging.csmGetLogFunction()(message); + } + + /** + * 現在のログ出力レベル設定の値を返す。 + * + * @return 現在のログ出力レベル設定の値 + */ + public static getLoggingLevel(): LogLevel { + if (s_option != null) { + return s_option.loggingLevel; + } + return LogLevel.LogLevel_Off; + } + + /** + * IDマネージャのインスタンスを取得する + * @return CubismManagerクラスのインスタンス + */ + public static getIdManager(): CubismIdManager { + return s_cubismIdManager; + } + + /** + * 静的クラスとして使用する + * インスタンス化させない + */ + private constructor() {} + } +} + +export class Option { + logFunction: Live2DCubismCore.csmLogFunction; // ログ出力の関数オブジェクト + loggingLevel: LogLevel; // ログ出力レベルの設定 +} + +/** + * ログ出力のレベル + */ +export enum LogLevel { + LogLevel_Verbose = 0, // 詳細ログ + LogLevel_Debug, // デバッグログ + LogLevel_Info, // Infoログ + LogLevel_Warning, // 警告ログ + LogLevel_Error, // エラーログ + LogLevel_Off // ログ出力無効 +} diff --git a/src/math/cubismmath.ts b/src/math/cubismmath.ts new file mode 100644 index 0000000..db806bd --- /dev/null +++ b/src/math/cubismmath.ts @@ -0,0 +1,195 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismvector2 } from './cubismvector2'; +import CubismVector2 = cubismvector2.CubismVector2; + +export namespace Live2DCubismFramework { + /** + * 数値計算などに使用するユーティリティクラス + */ + export class CubismMath { + /** + * 第一引数の値を最小値と最大値の範囲に収めた値を返す + * + * @param value 収められる値 + * @param min 範囲の最小値 + * @param max 範囲の最大値 + * @return 最小値と最大値の範囲に収めた値 + */ + static range(value: number, min: number, max: number): number { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + + return value; + } + + /** + * サイン関数の値を求める + * + * @param x 角度値(ラジアン) + * @return サイン関数sin(x)の値 + */ + static sin(x: number): number { + return Math.sin(x); + } + + /** + * コサイン関数の値を求める + * + * @param x 角度値(ラジアン) + * @return コサイン関数cos(x)の値 + */ + static cos(x: number): number { + return Math.cos(x); + } + + /** + * 値の絶対値を求める + * + * @param x 絶対値を求める値 + * @return 値の絶対値 + */ + static abs(x: number): number { + return Math.abs(x); + } + + /** + * 平方根(ルート)を求める + * @param x -> 平方根を求める値 + * @return 値の平方根 + */ + static sqrt(x: number): number { + return Math.sqrt(x); + } + + /** + * イージング処理されたサインを求める + * フェードイン・アウト時のイージングに利用できる + * + * @param value イージングを行う値 + * @return イージング処理されたサイン値 + */ + static getEasingSine(value: number): number { + if (value < 0.0) { + return 0.0; + } else if (value > 1.0) { + return 1.0; + } + + return 0.5 - 0.5 * this.cos(value * Math.PI); + } + + /** + * 大きい方の値を返す + * + * @param left 左辺の値 + * @param right 右辺の値 + * @return 大きい方の値 + */ + static max(left: number, right: number): number { + return left > right ? left : right; + } + + /** + * 小さい方の値を返す + * + * @param left 左辺の値 + * @param right 右辺の値 + * @return 小さい方の値 + */ + static min(left: number, right: number): number { + return left > right ? right : left; + } + + /** + * 角度値をラジアン値に変換する + * + * @param degrees 角度値 + * @return 角度値から変換したラジアン値 + */ + static degreesToRadian(degrees: number): number { + return (degrees / 180.0) * Math.PI; + } + + /** + * ラジアン値を角度値に変換する + * + * @param radian ラジアン値 + * @return ラジアン値から変換した角度値 + */ + static radianToDegrees(radian: number): number { + return (radian * 180.0) / Math.PI; + } + + /** + * 2つのベクトルからラジアン値を求める + * + * @param from 始点ベクトル + * @param to 終点ベクトル + * @return ラジアン値から求めた方向ベクトル + */ + static directionToRadian(from: CubismVector2, to: CubismVector2): number { + const q1: number = Math.atan2(to.y, to.x); + const q2: number = Math.atan2(from.y, from.x); + + let ret: number = q1 - q2; + + while (ret < -Math.PI) { + ret += Math.PI * 2.0; + } + + while (ret > Math.PI) { + ret -= Math.PI * 2.0; + } + + return ret; + } + + /** + * 2つのベクトルから角度値を求める + * + * @param from 始点ベクトル + * @param to 終点ベクトル + * @return 角度値から求めた方向ベクトル + */ + static directionToDegrees(from: CubismVector2, to: CubismVector2): number { + const radian: number = this.directionToRadian(from, to); + let degree: number = this.radianToDegrees(radian); + + if (to.x - from.x > 0.0) { + degree = -degree; + } + + return degree; + } + + /** + * ラジアン値を方向ベクトルに変換する。 + * + * @param totalAngle ラジアン値 + * @return ラジアン値から変換した方向ベクトル + */ + + static radianToDirection(totalAngle: number): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(); + + ret.x = this.sin(totalAngle); + ret.y = this.cos(totalAngle); + + return ret; + } + + /** + * コンストラクタ + */ + private constructor() {} + } +} diff --git a/src/math/cubismmatrix44.ts b/src/math/cubismmatrix44.ts new file mode 100644 index 0000000..fe01733 --- /dev/null +++ b/src/math/cubismmatrix44.ts @@ -0,0 +1,308 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + /** + * 4x4の行列 + * + * 4x4行列の便利クラス。 + */ + export class CubismMatrix44 { + /** + * コンストラクタ + */ + public constructor() { + this._tr = new Float32Array(16); // 4 * 4のサイズ + this.loadIdentity(); + } + + /** + * 受け取った2つの行列の乗算を行う。 + * + * @param a 行列a + * @param b 行列b + * @return 乗算結果の行列 + */ + public static multiply( + a: Float32Array, + b: Float32Array, + dst: Float32Array + ): void { + const c: Float32Array = new Float32Array([ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ]); + + const n = 4; + + for (let i = 0; i < n; ++i) { + for (let j = 0; j < n; ++j) { + for (let k = 0; k < n; ++k) { + c[j + i * 4] += a[k + i * 4] * b[j + k * 4]; + } + } + } + + for (let i = 0; i < 16; ++i) { + dst[i] = c[i]; + } + } + + /** + * 単位行列に初期化する + */ + public loadIdentity(): void { + const c: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + this.setMatrix(c); + } + + /** + * 行列を設定 + * + * @param tr 16個の浮動小数点数で表される4x4の行列 + */ + public setMatrix(tr: Float32Array): void { + for (let i = 0; i < 16; ++i) { + this._tr[i] = tr[i]; + } + } + + /** + * 行列を浮動小数点数の配列で取得 + * + * @return 16個の浮動小数点数で表される4x4の行列 + */ + public getArray(): Float32Array { + return this._tr; + } + + /** + * X軸の拡大率を取得 + * @return X軸の拡大率 + */ + public getScaleX(): number { + return this._tr[0]; + } + + /** + * Y軸の拡大率を取得する + * + * @return Y軸の拡大率 + */ + public getScaleY(): number { + return this._tr[5]; + } + + /** + * X軸の移動量を取得 + * @return X軸の移動量 + */ + public getTranslateX(): number { + return this._tr[12]; + } + + /** + * Y軸の移動量を取得 + * @return Y軸の移動量 + */ + public getTranslateY(): number { + return this._tr[13]; + } + + /** + * X軸の値を現在の行列で計算 + * + * @param src X軸の値 + * @return 現在の行列で計算されたX軸の値 + */ + public transformX(src: number): number { + return this._tr[0] * src + this._tr[12]; + } + + /** + * Y軸の値を現在の行列で計算 + * + * @param src Y軸の値 + * @return 現在の行列で計算されたY軸の値 + */ + public transformY(src: number): number { + return this._tr[5] * src + this._tr[13]; + } + + /** + * X軸の値を現在の行列で逆計算 + */ + public invertTransformX(src: number): number { + return (src - this._tr[12]) / this._tr[0]; + } + + /** + * Y軸の値を現在の行列で逆計算 + */ + public invertTransformY(src: number): number { + return (src - this._tr[13]) / this._tr[5]; + } + + /** + * 現在の行列の位置を起点にして移動 + * + * 現在の行列の位置を起点にして相対的に移動する。 + * + * @param x X軸の移動量 + * @param y Y軸の移動量 + */ + public translateRelative(x: number, y: number): void { + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + x, + y, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 現在の行列の位置を移動 + * + * 現在の行列の位置を指定した位置へ移動する + * + * @param x X軸の移動量 + * @param y y軸の移動量 + */ + public translate(x: number, y: number): void { + this._tr[12] = x; + this._tr[13] = y; + } + + /** + * 現在の行列のX軸の位置を指定した位置へ移動する + * + * @param x X軸の移動量 + */ + public translateX(x: number): void { + this._tr[12] = x; + } + + /** + * 現在の行列のY軸の位置を指定した位置へ移動する + * + * @param y Y軸の移動量 + */ + public translateY(y: number): void { + this._tr[13] = y; + } + + /** + * 現在の行列の拡大率を相対的に設定する + * + * @param x X軸の拡大率 + * @param y Y軸の拡大率 + */ + public scaleRelative(x: number, y: number): void { + const tr1: Float32Array = new Float32Array([ + x, + 0.0, + 0.0, + 0.0, + 0.0, + y, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 現在の行列の拡大率を指定した倍率に設定する + * + * @param x X軸の拡大率 + * @param y Y軸の拡大率 + */ + public scale(x: number, y: number): void { + this._tr[0] = x; + this._tr[5] = y; + } + + /** + * 現在の行列に行列を乗算 + * + * @param m 行列 + */ + public multiplyByMatrix(m: CubismMatrix44): void { + CubismMatrix44.multiply(m.getArray(), this._tr, this._tr); + } + + /** + * オブジェクトのコピーを生成する + */ + public clone(): CubismMatrix44 { + const cloneMatrix: CubismMatrix44 = new CubismMatrix44(); + + for (let i = 0; i < this._tr.length; i++) { + cloneMatrix._tr[i] = this._tr[i]; + } + + return cloneMatrix; + } + + protected _tr: Float32Array; // 4x4行列データ + } +} diff --git a/src/math/cubismmodelmatrix.ts b/src/math/cubismmodelmatrix.ts new file mode 100644 index 0000000..e654381 --- /dev/null +++ b/src/math/cubismmodelmatrix.ts @@ -0,0 +1,223 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as csmmap } from '../type/csmmap'; +import { Live2DCubismFramework as cubismmatrix44 } from './cubismmatrix44'; +import csmMap = csmmap.csmMap; +import iterator = csmmap.iterator; +import CubismMatrix44 = cubismmatrix44.CubismMatrix44; + +export namespace Live2DCubismFramework { + /** + * モデル座標設定用の4x4行列 + * + * モデル座標設定用の4x4行列クラス + */ + export class CubismModelMatrix extends CubismMatrix44 { + /** + * コンストラクタ + * + * @param w 横幅 + * @param h 縦幅 + */ + constructor(w?: number, h?: number) { + super(); + + this._width = w !== undefined ? w : 0.0; + this._height = h !== undefined ? h : 0.0; + + this.setHeight(1.0); + } + + /** + * 横幅を設定 + * + * @param w 横幅 + */ + public setWidth(w: number): void { + const scaleX: number = w / this._width; + const scaleY: number = scaleX; + this.scale(scaleX, scaleY); + } + + /** + * 縦幅を設定 + * @param h 縦幅 + */ + public setHeight(h: number): void { + const scaleX: number = h / this._height; + const scaleY: number = scaleX; + this.scale(scaleX, scaleY); + } + + /** + * 位置を設定 + * + * @param x X軸の位置 + * @param y Y軸の位置 + */ + public setPosition(x: number, y: number): void { + this.translate(x, y); + } + + /** + * 中心位置を設定 + * + * @param x X軸の中心位置 + * @param y Y軸の中心位置 + * + * @note widthかheightを設定したあとでないと、拡大率が正しく取得できないためずれる。 + */ + public setCenterPosition(x: number, y: number) { + this.centerX(x); + this.centerY(y); + } + + /** + * 上辺の位置を設定する + * + * @param y 上辺のY軸位置 + */ + public top(y: number): void { + this.setY(y); + } + + /** + * 下辺の位置を設定する + * + * @param y 下辺のY軸位置 + */ + public bottom(y: number) { + const h: number = this._height * this.getScaleY(); + + this.translateY(y - h); + } + + /** + * 左辺の位置を設定 + * + * @param x 左辺のX軸位置 + */ + public left(x: number): void { + this.setX(x); + } + + /** + * 右辺の位置を設定 + * + * @param x 右辺のX軸位置 + */ + public right(x: number): void { + const w = this._width * this.getScaleX(); + + this.translateX(x - w); + } + + /** + * X軸の中心位置を設定 + * + * @param x X軸の中心位置 + */ + public centerX(x: number): void { + const w = this._width * this.getScaleX(); + + this.translateX(x - w / 2.0); + } + + /** + * X軸の位置を設定 + * + * @param x X軸の位置 + */ + public setX(x: number): void { + this.translateX(x); + } + + /** + * Y軸の中心位置を設定 + * + * @param y Y軸の中心位置 + */ + public centerY(y: number): void { + const h: number = this._height * this.getScaleY(); + + this.translateY(y - h / 2.0); + } + + /** + * Y軸の位置を設定する + * + * @param y Y軸の位置 + */ + public setY(y: number): void { + this.translateY(y); + } + + /** + * レイアウト情報から位置を設定 + * + * @param layout レイアウト情報 + */ + public setupFromLayout(layout: csmMap): void { + const keyWidth = 'width'; + const keyHeight = 'height'; + const keyX = 'x'; + const keyY = 'y'; + const keyCenterX = 'center_x'; + const keyCenterY = 'center_y'; + const keyTop = 'top'; + const keyBottom = 'bottom'; + const keyLeft = 'left'; + const keyRight = 'right'; + + for ( + const ite: iterator = layout.begin(); + ite.notEqual(layout.end()); + ite.preIncrement() + ) { + const key: string = ite.ptr().first; + const value: number = ite.ptr().second; + + if (key == keyWidth) { + this.setWidth(value); + } else if (key == keyHeight) { + this.setHeight(value); + } + } + + for ( + const ite: iterator = layout.begin(); + ite.notEqual(layout.end()); + ite.preIncrement() + ) { + const key: string = ite.ptr().first; + const value: number = ite.ptr().second; + + if (key == keyX) { + this.setX(value); + } else if (key == keyY) { + this.setY(value); + } else if (key == keyCenterX) { + this.centerX(value); + } else if (key == keyCenterY) { + this.centerY(value); + } else if (key == keyTop) { + this.top(value); + } else if (key == keyBottom) { + this.bottom(value); + } else if (key == keyLeft) { + this.left(value); + } else if (key == keyRight) { + this.right(value); + } + } + } + + private _width: number; // 横幅 + private _height: number; // 縦幅 + } +} diff --git a/src/math/cubismtargetpoint.ts b/src/math/cubismtargetpoint.ts new file mode 100644 index 0000000..52b6c16 --- /dev/null +++ b/src/math/cubismtargetpoint.ts @@ -0,0 +1,164 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmath } from './cubismmath'; +import CubismMath = cubismmath.CubismMath; + +export namespace Live2DCubismFramework { + const FrameRate = 30; + const Epsilon = 0.01; + + /** + * 顔の向きの制御機能 + * + * 顔の向きの制御機能を提供するクラス。 + */ + export class CubismTargetPoint { + /** + * コンストラクタ + */ + public constructor() { + this._faceTargetX = 0.0; + this._faceTargetY = 0.0; + this._faceX = 0.0; + this._faceY = 0.0; + this._faceVX = 0.0; + this._faceVY = 0.0; + this._lastTimeSeconds = 0.0; + this._userTimeSeconds = 0.0; + } + + /** + * 更新処理 + */ + public update(deltaTimeSeconds: number): void { + // デルタ時間を加算する + this._userTimeSeconds += deltaTimeSeconds; + + // 首を中央から左右に振るときの平均的な速さは 秒速度。加速・減速を考慮して、その2倍を最高速度とする + // 顔の振り具合を、中央(0.0)から、左右は(+-1.0)とする + const faceParamMaxV: number = 40.0 / 10.0; // 7.5秒間に40分移動(5.3/sc) + const maxV: number = (faceParamMaxV * 1.0) / FrameRate; // 1frameあたりに変化できる速度の上限 + + if (this._lastTimeSeconds == 0.0) { + this._lastTimeSeconds = this._userTimeSeconds; + return; + } + + const deltaTimeWeight: number = + (this._userTimeSeconds - this._lastTimeSeconds) * FrameRate; + this._lastTimeSeconds = this._userTimeSeconds; + + // 最高速度になるまでの時間を + const timeToMaxSpeed = 0.15; + const frameToMaxSpeed: number = timeToMaxSpeed * FrameRate; // sec * frame/sec + const maxA: number = (deltaTimeWeight * maxV) / frameToMaxSpeed; // 1frameあたりの加速度 + + // 目指す向きは、(dx, dy)方向のベクトルとなる + const dx: number = this._faceTargetX - this._faceX; + const dy: number = this._faceTargetY - this._faceY; + + if (CubismMath.abs(dx) <= Epsilon && CubismMath.abs(dy) <= Epsilon) { + return; // 変化なし + } + + // 速度の最大よりも大きい場合は、速度を落とす + const d: number = CubismMath.sqrt(dx * dx + dy * dy); + + // 進行方向の最大速度ベクトル + const vx: number = (maxV * dx) / d; + const vy: number = (maxV * dy) / d; + + // 現在の速度から、新規速度への変化(加速度)を求める + let ax: number = vx - this._faceVX; + let ay: number = vy - this._faceVY; + + const a: number = CubismMath.sqrt(ax * ax + ay * ay); + + // 加速のとき + if (a < -maxA || a > maxA) { + ax *= maxA / a; + ay *= maxA / a; + } + + // 加速度を元の速度に足して、新速度とする + this._faceVX += ax; + this._faceVY += ay; + + // 目的の方向に近づいたとき、滑らかに減速するための処理 + // 設定された加速度で止まる事の出来る距離と速度の関係から + // 現在とりうる最高速度を計算し、それ以上の時は速度を落とす + // ※本来、人間は筋力で力(加速度)を調整できるため、より自由度が高いが、簡単な処理で済ませている + { + // 加速度、速度、距離の関係式。 + // 2 6 2 3 + // sqrt(a t + 16 a h t - 8 a h) - a t + // v = -------------------------------------- + // 2 + // 4 t - 2 + // (t=1) + // 時刻tは、あらかじめ加速度、速度を1/60(フレームレート、単位なし)で + // 考えているので、t=1として消してよい(※未検証) + + const maxV: number = + 0.5 * + (CubismMath.sqrt(maxA * maxA + 16.0 * maxA * d - 8.0 * maxA * d) - + maxA); + const curV: number = CubismMath.sqrt( + this._faceVX * this._faceVX + this._faceVY * this._faceVY + ); + + if (curV > maxV) { + // 現在の速度 > 最高速度のとき、最高速度まで減速 + this._faceVX *= maxV / curV; + this._faceVY *= maxV / curV; + } + } + + this._faceX += this._faceVX; + this._faceY += this._faceVY; + } + + /** + * X軸の顔の向きの値を取得 + * + * @return X軸の顔の向きの値(-1.0 ~ 1.0) + */ + public getX(): number { + return this._faceX; + } + + /** + * Y軸の顔の向きの値を取得 + * + * @return Y軸の顔の向きの値(-1.0 ~ 1.0) + */ + public getY(): number { + return this._faceY; + } + + /** + * 顔の向きの目標値を設定 + * + * @param x X軸の顔の向きの値(-1.0 ~ 1.0) + * @param y Y軸の顔の向きの値(-1.0 ~ 1.0) + */ + public set(x: number, y: number): void { + this._faceTargetX = x; + this._faceTargetY = y; + } + + private _faceTargetX: number; // 顔の向きのX目標値(この値に近づいていく) + private _faceTargetY: number; // 顔の向きのY目標値(この値に近づいていく) + private _faceX: number; // 顔の向きX(-1.0 ~ 1.0) + private _faceY: number; // 顔の向きY(-1.0 ~ 1.0) + private _faceVX: number; // 顔の向きの変化速度X + private _faceVY: number; // 顔の向きの変化速度Y + private _lastTimeSeconds: number; // 最後の実行時間[秒] + private _userTimeSeconds: number; // デルタ時間の積算値[秒] + } +} diff --git a/src/math/cubismvector2.ts b/src/math/cubismvector2.ts new file mode 100644 index 0000000..b85c45a --- /dev/null +++ b/src/math/cubismvector2.ts @@ -0,0 +1,163 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + /** + * 2次元ベクトル型 + * + * 2次元ベクトル型の機能を提供する。 + */ + export class CubismVector2 { + /** + * コンストラクタ + */ + public constructor(public x?: number, public y?: number) { + this.x = x == undefined ? 0.0 : x; + + this.y = y == undefined ? 0.0 : y; + } + + /** + * ベクトルの加算 + * + * @param vector2 加算するベクトル値 + * @return 加算結果 ベクトル値 + */ + public add(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x + vector2.x; + ret.y = this.y + vector2.y; + return ret; + } + + /** + * ベクトルの減算 + * + * @param vector2 減算するベクトル値 + * @return 減算結果 ベクトル値 + */ + public substract(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x - vector2.x; + ret.y = this.y - vector2.y; + return ret; + } + + /** + * ベクトルの乗算 + * + * @param vector2 乗算するベクトル値 + * @return 乗算結果 ベクトル値 + */ + public multiply(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x * vector2.x; + ret.y = this.y * vector2.y; + return ret; + } + + /** + * ベクトルの乗算(スカラー) + * + * @param scalar 乗算するスカラー値 + * @return 乗算結果 ベクトル値 + */ + public multiplyByScaler(scalar: number): CubismVector2 { + return this.multiply(new CubismVector2(scalar, scalar)); + } + + /** + * ベクトルの除算 + * + * @param vector2 除算するベクトル値 + * @return 除算結果 ベクトル値 + */ + public division(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x / vector2.x; + ret.y = this.y / vector2.y; + return ret; + } + + /** + * ベクトルの除算(スカラー) + * + * @param scalar 除算するスカラー値 + * @return 除算結果 ベクトル値 + */ + public divisionByScalar(scalar: number): CubismVector2 { + return this.division(new CubismVector2(scalar, scalar)); + } + + /** + * ベクトルの長さを取得する + * + * @return ベクトルの長さ + */ + public getLength(): number { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + /** + * ベクトルの距離の取得 + * + * @param a 点 + * @return ベクトルの距離 + */ + public getDistanceWith(a: CubismVector2): number { + return Math.sqrt( + (this.x - a.x) * (this.x - a.x) + (this.y - a.y) * (this.y - a.y) + ); + } + + /** + * ドット積の計算 + * + * @param a 値 + * @return 結果 + */ + public dot(a: CubismVector2): number { + return this.x * a.x + this.y * a.y; + } + + /** + * 正規化の適用 + */ + public normalize(): void { + const length: number = Math.pow(this.x * this.x + this.y * this.y, 0.5); + + this.x = this.x / length; + this.y = this.y / length; + } + + /** + * 等しさの確認(等しいか?) + * + * 値が等しいか? + * + * @param rhs 確認する値 + * @return true 値は等しい + * @return false 値は等しくない + */ + public isEqual(rhs: CubismVector2): boolean { + return this.x == rhs.x && this.y == rhs.y; + } + + /** + * 等しさの確認(等しくないか?) + * + * 値が等しくないか? + * + * @param rhs 確認する値 + * @return true 値は等しくない + * @return false 値は等しい + */ + public isNotEqual(rhs: CubismVector2): boolean { + return !this.isEqual(rhs); + } + } +} diff --git a/src/math/cubismviewmatrix.ts b/src/math/cubismviewmatrix.ts new file mode 100644 index 0000000..fc23a41 --- /dev/null +++ b/src/math/cubismviewmatrix.ts @@ -0,0 +1,337 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmatrix44 } from './cubismmatrix44'; +import CubismMatrix44 = cubismmatrix44.CubismMatrix44; + +export namespace Live2DCubismFramework { + /** + * カメラの位置変更に使うと便利な4x4行列 + * + * カメラの位置変更に使うと便利な4x4行列のクラス。 + */ + export class CubismViewMatrix extends CubismMatrix44 { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._screenLeft = 0.0; + this._screenRight = 0.0; + this._screenTop = 0.0; + this._screenBottom = 0.0; + this._maxLeft = 0.0; + this._maxRight = 0.0; + this._maxTop = 0.0; + this._maxBottom = 0.0; + this._maxScale = 0.0; + this._minScale = 0.0; + } + + /** + * 移動を調整 + * + * @param x X軸の移動量 + * @param y Y軸の移動量 + */ + public adjustTranslate(x: number, y: number): void { + if (this._tr[0] * this._maxLeft + (this._tr[12] + x) > this._screenLeft) { + x = this._screenLeft - this._tr[0] * this._maxLeft - this._tr[12]; + } + + if ( + this._tr[0] * this._maxRight + (this._tr[12] + x) < + this._screenRight + ) { + x = this._screenRight - this._tr[0] * this._maxRight - this._tr[12]; + } + + if (this._tr[5] * this._maxTop + (this._tr[13] + y) < this._screenTop) { + y = this._screenTop - this._tr[5] * this._maxTop - this._tr[13]; + } + + if ( + this._tr[5] * this._maxBottom + (this._tr[13] + y) > + this._screenBottom + ) { + y = this._screenBottom - this._tr[5] * this._maxBottom - this._tr[13]; + } + + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + x, + y, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 拡大率を調整 + * + * @param cx 拡大を行うX軸の中心位置 + * @param cy 拡大を行うY軸の中心位置 + * @param scale 拡大率 + */ + public adjustScale(cx: number, cy: number, scale: number): void { + const maxScale: number = this.getMaxScale(); + const minScale: number = this.getMinScale(); + + const targetScale = scale * this._tr[0]; + + if (targetScale < minScale) { + if (this._tr[0] > 0.0) { + scale = minScale / this._tr[0]; + } + } else if (targetScale > maxScale) { + if (this._tr[0] > 0.0) { + scale = maxScale / this._tr[0]; + } + } + + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + cx, + cy, + 0.0, + 1.0 + ]); + + const tr2: Float32Array = new Float32Array([ + scale, + 0.0, + 0.0, + 0.0, + 0.0, + scale, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + const tr3: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -cx, + -cy, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr3, this._tr, this._tr); + CubismMatrix44.multiply(tr2, this._tr, this._tr); + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * デバイスに対応する論理座養生の範囲の設定 + * + * @param left 左辺のX軸の位置 + * @param right 右辺のX軸の位置 + * @param bottom 下辺のY軸の位置 + * @param top 上辺のY軸の位置 + */ + public setScreenRect( + left: number, + right: number, + bottom: number, + top: number + ): void { + this._screenLeft = left; + this._screenRight = right; + this._screenBottom = bottom; + this._screenTop = top; + } + + /** + * デバイスに対応する論理座標上の移動可能範囲の設定 + * @param left 左辺のX軸の位置 + * @param right 右辺のX軸の位置 + * @param bottom 下辺のY軸の位置 + * @param top 上辺のY軸の位置 + */ + public setMaxScreenRect( + left: number, + right: number, + bottom: number, + top: number + ): void { + this._maxLeft = left; + this._maxRight = right; + this._maxTop = top; + this._maxBottom = bottom; + } + + /** + * 最大拡大率の設定 + * @param maxScale 最大拡大率 + */ + public setMaxScale(maxScale: number): void { + this._maxScale = maxScale; + } + + /** + * 最小拡大率の設定 + * @param minScale 最小拡大率 + */ + public setMinScale(minScale: number): void { + this._minScale = minScale; + } + + /** + * 最大拡大率の取得 + * @return 最大拡大率 + */ + public getMaxScale(): number { + return this._maxScale; + } + + /** + * 最小拡大率の取得 + * @return 最小拡大率 + */ + public getMinScale(): number { + return this._minScale; + } + + /** + * 拡大率が最大になっているかを確認する + * + * @return true 拡大率は最大 + * @return false 拡大率は最大ではない + */ + public isMaxScale(): boolean { + return this.getScaleX() >= this._maxScale; + } + + /** + * 拡大率が最小になっているかを確認する + * + * @return true 拡大率は最小 + * @return false 拡大率は最小ではない + */ + public isMinScale(): boolean { + return this.getScaleX() <= this._minScale; + } + + /** + * デバイスに対応する論理座標の左辺のX軸位置を取得する + * @return デバイスに対応する論理座標の左辺のX軸位置 + */ + public getScreenLeft(): number { + return this._screenLeft; + } + + /** + * デバイスに対応する論理座標の右辺のX軸位置を取得する + * @return デバイスに対応する論理座標の右辺のX軸位置 + */ + public getScreenRight(): number { + return this._screenRight; + } + + /** + * デバイスに対応する論理座標の下辺のY軸位置を取得する + * @return デバイスに対応する論理座標の下辺のY軸位置 + */ + public getScreenBottom(): number { + return this._screenBottom; + } + + /** + * デバイスに対応する論理座標の上辺のY軸位置を取得する + * @return デバイスに対応する論理座標の上辺のY軸位置 + */ + public getScreenTop(): number { + return this._screenTop; + } + + /** + * 左辺のX軸位置の最大値の取得 + * @return 左辺のX軸位置の最大値 + */ + public getMaxLeft(): number { + return this._maxLeft; + } + + /** + * 右辺のX軸位置の最大値の取得 + * @return 右辺のX軸位置の最大値 + */ + public getMaxRight(): number { + return this._maxRight; + } + + /** + * 下辺のY軸位置の最大値の取得 + * @return 下辺のY軸位置の最大値 + */ + public getMaxBottom(): number { + return this._maxBottom; + } + + /** + * 上辺のY軸位置の最大値の取得 + * @return 上辺のY軸位置の最大値 + */ + public getMaxTop(): number { + return this._maxTop; + } + + private _screenLeft: number; // デバイスに対応する論理座標上の範囲(左辺X軸位置) + private _screenRight: number; // デバイスに対応する論理座標上の範囲(右辺X軸位置) + private _screenTop: number; // デバイスに対応する論理座標上の範囲(上辺Y軸位置) + private _screenBottom: number; // デバイスに対応する論理座標上の範囲(下辺Y軸位置) + private _maxLeft: number; // 論理座標上の移動可能範囲(左辺X軸位置) + private _maxRight: number; // 論理座標上の移動可能範囲(右辺X軸位置) + private _maxTop: number; // 論理座標上の移動可能範囲(上辺Y軸位置) + private _maxBottom: number; // 論理座標上の移動可能範囲(下辺Y軸位置) + private _maxScale: number; // 拡大率の最大値 + private _minScale: number; // 拡大率の最小値 + } +} diff --git a/src/model/cubismmoc.ts b/src/model/cubismmoc.ts new file mode 100644 index 0000000..4733495 --- /dev/null +++ b/src/model/cubismmoc.ts @@ -0,0 +1,100 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmodel } from './cubismmodel'; +import CubismModel = cubismmodel.CubismModel; +import { CSM_ASSERT } from '../utils/cubismdebug'; + +export namespace Live2DCubismFramework { + /** + * Mocデータの管理 + * + * Mocデータの管理を行うクラス。 + */ + export class CubismMoc { + /** + * Mocデータの作成 + */ + public static create(mocBytes: ArrayBuffer): CubismMoc { + let cubismMoc: CubismMoc = null; + const moc: Live2DCubismCore.Moc = Live2DCubismCore.Moc.fromArrayBuffer( + mocBytes + ); + + if (moc) { + cubismMoc = new CubismMoc(moc); + } + + return cubismMoc; + } + + /** + * Mocデータを削除 + * + * Mocデータを削除する + */ + public static delete(moc: CubismMoc): void { + moc._moc._release(); + moc._moc = null; + moc = null; + } + + /** + * モデルを作成する + * + * @return Mocデータから作成されたモデル + */ + createModel(): CubismModel { + let cubismModel: CubismModel = null; + + const model: Live2DCubismCore.Model = Live2DCubismCore.Model.fromMoc( + this._moc + ); + + if (model) { + cubismModel = new CubismModel(model); + cubismModel.initialize(); + + ++this._modelCount; + } + + return cubismModel; + } + + /** + * モデルを削除する + */ + deleteModel(model: CubismModel): void { + if (model != null) { + model.release(); + model = null; + --this._modelCount; + } + } + + /** + * コンストラクタ + */ + private constructor(moc: Live2DCubismCore.Moc) { + this._moc = moc; + this._modelCount = 0; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CSM_ASSERT(this._modelCount == 0); + + this._moc._release(); + this._moc = null; + } + + _moc: Live2DCubismCore.Moc; // Mocデータ + _modelCount: number; // Mocデータから作られたモデルの個数 + } +} diff --git a/src/model/cubismmodel.ts b/src/model/cubismmodel.ts new file mode 100644 index 0000000..f520c19 --- /dev/null +++ b/src/model/cubismmodel.ts @@ -0,0 +1,822 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismrenderer } from '../rendering/cubismrenderer'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as csmmap } from '../type/csmmap'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { CSM_ASSERT } from '../utils/cubismdebug'; +import CubismFramework = cubismframework.CubismFramework; +import CubismBlendMode = cubismrenderer.CubismBlendMode; +import csmVector = csmvector.csmVector; +import csmMap = csmmap.csmMap; +import CubismIdHandle = cubismid.CubismIdHandle; + +export namespace Live2DCubismFramework { + /** + * モデル + * + * Mocデータから生成されるモデルのクラス。 + */ + export class CubismModel { + /** + * モデルのパラメータの更新 + */ + public update(): void { + // Update model + this._model.update(); + + this._model.drawables.resetDynamicFlags(); + } + + /** + * キャンバスの幅を取得する + */ + public getCanvasWidth(): number { + if (this._model == null) { + return 0.0; + } + + return ( + this._model.canvasinfo.CanvasWidth / + this._model.canvasinfo.PixelsPerUnit + ); + } + + /** + * キャンバスの高さを取得する + */ + public getCanvasHeight(): number { + if (this._model == null) { + return 0.0; + } + + return ( + this._model.canvasinfo.CanvasHeight / + this._model.canvasinfo.PixelsPerUnit + ); + } + + /** + * パラメータを保存する + */ + public saveParameters(): void { + const parameterCount: number = this._model.parameters.count; + const savedParameterCount: number = this._savedParameters.getSize(); + + for (let i = 0; i < parameterCount; ++i) { + if (i < savedParameterCount) { + this._savedParameters.set(i, this._parameterValues[i]); + } else { + this._savedParameters.pushBack(this._parameterValues[i]); + } + } + } + + /** + * モデルを取得 + */ + public getModel(): Live2DCubismCore.Model { + return this._model; + } + + /** + * パーツのインデックスを取得 + * @param partId パーツのID + * @return パーツのインデックス + */ + public getPartIndex(partId: CubismIdHandle): number { + let partIndex: number; + const partCount: number = this._model.parts.count; + + for (partIndex = 0; partIndex < partCount; ++partIndex) { + if (partId == this._partIds.at(partIndex)) { + return partIndex; + } + } + + // モデルに存在していない場合、非存在パーツIDリスト内にあるかを検索し、そのインデックスを返す + if (this._notExistPartId.isExist(partId)) { + return this._notExistPartId.getValue(partId); + } + + // 非存在パーツIDリストにない場合、新しく要素を追加する + partIndex = partCount + this._notExistPartId.getSize(); + this._notExistPartId.setValue(partId, partIndex); + this._notExistPartOpacities.appendKey(partIndex); + + return partIndex; + } + + /** + * パーツの個数の取得 + * @return パーツの個数 + */ + public getPartCount(): number { + const partCount: number = this._model.parts.count; + return partCount; + } + + /** + * パーツの不透明度の設定(Index) + * @param partIndex パーツのインデックス + * @param opacity 不透明度 + */ + public setPartOpacityByIndex(partIndex: number, opacity: number): void { + if (this._notExistPartOpacities.isExist(partIndex)) { + this._notExistPartOpacities.setValue(partIndex, opacity); + return; + } + + // インデックスの範囲内検知 + CSM_ASSERT(0 <= partIndex && partIndex < this.getPartCount()); + + this._partOpacities[partIndex] = opacity; + } + + /** + * パーツの不透明度の設定(Id) + * @param partId パーツのID + * @param opacity パーツの不透明度 + */ + public setPartOpacityById(partId: CubismIdHandle, opacity: number): void { + // 高速化のためにPartIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const index: number = this.getPartIndex(partId); + + if (index < 0) { + return; // パーツがないのでスキップ + } + + this.setPartOpacityByIndex(index, opacity); + } + + /** + * パーツの不透明度の取得(index) + * @param partIndex パーツのインデックス + * @return パーツの不透明度 + */ + public getPartOpacityByIndex(partIndex: number): number { + if (this._notExistPartOpacities.isExist(partIndex)) { + // モデルに存在しないパーツIDの場合、非存在パーツリストから不透明度を返す。 + return this._notExistPartOpacities.getValue(partIndex); + } + + // インデックスの範囲内検知 + CSM_ASSERT(0 <= partIndex && partIndex < this.getPartCount()); + + return this._partOpacities[partIndex]; + } + + /** + * パーツの不透明度の取得(id) + * @param partId パーツのId + * @return パーツの不透明度 + */ + public getPartOpacityById(partId: CubismIdHandle): number { + // 高速化のためにPartIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const index: number = this.getPartIndex(partId); + + if (index < 0) { + return 0; // パーツが無いのでスキップ + } + + return this.getPartOpacityByIndex(index); + } + + /** + * パラメータのインデックスの取得 + * @param パラメータID + * @return パラメータのインデックス + */ + public getParameterIndex(parameterId: CubismIdHandle): number { + let parameterIndex: number; + const idCount: number = this._model.parameters.count; + + for (parameterIndex = 0; parameterIndex < idCount; ++parameterIndex) { + if (parameterId != this._parameterIds.at(parameterIndex)) { + continue; + } + + return parameterIndex; + } + + // モデルに存在していない場合、非存在パラメータIDリスト内を検索し、そのインデックスを返す + if (this._notExistParameterId.isExist(parameterId)) { + return this._notExistParameterId.getValue(parameterId); + } + + // 非存在パラメータIDリストにない場合新しく要素を追加する + parameterIndex = + this._model.parameters.count + this._notExistParameterId.getSize(); + + this._notExistParameterId.setValue(parameterId, parameterIndex); + this._notExistParameterValues.appendKey(parameterIndex); + + return parameterIndex; + } + + /** + * パラメータの個数の取得 + * @return パラメータの個数 + */ + public getParameterCount(): number { + return this._model.parameters.count; + } + + /** + * パラメータの最大値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの最大値 + */ + public getParameterMaximumValue(parameterIndex: number): number { + return this._model.parameters.maximumValues[parameterIndex]; + } + + /** + * パラメータの最小値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの最小値 + */ + public getParameterMinimumValue(parameterIndex: number): number { + return this._model.parameters.minimumValues[parameterIndex]; + } + + /** + * パラメータのデフォルト値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータのデフォルト値 + */ + public getParameterDefaultValue(parameterIndex: number): number { + return this._model.parameters.defaultValues[parameterIndex]; + } + + /** + * パラメータの値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの値 + */ + public getParameterValueByIndex(parameterIndex: number): number { + if (this._notExistParameterValues.isExist(parameterIndex)) { + return this._notExistParameterValues.getValue(parameterIndex); + } + + // インデックスの範囲内検知 + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + return this._parameterValues[parameterIndex]; + } + + /** + * パラメータの値の取得 + * @param parameterId パラメータのID + * @return パラメータの値 + */ + public getParameterValueById(parameterId: CubismIdHandle): number { + // 高速化のためにparameterIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const parameterIndex: number = this.getParameterIndex(parameterId); + return this.getParameterValueByIndex(parameterIndex); + } + + /** + * パラメータの値の設定 + * @param parameterIndex パラメータのインデックス + * @param value パラメータの値 + * @param weight 重み + */ + public setParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + if (this._notExistParameterValues.isExist(parameterIndex)) { + this._notExistParameterValues.setValue( + parameterIndex, + weight == 1 + ? value + : this._notExistParameterValues.getValue(parameterIndex) * + (1 - weight) + + value * weight + ); + + return; + } + + // インデックスの範囲内検知 + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + if (this._model.parameters.maximumValues[parameterIndex] < value) { + value = this._model.parameters.maximumValues[parameterIndex]; + } + if (this._model.parameters.minimumValues[parameterIndex] > value) { + value = this._model.parameters.minimumValues[parameterIndex]; + } + + this._parameterValues[parameterIndex] = + weight == 1 + ? value + : (this._parameterValues[parameterIndex] = + this._parameterValues[parameterIndex] * (1 - weight) + + value * weight); + } + + /** + * パラメータの値の設定 + * @param parameterId パラメータのID + * @param value パラメータの値 + * @param weight 重み + */ + public setParameterValueById( + parameterId: CubismIdHandle, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.setParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の加算(index) + * @param parameterIndex パラメータインデックス + * @param value 加算する値 + * @param weight 重み + */ + public addParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + this.setParameterValueByIndex( + parameterIndex, + this.getParameterValueByIndex(parameterIndex) + value * weight + ); + } + + /** + * パラメータの値の加算(id) + * @param parameterId パラメータID + * @param value 加算する値 + * @param weight 重み + */ + public addParameterValueById( + parameterId: any, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.addParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の乗算 + * @param parameterId パラメータのID + * @param value 乗算する値 + * @param weight 重み + */ + public multiplyParameterValueById( + parameterId: CubismIdHandle, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.multiplyParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の乗算 + * @param parameterIndex パラメータのインデックス + * @param value 乗算する値 + * @param weight 重み + */ + public multiplyParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + this.setParameterValueByIndex( + parameterIndex, + this.getParameterValueByIndex(parameterIndex) * + (1.0 + (value - 1.0) * weight) + ); + } + + /** + * Drawableのインデックスの取得 + * @param drawableId DrawableのID + * @return Drawableのインデックス + */ + public getDrawableIndex(drawableId: CubismIdHandle): number { + const drawableCount = this._model.drawables.count; + + for ( + let drawableIndex = 0; + drawableIndex < drawableCount; + ++drawableIndex + ) { + if (this._drawableIds.at(drawableIndex) == drawableId) { + return drawableIndex; + } + } + + return -1; + } + + /** + * Drawableの個数の取得 + * @return drawableの個数 + */ + public getDrawableCount(): number { + const drawableCount = this._model.drawables.count; + return drawableCount; + } + + /** + * DrawableのIDを取得する + * @param drawableIndex Drawableのインデックス + * @return drawableのID + */ + public getDrawableId(drawableIndex: number): CubismIdHandle { + const parameterIds: string[] = this._model.drawables.ids; + return CubismFramework.getIdManager().getId(parameterIds[drawableIndex]); + } + + /** + * Drawableの描画順リストの取得 + * @return Drawableの描画順リスト + */ + public getDrawableRenderOrders(): Int32Array { + const renderOrders: Int32Array = this._model.drawables.renderOrders; + return renderOrders; + } + + /** + * Drawableのテクスチャインデックスリストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのテクスチャインデックスリスト + */ + public getDrawableTextureIndices(drawableIndex: number): number { + const textureIndices: Int32Array = this._model.drawables.textureIndices; + return textureIndices[drawableIndex]; + } + + /** + * DrawableのVertexPositionsの変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの頂点情報が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @retval true Drawableの頂点情報が直近のCubismModel.update関数で変化した + * @retval false Drawableの頂点情報が直近のCubismModel.update関数で変化していない + */ + public getDrawableDynamicFlagVertexPositionsDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasVertexPositionsDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの頂点インデックスの個数の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点インデックスの個数 + */ + public getDrawableVertexIndexCount(drawableIndex: number): number { + const indexCounts: Int32Array = this._model.drawables.indexCounts; + return indexCounts[drawableIndex]; + } + + /** + * Drawableの頂点の個数の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点の個数 + */ + public getDrawableVertexCount(drawableIndex: number): number { + const vertexCounts = this._model.drawables.vertexCounts; + return vertexCounts[drawableIndex]; + } + + /** + * Drawableの頂点リストの取得 + * @param drawableIndex drawableのインデックス + * @return drawableの頂点リスト + */ + public getDrawableVertices(drawableIndex: number): Float32Array { + return this.getDrawableVertexPositions(drawableIndex); + } + + /** + * Drawableの頂点インデックスリストの取得 + * @param drarableIndex Drawableのインデックス + * @return drawableの頂点インデックスリスト + */ + public getDrawableVertexIndices(drawableIndex: number): Uint16Array { + const indicesArray: Uint16Array[] = this._model.drawables.indices; + return indicesArray[drawableIndex]; + } + + /** + * Drawableの頂点リストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点リスト + */ + public getDrawableVertexPositions(drawableIndex: number): Float32Array { + const verticesArray: Float32Array[] = this._model.drawables + .vertexPositions; + return verticesArray[drawableIndex]; + } + + /** + * Drawableの頂点のUVリストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点UVリスト + */ + public getDrawableVertexUvs(drawableIndex: number): Float32Array { + const uvsArray: Float32Array[] = this._model.drawables.vertexUvs; + return uvsArray[drawableIndex]; + } + + /** + * Drawableの不透明度の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの不透明度 + */ + public getDrawableOpacity(drawableIndex: number): number { + const opacities: Float32Array = this._model.drawables.opacities; + return opacities[drawableIndex]; + } + + /** + * Drawableのカリング情報の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのカリング情報 + */ + public getDrawableCulling(drawableIndex: number): boolean { + const constantFlags = this._model.drawables.constantFlags; + + return !Live2DCubismCore.Utils.hasIsDoubleSidedBit( + constantFlags[drawableIndex] + ); + } + + /** + * Drawableのブレンドモードを取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのブレンドモード + */ + public getDrawableBlendMode(drawableIndex: number): CubismBlendMode { + const constantFlags = this._model.drawables.constantFlags; + + return Live2DCubismCore.Utils.hasBlendAdditiveBit( + constantFlags[drawableIndex] + ) + ? CubismBlendMode.CubismBlendMode_Additive + : Live2DCubismCore.Utils.hasBlendMultiplicativeBit( + constantFlags[drawableIndex] + ) + ? CubismBlendMode.CubismBlendMode_Multiplicative + : CubismBlendMode.CubismBlendMode_Normal; + } + + /** + * Drawableのマスクの反転使用の取得 + * + * Drawableのマスク使用時の反転設定を取得する。 + * マスクを使用しない場合は無視される。 + * + * @param drawableIndex Drawableのインデックス + * @return Drawableの反転設定 + */ + public getDrawableInvertedMaskBit(drawableIndex: number): boolean { + const constantFlags: Uint8Array = this._model.drawables.constantFlags; + + return Live2DCubismCore.Utils.hasIsInvertedMaskBit( + constantFlags[drawableIndex] + ); + } + + /** + * Drawableのクリッピングマスクリストの取得 + * @return Drawableのクリッピングマスクリスト + */ + public getDrawableMasks(): Int32Array[] { + const masks: Int32Array[] = this._model.drawables.masks; + return masks; + } + + /** + * Drawableのクリッピングマスクの個数リストの取得 + * @return Drawableのクリッピングマスクの個数リスト + */ + public getDrawableMaskCounts(): Int32Array { + const maskCounts: Int32Array = this._model.drawables.maskCounts; + return maskCounts; + } + + /** + * クリッピングマスクの使用状態 + * + * @return true クリッピングマスクを使用している + * @return false クリッピングマスクを使用していない + */ + public isUsingMasking(): boolean { + for (let d = 0; d < this._model.drawables.count; ++d) { + if (this._model.drawables.maskCounts[d] <= 0) { + continue; + } + return true; + } + return false; + } + + /** + * Drawableの表示情報を取得する + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableが表示 + * @return false Drawableが非表示 + */ + public getDrawableDynamicFlagIsVisible(drawableIndex: number): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasIsVisibleBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * DrawableのDrawOrderの変化情報の取得 + * + * 直近のCubismModel.update関数でdrawableのdrawOrderが変化したかを取得する。 + * drawOrderはartMesh上で指定する0から1000の情報 + * @param drawableIndex drawableのインデックス + * @return true drawableの不透明度が直近のCubismModel.update関数で変化した + * @return false drawableの不透明度が直近のCubismModel.update関数で変化している + */ + public getDrawableDynamicFlagVisibilityDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasVisibilityDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの不透明度の変化情報の取得 + * + * 直近のCubismModel.update関数でdrawableの不透明度が変化したかを取得する。 + * + * @param drawableIndex drawableのインデックス + * @return true Drawableの不透明度が直近のCubismModel.update関数で変化した + * @return false Drawableの不透明度が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagOpacityDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasOpacityDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの描画順序の変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの描画の順序が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableの描画の順序が直近のCubismModel.update関数で変化した + * @return false Drawableの描画の順序が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagRenderOrderDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasRenderOrderDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * 保存されたパラメータの読み込み + */ + public loadParameters(): void { + let parameterCount: number = this._model.parameters.count; + const savedParameterCount: number = this._savedParameters.getSize(); + + if (parameterCount > savedParameterCount) { + parameterCount = savedParameterCount; + } + + for (let i = 0; i < parameterCount; ++i) { + this._parameterValues[i] = this._savedParameters.at(i); + } + } + + /** + * 初期化する + */ + public initialize(): void { + CSM_ASSERT(this._model); + + this._parameterValues = this._model.parameters.values; + this._partOpacities = this._model.parts.opacities; + this._parameterMaximumValues = this._model.parameters.maximumValues; + this._parameterMinimumValues = this._model.parameters.minimumValues; + + { + const parameterIds: string[] = this._model.parameters.ids; + const parameterCount: number = this._model.parameters.count; + + this._parameterIds.prepareCapacity(parameterCount); + for (let i = 0; i < parameterCount; ++i) { + this._parameterIds.pushBack( + CubismFramework.getIdManager().getId(parameterIds[i]) + ); + } + } + + { + const partIds: string[] = this._model.parts.ids; + const partCount: number = this._model.parts.count; + + this._partIds.prepareCapacity(partCount); + for (let i = 0; i < partCount; ++i) { + this._partIds.pushBack( + CubismFramework.getIdManager().getId(partIds[i]) + ); + } + } + + { + const drawableIds: string[] = this._model.drawables.ids; + const drawableCount: number = this._model.drawables.count; + + this._drawableIds.prepareCapacity(drawableCount); + for (let i = 0; i < drawableCount; ++i) { + this._drawableIds.pushBack( + CubismFramework.getIdManager().getId(drawableIds[i]) + ); + } + } + } + + /** + * コンストラクタ + * @param model モデル + */ + public constructor(model: Live2DCubismCore.Model) { + this._model = model; + this._parameterValues = null; + this._parameterMaximumValues = null; + this._parameterMinimumValues = null; + this._partOpacities = null; + this._savedParameters = new csmVector(); + this._parameterIds = new csmVector(); + this._drawableIds = new csmVector(); + this._partIds = new csmVector(); + + this._notExistPartId = new csmMap(); + this._notExistParameterId = new csmMap(); + this._notExistParameterValues = new csmMap(); + this._notExistPartOpacities = new csmMap(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._model.release(); + this._model = null; + } + + private _notExistPartOpacities: csmMap; // 存在していないパーツの不透明度のリスト + private _notExistPartId: csmMap; // 存在していないパーツIDのリスト + + private _notExistParameterValues: csmMap; // 存在していないパラメータの値のリスト + private _notExistParameterId: csmMap; // 存在していないパラメータIDのリスト + + private _savedParameters: csmVector; // 保存されたパラメータ + + private _model: Live2DCubismCore.Model; // モデル + + private _parameterValues: Float32Array; // パラメータの値のリスト + private _parameterMaximumValues: Float32Array; // パラメータの最大値のリスト + private _parameterMinimumValues: Float32Array; // パラメータの最小値のリスト + + private _partOpacities: Float32Array; // パーツの不透明度のリスト + + private _parameterIds: csmVector; + private _partIds: csmVector; + private _drawableIds: csmVector; + } +} diff --git a/src/model/cubismmodeluserdata.ts b/src/model/cubismmodeluserdata.ts new file mode 100644 index 0000000..f1381fa --- /dev/null +++ b/src/model/cubismmodeluserdata.ts @@ -0,0 +1,136 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmodeluserdatajson } from './cubismmodeluserdatajson'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import CubismFramework = cubismframework.CubismFramework; +import csmVector = csmvector.csmVector; +import csmString = csmstring.csmString; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismModelUserDataJson = cubismmodeluserdatajson.CubismModelUserDataJson; + +export namespace Live2DCubismFramework { + const ArtMesh = 'ArtMesh'; + + /** + * ユーザーデータインターフェース + * + * Jsonから読み込んだユーザーデータを記録しておくための構造体 + */ + export class CubismModelUserDataNode { + targetType: CubismIdHandle; // ユーザーデータターゲットタイプ + targetId: CubismIdHandle; // ユーザーデータターゲットのID + value: csmString; // ユーザーデータ + } + + /** + * ユーザデータの管理クラス + * + * ユーザデータをロード、管理、検索インターフェイス、解放までを行う。 + */ + export class CubismModelUserData { + /** + * インスタンスの作成 + * + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number + ): CubismModelUserData { + const ret: CubismModelUserData = new CubismModelUserData(); + + ret.parseUserData(buffer, size); + + return ret; + } + + /** + * インスタンスを破棄する + * + * @param modelUserData 破棄するインスタンス + */ + public static delete(modelUserData: CubismModelUserData): void { + if (modelUserData != null) { + modelUserData.release(); + modelUserData = null; + } + } + + /** + * ArtMeshのユーザーデータのリストの取得 + * + * @return ユーザーデータリスト + */ + public getArtMeshUserDatas(): csmVector { + return this._artMeshUserDataNode; + } + + /** + * userdata3.jsonのパース + * + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parseUserData(buffer: ArrayBuffer, size: number): void { + let json: CubismModelUserDataJson = new CubismModelUserDataJson( + buffer, + size + ); + + const typeOfArtMesh = CubismFramework.getIdManager().getId(ArtMesh); + const nodeCount: number = json.getUserDataCount(); + + for (let i = 0; i < nodeCount; i++) { + const addNode: CubismModelUserDataNode = new CubismModelUserDataNode(); + + addNode.targetId = json.getUserDataId(i); + addNode.targetType = CubismFramework.getIdManager().getId( + json.getUserDataTargetType(i) + ); + addNode.value = new csmString(json.getUserDataValue(i)); + this._userDataNodes.pushBack(addNode); + + if (addNode.targetType == typeOfArtMesh) { + this._artMeshUserDataNode.pushBack(addNode); + } + } + + json.release(); + json = void 0; + } + + /** + * コンストラクタ + */ + public constructor() { + this._userDataNodes = new csmVector(); + this._artMeshUserDataNode = new csmVector(); + } + + /** + * デストラクタ相当の処理 + * + * ユーザーデータ構造体配列を解放する + */ + public release(): void { + for (let i = 0; i < this._userDataNodes.getSize(); ++i) { + this._userDataNodes.set(i, null); + } + + this._userDataNodes = null; + } + + private _userDataNodes: csmVector; // ユーザーデータ構造体配列 + private _artMeshUserDataNode: csmVector; // 閲覧リストの保持 + } +} diff --git a/src/model/cubismmodeluserdatajson.ts b/src/model/cubismmodeluserdatajson.ts new file mode 100644 index 0000000..0d9334d --- /dev/null +++ b/src/model/cubismmodeluserdatajson.ts @@ -0,0 +1,114 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismjson } from '../utils/cubismjson'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import CubismFramework = cubismframework.CubismFramework; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismJson = cubismjson.CubismJson; + +export namespace Live2DCubismFramework { + const Meta = 'Meta'; + const UserDataCount = 'UserDataCount'; + const TotalUserDataSize = 'TotalUserDataSize'; + const UserData = 'UserData'; + const Target = 'Target'; + const Id = 'Id'; + const Value = 'Value'; + + export class CubismModelUserDataJson { + /** + * コンストラクタ + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * ユーザーデータ個数の取得 + * @return ユーザーデータの個数 + */ + public getUserDataCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(UserDataCount) + .toInt(); + } + + /** + * ユーザーデータ総文字列数の取得 + * + * @return ユーザーデータ総文字列数 + */ + public getTotalUserDataSize(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalUserDataSize) + .toInt(); + } + + /** + * ユーザーデータのタイプの取得 + * + * @return ユーザーデータのタイプ + */ + public getUserDataTargetType(i: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Target) + .getRawString(); + } + + /** + * ユーザーデータのターゲットIDの取得 + * + * @param i インデックス + * @return ユーザーデータターゲットID + */ + public getUserDataId(i: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * ユーザーデータの文字列の取得 + * + * @param i インデックス + * @return ユーザーデータ + */ + public getUserDataValue(i: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Value) + .getRawString(); + } + + private _json: CubismJson; + } +} diff --git a/src/model/cubismusermodel.ts b/src/model/cubismusermodel.ts new file mode 100644 index 0000000..56e9a59 --- /dev/null +++ b/src/model/cubismusermodel.ts @@ -0,0 +1,453 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as cubismmotionmanager } from '../motion/cubismmotionmanager'; +import { Live2DCubismFramework as cubismtargetpoint } from '../math/cubismtargetpoint'; +import { Live2DCubismFramework as cubismmodelmatrix } from '../math/cubismmodelmatrix'; +import { Live2DCubismFramework as cubismmoc } from './cubismmoc'; +import { Live2DCubismFramework as cubismmodel } from './cubismmodel'; +import { Live2DCubismFramework as acubismmotion } from '../motion/acubismmotion'; +import { Live2DCubismFramework as cubismmotion } from '../motion/cubismmotion'; +import { Live2DCubismFramework as cubismexpressionmotion } from '../motion/cubismexpressionmotion'; +import { Live2DCubismFramework as cubismpose } from '../effect/cubismpose'; +import { Live2DCubismFramework as cubismmodeluserdata } from './cubismmodeluserdata'; +import { Live2DCubismFramework as cubismphysics } from '../physics/cubismphysics'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import { Live2DCubismFramework as cubismmotionqueuemanager } from '../motion/cubismmotionqueuemanager'; +import { Live2DCubismFramework as cubismbreath } from '../effect/cubismbreath'; +import { Live2DCubismFramework as cubismeyeblink } from '../effect/cubismeyeblink'; +import { Live2DCubismFramework as cubismrenderer_webgl } from '../rendering/cubismrenderer_webgl'; +import { CubismLogError, CubismLogInfo } from '../utils/cubismdebug'; +import CubismRenderer_WebGL = cubismrenderer_webgl.CubismRenderer_WebGL; +import CubismEyeBlink = cubismeyeblink.CubismEyeBlink; +import CubismBreath = cubismbreath.CubismBreath; +import CubismMotionQueueManager = cubismmotionqueuemanager.CubismMotionQueueManager; +import csmString = csmstring.csmString; +import Constant = cubismframework.Constant; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismPhysics = cubismphysics.CubismPhysics; +import CubismModelUserData = cubismmodeluserdata.CubismModelUserData; +import CubismPose = cubismpose.CubismPose; +import CubismExpressionMotion = cubismexpressionmotion.CubismExpressionMotion; +import CubismMotion = cubismmotion.CubismMotion; +import ACubismMotion = acubismmotion.ACubismMotion; +import FinishedMotionCallback = acubismmotion.FinishedMotionCallback; +import CubismModel = cubismmodel.CubismModel; +import CubismMoc = cubismmoc.CubismMoc; +import CubismModelMatrix = cubismmodelmatrix.CubismModelMatrix; +import CubismTargetPoint = cubismtargetpoint.CubismTargetPoint; +import CubismMotionManager = cubismmotionmanager.CubismMotionManager; + +export namespace Live2DCubismFramework { + /** + * ユーザーが実際に使用するモデル + * + * ユーザーが実際に使用するモデルの基底クラス。これを継承してユーザーが実装する。 + */ + export class CubismUserModel { + /** + * 初期化状態の取得 + * + * 初期化されている状態か? + * + * @return true 初期化されている + * @return false 初期化されていない + */ + public isInitialized(): boolean { + return this._initialized; + } + + /** + * 初期化状態の設定 + * + * 初期化状態を設定する。 + * + * @param v 初期化状態 + */ + public setInitialized(v: boolean): void { + this._initialized = v; + } + + /** + * 更新状態の取得 + * + * 更新されている状態か? + * + * @return true 更新されている + * @return false 更新されていない + */ + public isUpdating(): boolean { + return this._updating; + } + + /** + * 更新状態の設定 + * + * 更新状態を設定する + * + * @param v 更新状態 + */ + public setUpdating(v: boolean): void { + this._updating = v; + } + + /** + * マウスドラッグ情報の設定 + * @param ドラッグしているカーソルのX位置 + * @param ドラッグしているカーソルのY位置 + */ + public setDragging(x: number, y: number): void { + this._dragManager.set(x, y); + } + + /** + * 加速度の情報を設定する + * @param x X軸方向の加速度 + * @param y Y軸方向の加速度 + * @param z Z軸方向の加速度 + */ + public setAcceleration(x: number, y: number, z: number): void { + this._accelerationX = x; + this._accelerationY = y; + this._accelerationZ = z; + } + + /** + * モデル行列を取得する + * @return モデル行列 + */ + public getModelMatrix(): CubismModelMatrix { + return this._modelMatrix; + } + + /** + * 不透明度の設定 + * @param a 不透明度 + */ + public setOpacity(a: number): void { + this._opacity = a; + } + + /** + * 不透明度の取得 + * @return 不透明度 + */ + public getOpacity(): number { + return this._opacity; + } + + /** + * モデルデータを読み込む + * + * @param buffer moc3ファイルが読み込まれているバッファ + */ + public loadModel(buffer: ArrayBuffer) { + this._moc = CubismMoc.create(buffer); + this._model = this._moc.createModel(); + this._model.saveParameters(); + + if (this._moc == null || this._model == null) { + CubismLogError('Failed to CreateModel().'); + return; + } + + this._modelMatrix = new CubismModelMatrix( + this._model.getCanvasWidth(), + this._model.getCanvasHeight() + ); + } + + /** + * モーションデータを読み込む + * @param buffer motion3.jsonファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @param name モーションの名前 + * @param onFinishedMotionHandler モーション再生終了時に呼び出されるコールバック関数 + * @return モーションクラス + */ + public loadMotion = ( + buffer: ArrayBuffer, + size: number, + name: string, + onFinishedMotionHandler?: FinishedMotionCallback + ) => CubismMotion.create(buffer, size, onFinishedMotionHandler); + + /** + * 表情データの読み込み + * @param buffer expファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @param name 表情の名前 + */ + public loadExpression( + buffer: ArrayBuffer, + size: number, + name: string + ): ACubismMotion { + return CubismExpressionMotion.create(buffer, size); + } + + /** + * ポーズデータの読み込み + * @param buffer pose3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadPose(buffer: ArrayBuffer, size: number): void { + this._pose = CubismPose.create(buffer, size); + } + + /** + * モデルに付属するユーザーデータを読み込む + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadUserData(buffer: ArrayBuffer, size: number): void { + this._modelUserData = CubismModelUserData.create(buffer, size); + } + + /** + * 物理演算データの読み込み + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadPhysics(buffer: ArrayBuffer, size: number): void { + this._physics = CubismPhysics.create(buffer, size); + } + + /** + * 当たり判定の取得 + * @param drawableId 検証したいDrawableのID + * @param pointX X位置 + * @param pointY Y位置 + * @return true ヒットしている + * @return false ヒットしていない + */ + public isHit( + drawableId: CubismIdHandle, + pointX: number, + pointY: number + ): boolean { + const drawIndex: number = this._model.getDrawableIndex(drawableId); + + if (drawIndex < 0) { + return false; // 存在しない場合はfalse + } + + const count: number = this._model.getDrawableVertexCount(drawIndex); + const vertices: Float32Array = this._model.getDrawableVertices(drawIndex); + + let left: number = vertices[0]; + let right: number = vertices[0]; + let top: number = vertices[1]; + let bottom: number = vertices[1]; + + for (let j = 1; j < count; ++j) { + const x = vertices[Constant.vertexOffset + j * Constant.vertexStep]; + const y = vertices[Constant.vertexOffset + j * Constant.vertexStep + 1]; + + if (x < left) { + left = x; // Min x + } + + if (x > right) { + right = x; // Max x + } + + if (y < top) { + top = y; // Min y + } + + if (y > bottom) { + bottom = y; // Max y + } + } + + const tx: number = this._modelMatrix.invertTransformX(pointX); + const ty: number = this._modelMatrix.invertTransformY(pointY); + + return left <= tx && tx <= right && top <= ty && ty <= bottom; + } + + /** + * モデルの取得 + * @return モデル + */ + public getModel(): CubismModel { + return this._model; + } + + /** + * レンダラの取得 + * @return レンダラ + */ + public getRenderer(): CubismRenderer_WebGL { + return this._renderer; + } + + /** + * レンダラを作成して初期化を実行する + */ + public createRenderer(): void { + if (this._renderer) { + this.deleteRenderer(); + } + + this._renderer = new CubismRenderer_WebGL(); + this._renderer.initialize(this._model); + } + + /** + * レンダラの解放 + */ + public deleteRenderer(): void { + if (this._renderer != null) { + this._renderer.release(); + this._renderer = null; + } + } + + /** + * イベント発火時の標準処理 + * + * Eventが再生処理時にあった場合の処理をする。 + * 継承で上書きすることを想定している。 + * 上書きしない場合はログ出力をする。 + * + * @param eventValue 発火したイベントの文字列データ + */ + public motionEventFired(eventValue: csmString): void { + CubismLogInfo('{0}', eventValue.s); + } + + /** + * イベント用のコールバック + * + * CubismMotionQueueManagerにイベント用に登録するためのCallback。 + * CubismUserModelの継承先のEventFiredを呼ぶ。 + * + * @param caller 発火したイベントを管理していたモーションマネージャー、比較用 + * @param eventValue 発火したイベントの文字列データ + * @param customData CubismUserModelを継承したインスタンスを想定 + */ + public static cubismDefaultMotionEventCallback( + caller: CubismMotionQueueManager, + eventValue: csmString, + customData: CubismUserModel + ): void { + const model: CubismUserModel = customData; + + if (model != null) { + model.motionEventFired(eventValue); + } + } + + /** + * コンストラクタ + */ + public constructor() { + // 各変数初期化 + this._moc = null; + this._model = null; + this._motionManager = null; + this._expressionManager = null; + this._eyeBlink = null; + this._breath = null; + this._modelMatrix = null; + this._pose = null; + this._dragManager = null; + this._physics = null; + this._modelUserData = null; + this._initialized = false; + this._updating = false; + this._opacity = 1.0; + this._lipsync = true; + this._lastLipSyncValue = 0.0; + this._dragX = 0.0; + this._dragY = 0.0; + this._accelerationX = 0.0; + this._accelerationY = 0.0; + this._accelerationZ = 0.0; + this._debugMode = false; + this._renderer = null; + + // モーションマネージャーを作成 + this._motionManager = new CubismMotionManager(); + this._motionManager.setEventCallback( + CubismUserModel.cubismDefaultMotionEventCallback, + this + ); + + // 表情マネージャーを作成 + this._expressionManager = new CubismMotionManager(); + + // ドラッグによるアニメーション + this._dragManager = new CubismTargetPoint(); + } + + /** + * デストラクタに相当する処理 + */ + public release() { + if (this._motionManager != null) { + this._motionManager.release(); + this._motionManager = null; + } + + if (this._expressionManager != null) { + this._expressionManager.release(); + this._expressionManager = null; + } + + if (this._moc != null) { + this._moc.deleteModel(this._model); + this._moc.release(); + this._moc = null; + } + + this._modelMatrix = null; + + CubismPose.delete(this._pose); + CubismEyeBlink.delete(this._eyeBlink); + CubismBreath.delete(this._breath); + + this._dragManager = null; + + CubismPhysics.delete(this._physics); + CubismModelUserData.delete(this._modelUserData); + + this.deleteRenderer(); + } + + protected _moc: CubismMoc; // Mocデータ + protected _model: CubismModel; // Modelインスタンス + + protected _motionManager: CubismMotionManager; // モーション管理 + protected _expressionManager: CubismMotionManager; // 表情管理 + protected _eyeBlink: CubismEyeBlink; // 自動まばたき + protected _breath: CubismBreath; // 呼吸 + protected _modelMatrix: CubismModelMatrix; // モデル行列 + protected _pose: CubismPose; // ポーズ管理 + protected _dragManager: CubismTargetPoint; // マウスドラッグ + protected _physics: CubismPhysics; // 物理演算 + protected _modelUserData: CubismModelUserData; // ユーザーデータ + + protected _initialized: boolean; // 初期化されたかどうか + protected _updating: boolean; // 更新されたかどうか + protected _opacity: number; // 不透明度 + protected _lipsync: boolean; // リップシンクするかどうか + protected _lastLipSyncValue: number; // 最後のリップシンクの制御地 + protected _dragX: number; // マウスドラッグのX位置 + protected _dragY: number; // マウスドラッグのY位置 + protected _accelerationX: number; // X軸方向の加速度 + protected _accelerationY: number; // Y軸方向の加速度 + protected _accelerationZ: number; // Z軸方向の加速度 + protected _debugMode: boolean; // デバッグモードかどうか + + private _renderer: CubismRenderer_WebGL; // レンダラ + } +} diff --git a/src/motion/acubismmotion.ts b/src/motion/acubismmotion.ts new file mode 100644 index 0000000..7391e3d --- /dev/null +++ b/src/motion/acubismmotion.ts @@ -0,0 +1,278 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmath } from '../math/cubismmath'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubismmotionqueueentry } from './cubismmotionqueueentry'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { CSM_ASSERT } from '../utils/cubismdebug'; +import csmVector = csmvector.csmVector; +import csmString = csmstring.csmString; +import CubismMotionQueueEntry = cubismmotionqueueentry.CubismMotionQueueEntry; +import CubismModel = cubismmodel.CubismModel; +import CubismMath = cubismmath.CubismMath; + +export namespace Live2DCubismFramework { + /** モーション再生終了コールバック関数定義 */ + export type FinishedMotionCallback = (self: ACubismMotion) => void; + + /** + * モーションの抽象基底クラス + * + * モーションの抽象基底クラス。MotionQueueManagerによってモーションの再生を管理する。 + */ + export abstract class ACubismMotion { + /** + * インスタンスの破棄 + */ + public static delete(motion: ACubismMotion): void { + motion.release(); + motion = void 0; + motion = null; + } + + /** + * コンストラクタ + */ + public constructor() { + this._fadeInSeconds = -1.0; + this._fadeOutSeconds = -1.0; + this._weight = 1.0; + this._offsetSeconds = 0.0; // 再生の開始時刻 + this._firedEventValues = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._weight = 0.0; + } + + /** + * モデルのパラメータ + * @param model 対象のモデル + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @param userTimeSeconds デルタ時間の積算値[秒] + */ + public updateParameters( + model: CubismModel, + motionQueueEntry: CubismMotionQueueEntry, + userTimeSeconds: number + ): void { + if (!motionQueueEntry.isAvailable() || motionQueueEntry.isFinished()) { + return; + } + + if (!motionQueueEntry.isStarted()) { + motionQueueEntry.setIsStarted(true); + motionQueueEntry.setStartTime(userTimeSeconds - this._offsetSeconds); // モーションの開始時刻を記録 + motionQueueEntry.setFadeInStartTime(userTimeSeconds); // フェードインの開始時刻 + + const duration: number = this.getDuration(); + + if (motionQueueEntry.getEndTime() < 0) { + // 開始していないうちに終了設定している場合がある。 + motionQueueEntry.setEndTime( + duration <= 0 ? -1 : motionQueueEntry.getStartTime() + duration + ); + // duration == -1 の場合はループする + } + } + + let fadeWeight: number = this._weight; // 現在の値と掛け合わせる割合 + + //---- フェードイン・アウトの処理 ---- + // 単純なサイン関数でイージングする + const fadeIn: number = + this._fadeInSeconds == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + this._fadeInSeconds + ); + + const fadeOut: number = + this._fadeOutSeconds == 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + this._fadeOutSeconds + ); + + fadeWeight = fadeWeight * fadeIn * fadeOut; + + motionQueueEntry.setState(userTimeSeconds, fadeWeight); + + CSM_ASSERT(0.0 <= fadeWeight && fadeWeight <= 1.0); + + //---- 全てのパラメータIDをループする ---- + this.doUpdateParameters( + model, + userTimeSeconds, + fadeWeight, + motionQueueEntry + ); + + // 後処理 + // 終了時刻を過ぎたら終了フラグを立てる(CubismMotionQueueManager) + if ( + motionQueueEntry.getEndTime() > 0 && + motionQueueEntry.getEndTime() < userTimeSeconds + ) { + motionQueueEntry.setIsFinished(true); // 終了 + } + } + + /** + * フェードインの時間を設定する + * @param fadeInSeconds フェードインにかかる時間[秒] + */ + public setFadeInTime(fadeInSeconds: number): void { + this._fadeInSeconds = fadeInSeconds; + } + + /** + * フェードアウトの時間を設定する + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + */ + public setFadeOutTime(fadeOutSeconds: number): void { + this._fadeOutSeconds = fadeOutSeconds; + } + + /** + * フェードアウトにかかる時間の取得 + * @return フェードアウトにかかる時間[秒] + */ + public getFadeOutTime(): number { + return this._fadeOutSeconds; + } + + /** + * フェードインにかかる時間の取得 + * @return フェードインにかかる時間[秒] + */ + public getFadeInTime(): number { + return this._fadeInSeconds; + } + + /** + * モーション適用の重みの設定 + * @param weight 重み(0.0 - 1.0) + */ + public setWeight(weight: number): void { + this._weight = weight; + } + + /** + * モーション適用の重みの取得 + * @return 重み(0.0 - 1.0) + */ + public getWeight(): number { + return this._weight; + } + + /** + * モーションの長さの取得 + * @return モーションの長さ[秒] + * + * @note ループの時は「-1」。 + * ループでない場合は、オーバーライドする。 + * 正の値の時は取得される時間で終了する。 + * 「-1」の時は外部から停止命令がない限り終わらない処理となる。 + */ + public getDuration(): number { + return -1.0; + } + + /** + * モーションのループ1回分の長さの取得 + * @return モーションのループ一回分の長さ[秒] + * + * @note ループしない場合は、getDuration()と同じ値を返す + * ループ一回分の長さが定義できない場合(プログラム的に動き続けるサブクラスなど)の場合は「-1」を返す + */ + public getLoopDuration(): number { + return -1.0; + } + + /** + * モーション再生の開始時刻の設定 + * @param offsetSeconds モーション再生の開始時刻[秒] + */ + public setOffsetTime(offsetSeconds: number): void { + this._offsetSeconds = offsetSeconds; + } + + /** + * モデルのパラメータ更新 + * + * イベント発火のチェック。 + * 入力する時間は呼ばれるモーションタイミングを0とした秒数で行う。 + * + * @param beforeCheckTimeSeconds 前回のイベントチェック時間[秒] + * @param motionTimeSeconds 今回の再生時間[秒] + */ + public getFiredEvent( + beforeCheckTimeSeconds: number, + motionTimeSeconds: number + ): csmVector { + return this._firedEventValues; + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @param weight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @return true モデルへパラメータ値の反映あり + * @return false モデルへのパラメータ値の反映なし(モーションの変化なし) + */ + public abstract doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + weight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void; + + /** + * モーション再生終了コールバックの登録 + * + * モーション再生終了コールバックを登録する。 + * isFinishedフラグを設定するタイミングで呼び出される。 + * 以下の状態の際には呼び出されない: + * 1. 再生中のモーションが「ループ」として設定されているとき + * 2. コールバックが登録されていない時 + * + * @param onFinishedMotionHandler モーション再生終了コールバック関数 + */ + public setFinishedMotionHandler = ( + onFinishedMotionHandler: FinishedMotionCallback + ) => (this._onFinishedMotion = onFinishedMotionHandler); + + /** + * モーション再生終了コールバックの取得 + * + * モーション再生終了コールバックを取得する。 + * + * @return 登録されているモーション再生終了コールバック関数 + */ + public getFinishedMotionHandler = () => this._onFinishedMotion; + + public _fadeInSeconds: number; // フェードインにかかる時間[秒] + public _fadeOutSeconds: number; // フェードアウトにかかる時間[秒] + public _weight: number; // モーションの重み + public _offsetSeconds: number; // モーション再生の開始時間[秒] + + public _firedEventValues: csmVector; + + // モーション再生終了コールバック関数 + public _onFinishedMotion?: FinishedMotionCallback; + } +} diff --git a/src/motion/cubismexpressionmotion.ts b/src/motion/cubismexpressionmotion.ts new file mode 100644 index 0000000..3fd679c --- /dev/null +++ b/src/motion/cubismexpressionmotion.ts @@ -0,0 +1,199 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as acubismmotion } from './acubismmotion'; +import { Live2DCubismFramework as cubismjson } from '../utils/cubismjson'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubismmotionqueueentry } from './cubismmotionqueueentry'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import JsonFloat = cubismjson.JsonFloat; +import csmVector = csmvector.csmVector; +import CubismMotionQueueEntry = cubismmotionqueueentry.CubismMotionQueueEntry; +import CubismModel = cubismmodel.CubismModel; +import CubismFramework = cubismframework.CubismFramework; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismJson = cubismjson.CubismJson; +import Value = cubismjson.Value; +import ACubismMotion = acubismmotion.ACubismMotion; + +export namespace Live2DCubismFramework { + // exp3.jsonのキーとデフォルト + const ExpressionKeyFadeIn = 'FadeInTime'; + const ExpressionKeyFadeOut = 'FadeOutTime'; + const ExpressionKeyParameters = 'Parameters'; + const ExpressionKeyId = 'Id'; + const ExpressionKeyValue = 'Value'; + const ExpressionKeyBlend = 'Blend'; + const BlendValueAdd = 'Add'; + const BlendValueMultiply = 'Multiply'; + const BlendValueOverwrite = 'Overwrite'; + const DefaultFadeTime = 1.0; + + /** + * 表情のモーション + * + * 表情のモーションクラス。 + */ + export class CubismExpressionMotion extends ACubismMotion { + /** + * インスタンスを作成する。 + * @param buffer expファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number + ): CubismExpressionMotion { + const expression: CubismExpressionMotion = new CubismExpressionMotion(); + + const json: CubismJson = CubismJson.create(buffer, size); + const root: Value = json.getRoot(); + + expression.setFadeInTime( + root.getValueByString(ExpressionKeyFadeIn).toFloat(DefaultFadeTime) + ); // フェードイン + expression.setFadeOutTime( + root.getValueByString(ExpressionKeyFadeOut).toFloat(DefaultFadeTime) + ); // フェードアウト + + // 各パラメータについて + const parameterCount = root + .getValueByString(ExpressionKeyParameters) + .getSize(); + expression._parameters.prepareCapacity(parameterCount); + + for (let i = 0; i < parameterCount; ++i) { + const param: Value = root + .getValueByString(ExpressionKeyParameters) + .getValueByIndex(i); + const parameterId: CubismIdHandle = CubismFramework.getIdManager().getId( + param.getValueByString(ExpressionKeyId).getRawString() + ); // パラメータID + + const value: number = param + .getValueByString(ExpressionKeyValue) + .toFloat(); // 値 + + // 計算方法の設定 + let blendType: ExpressionBlendType; + + if ( + param.getValueByString(ExpressionKeyBlend).isNull() || + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueAdd + ) { + blendType = ExpressionBlendType.ExpressionBlendType_Add; + } else if ( + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueMultiply + ) { + blendType = ExpressionBlendType.ExpressionBlendType_Multiply; + } else if ( + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueOverwrite + ) { + blendType = ExpressionBlendType.ExpressionBlendType_Overwrite; + } else { + // その他 仕様にない値を設定した時は加算モードにすることで復旧 + blendType = ExpressionBlendType.ExpressionBlendType_Add; + } + + // 設定オブジェクトを作成してリストに追加する + const item: ExpressionParameter = new ExpressionParameter(); + + item.parameterId = parameterId; + item.blendType = blendType; + item.value = value; + + expression._parameters.pushBack(item); + } + + CubismJson.delete(json); // JSONデータは不要になったら削除する + return expression; + } + + /** + * モデルのパラメータの更新の実行 + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @param weight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + public doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + weight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void { + for (let i = 0; i < this._parameters.getSize(); ++i) { + const parameter: ExpressionParameter = this._parameters.at(i); + + switch (parameter.blendType) { + case ExpressionBlendType.ExpressionBlendType_Add: { + model.addParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + case ExpressionBlendType.ExpressionBlendType_Multiply: { + model.multiplyParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + case ExpressionBlendType.ExpressionBlendType_Overwrite: { + model.setParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + default: + // 仕様にない値を設定した時はすでに加算モードになっている + break; + } + } + } + + /** + * コンストラクタ + */ + constructor() { + super(); + + this._parameters = new csmVector(); + } + + _parameters: csmVector; // 表情のパラメータ情報リスト + } + + /** + * 表情パラメータ値の計算方式 + */ + export enum ExpressionBlendType { + ExpressionBlendType_Add = 0, // 加算 + ExpressionBlendType_Multiply = 1, // 乗算 + ExpressionBlendType_Overwrite = 2 // 上書き + } + + /** + * 表情のパラメータ情報 + */ + export class ExpressionParameter { + parameterId: CubismIdHandle; // パラメータID + blendType: ExpressionBlendType; // パラメータの演算種類 + value: number; // 値 + } +} diff --git a/src/motion/cubismmotion.ts b/src/motion/cubismmotion.ts new file mode 100644 index 0000000..7dd13df --- /dev/null +++ b/src/motion/cubismmotion.ts @@ -0,0 +1,945 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmotionjson } from './cubismmotionjson'; +import { Live2DCubismFramework as cubismmotioninternal } from './cubismmotioninternal'; +import { Live2DCubismFramework as acubismmotion } from './acubismmotion'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as cubismmotionqueueentry } from './cubismmotionqueueentry'; +import { Live2DCubismFramework as cubismmath } from '../math/cubismmath'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import { CubismLogDebug, CSM_ASSERT } from '../utils/cubismdebug'; +import csmString = csmstring.csmString; +import CubismMotionData = cubismmotioninternal.CubismMotionData; +import CubismMotionSegment = cubismmotioninternal.CubismMotionSegment; +import CubismMotionPoint = cubismmotioninternal.CubismMotionPoint; +import CubismMotionEvent = cubismmotioninternal.CubismMotionEvent; +import CubismMotionSegmentType = cubismmotioninternal.CubismMotionSegmentType; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismMotionCurve = cubismmotioninternal.CubismMotionCurve; +import CubismMotionCurveTarget = cubismmotioninternal.CubismMotionCurveTarget; +import csmVector = csmvector.csmVector; +import CubismMath = cubismmath.CubismMath; +import CubismMotionQueueEntry = cubismmotionqueueentry.CubismMotionQueueEntry; +import CubismFramework = cubismframework.CubismFramework; +import CubismModel = cubismmodel.CubismModel; +import ACubismMotion = acubismmotion.ACubismMotion; +import FinishedMotionCallback = acubismmotion.FinishedMotionCallback; +import CubismMotionJson = cubismmotionjson.CubismMotionJson; + +export namespace Live2DCubismFramework { + const EffectNameEyeBlink = 'EyeBlink'; + const EffectNameLipSync = 'LipSync'; + const TargetNameModel = 'Model'; + const TargetNameParameter = 'Parameter'; + const TargetNamePartOpacity = 'PartOpacity'; + + function lerpPoints( + a: CubismMotionPoint, + b: CubismMotionPoint, + t: number + ): CubismMotionPoint { + const result: CubismMotionPoint = new CubismMotionPoint(); + + result.time = a.time + (b.time - a.time) * t; + result.value = a.value + (b.value - a.value) * t; + + return result; + } + + function linearEvaluate(points: CubismMotionPoint[], time: number): number { + let t: number = (time - points[0].time) / (points[1].time - points[0].time); + + if (t < 0.0) { + t = 0.0; + } + + return points[0].value + (points[1].value - points[0].value) * t; + } + + function bezierEvaluate(points: CubismMotionPoint[], time: number): number { + let t: number = (time - points[0].time) / (points[3].time - points[0].time); + + if (t < 0.0) { + t = 0.0; + } + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; + } + + function steppedEvaluate(points: CubismMotionPoint[], time: number): number { + return points[0].value; + } + + function inverseSteppedEvaluate( + points: CubismMotionPoint[], + time: number + ): number { + return points[1].value; + } + + function evaluateCurve( + motionData: CubismMotionData, + index: number, + time: number + ): number { + // Find segment to evaluate. + const curve: CubismMotionCurve = motionData.curves.at(index); + + let target = -1; + const totalSegmentCount: number = + curve.baseSegmentIndex + curve.segmentCount; + let pointPosition = 0; + for (let i: number = curve.baseSegmentIndex; i < totalSegmentCount; ++i) { + // Get first point of next segment. + pointPosition = + motionData.segments.at(i).basePointIndex + + (motionData.segments.at(i).segmentType == + CubismMotionSegmentType.CubismMotionSegmentType_Bezier + ? 3 + : 1); + + // Break if time lies within current segment. + if (motionData.points.at(pointPosition).time > time) { + target = i; + break; + } + } + + if (target == -1) { + return motionData.points.at(pointPosition).value; + } + + const segment: CubismMotionSegment = motionData.segments.at(target); + + return segment.evaluate( + motionData.points.get(segment.basePointIndex), + time + ); + } + + /** + * モーションクラス + * + * モーションのクラス。 + */ + export class CubismMotion extends ACubismMotion { + /** + * インスタンスを作成する + * + * @param buffer motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @param onFinishedMotionHandler モーション再生終了時に呼び出されるコールバック関数 + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number, + onFinishedMotionHandler?: FinishedMotionCallback + ): CubismMotion { + const ret = new CubismMotion(); + + ret.parse(buffer, size); + ret._sourceFrameRate = ret._motionData.fps; + ret._loopDurationSeconds = ret._motionData.duration; + ret._onFinishedMotion = onFinishedMotionHandler; + + // NOTE: Editorではループありのモーション書き出しは非対応 + // ret->_loop = (ret->_motionData->Loop > 0); + return ret; + } + + /** + * モデルのパラメータの更新の実行 + * @param model 対象のモデル + * @param userTimeSeconds 現在の時刻[秒] + * @param fadeWeight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + public doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + fadeWeight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void { + if (this._modelCurveIdEyeBlink == null) { + this._modelCurveIdEyeBlink = CubismFramework.getIdManager().getId( + EffectNameEyeBlink + ); + } + + if (this._modelCurveIdLipSync == null) { + this._modelCurveIdLipSync = CubismFramework.getIdManager().getId( + EffectNameLipSync + ); + } + + let timeOffsetSeconds: number = + userTimeSeconds - motionQueueEntry.getStartTime(); + + if (timeOffsetSeconds < 0.0) { + timeOffsetSeconds = 0.0; // エラー回避 + } + + let lipSyncValue: number = Number.MAX_VALUE; + let eyeBlinkValue: number = Number.MAX_VALUE; + + //まばたき、リップシンクのうちモーションの適用を検出するためのビット(maxFlagCount個まで + const MaxTargetSize = 64; + let lipSyncFlags = 0; + let eyeBlinkFlags = 0; + + //瞬き、リップシンクのターゲット数が上限を超えている場合 + if (this._eyeBlinkParameterIds.getSize() > MaxTargetSize) { + CubismLogDebug( + 'too many eye blink targets : {0}', + this._eyeBlinkParameterIds.getSize() + ); + } + if (this._lipSyncParameterIds.getSize() > MaxTargetSize) { + CubismLogDebug( + 'too many lip sync targets : {0}', + this._lipSyncParameterIds.getSize() + ); + } + + const tmpFadeIn: number = + this._fadeInSeconds <= 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + this._fadeInSeconds + ); + + const tmpFadeOut: number = + this._fadeOutSeconds <= 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + this._fadeOutSeconds + ); + let value: number; + let c: number, parameterIndex: number; + + // 'Repeat' time as necessary. + let time: number = timeOffsetSeconds; + + if (this._isLoop) { + while (time > this._motionData.duration) { + time -= this._motionData.duration; + } + } + + const curves: csmVector = this._motionData.curves; + + // Evaluate model curves. + for ( + c = 0; + c < this._motionData.curveCount && + curves.at(c).type == + CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + ++c + ) { + // Evaluate curve and call handler. + value = evaluateCurve(this._motionData, c, time); + + if (curves.at(c).id == this._modelCurveIdEyeBlink) { + eyeBlinkValue = value; + } else if (curves.at(c).id == this._modelCurveIdLipSync) { + lipSyncValue = value; + } + } + + let parameterMotionCurveCount = 0; + + for ( + ; + c < this._motionData.curveCount && + curves.at(c).type == + CubismMotionCurveTarget.CubismMotionCurveTarget_Parameter; + ++c + ) { + parameterMotionCurveCount++; + + // Find parameter index. + parameterIndex = model.getParameterIndex(curves.at(c).id); + + // Skip curve evaluation if no value in sink. + if (parameterIndex == -1) { + continue; + } + + const sourceValue: number = model.getParameterValueByIndex( + parameterIndex + ); + + // Evaluate curve and apply value. + value = evaluateCurve(this._motionData, c, time); + + if (eyeBlinkValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._eyeBlinkParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + if (this._eyeBlinkParameterIds.at(i) == curves.at(c).id) { + value *= eyeBlinkValue; + eyeBlinkFlags |= 1 << i; + break; + } + } + } + + if (lipSyncValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._lipSyncParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + if (this._lipSyncParameterIds.at(i) == curves.at(c).id) { + value += lipSyncValue; + lipSyncFlags |= 1 << i; + break; + } + } + } + + let v: number; + + // パラメータごとのフェード + if (curves.at(c).fadeInTime < 0.0 && curves.at(c).fadeOutTime < 0.0) { + // モーションのフェードを適用 + v = sourceValue + (value - sourceValue) * fadeWeight; + } else { + // パラメータに対してフェードインかフェードアウトが設定してある場合はそちらを適用 + let fin: number; + let fout: number; + + if (curves.at(c).fadeInTime < 0.0) { + fin = tmpFadeIn; + } else { + fin = + curves.at(c).fadeInTime == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + curves.at(c).fadeInTime + ); + } + + if (curves.at(c).fadeOutTime < 0.0) { + fout = tmpFadeOut; + } else { + fout = + curves.at(c).fadeOutTime == 0.0 || + motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + curves.at(c).fadeOutTime + ); + } + + const paramWeight: number = this._weight * fin * fout; + + // パラメータごとのフェードを適用 + v = sourceValue + (value - sourceValue) * paramWeight; + } + + model.setParameterValueByIndex(parameterIndex, v, 1.0); + } + + { + if (eyeBlinkValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._eyeBlinkParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + const sourceValue: number = model.getParameterValueById( + this._eyeBlinkParameterIds.at(i) + ); + + // モーションでの上書きがあった時にはまばたきは適用しない + if ((eyeBlinkFlags >> i) & 0x01) { + continue; + } + + const v: number = + sourceValue + (eyeBlinkValue - sourceValue) * fadeWeight; + + model.setParameterValueById(this._eyeBlinkParameterIds.at(i), v); + } + } + + if (lipSyncValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._lipSyncParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + const sourceValue: number = model.getParameterValueById( + this._lipSyncParameterIds.at(i) + ); + + // モーションでの上書きがあった時にはリップシンクは適用しない + if ((lipSyncFlags >> i) & 0x01) { + continue; + } + + const v: number = + sourceValue + (lipSyncValue - sourceValue) * fadeWeight; + + model.setParameterValueById(this._lipSyncParameterIds.at(i), v); + } + } + } + + for ( + ; + c < this._motionData.curveCount && + curves.at(c).type == + CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; + ++c + ) { + // Find parameter index. + parameterIndex = model.getParameterIndex(curves.at(c).id); + + // Skip curve evaluation if no value in sink. + if (parameterIndex == -1) { + continue; + } + + // Evaluate curve and apply value. + value = evaluateCurve(this._motionData, c, time); + + model.setParameterValueByIndex(parameterIndex, value); + } + + if (timeOffsetSeconds >= this._motionData.duration) { + if (this._isLoop) { + motionQueueEntry.setStartTime(userTimeSeconds); // 最初の状態へ + if (this._isLoopFadeIn) { + // ループ内でループ用フェードインが有効の時は、フェードイン設定し直し + motionQueueEntry.setFadeInStartTime(userTimeSeconds); + } + } else { + if (this._onFinishedMotion) { + this._onFinishedMotion(this); + } + + motionQueueEntry.setIsFinished(true); + } + } + this._lastWeight = fadeWeight; + } + + /** + * ループ情報の設定 + * @param loop ループ情報 + */ + public setIsLoop(loop: boolean): void { + this._isLoop = loop; + } + + /** + * ループ情報の取得 + * @return true ループする + * @return false ループしない + */ + public isLoop(): boolean { + return this._isLoop; + } + + /** + * ループ時のフェードイン情報の設定 + * @param loopFadeIn ループ時のフェードイン情報 + */ + public setIsLoopFadeIn(loopFadeIn: boolean): void { + this._isLoopFadeIn = loopFadeIn; + } + + /** + * ループ時のフェードイン情報の取得 + * + * @return true する + * @return false しない + */ + public isLoopFadeIn(): boolean { + return this._isLoopFadeIn; + } + + /** + * モーションの長さを取得する。 + * + * @return モーションの長さ[秒] + */ + public getDuration(): number { + return this._isLoop ? -1.0 : this._loopDurationSeconds; + } + + /** + * モーションのループ時の長さを取得する。 + * + * @return モーションのループ時の長さ[秒] + */ + public getLoopDuration(): number { + return this._loopDurationSeconds; + } + + /** + * パラメータに対するフェードインの時間を設定する。 + * + * @param parameterId パラメータID + * @param value フェードインにかかる時間[秒] + */ + public setParameterFadeInTime( + parameterId: CubismIdHandle, + value: number + ): void { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + curves.at(i).fadeInTime = value; + return; + } + } + } + + /** + * パラメータに対するフェードアウトの時間の設定 + * @param parameterId パラメータID + * @param value フェードアウトにかかる時間[秒] + */ + public setParameterFadeOutTime( + parameterId: CubismIdHandle, + value: number + ): void { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + curves.at(i).fadeOutTime = value; + return; + } + } + } + + /** + * パラメータに対するフェードインの時間の取得 + * @param parameterId パラメータID + * @return フェードインにかかる時間[秒] + */ + public getParameterFadeInTime(parameterId: CubismIdHandle): number { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + return curves.at(i).fadeInTime; + } + } + + return -1; + } + + /** + * パラメータに対するフェードアウトの時間を取得 + * + * @param parameterId パラメータID + * @return フェードアウトにかかる時間[秒] + */ + public getParameterFadeOutTime(parameterId: CubismIdHandle): number { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + return curves.at(i).fadeOutTime; + } + } + + return -1; + } + + /** + * 自動エフェクトがかかっているパラメータIDリストの設定 + * @param eyeBlinkParameterIds 自動まばたきがかかっているパラメータIDのリスト + * @param lipSyncParameterIds リップシンクがかかっているパラメータIDのリスト + */ + public setEffectIds( + eyeBlinkParameterIds: csmVector, + lipSyncParameterIds: csmVector + ): void { + this._eyeBlinkParameterIds = eyeBlinkParameterIds; + this._lipSyncParameterIds = lipSyncParameterIds; + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + this._sourceFrameRate = 30.0; + this._loopDurationSeconds = -1.0; + this._isLoop = false; // trueから false へデフォルトを変更 + this._isLoopFadeIn = true; // ループ時にフェードインが有効かどうかのフラグ + this._lastWeight = 0.0; + this._motionData = null; + this._modelCurveIdEyeBlink = null; + this._modelCurveIdLipSync = null; + this._eyeBlinkParameterIds = null; + this._lipSyncParameterIds = null; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._motionData = void 0; + this._motionData = null; + } + + /** + * motion3.jsonをパースする。 + * + * @param motionJson motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parse(motionJson: ArrayBuffer, size: number): void { + this._motionData = new CubismMotionData(); + + let json: CubismMotionJson = new CubismMotionJson(motionJson, size); + + this._motionData.duration = json.getMotionDuration(); + this._motionData.loop = json.isMotionLoop(); + this._motionData.curveCount = json.getMotionCurveCount(); + this._motionData.fps = json.getMotionFps(); + this._motionData.eventCount = json.getEventCount(); + + if (json.isExistMotionFadeInTime()) { + this._fadeInSeconds = + json.getMotionFadeInTime() < 0.0 ? 1.0 : json.getMotionFadeInTime(); + } else { + this._fadeInSeconds = 1.0; + } + + if (json.isExistMotionFadeOutTime()) { + this._fadeOutSeconds = + json.getMotionFadeOutTime() < 0.0 ? 1.0 : json.getMotionFadeOutTime(); + } else { + this._fadeOutSeconds = 1.0; + } + + this._motionData.curves.updateSize( + this._motionData.curveCount, + CubismMotionCurve, + true + ); + this._motionData.segments.updateSize( + json.getMotionTotalSegmentCount(), + CubismMotionSegment, + true + ); + this._motionData.points.updateSize( + json.getMotionTotalPointCount(), + CubismMotionPoint, + true + ); + this._motionData.events.updateSize( + this._motionData.eventCount, + CubismMotionEvent, + true + ); + + let totalPointCount = 0; + let totalSegmentCount = 0; + + // Curves + for ( + let curveCount = 0; + curveCount < this._motionData.curveCount; + ++curveCount + ) { + if (json.getMotionCurveTarget(curveCount) == TargetNameModel) { + this._motionData.curves.at(curveCount).type = + CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + } else if ( + json.getMotionCurveTarget(curveCount) == TargetNameParameter + ) { + this._motionData.curves.at(curveCount).type = + CubismMotionCurveTarget.CubismMotionCurveTarget_Parameter; + } else if ( + json.getMotionCurveTarget(curveCount) == TargetNamePartOpacity + ) { + this._motionData.curves.at(curveCount).type = + CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; + } + + this._motionData.curves.at(curveCount).id = json.getMotionCurveId( + curveCount + ); + + this._motionData.curves.at( + curveCount + ).baseSegmentIndex = totalSegmentCount; + + this._motionData.curves.at( + curveCount + ).fadeInTime = json.isExistMotionCurveFadeInTime(curveCount) + ? json.getMotionCurveFadeInTime(curveCount) + : -1.0; + this._motionData.curves.at( + curveCount + ).fadeOutTime = json.isExistMotionCurveFadeOutTime(curveCount) + ? json.getMotionCurveFadeOutTime(curveCount) + : -1.0; + + // Segments + for ( + let segmentPosition = 0; + segmentPosition < json.getMotionCurveSegmentCount(curveCount); + + ) { + if (segmentPosition == 0) { + this._motionData.segments.at( + totalSegmentCount + ).basePointIndex = totalPointCount; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment(curveCount, segmentPosition); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + + totalPointCount += 1; + segmentPosition += 2; + } else { + this._motionData.segments.at(totalSegmentCount).basePointIndex = + totalPointCount - 1; + } + + const segment: number = json.getMotionCurveSegment( + curveCount, + segmentPosition + ); + switch (segment) { + case CubismMotionSegmentType.CubismMotionSegmentType_Linear: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Linear; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = linearEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + case CubismMotionSegmentType.CubismMotionSegmentType_Bezier: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Bezier; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = bezierEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + this._motionData.points.at( + totalPointCount + 1 + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 3 + ); + this._motionData.points.at( + totalPointCount + 1 + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 4 + ); + + this._motionData.points.at( + totalPointCount + 2 + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 5 + ); + this._motionData.points.at( + totalPointCount + 2 + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 6 + ); + + totalPointCount += 3; + segmentPosition += 7; + + break; + } + + case CubismMotionSegmentType.CubismMotionSegmentType_Stepped: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Stepped; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = steppedEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + + case CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = inverseSteppedEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + default: { + CSM_ASSERT(0); + break; + } + } + + ++this._motionData.curves.at(curveCount).segmentCount; + ++totalSegmentCount; + } + } + + for ( + let userdatacount = 0; + userdatacount < json.getEventCount(); + ++userdatacount + ) { + this._motionData.events.at(userdatacount).fireTime = json.getEventTime( + userdatacount + ); + this._motionData.events.at(userdatacount).value = json.getEventValue( + userdatacount + ); + } + + json.release(); + json = void 0; + json = null; + } + + /** + * モデルのパラメータ更新 + * + * イベント発火のチェック。 + * 入力する時間は呼ばれるモーションタイミングを0とした秒数で行う。 + * + * @param beforeCheckTimeSeconds 前回のイベントチェック時間[秒] + * @param motionTimeSeconds 今回の再生時間[秒] + */ + public getFiredEvent( + beforeCheckTimeSeconds: number, + motionTimeSeconds: number + ): csmVector { + this._firedEventValues.updateSize(0); + + // イベントの発火チェック + for (let u = 0; u < this._motionData.eventCount; ++u) { + if ( + this._motionData.events.at(u).fireTime > beforeCheckTimeSeconds && + this._motionData.events.at(u).fireTime <= motionTimeSeconds + ) { + this._firedEventValues.pushBack( + new csmString(this._motionData.events.at(u).value.s) + ); + } + } + + return this._firedEventValues; + } + + public _sourceFrameRate: number; // ロードしたファイルのFPS。記述が無ければデフォルト値15fpsとなる + public _loopDurationSeconds: number; // mtnファイルで定義される一連のモーションの長さ + public _isLoop: boolean; // ループするか? + public _isLoopFadeIn: boolean; // ループ時にフェードインが有効かどうかのフラグ。初期値では有効。 + public _lastWeight: number; // 最後に設定された重み + + public _motionData: CubismMotionData; // 実際のモーションデータ本体 + + public _eyeBlinkParameterIds: csmVector; // 自動まばたきを適用するパラメータIDハンドルのリスト。 モデル(モデルセッティング)とパラメータを対応付ける。 + public _lipSyncParameterIds: csmVector; // リップシンクを適用するパラメータIDハンドルのリスト。 モデル(モデルセッティング)とパラメータを対応付ける。 + + public _modelCurveIdEyeBlink: CubismIdHandle; // モデルが持つ自動まばたき用パラメータIDのハンドル。 モデルとモーションを対応付ける。 + public _modelCurveIdLipSync: CubismIdHandle; // モデルが持つリップシンク用パラメータIDのハンドル。 モデルとモーションを対応付ける。 + } +} diff --git a/src/motion/cubismmotioninternal.ts b/src/motion/cubismmotioninternal.ts new file mode 100644 index 0000000..705a336 --- /dev/null +++ b/src/motion/cubismmotioninternal.ts @@ -0,0 +1,140 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import csmVector = csmvector.csmVector; +import csmString = csmstring.csmString; +import CubismIdHandle = cubismid.CubismIdHandle; + +export namespace Live2DCubismFramework { + /** + * @brief モーションカーブの種類 + * + * モーションカーブの種類。 + */ + export enum CubismMotionCurveTarget { + CubismMotionCurveTarget_Model, // モデルに対して + CubismMotionCurveTarget_Parameter, // パラメータに対して + CubismMotionCurveTarget_PartOpacity // パーツの不透明度に対して + } + + /** + * @brief モーションカーブのセグメントの種類 + * + * モーションカーブのセグメントの種類。 + */ + export enum CubismMotionSegmentType { + CubismMotionSegmentType_Linear = 0, // リニア + CubismMotionSegmentType_Bezier = 1, // ベジェ曲線 + CubismMotionSegmentType_Stepped = 2, // ステップ + CubismMotionSegmentType_InverseStepped = 3 // インバースステップ + } + + /** + * @brief モーションカーブの制御点 + * + * モーションカーブの制御点。 + */ + export class CubismMotionPoint { + time = 0.0; // 時間[秒] + value = 0.0; // 値 + } + + /** + * モーションカーブのセグメントの評価関数 + * + * @param points モーションカーブの制御点リスト + * @param time 評価する時間[秒] + */ + export interface csmMotionSegmentEvaluationFunction { + (points: CubismMotionPoint[], time: number): number; + } + + /** + * @brief モーションカーブのセグメント + * + * モーションカーブのセグメント。 + */ + export class CubismMotionSegment { + /** + * @brief コンストラクタ + * + * コンストラクタ。 + */ + public constructor() { + this.evaluate = null; + this.basePointIndex = 0; + this.segmentType = 0; + } + + evaluate: csmMotionSegmentEvaluationFunction; // 使用する評価関数 + basePointIndex: number; // 最初のセグメントへのインデックス + segmentType: number; // セグメントの種類 + } + + /** + * @brief モーションカーブ + * + * モーションカーブ。 + */ + export class CubismMotionCurve { + public constructor() { + this.type = CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + this.segmentCount = 0; + this.baseSegmentIndex = 0; + this.fadeInTime = 0.0; + this.fadeOutTime = 0.0; + } + + type: CubismMotionCurveTarget; // カーブの種類 + id: CubismIdHandle; // カーブのID + segmentCount: number; // セグメントの個数 + baseSegmentIndex: number; // 最初のセグメントのインデックス + fadeInTime: number; // フェードインにかかる時間[秒] + fadeOutTime: number; // フェードアウトにかかる時間[秒] + } + + /** + * イベント。 + */ + export class CubismMotionEvent { + fireTime = 0.0; + value: csmString; + } + + /** + * @brief モーションデータ + * + * モーションデータ。 + */ + export class CubismMotionData { + public constructor() { + this.duration = 0.0; + this.loop = false; + this.curveCount = 0; + this.eventCount = 0; + this.fps = 0.0; + + this.curves = new csmVector(); + this.segments = new csmVector(); + this.points = new csmVector(); + this.events = new csmVector(); + } + + duration: number; // モーションの長さ[秒] + loop: boolean; // ループするかどうか + curveCount: number; // カーブの個数 + eventCount: number; // UserDataの個数 + fps: number; // フレームレート + curves: csmVector; // カーブのリスト + segments: csmVector; // セグメントのリスト + points: csmVector; // ポイントのリスト + events: csmVector; // イベントのリスト + } +} diff --git a/src/motion/cubismmotionjson.ts b/src/motion/cubismmotionjson.ts new file mode 100644 index 0000000..77eed8f --- /dev/null +++ b/src/motion/cubismmotionjson.ts @@ -0,0 +1,359 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismjson } from '../utils/cubismjson'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import csmString = csmstring.csmString; +import CubismFramework = cubismframework.CubismFramework; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismJson = cubismjson.CubismJson; + +export namespace Live2DCubismFramework { + // JSON keys + const Meta = 'Meta'; + const Duration = 'Duration'; + const Loop = 'Loop'; + const CurveCount = 'CurveCount'; + const Fps = 'Fps'; + const TotalSegmentCount = 'TotalSegmentCount'; + const TotalPointCount = 'TotalPointCount'; + const Curves = 'Curves'; + const Target = 'Target'; + const Id = 'Id'; + const FadeInTime = 'FadeInTime'; + const FadeOutTime = 'FadeOutTime'; + const Segments = 'Segments'; + const UserData = 'UserData'; + const UserDataCount = 'UserDataCount'; + const TotalUserDataSize = 'TotalUserDataSize'; + const Time = 'Time'; + const Value = 'Value'; + + /** + * motion3.jsonのコンテナ。 + */ + export class CubismMotionJson { + /** + * コンストラクタ + * @param buffer motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * モーションの長さを取得する + * @return モーションの長さ[秒] + */ + public getMotionDuration(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Duration) + .toFloat(); + } + + /** + * モーションのループ情報の取得 + * @return true ループする + * @return false ループしない + */ + public isMotionLoop(): boolean { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Loop) + .toBoolean(); + } + + /** + * モーションカーブの個数の取得 + * @return モーションカーブの個数 + */ + public getMotionCurveCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(CurveCount) + .toInt(); + } + + /** + * モーションのフレームレートの取得 + * @return フレームレート[FPS] + */ + public getMotionFps(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Fps) + .toFloat(); + } + + /** + * モーションのセグメントの総合計の取得 + * @return モーションのセグメントの取得 + */ + public getMotionTotalSegmentCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalSegmentCount) + .toInt(); + } + + /** + * モーションのカーブの制御店の総合計の取得 + * @return モーションのカーブの制御点の総合計 + */ + public getMotionTotalPointCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalPointCount) + .toInt(); + } + + /** + * モーションのフェードイン時間の存在 + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionFadeInTime(): boolean { + return !this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeInTime) + .isNull(); + } + + /** + * モーションのフェードアウト時間の存在 + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionFadeOutTime(): boolean { + return !this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeOutTime) + .isNull(); + } + + /** + * モーションのフェードイン時間の取得 + * @return フェードイン時間[秒] + */ + public getMotionFadeInTime(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーションのフェードアウト時間の取得 + * @return フェードアウト時間[秒] + */ + public getMotionFadeOutTime(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * モーションのカーブの種類の取得 + * @param curveIndex カーブのインデックス + * @return カーブの種類 + */ + public getMotionCurveTarget(curveIndex: number): string { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Target) + .getRawString(); + } + + /** + * モーションのカーブのIDの取得 + * @param curveIndex カーブのインデックス + * @return カーブのID + */ + public getMotionCurveId(curveIndex: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * モーションのカーブのフェードイン時間の存在 + * @param curveIndex カーブのインデックス + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionCurveFadeInTime(curveIndex: number): boolean { + return !this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeInTime) + .isNull(); + } + + /** + * モーションのカーブのフェードアウト時間の存在 + * @param curveIndex カーブのインデックス + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionCurveFadeOutTime(curveIndex: number): boolean { + return !this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeOutTime) + .isNull(); + } + + /** + * モーションのカーブのフェードイン時間の取得 + * @param curveIndex カーブのインデックス + * @return フェードイン時間[秒] + */ + public getMotionCurveFadeInTime(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーションのカーブのフェードアウト時間の取得 + * @param curveIndex カーブのインデックス + * @return フェードアウト時間[秒] + */ + public getMotionCurveFadeOutTime(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * モーションのカーブのセグメントの個数を取得する + * @param curveIndex カーブのインデックス + * @return モーションのカーブのセグメントの個数 + */ + public getMotionCurveSegmentCount(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Segments) + .getVector() + .getSize(); + } + + /** + * モーションのカーブのセグメントの値の取得 + * @param curveIndex カーブのインデックス + * @param segmentIndex セグメントのインデックス + * @return セグメントの値 + */ + public getMotionCurveSegment( + curveIndex: number, + segmentIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Segments) + .getValueByIndex(segmentIndex) + .toFloat(); + } + + /** + * イベントの個数の取得 + * @return イベントの個数 + */ + public getEventCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(UserDataCount) + .toInt(); + } + + /** + * イベントの総文字数の取得 + * @return イベントの総文字数 + */ + public getTotalEventValueSize(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalUserDataSize) + .toInt(); + } + + /** + * イベントの時間の取得 + * @param userDataIndex イベントのインデックス + * @return イベントの時間[秒] + */ + public getEventTime(userDataIndex: number): number { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(userDataIndex) + .getValueByString(Time) + .toInt(); + } + + /** + * イベントの取得 + * @param userDataIndex イベントのインデックス + * @return イベントの文字列 + */ + public getEventValue(userDataIndex: number): csmString { + return new csmString( + this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(userDataIndex) + .getValueByString(Value) + .getRawString() + ); + } + + _json: CubismJson; // motion3.jsonのデータ + } +} diff --git a/src/motion/cubismmotionmanager.ts b/src/motion/cubismmotionmanager.ts new file mode 100644 index 0000000..5f24d9d --- /dev/null +++ b/src/motion/cubismmotionmanager.ts @@ -0,0 +1,124 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmotionqueuemanager } from './cubismmotionqueuemanager'; +import { Live2DCubismFramework as acubismmotion } from './acubismmotion'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import CubismMotionQueueEntryHandle = cubismmotionqueuemanager.CubismMotionQueueEntryHandle; +import CubismModel = cubismmodel.CubismModel; +import ACubismMotion = acubismmotion.ACubismMotion; +import CubismMotionQueueManager = cubismmotionqueuemanager.CubismMotionQueueManager; + +export namespace Live2DCubismFramework { + /** + * モーションの管理 + * + * モーションの管理を行うクラス + */ + export class CubismMotionManager extends CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._currentPriority = 0; + this._reservePriority = 0; + } + + /** + * 再生中のモーションの優先度の取得 + * @return モーションの優先度 + */ + public getCurrentPriority(): number { + return this._currentPriority; + } + + /** + * 予約中のモーションの優先度を取得する。 + * @return モーションの優先度 + */ + public getReservePriority(): number { + return this._reservePriority; + } + + /** + * 予約中のモーションの優先度を設定する。 + * @param val 優先度 + */ + public setReservePriority(val: number): void { + this._reservePriority = val; + } + + /** + * 優先度を設定してモーションを開始する。 + * + * @param motion モーション + * @param autoDelete 再生が狩猟したモーションのインスタンスを削除するならtrue + * @param priority 優先度 + * @return 開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」 + */ + public startMotionPriority( + motion: ACubismMotion, + autoDelete: boolean, + priority: number + ): CubismMotionQueueEntryHandle { + if (priority == this._reservePriority) { + this._reservePriority = 0; // 予約を解除 + } + + this._currentPriority = priority; // 再生中モーションの優先度を設定 + + return super.startMotion(motion, autoDelete, this._userTimeSeconds); + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + * @return true 更新されている + * @return false 更新されていない + */ + public updateMotion(model: CubismModel, deltaTimeSeconds: number): boolean { + this._userTimeSeconds += deltaTimeSeconds; + + const updated: boolean = super.doUpdateMotion( + model, + this._userTimeSeconds + ); + + if (this.isFinished()) { + this._currentPriority = 0; // 再生中のモーションの優先度を解除 + } + + return updated; + } + + /** + * モーションを予約する。 + * + * @param priority 優先度 + * @return true 予約できた + * @return false 予約できなかった + */ + public reserveMotion(priority: number): boolean { + if ( + priority <= this._reservePriority || + priority <= this._currentPriority + ) { + return false; + } + + this._reservePriority = priority; + + return true; + } + + _currentPriority: number; // 現在再生中のモーションの優先度 + _reservePriority: number; // 再生予定のモーションの優先度。再生中は0になる。モーションファイルを別スレッドで読み込むときの機能。 + } +} diff --git a/src/motion/cubismmotionqueueentry.ts b/src/motion/cubismmotionqueueentry.ts new file mode 100644 index 0000000..6f57b70 --- /dev/null +++ b/src/motion/cubismmotionqueueentry.ts @@ -0,0 +1,219 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as acubismmotion } from './acubismmotion'; +import { Live2DCubismFramework as cubismmotionqueuemanager } from './cubismmotionqueuemanager'; +import CubismMotionQueueEntryHandle = cubismmotionqueuemanager.CubismMotionQueueEntryHandle; +import ACubismMotion = acubismmotion.ACubismMotion; + +export namespace Live2DCubismFramework { + /** + * CubismMotionQueueManagerで再生している各モーションの管理クラス。 + */ + export class CubismMotionQueueEntry { + /** + * コンストラクタ + */ + public constructor() { + this._autoDelete = false; + this._motion = null; + this._available = true; + this._finished = false; + this._started = false; + this._startTimeSeconds = -1.0; + this._fadeInStartTimeSeconds = 0.0; + this._endTimeSeconds = -1.0; + this._stateTimeSeconds = 0.0; + this._stateWeight = 0.0; + this._lastEventCheckSeconds = 0.0; + this._motionQueueEntryHandle = this; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._autoDelete && this._motion) { + ACubismMotion.delete(this._motion); // + } + } + + /** + * フェードアウトの開始 + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + * @param userTimeSeconds デルタ時間の積算値[秒] + */ + public startFadeout(fadeoutSeconds: number, userTimeSeconds: number): void { + const newEndTimeSeconds: number = userTimeSeconds + fadeoutSeconds; + + if ( + this._endTimeSeconds < 0.0 || + newEndTimeSeconds < this._endTimeSeconds + ) { + this._endTimeSeconds = newEndTimeSeconds; + } + } + + /** + * モーションの終了の確認 + * + * @return true モーションが終了した + * @return false 終了していない + */ + public isFinished(): boolean { + return this._finished; + } + + /** + * モーションの開始の確認 + * @return true モーションが開始した + * @return false 開始していない + */ + public isStarted(): boolean { + return this._started; + } + + /** + * モーションの開始時刻の取得 + * @return モーションの開始時刻[秒] + */ + public getStartTime(): number { + return this._startTimeSeconds; + } + + /** + * フェードインの開始時刻の取得 + * @return フェードインの開始時刻[秒] + */ + public getFadeInStartTime(): number { + return this._fadeInStartTimeSeconds; + } + + /** + * フェードインの終了時刻の取得 + * @return フェードインの終了時刻の取得 + */ + public getEndTime(): number { + return this._endTimeSeconds; + } + + /** + * モーションの開始時刻の設定 + * @param startTime モーションの開始時刻 + */ + public setStartTime(startTime: number): void { + this._startTimeSeconds = startTime; + } + + /** + * フェードインの開始時刻の設定 + * @param startTime フェードインの開始時刻[秒] + */ + public setFadeInStartTime(startTime: number): void { + this._fadeInStartTimeSeconds = startTime; + } + + /** + * フェードインの終了時刻の設定 + * @param endTime フェードインの終了時刻[秒] + */ + public setEndTime(endTime: number): void { + this._endTimeSeconds = endTime; + } + + /** + * モーションの終了の設定 + * @param f trueならモーションの終了 + */ + public setIsFinished(f: boolean): void { + this._finished = f; + } + + /** + * モーション開始の設定 + * @param f trueならモーションの開始 + */ + public setIsStarted(f: boolean): void { + this._started = f; + } + + /** + * モーションの有効性の確認 + * @return true モーションは有効 + * @return false モーションは無効 + */ + public isAvailable(): boolean { + return this._available; + } + + /** + * モーションの有効性の設定 + * @param v trueならモーションは有効 + */ + public setIsAvailable(v: boolean): void { + this._available = v; + } + + /** + * モーションの状態の設定 + * @param timeSeconds 現在時刻[秒] + * @param weight モーション尾重み + */ + public setState(timeSeconds: number, weight: number): void { + this._stateTimeSeconds = timeSeconds; + this._stateWeight = weight; + } + + /** + * モーションの現在時刻の取得 + * @return モーションの現在時刻[秒] + */ + public getStateTime(): number { + return this._stateTimeSeconds; + } + + /** + * モーションの重みの取得 + * @return モーションの重み + */ + public getStateWeight(): number { + return this._stateWeight; + } + + /** + * 最後にイベントの発火をチェックした時間を取得 + * + * @return 最後にイベントの発火をチェックした時間[秒] + */ + public getLastCheckEventTime(): number { + return this._lastEventCheckSeconds; + } + + /** + * 最後にイベントをチェックした時間を設定 + * @param checkTime 最後にイベントをチェックした時間[秒] + */ + public setLastCheckEventTime(checkTime: number): void { + this._lastEventCheckSeconds = checkTime; + } + + _autoDelete: boolean; // 自動削除 + _motion: ACubismMotion; // モーション + + _available: boolean; // 有効化フラグ + _finished: boolean; // 終了フラグ + _started: boolean; // 開始フラグ + _startTimeSeconds: number; // モーション再生開始時刻[秒] + _fadeInStartTimeSeconds: number; // フェードイン開始時刻(ループの時は初回のみ)[秒] + _endTimeSeconds: number; // 終了予定時刻[秒] + _stateTimeSeconds: number; // 時刻の状態[秒] + _stateWeight: number; // 重みの状態 + _lastEventCheckSeconds: number; // 最終のMotion側のチェックした時間 + + _motionQueueEntryHandle: CubismMotionQueueEntryHandle; // インスタンスごとに一意の値を持つ識別番号 + } +} diff --git a/src/motion/cubismmotionqueuemanager.ts b/src/motion/cubismmotionqueuemanager.ts new file mode 100644 index 0000000..5251803 --- /dev/null +++ b/src/motion/cubismmotionqueuemanager.ts @@ -0,0 +1,347 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as acubismmotion } from './acubismmotion'; +import { Live2DCubismFramework as cubismmotionqueueentry } from './cubismmotionqueueentry'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import csmString = csmstring.csmString; +import CubismModel = cubismmodel.CubismModel; +import csmVector = csmvector.csmVector; +import iterator = csmvector.iterator; +import CubismMotionQueueEntry = cubismmotionqueueentry.CubismMotionQueueEntry; +import ACubismMotion = acubismmotion.ACubismMotion; + +export namespace Live2DCubismFramework { + /** + * モーション再生の管理 + * + * モーション再生の管理用クラス。CubismMotionモーションなどACubismMotionのサブクラスを再生するために使用する。 + * + * @note 再生中に別のモーションが StartMotion()された場合は、新しいモーションに滑らかに変化し旧モーションは中断する。 + * 表情用モーション、体用モーションなどを分けてモーション化した場合など、 + * 複数のモーションを同時に再生させる場合は、複数のCubismMotionQueueManagerインスタンスを使用する。 + */ + export class CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + this._userTimeSeconds = 0.0; + this._eventCallBack = null; + this._eventCustomData = null; + this._motions = new csmVector(); + } + + /** + * デストラクタ + */ + public release(): void { + for (let i = 0; i < this._motions.getSize(); ++i) { + if (this._motions.at(i)) { + this._motions.at(i).release(); + this._motions.set(i, void 0); + this._motions.set(i, null); + } + } + + this._motions = null; + } + + /** + * 指定したモーションの開始 + * + * 指定したモーションを開始する。同じタイプのモーションが既にある場合は、既存のモーションに終了フラグを立て、フェードアウトを開始させる。 + * + * @param motion 開始するモーション + * @param autoDelete 再生が終了したモーションのインスタンスを削除するなら true + * @param userTimeSeconds デルタ時間の積算値[秒] + * @return 開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」 + */ + public startMotion( + motion: ACubismMotion, + autoDelete: boolean, + userTimeSeconds: number + ): CubismMotionQueueEntryHandle { + if (motion == null) { + return InvalidMotionQueueEntryHandleValue; + } + + let motionQueueEntry: CubismMotionQueueEntry = null; + + // 既にモーションがあれば終了フラグを立てる + for (let i = 0; i < this._motions.getSize(); ++i) { + motionQueueEntry = this._motions.at(i); + if (motionQueueEntry == null) { + continue; + } + + motionQueueEntry.startFadeout( + motionQueueEntry._motion.getFadeOutTime(), + userTimeSeconds + ); // フェードアウトを開始し終了する + } + + motionQueueEntry = new CubismMotionQueueEntry(); // 終了時に破棄する + motionQueueEntry._autoDelete = autoDelete; + motionQueueEntry._motion = motion; + + this._motions.pushBack(motionQueueEntry); + + return motionQueueEntry._motionQueueEntryHandle; + } + + /** + * 全てのモーションの終了の確認 + * @return true 全て終了している + * @return false 終了していない + */ + public isFinished(): boolean { + // ------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + + ) { + let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + ite = this._motions.erase(ite); // 削除 + continue; + } + + const motion: ACubismMotion = motionQueueEntry._motion; + + if (motion == null) { + motionQueueEntry.release(); + motionQueueEntry = void 0; + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + continue; + } + + // ----- 終了済みの処理があれば削除する ------ + if (!motionQueueEntry.isFinished()) { + return false; + } else { + ite.preIncrement(); + } + } + + return true; + } + + /** + * 指定したモーションの終了の確認 + * @param motionQueueEntryNumber モーションの識別番号 + * @return true 全て終了している + * @return false 終了していない + */ + public isFinishedByHandle( + motionQueueEntryNumber: CubismMotionQueueEntryHandle + ): boolean { + // 既にモーションがあれば終了フラグを立てる + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + ite.increment() + ) { + const motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + continue; + } + + if ( + motionQueueEntry._motionQueueEntryHandle == motionQueueEntryNumber && + !motionQueueEntry.isFinished() + ) { + return false; + } + } + return true; + } + + /** + * 全てのモーションを停止する + */ + public stopAllMotions(): void { + // ------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + + ) { + let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + ite = this._motions.erase(ite); + + continue; + } + + // ----- 終了済みの処理があれば削除する ------ + motionQueueEntry.release(); + motionQueueEntry = void 0; + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + } + } + + /** + * 指定したCubismMotionQueueEntryの取得 + + * @param motionQueueEntryNumber モーションの識別番号 + * @return 指定したCubismMotionQueueEntry + * @return null 見つからなかった + */ + public getCubismMotionQueueEntry( + motionQueueEntryNumber: any + ): CubismMotionQueueEntry { + //------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + ite.preIncrement() + ) { + const motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + continue; + } + + if ( + motionQueueEntry._motionQueueEntryHandle == motionQueueEntryNumber + ) { + return motionQueueEntry; + } + } + + return null; + } + + /** + * イベントを受け取るCallbackの登録 + * + * @param callback コールバック関数 + * @param customData コールバックに返されるデータ + */ + public setEventCallback( + callback: CubismMotionEventFunction, + customData: any = null + ): void { + this._eventCallBack = callback; + this._eventCustomData = customData; + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @return true モデルへパラメータ値の反映あり + * @return false モデルへパラメータ値の反映なし(モーションの変化なし) + */ + public doUpdateMotion( + model: CubismModel, + userTimeSeconds: number + ): boolean { + let updated = false; + + // ------- 処理を行う -------- + // 既にモーションがあれば終了フラグを立てる + + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + + ) { + let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + ite = this._motions.erase(ite); // 削除 + continue; + } + + const motion: ACubismMotion = motionQueueEntry._motion; + + if (motion == null) { + motionQueueEntry.release(); + motionQueueEntry = void 0; + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + + continue; + } + + // ------ 値を反映する ------ + motion.updateParameters(model, motionQueueEntry, userTimeSeconds); + updated = true; + + // ------ ユーザトリガーイベントを検査する ---- + const firedList: csmVector = motion.getFiredEvent( + motionQueueEntry.getLastCheckEventTime() - + motionQueueEntry.getStartTime(), + userTimeSeconds - motionQueueEntry.getStartTime() + ); + + for (let i = 0; i < firedList.getSize(); ++i) { + this._eventCallBack(this, firedList.at(i), this._eventCustomData); + } + + motionQueueEntry.setLastCheckEventTime(userTimeSeconds); + + // ------ 終了済みの処理があれば削除する ------ + if (motionQueueEntry.isFinished()) { + motionQueueEntry.release(); + motionQueueEntry = void 0; + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + } else { + ite.preIncrement(); + } + } + + return updated; + } + _userTimeSeconds: number; // デルタ時間の積算値[秒] + + _motions: csmVector; // モーション + _eventCallBack: CubismMotionEventFunction; // コールバック関数 + _eventCustomData: any; // コールバックに戻されるデータ + } + + /** + * イベントのコールバック関数を定義 + * + * イベントのコールバックに登録できる関数の型情報 + * @param caller 発火したイベントを再生させたCubismMotionQueueManager + * @param eventValue 発火したイベントの文字列データ + * @param customData コールバックに返される登録時に指定されたデータ + */ + export interface CubismMotionEventFunction { + ( + caller: CubismMotionQueueManager, + eventValue: csmString, + customData: any + ): void; + } + + /** + * モーションの識別番号 + * + * モーションの識別番号の定義 + */ + export declare type CubismMotionQueueEntryHandle = any; + export const InvalidMotionQueueEntryHandleValue: CubismMotionQueueEntryHandle = -1; +} diff --git a/src/physics/cubismphysics.ts b/src/physics/cubismphysics.ts new file mode 100644 index 0000000..0d6d65b --- /dev/null +++ b/src/physics/cubismphysics.ts @@ -0,0 +1,949 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismphysicsinternal } from './cubismphysicsinternal'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubismvector2 } from '../math/cubismvector2'; +import { Live2DCubismFramework as cubismmath } from '../math/cubismmath'; +import { Live2DCubismFramework as cubismphysicsjson } from './cubismphysicsjson'; +import CubismPhysicsJson = cubismphysicsjson.CubismPhysicsJson; +import CubismMath = cubismmath.CubismMath; +import CubismPhysicsRig = cubismphysicsinternal.CubismPhysicsRig; +import CubismPhysicsSubRig = cubismphysicsinternal.CubismPhysicsSubRig; +import CubismPhysicsInput = cubismphysicsinternal.CubismPhysicsInput; +import CubismPhysicsOutput = cubismphysicsinternal.CubismPhysicsOutput; +import CubismPhysicsParticle = cubismphysicsinternal.CubismPhysicsParticle; +import CubismPhysicsSource = cubismphysicsinternal.CubismPhysicsSource; +import CubismPhysicsTargetType = cubismphysicsinternal.CubismPhysicsTargetType; +import CubismPhysicsNormalization = cubismphysicsinternal.CubismPhysicsNormalization; +import CubismVector2 = cubismvector2.CubismVector2; +import CubismModel = cubismmodel.CubismModel; + +export namespace Live2DCubismFramework { + // physics types tags. + const PhysicsTypeTagX = 'X'; + const PhysicsTypeTagY = 'Y'; + const PhysicsTypeTagAngle = 'Angle'; + + // Constant of air resistance. + const AirResistance = 5.0; + + // Constant of maximum weight of input and output ratio. + const MaximumWeight = 100.0; + + // Constant of threshold of movement. + const MovementThreshold = 0.001; + + /** + * 物理演算クラス + */ + export class CubismPhysics { + /** + * インスタンスの作成 + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create(buffer: ArrayBuffer, size: number): CubismPhysics { + const ret: CubismPhysics = new CubismPhysics(); + + ret.parse(buffer, size); + ret._physicsRig.gravity.y = 0; + + return ret; + } + + /** + * インスタンスを破棄する + * @param physics 破棄するインスタンス + */ + public static delete(physics: CubismPhysics): void { + if (physics != null) { + physics.release(); + physics = null; + } + } + + /** + * 物理演算の評価 + * @param model 物理演算の結果を適用するモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public evaluate(model: CubismModel, deltaTimeSeconds: number): void { + let totalAngle: { angle: number }; + let weight: number; + let radAngle: number; + let outputValue: number; + const totalTranslation: CubismVector2 = new CubismVector2(); + let currentSetting: CubismPhysicsSubRig; + let currentInput: CubismPhysicsInput[]; + let currentOutput: CubismPhysicsOutput[]; + let currentParticles: CubismPhysicsParticle[]; + + let parameterValue: Float32Array; + let parameterMaximumValue: Float32Array; + let parameterMinimumValue: Float32Array; + let parameterDefaultValue: Float32Array; + + parameterValue = model.getModel().parameters.values; + parameterMaximumValue = model.getModel().parameters.maximumValues; + parameterMinimumValue = model.getModel().parameters.minimumValues; + parameterDefaultValue = model.getModel().parameters.defaultValues; + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + totalAngle = { angle: 0.0 }; + totalTranslation.x = 0.0; + totalTranslation.y = 0.0; + currentSetting = this._physicsRig.settings.at(settingIndex); + currentInput = this._physicsRig.inputs.get( + currentSetting.baseInputIndex + ); + currentOutput = this._physicsRig.outputs.get( + currentSetting.baseOutputIndex + ); + currentParticles = this._physicsRig.particles.get( + currentSetting.baseParticleIndex + ); + + // Load input parameters + for (let i = 0; i < currentSetting.inputCount; ++i) { + weight = currentInput[i].weight / MaximumWeight; + + if (currentInput[i].sourceParameterIndex == -1) { + currentInput[i].sourceParameterIndex = model.getParameterIndex( + currentInput[i].source.id + ); + } + + currentInput[i].getNormalizedParameterValue( + totalTranslation, + totalAngle, + parameterValue[currentInput[i].sourceParameterIndex], + parameterMinimumValue[currentInput[i].sourceParameterIndex], + parameterMaximumValue[currentInput[i].sourceParameterIndex], + parameterDefaultValue[currentInput[i].sourceParameterIndex], + currentSetting.normalizationPosition, + currentSetting.normalizationAngle, + currentInput[0].reflect, + weight + ); + } + + radAngle = CubismMath.degreesToRadian(-totalAngle.angle); + + totalTranslation.x = + totalTranslation.x * CubismMath.cos(radAngle) - + totalTranslation.y * CubismMath.sin(radAngle); + totalTranslation.y = + totalTranslation.x * CubismMath.sin(radAngle) + + totalTranslation.y * CubismMath.cos(radAngle); + + // Calculate particles position. + updateParticles( + currentParticles, + currentSetting.particleCount, + totalTranslation, + totalAngle.angle, + this._options.wind, + MovementThreshold * currentSetting.normalizationPosition.maximum, + deltaTimeSeconds, + AirResistance + ); + + // Update output parameters. + for (let i = 0; i < currentSetting.outputCount; ++i) { + const particleIndex = currentOutput[i].vertexIndex; + + if ( + particleIndex < 1 || + particleIndex >= currentSetting.particleCount + ) { + break; + } + + if (currentOutput[i].destinationParameterIndex == -1) { + currentOutput[ + i + ].destinationParameterIndex = model.getParameterIndex( + currentOutput[i].destination.id + ); + } + + const translation: CubismVector2 = new CubismVector2(); + translation.x = + currentParticles[particleIndex].position.x - + currentParticles[particleIndex - 1].position.x; + translation.y = + currentParticles[particleIndex].position.y - + currentParticles[particleIndex - 1].position.y; + + outputValue = currentOutput[i].getValue( + translation, + currentParticles, + particleIndex, + currentOutput[i].reflect, + this._options.gravity + ); + + const destinationParameterIndex: number = + currentOutput[i].destinationParameterIndex; + const outParameterValue: Float32Array = + !Float32Array.prototype.slice && + 'subarray' in Float32Array.prototype + ? JSON.parse( + JSON.stringify( + parameterValue.subarray(destinationParameterIndex) + ) + ) // 値渡しするため、JSON.parse, JSON.stringify + : parameterValue.slice(destinationParameterIndex); + + updateOutputParameterValue( + outParameterValue, + parameterMinimumValue[destinationParameterIndex], + parameterMaximumValue[destinationParameterIndex], + outputValue, + currentOutput[i] + ); + + // 値を反映 + for ( + let offset: number = destinationParameterIndex, outParamIndex = 0; + offset < parameterValue.length; + offset++, outParamIndex++ + ) { + parameterValue[offset] = outParameterValue[outParamIndex]; + } + } + } + } + + /** + * オプションの設定 + * @param options オプション + */ + public setOptions(options: Options): void { + this._options = options; + } + + /** + * オプションの取得 + * @return オプション + */ + public getOption(): Options { + return this._options; + } + + /** + * コンストラクタ + */ + public constructor() { + this._physicsRig = null; + + // set default options + this._options = new Options(); + this._options.gravity.y = -1.0; + this._options.gravity.x = 0; + this._options.wind.x = 0; + this._options.wind.y = 0; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._physicsRig = void 0; + this._physicsRig = null; + } + + /** + * physics3.jsonをパースする。 + * @param physicsJson physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parse(physicsJson: ArrayBuffer, size: number): void { + this._physicsRig = new CubismPhysicsRig(); + + let json: CubismPhysicsJson = new CubismPhysicsJson(physicsJson, size); + + this._physicsRig.gravity = json.getGravity(); + this._physicsRig.wind = json.getWind(); + this._physicsRig.subRigCount = json.getSubRigCount(); + + this._physicsRig.settings.updateSize( + this._physicsRig.subRigCount, + CubismPhysicsSubRig, + true + ); + this._physicsRig.inputs.updateSize( + json.getTotalInputCount(), + CubismPhysicsInput, + true + ); + this._physicsRig.outputs.updateSize( + json.getTotalOutputCount(), + CubismPhysicsOutput, + true + ); + this._physicsRig.particles.updateSize( + json.getVertexCount(), + CubismPhysicsParticle, + true + ); + + let inputIndex = 0, + outputIndex = 0, + particleIndex = 0; + + for (let i = 0; i < this._physicsRig.settings.getSize(); ++i) { + this._physicsRig.settings.at( + i + ).normalizationPosition.minimum = json.getNormalizationPositionMinimumValue( + i + ); + this._physicsRig.settings.at( + i + ).normalizationPosition.maximum = json.getNormalizationPositionMaximumValue( + i + ); + this._physicsRig.settings.at( + i + ).normalizationPosition.defalut = json.getNormalizationPositionDefaultValue( + i + ); + + this._physicsRig.settings.at( + i + ).normalizationAngle.minimum = json.getNormalizationAngleMinimumValue( + i + ); + this._physicsRig.settings.at( + i + ).normalizationAngle.maximum = json.getNormalizationAngleMaximumValue( + i + ); + this._physicsRig.settings.at( + i + ).normalizationAngle.defalut = json.getNormalizationAngleDefaultValue( + i + ); + + // Input + this._physicsRig.settings.at(i).inputCount = json.getInputCount(i); + this._physicsRig.settings.at(i).baseInputIndex = inputIndex; + + for (let j = 0; j < this._physicsRig.settings.at(i).inputCount; ++j) { + this._physicsRig.inputs.at(inputIndex + j).sourceParameterIndex = -1; + this._physicsRig.inputs.at( + inputIndex + j + ).weight = json.getInputWeight(i, j); + this._physicsRig.inputs.at( + inputIndex + j + ).reflect = json.getInputReflect(i, j); + + if (json.getInputType(i, j) == PhysicsTypeTagX) { + this._physicsRig.inputs.at(inputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_X; + this._physicsRig.inputs.at( + inputIndex + j + ).getNormalizedParameterValue = getInputTranslationXFromNormalizedParameterValue; + } else if (json.getInputType(i, j) == PhysicsTypeTagY) { + this._physicsRig.inputs.at(inputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Y; + this._physicsRig.inputs.at( + inputIndex + j + ).getNormalizedParameterValue = getInputTranslationYFromNormalizedParamterValue; + } else if (json.getInputType(i, j) == PhysicsTypeTagAngle) { + this._physicsRig.inputs.at(inputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Angle; + this._physicsRig.inputs.at( + inputIndex + j + ).getNormalizedParameterValue = getInputAngleFromNormalizedParameterValue; + } + + this._physicsRig.inputs.at(inputIndex + j).source.targetType = + CubismPhysicsTargetType.CubismPhysicsTargetType_Parameter; + this._physicsRig.inputs.at( + inputIndex + j + ).source.id = json.getInputSourceId(i, j); + } + inputIndex += this._physicsRig.settings.at(i).inputCount; + + // Output + this._physicsRig.settings.at(i).outputCount = json.getOutputCount(i); + this._physicsRig.settings.at(i).baseOutputIndex = outputIndex; + + for (let j = 0; j < this._physicsRig.settings.at(i).outputCount; ++j) { + this._physicsRig.outputs.at( + outputIndex + j + ).destinationParameterIndex = -1; + this._physicsRig.outputs.at( + outputIndex + j + ).vertexIndex = json.getOutputVertexIndex(i, j); + this._physicsRig.outputs.at( + outputIndex + j + ).angleScale = json.getOutputAngleScale(i, j); + this._physicsRig.outputs.at( + outputIndex + j + ).weight = json.getOutputWeight(i, j); + this._physicsRig.outputs.at(outputIndex + j).destination.targetType = + CubismPhysicsTargetType.CubismPhysicsTargetType_Parameter; + + this._physicsRig.outputs.at( + outputIndex + j + ).destination.id = json.getOutputDestinationId(i, j); + + if (json.getOutputType(i, j) == PhysicsTypeTagX) { + this._physicsRig.outputs.at(outputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_X; + this._physicsRig.outputs.at( + outputIndex + j + ).getValue = getOutputTranslationX; + this._physicsRig.outputs.at( + outputIndex + j + ).getScale = getOutputScaleTranslationX; + } else if (json.getOutputType(i, j) == PhysicsTypeTagY) { + this._physicsRig.outputs.at(outputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Y; + this._physicsRig.outputs.at( + outputIndex + j + ).getValue = getOutputTranslationY; + this._physicsRig.outputs.at( + outputIndex + j + ).getScale = getOutputScaleTranslationY; + } else if (json.getOutputType(i, j) == PhysicsTypeTagAngle) { + this._physicsRig.outputs.at(outputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Angle; + this._physicsRig.outputs.at( + outputIndex + j + ).getValue = getOutputAngle; + this._physicsRig.outputs.at( + outputIndex + j + ).getScale = getOutputScaleAngle; + } + + this._physicsRig.outputs.at( + outputIndex + j + ).reflect = json.getOutputReflect(i, j); + } + outputIndex += this._physicsRig.settings.at(i).outputCount; + + // Particle + this._physicsRig.settings.at(i).particleCount = json.getParticleCount( + i + ); + this._physicsRig.settings.at(i).baseParticleIndex = particleIndex; + + for ( + let j = 0; + j < this._physicsRig.settings.at(i).particleCount; + ++j + ) { + this._physicsRig.particles.at( + particleIndex + j + ).mobility = json.getParticleMobility(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).delay = json.getParticleDelay(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).acceleration = json.getParticleAcceleration(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).radius = json.getParticleRadius(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).position = json.getParticlePosition(i, j); + } + + particleIndex += this._physicsRig.settings.at(i).particleCount; + } + + this.initialize(); + + json.release(); + json = void 0; + json = null; + } + + /** + * 初期化する + */ + public initialize(): void { + let strand: CubismPhysicsParticle[]; + let currentSetting: CubismPhysicsSubRig; + let radius: CubismVector2; + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + currentSetting = this._physicsRig.settings.at(settingIndex); + strand = this._physicsRig.particles.get( + currentSetting.baseParticleIndex + ); + + // Initialize the top of particle. + strand[0].initialPosition = new CubismVector2(0.0, 0.0); + strand[0].lastPosition = new CubismVector2( + strand[0].initialPosition.x, + strand[0].initialPosition.y + ); + strand[0].lastGravity = new CubismVector2(0.0, -1.0); + strand[0].lastGravity.y *= -1.0; + strand[0].velocity = new CubismVector2(0.0, 0.0); + strand[0].force = new CubismVector2(0.0, 0.0); + + // Initialize paritcles. + for (let i = 1; i < currentSetting.particleCount; ++i) { + radius = new CubismVector2(0.0, 0.0); + radius.y = strand[i].radius; + strand[i].initialPosition = new CubismVector2( + strand[i - 1].initialPosition.x + radius.x, + strand[i - 1].initialPosition.y + radius.y + ); + strand[i].position = new CubismVector2( + strand[i].initialPosition.x, + strand[i].initialPosition.y + ); + strand[i].lastPosition = new CubismVector2( + strand[i].initialPosition.x, + strand[i].initialPosition.y + ); + strand[i].lastGravity = new CubismVector2(0.0, -1.0); + strand[i].lastGravity.y *= -1.0; + strand[i].velocity = new CubismVector2(0.0, 0.0); + strand[i].force = new CubismVector2(0.0, 0.0); + } + } + } + + _physicsRig: CubismPhysicsRig; // 物理演算のデータ + _options: Options; // オプション + } + + /** + * 物理演算のオプション + */ + export class Options { + constructor() { + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + + gravity: CubismVector2; // 重力方向 + wind: CubismVector2; // 風の方向 + } + + /** + * Gets sign. + * + * @param value Evaluation target value. + * + * @return Sign of value. + */ + function sign(value: number): number { + let ret = 0; + + if (value > 0.0) { + ret = 1; + } else if (value < 0.0) { + ret = -1; + } + + return ret; + } + + function getInputTranslationXFromNormalizedParameterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number + ): void { + targetTranslation.x += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationPosition.minimum, + normalizationPosition.maximum, + normalizationPosition.defalut, + isInverted + ) * weight; + } + + function getInputTranslationYFromNormalizedParamterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number + ): void { + targetTranslation.y += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationPosition.minimum, + normalizationPosition.maximum, + normalizationPosition.defalut, + isInverted + ) * weight; + } + + function getInputAngleFromNormalizedParameterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizaitionPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number + ): void { + targetAngle.angle += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationAngle.minimum, + normalizationAngle.maximum, + normalizationAngle.defalut, + isInverted + ) * weight; + } + + function getOutputTranslationX( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 + ): number { + let outputValue: number = translation.x; + + if (isInverted) { + outputValue *= -1.0; + } + + return outputValue; + } + + function getOutputTranslationY( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 + ): number { + let outputValue: number = translation.y; + + if (isInverted) { + outputValue *= -1.0; + } + return outputValue; + } + + function getOutputAngle( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 + ): number { + let outputValue: number; + + if (particleIndex >= 2) { + parentGravity = particles[particleIndex - 1].position.substract( + particles[particleIndex - 2].position + ); + } else { + parentGravity = parentGravity.multiplyByScaler(-1.0); + } + + outputValue = CubismMath.directionToRadian(parentGravity, translation); + + if (isInverted) { + outputValue *= -1.0; + } + + return outputValue; + } + + function getRangeValue(min: number, max: number): number { + const maxValue: number = CubismMath.max(min, max); + const minValue: number = CubismMath.min(min, max); + + return CubismMath.abs(maxValue - minValue); + } + + function getDefaultValue(min: number, max: number): number { + const minValue: number = CubismMath.min(min, max); + return minValue + getRangeValue(min, max) / 2.0; + } + + function getOutputScaleTranslationX( + translationScale: CubismVector2, + angleScale: number + ): number { + return JSON.parse(JSON.stringify(translationScale.x)); + } + + function getOutputScaleTranslationY( + translationScale: CubismVector2, + angleScale: number + ): number { + return JSON.parse(JSON.stringify(translationScale.y)); + } + + function getOutputScaleAngle( + translationScale: CubismVector2, + angleScale: number + ): number { + return JSON.parse(JSON.stringify(angleScale)); + } + + /** + * Updates particles. + * + * @param strand Target array of particle. + * @param strandCount Count of particle. + * @param totalTranslation Total translation value. + * @param totalAngle Total angle. + * @param windDirection Direction of Wind. + * @param thresholdValue Threshold of movement. + * @param deltaTimeSeconds Delta time. + * @param airResistance Air resistance. + */ + function updateParticles( + strand: CubismPhysicsParticle[], + strandCount: number, + totalTranslation: CubismVector2, + totalAngle: number, + windDirection: CubismVector2, + thresholdValue: number, + deltaTimeSeconds: number, + airResistance: number + ) { + let totalRadian: number; + let delay: number; + let radian: number; + let currentGravity: CubismVector2; + let direction: CubismVector2 = new CubismVector2(0.0, 0.0); + let velocity: CubismVector2 = new CubismVector2(0.0, 0.0); + let force: CubismVector2 = new CubismVector2(0.0, 0.0); + let newDirection: CubismVector2 = new CubismVector2(0.0, 0.0); + + strand[0].position = new CubismVector2( + totalTranslation.x, + totalTranslation.y + ); + + totalRadian = CubismMath.degreesToRadian(totalAngle); + currentGravity = CubismMath.radianToDirection(totalRadian); + currentGravity.normalize(); + + for (let i = 1; i < strandCount; ++i) { + strand[i].force = currentGravity + .multiplyByScaler(strand[i].acceleration) + .add(windDirection); + + strand[i].lastPosition = new CubismVector2( + strand[i].position.x, + strand[i].position.y + ); + + delay = strand[i].delay * deltaTimeSeconds * 30.0; + + direction = strand[i].position.substract(strand[i - 1].position); + + radian = + CubismMath.directionToRadian(strand[i].lastGravity, currentGravity) / + airResistance; + + direction.x = + CubismMath.cos(radian) * direction.x - + direction.y * CubismMath.sin(radian); + direction.y = + CubismMath.sin(radian) * direction.x + + direction.y * CubismMath.cos(radian); + + strand[i].position = strand[i - 1].position.add(direction); + + velocity = strand[i].velocity.multiplyByScaler(delay); + force = strand[i].force.multiplyByScaler(delay).multiplyByScaler(delay); + + strand[i].position = strand[i].position.add(velocity).add(force); + + newDirection = strand[i].position.substract(strand[i - 1].position); + newDirection.normalize(); + + strand[i].position = strand[i - 1].position.add( + newDirection.multiplyByScaler(strand[i].radius) + ); + + if (CubismMath.abs(strand[i].position.x) < thresholdValue) { + strand[i].position.x = 0.0; + } + + if (delay != 0.0) { + strand[i].velocity = strand[i].position.substract( + strand[i].lastPosition + ); + strand[i].velocity = strand[i].velocity.divisionByScalar(delay); + strand[i].velocity = strand[i].velocity.multiplyByScaler( + strand[i].mobility + ); + } + + strand[i].force = new CubismVector2(0.0, 0.0); + strand[i].lastGravity = new CubismVector2( + currentGravity.x, + currentGravity.y + ); + } + } + + /** + * Updates output parameter value. + * @param parameterValue Target parameter value. + * @param parameterValueMinimum Minimum of parameter value. + * @param parameterValueMaximum Maximum of parameter value. + * @param translation Translation value. + */ + function updateOutputParameterValue( + parameterValue: Float32Array, + parameterValueMinimum: number, + parameterValueMaximum: number, + translation: number, + output: CubismPhysicsOutput + ): void { + let outputScale: number; + let value: number; + let weight: number; + + outputScale = output.getScale(output.translationScale, output.angleScale); + + value = translation * outputScale; + + if (value < parameterValueMinimum) { + if (value < output.valueBelowMinimum) { + output.valueBelowMinimum = value; + } + + value = parameterValueMinimum; + } else if (value > parameterValueMaximum) { + if (value > output.valueExceededMaximum) { + output.valueExceededMaximum = value; + } + + value = parameterValueMaximum; + } + + weight = output.weight / MaximumWeight; + + if (weight >= 1.0) { + parameterValue[0] = value; + } else { + value = parameterValue[0] * (1.0 - weight) + value * weight; + parameterValue[0] = value; + } + } + + function normalizeParameterValue( + value: number, + parameterMinimum: number, + parameterMaximum: number, + parameterDefault: number, + normalizedMinimum: number, + normalizedMaximum: number, + normalizedDefault: number, + isInverted: boolean + ) { + let result = 0.0; + + const maxValue: number = CubismMath.max(parameterMaximum, parameterMinimum); + + if (maxValue < value) { + value = maxValue; + } + + const minValue: number = CubismMath.min(parameterMaximum, parameterMinimum); + + if (minValue > value) { + value = minValue; + } + + const minNormValue: number = CubismMath.min( + normalizedMinimum, + normalizedMaximum + ); + const maxNormValue: number = CubismMath.max( + normalizedMinimum, + normalizedMaximum + ); + const middleNormValue: number = normalizedDefault; + + const middleValue: number = getDefaultValue(minValue, maxValue); + const paramValue: number = value - middleValue; + + switch (sign(paramValue)) { + case 1: { + const nLength: number = maxNormValue - middleNormValue; + const pLength: number = maxValue - middleValue; + + if (pLength != 0.0) { + result = paramValue * (nLength / pLength); + result += middleNormValue; + } + + break; + } + case -1: { + const nLength: number = minNormValue - middleNormValue; + const pLength: number = minValue - middleValue; + + if (pLength != 0.0) { + result = paramValue * (nLength / pLength); + result += middleNormValue; + } + + break; + } + case 0: { + result = middleNormValue; + + break; + } + default: { + break; + } + } + + return isInverted ? result : result * -1.0; + } +} diff --git a/src/physics/cubismphysicsinternal.ts b/src/physics/cubismphysicsinternal.ts new file mode 100644 index 0000000..3832ca4 --- /dev/null +++ b/src/physics/cubismphysicsinternal.ts @@ -0,0 +1,225 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismvector2 } from '../math/cubismvector2'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import csmVector = csmvector.csmVector; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismVector2 = cubismvector2.CubismVector2; + +export namespace Live2DCubismFramework { + /** + * 物理演算の適用先の種類 + */ + export enum CubismPhysicsTargetType { + CubismPhysicsTargetType_Parameter // パラメータに対して適用 + } + + /** + * 物理演算の入力の種類 + */ + export enum CubismPhysicsSource { + CubismPhysicsSource_X, // X軸の位置から + CubismPhysicsSource_Y, // Y軸の位置から + CubismPhysicsSource_Angle // 角度から + } + + /** + * @brief 物理演算で使用する外部の力 + * + * 物理演算で使用する外部の力。 + */ + export class PhysicsJsonEffectiveForces { + constructor() { + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + gravity: CubismVector2; // 重力 + wind: CubismVector2; // 風 + } + + /** + * 物理演算のパラメータ情報 + */ + export class CubismPhysicsParameter { + id: CubismIdHandle; // パラメータ + targetType: CubismPhysicsTargetType; // 適用先の種類 + } + + /** + * 物理演算の正規化情報 + */ + export class CubismPhysicsNormalization { + minimum: number; // 最大値 + maximum: number; // 最小値 + defalut: number; // デフォルト値 + } + + /** + * 物理演算の演算委使用する物理点の情報 + */ + export class CubismPhysicsParticle { + constructor() { + this.initialPosition = new CubismVector2(0, 0); + this.position = new CubismVector2(0, 0); + this.lastPosition = new CubismVector2(0, 0); + this.lastGravity = new CubismVector2(0, 0); + this.force = new CubismVector2(0, 0); + this.velocity = new CubismVector2(0, 0); + } + + initialPosition: CubismVector2; // 初期位置 + mobility: number; // 動きやすさ + delay: number; // 遅れ + acceleration: number; // 加速度 + radius: number; // 距離 + position: CubismVector2; // 現在の位置 + lastPosition: CubismVector2; // 最後の位置 + lastGravity: CubismVector2; // 最後の重力 + force: CubismVector2; // 現在かかっている力 + velocity: CubismVector2; // 現在の速度 + } + + /** + * 物理演算の物理点の管理 + */ + export class CubismPhysicsSubRig { + constructor() { + this.normalizationPosition = new CubismPhysicsNormalization(); + this.normalizationAngle = new CubismPhysicsNormalization(); + } + inputCount: number; // 入力の個数 + outputCount: number; // 出力の個数 + particleCount: number; // 物理点の個数 + baseInputIndex: number; // 入力の最初のインデックス + baseOutputIndex: number; // 出力の最初のインデックス + baseParticleIndex: number; // 物理点の最初のインデックス + normalizationPosition: CubismPhysicsNormalization; // 正規化された位置 + normalizationAngle: CubismPhysicsNormalization; // 正規化された角度 + } + + /** + * 正規化されたパラメータの取得関数の宣言 + * @param targetTranslation // 演算結果の移動値 + * @param targetAngle // 演算結果の角度 + * @param value // パラメータの値 + * @param parameterMinimunValue // パラメータの最小値 + * @param parameterMaximumValue // パラメータの最大値 + * @param parameterDefaultValue // パラメータのデフォルト値 + * @param normalizationPosition // 正規化された位置 + * @param normalizationAngle // 正規化された角度 + * @param isInverted // 値が反転されているか? + * @param weight // 重み + */ + export interface normalizedPhysicsParameterValueGetter { + ( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimunValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number + ): void; + } + + /** + * 物理演算の値の取得関数の宣言 + * @param translation 移動値 + * @param particles 物理点のリスト + * @param isInverted 値が反映されているか + * @param parentGravity 重力 + * @return 値 + */ + export interface physicsValueGetter { + ( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 + ): number; + } + + /** + * 物理演算のスケールの取得関数の宣言 + * @param translationScale 移動値のスケール + * @param angleScale 角度のスケール + * @return スケール値 + */ + export interface physicsScaleGetter { + (translationScale: CubismVector2, angleScale: number): number; + } + + /** + * 物理演算の入力情報 + */ + export class CubismPhysicsInput { + constructor() { + this.source = new CubismPhysicsParameter(); + } + source: CubismPhysicsParameter; // 入力元のパラメータ + sourceParameterIndex: number; // 入力元のパラメータのインデックス + weight: number; // 重み + type: number; // 入力の種類 + reflect: boolean; // 値が反転されているかどうか + getNormalizedParameterValue: normalizedPhysicsParameterValueGetter; // 正規化されたパラメータ値の取得関数 + } + + /** + * @brief 物理演算の出力情報 + * + * 物理演算の出力情報。 + */ + export class CubismPhysicsOutput { + constructor() { + this.destination = new CubismPhysicsParameter(); + this.translationScale = new CubismVector2(0, 0); + } + + destination: CubismPhysicsParameter; // 出力先のパラメータ + destinationParameterIndex: number; // 出力先のパラメータのインデックス + vertexIndex: number; // 振り子のインデックス + translationScale: CubismVector2; // 移動値のスケール + angleScale: number; // 角度のスケール + weight: number; // 重み + type: CubismPhysicsSource; // 出力の種類 + reflect: boolean; // 値が反転されているかどうか + valueBelowMinimum: number; // 最小値を下回った時の値 + valueExceededMaximum: number; // 最大値をこえた時の値 + getValue: physicsValueGetter; // 物理演算の値の取得関数 + getScale: physicsScaleGetter; // 物理演算のスケール値の取得関数 + } + + /** + * @brief 物理演算のデータ + * + * 物理演算のデータ。 + */ + export class CubismPhysicsRig { + constructor() { + this.settings = new csmVector(); + this.inputs = new csmVector(); + this.outputs = new csmVector(); + this.particles = new csmVector(); + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + + subRigCount: number; // 物理演算の物理点の個数 + settings: csmVector; // 物理演算の物理点の管理のリスト + inputs: csmVector; // 物理演算の入力のリスト + outputs: csmVector; // 物理演算の出力のリスト + particles: csmVector; // 物理演算の物理点のリスト + gravity: CubismVector2; // 重力 + wind: CubismVector2; // 風 + } +} diff --git a/src/physics/cubismphysicsjson.ts b/src/physics/cubismphysicsjson.ts new file mode 100644 index 0000000..10956c1 --- /dev/null +++ b/src/physics/cubismphysicsjson.ts @@ -0,0 +1,649 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismjson } from '../utils/cubismjson'; +import { Live2DCubismFramework as cubismvector2 } from '../math/cubismvector2'; +import { Live2DCubismFramework as cubismid } from '../id/cubismid'; +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import CubismFramework = cubismframework.CubismFramework; +import CubismIdHandle = cubismid.CubismIdHandle; +import CubismVector2 = cubismvector2.CubismVector2; +import CubismJson = cubismjson.CubismJson; + +export namespace Live2DCubismFramework { + // JSON keys + const Position = 'Position'; + const X = 'X'; + const Y = 'Y'; + const Angle = 'Angle'; + const Type = 'Type'; + const Id = 'Id'; + + // Meta + const Meta = 'Meta'; + const EffectiveForces = 'EffectiveForces'; + const TotalInputCount = 'TotalInputCount'; + const TotalOutputCount = 'TotalOutputCount'; + const PhysicsSettingCount = 'PhysicsSettingCount'; + const Gravity = 'Gravity'; + const Wind = 'Wind'; + const VertexCount = 'VertexCount'; + + // PhysicsSettings + const PhysicsSettings = 'PhysicsSettings'; + const Normalization = 'Normalization'; + const Minimum = 'Minimum'; + const Maximum = 'Maximum'; + const Default = 'Default'; + const Reflect = 'Reflect'; + const Weight = 'Weight'; + + // Input + const Input = 'Input'; + const Source = 'Source'; + + // Output + const Output = 'Output'; + const Scale = 'Scale'; + const VertexIndex = 'VertexIndex'; + const Destination = 'Destination'; + + // Particle + const Vertices = 'Vertices'; + const Mobility = 'Mobility'; + const Delay = 'Delay'; + const Radius = 'Radius'; + const Acceleration = 'Acceleration'; + + /** + * physics3.jsonのコンテナ。 + */ + export class CubismPhysicsJson { + /** + * コンストラクタ + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * 重力の取得 + * @return 重力 + */ + public getGravity(): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Gravity) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Gravity) + .getValueByString(Y) + .toFloat(); + return ret; + } + + /** + * 風の取得 + * @return 風 + */ + public getWind(): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Wind) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Wind) + .getValueByString(Y) + .toFloat(); + return ret; + } + + /** + * 物理店の管理の個数の取得 + * @return 物理店の管理の個数 + */ + public getSubRigCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(PhysicsSettingCount) + .toInt(); + } + + /** + * 入力の総合計の取得 + * @return 入力の総合計 + */ + public getTotalInputCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalInputCount) + .toInt(); + } + + /** + * 出力の総合計の取得 + * @return 出力の総合計 + */ + public getTotalOutputCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalOutputCount) + .toInt(); + } + + /** + * 物理点の個数の取得 + * @return 物理点の個数 + */ + public getVertexCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(VertexCount) + .toInt(); + } + + /** + * 正規化された位置の最小値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置の最小値 + */ + public getNormalizationPositionMinimumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Minimum) + .toFloat(); + } + + /** + * 正規化された位置の最大値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置の最大値 + */ + public getNormalizationPositionMaximumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Maximum) + .toFloat(); + } + + /** + * 正規化された位置のデフォルト値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置のデフォルト値 + */ + public getNormalizationPositionDefaultValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Default) + .toFloat(); + } + + /** + * 正規化された角度の最小値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された角度の最小値 + */ + public getNormalizationAngleMinimumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Minimum) + .toFloat(); + } + + /** + * 正規化された角度の最大値の取得 + * @param physicsSettingIndex + * @return 正規化された角度の最大値 + */ + public getNormalizationAngleMaximumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Maximum) + .toFloat(); + } + + /** + * 正規化された角度のデフォルト値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された角度のデフォルト値 + */ + public getNormalizationAngleDefaultValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Default) + .toFloat(); + } + + /** + * 入力の個数の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 入力の個数 + */ + public getInputCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getVector() + .getSize(); + } + + /** + * 入力の重みの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の重み + */ + public getInputWeight( + physicsSettingIndex: number, + inputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Weight) + .toFloat(); + } + + /** + * 入力の反転の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の反転 + */ + public getInputReflect( + physicsSettingIndex: number, + inputIndex: number + ): boolean { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Reflect) + .toBoolean(); + } + + /** + * 入力の種類の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の種類 + */ + public getInputType( + physicsSettingIndex: number, + inputIndex: number + ): string { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Type) + .getRawString(); + } + + /** + * 入力元のIDの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力元のID + */ + public getInputSourceId( + physicsSettingIndex: number, + inputIndex: number + ): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Source) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 出力の個数の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 出力の個数 + */ + public getOutputCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getVector() + .getSize(); + } + + /** + * 出力の物理点のインデックスの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の物理点のインデックス + */ + public getOutputVertexIndex( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(VertexIndex) + .toInt(); + } + + /** + * 出力の角度のスケールを取得する + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の角度のスケール + */ + public getOutputAngleScale( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Scale) + .toFloat(); + } + + /** + * 出力の重みの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の重み + */ + public getOutputWeight( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Weight) + .toFloat(); + } + + /** + * 出力先のIDの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力先のID + */ + public getOutputDestinationId( + physicsSettingIndex: number, + outputIndex: number + ): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Destination) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 出力の種類の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の種類 + */ + public getOutputType( + physicsSettingIndex: number, + outputIndex: number + ): string { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Type) + .getRawString(); + } + + /** + * 出力の反転の取得 + * @param physicsSettingIndex 物理演算のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の反転 + */ + public getOutputReflect( + physicsSettingIndex: number, + outputIndex: number + ): boolean { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Reflect) + .toBoolean(); + } + + /** + * 物理点の個数の取得 + * @param physicsSettingIndex 物理演算男設定のインデックス + * @return 物理点の個数 + */ + public getParticleCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getVector() + .getSize(); + } + + /** + * 物理点の動きやすさの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の動きやすさ + */ + public getParticleMobility( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Mobility) + .toFloat(); + } + + /** + * 物理点の遅れの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の遅れ + */ + public getParticleDelay( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Delay) + .toFloat(); + } + + /** + * 物理点の加速度の取得 + * @param physicsSettingIndex 物理演算の設定 + * @param vertexIndex 物理点のインデックス + * @return 物理点の加速度 + */ + public getParticleAcceleration( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Acceleration) + .toFloat(); + } + + /** + * 物理点の距離の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の距離 + */ + public getParticleRadius( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Radius) + .toInt(); + } + + /** + * 物理点の位置の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexInde 物理点のインデックス + * @return 物理点の位置 + */ + public getParticlePosition( + physicsSettingIndex: number, + vertexIndex: number + ): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Position) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Position) + .getValueByString(Y) + .toFloat(); + return ret; + } + + _json: CubismJson; // physics3.jsonデータ + } +} diff --git a/src/rendering/cubismrenderer.ts b/src/rendering/cubismrenderer.ts new file mode 100644 index 0000000..3bed02a --- /dev/null +++ b/src/rendering/cubismrenderer.ts @@ -0,0 +1,267 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismmatrix44 } from '../math/cubismmatrix44'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import CubismModel = cubismmodel.CubismModel; +import CubismMatrix44 = cubismmatrix44.CubismMatrix44; + +export namespace Live2DCubismFramework { + /** + * モデル描画を処理するレンダラ + * + * サブクラスに環境依存の描画命令を記述する。 + */ + export abstract class CubismRenderer { + /** + * レンダラのインスタンスを生成して取得する + * + * @return レンダラのインスタンス + */ + public static create(): CubismRenderer { + return null; + } + + /** + * レンダラのインスタンスを解放する + */ + public static delete(renderer: CubismRenderer): void { + renderer = null; + } + + /** + * レンダラの初期化処理を実行する + * 引数に渡したモデルからレンダラの初期化処理に必要な情報を取り出すことができる + * @param model モデルのインスタンス + */ + public initialize(model: CubismModel): void { + this._model = model; + } + + /** + * モデルを描画する + */ + public drawModel(): void { + if (this.getModel() == null) return; + + this.doDrawModel(); + } + + /** + * Model-View-Projection 行列をセットする + * 配列は複製されるので、元の配列は外で破棄して良い + * @param matrix44 Model-View-Projection 行列 + */ + public setMvpMatrix(matrix44: CubismMatrix44): void { + this._mvpMatrix4x4.setMatrix(matrix44.getArray()); + } + + /** + * Model-View-Projection 行列を取得する + * @return Model-View-Projection 行列 + */ + public getMvpMatrix(): CubismMatrix44 { + return this._mvpMatrix4x4; + } + + /** + * モデルの色をセットする + * 各色0.0~1.0の間で指定する(1.0が標準の状態) + * @param red 赤チャンネルの値 + * @param green 緑チャンネルの値 + * @param blue 青チャンネルの値 + * @param alpha αチャンネルの値 + */ + public setModelColor( + red: number, + green: number, + blue: number, + alpha: number + ): void { + if (red < 0.0) { + red = 0.0; + } else if (red > 1.0) { + red = 1.0; + } + + if (green < 0.0) { + green = 0.0; + } else if (green > 1.0) { + green = 1.0; + } + + if (blue < 0.0) { + blue = 0.0; + } else if (blue > 1.0) { + blue = 1.0; + } + + if (alpha < 0.0) { + alpha = 0.0; + } else if (alpha > 1.0) { + alpha = 1.0; + } + + this._modelColor.R = red; + this._modelColor.G = green; + this._modelColor.B = blue; + this._modelColor.A = alpha; + } + + /** + * モデルの色を取得する + * 各色0.0~1.0の間で指定する(1.0が標準の状態) + * + * @return RGBAのカラー情報 + */ + public getModelColor(): CubismTextureColor { + return JSON.parse(JSON.stringify(this._modelColor)); + } + + /** + * 乗算済みαの有効・無効をセットする + * 有効にするならtrue、無効にするならfalseをセットする + */ + public setIsPremultipliedAlpha(enable: boolean): void { + this._isPremultipliedAlpha = enable; + } + + /** + * 乗算済みαの有効・無効を取得する + * @return true 乗算済みのα有効 + * @return false 乗算済みのα無効 + */ + public isPremultipliedAlpha(): boolean { + return this._isPremultipliedAlpha; + } + + /** + * カリング(片面描画)の有効・無効をセットする。 + * 有効にするならtrue、無効にするならfalseをセットする + */ + public setIsCulling(culling: boolean): void { + this._isCulling = culling; + } + + /** + * カリング(片面描画)の有効・無効を取得する。 + * @return true カリング有効 + * @return false カリング無効 + */ + public isCulling(): boolean { + return this._isCulling; + } + + /** + * テクスチャの異方性フィルタリングのパラメータをセットする + * パラメータ値の影響度はレンダラの実装に依存する + * @param n パラメータの値 + */ + public setAnisotropy(n: number): void { + this._anisortopy = n; + } + + /** + * テクスチャの異方性フィルタリングのパラメータをセットする + * @return 異方性フィルタリングのパラメータ + */ + public getAnisotropy(): number { + return this._anisortopy; + } + + /** + * レンダリングするモデルを取得する + * @return レンダリングするモデル + */ + public getModel(): CubismModel { + return this._model; + } + + /** + * コンストラクタ + */ + protected constructor() { + this._isCulling = false; + this._isPremultipliedAlpha = false; + this._anisortopy = 0.0; + this._model = null; + this._modelColor = new CubismTextureColor(); + + // 単位行列に初期化 + this._mvpMatrix4x4 = new CubismMatrix44(); + this._mvpMatrix4x4.loadIdentity(); + } + + /** + * モデル描画の実装 + */ + public abstract doDrawModel(): void; + + /** + * 描画オブジェクト(アートメッシュ)を描画する + * ポリゴンメッシュとテクスチャ番号をセットで渡す。 + * @param textureNo 描画するテクスチャ番号 + * @param indexCount 描画オブジェクトのインデックス値 + * @param vertexCount ポリゴンメッシュの頂点数 + * @param indexArray ポリゴンメッシュ頂点のインデックス配列 + * @param vertexArray ポリゴンメッシュの頂点配列 + * @param uvArray uv配列 + * @param opacity 不透明度 + * @param colorBlendMode カラーブレンディングのタイプ + * @param invertedMask マスク使用時のマスクの反転使用 + */ + public abstract drawMesh( + textureNo: number, + indexCount: number, + vertexCount: number, + indexArray: Uint16Array, + vertexArray: Float32Array, + uvArray: Float32Array, + opacity: number, + colorBlendMode: CubismBlendMode, + invertedMask: boolean + ): void; + + /** + * レンダラが保持する静的なリソースを開放する + */ + public static staticRelease: Function; + + protected _mvpMatrix4x4: CubismMatrix44; // Model-View-Projection 行列 + protected _modelColor: CubismTextureColor; // モデル自体のカラー(RGBA) + protected _isCulling: boolean; // カリングが有効ならtrue + protected _isPremultipliedAlpha: boolean; // 乗算済みαならtrue + protected _anisortopy: any; // テクスチャの異方性フィルタリングのパラメータ + protected _model: CubismModel; // レンダリング対象のモデル + } + + export enum CubismBlendMode { + CubismBlendMode_Normal = 0, // 通常 + CubismBlendMode_Additive = 1, // 加算 + CubismBlendMode_Multiplicative = 2 // 乗算 + } + + /** + * テクスチャの色をRGBAで扱うためのクラス + */ + export class CubismTextureColor { + /** + * コンストラクタ + */ + constructor() { + this.R = 1.0; + this.G = 1.0; + this.B = 1.0; + this.A = 1.0; + } + + R: number; // 赤チャンネル + G: number; // 緑チャンネル + B: number; // 青チャンネル + A: number; // αチャンネル + } +} diff --git a/src/rendering/cubismrenderer_webgl.ts b/src/rendering/cubismrenderer_webgl.ts new file mode 100644 index 0000000..be11637 --- /dev/null +++ b/src/rendering/cubismrenderer_webgl.ts @@ -0,0 +1,2259 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as cubismframework } from '../live2dcubismframework'; +import { Live2DCubismFramework as csmrect } from '../type/csmrectf'; +import { Live2DCubismFramework as cubismrenderer } from './cubismrenderer'; +import { Live2DCubismFramework as cubismmodel } from '../model/cubismmodel'; +import { Live2DCubismFramework as cubsimmatrix44 } from '../math/cubismmatrix44'; +import { Live2DCubismFramework as csmmap } from '../type/csmmap'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { CubismLogError } from '../utils/cubismdebug'; +import Constant = cubismframework.Constant; +import CubismMatrix44 = cubsimmatrix44.CubismMatrix44; +import csmRect = csmrect.csmRect; +import csmMap = csmmap.csmMap; +import csmVector = csmvector.csmVector; +import CubismModel = cubismmodel.CubismModel; +import CubismRenderer = cubismrenderer.CubismRenderer; +import CubismBlendMode = cubismrenderer.CubismBlendMode; +import CubismTextureColor = cubismrenderer.CubismTextureColor; + +export namespace Live2DCubismFramework { + const ColorChannelCount = 4; // 実験時に1チャンネルの場合は1、RGBだけの場合は3、アルファも含める場合は4 + + const shaderCount = 10; // シェーダーの数 = マスク生成用 + (通常用 + 加算 + 乗算) * (マスク無の乗算済アルファ対応版 + マスク有の乗算済アルファ対応版 + マスク有反転の乗算済アルファ対応版) + let s_instance: CubismShader_WebGL; + let s_viewport: number[]; + let s_fbo: WebGLFramebuffer; + + /** + * クリッピングマスクの処理を実行するクラス + */ + export class CubismClippingManager_WebGL { + /** + * カラーチャンネル(RGBA)のフラグを取得する + * @param channelNo カラーチャンネル(RGBA)の番号(0:R, 1:G, 2:B, 3:A) + */ + public getChannelFlagAsColor(channelNo: number): CubismTextureColor { + return this._channelColors.at(channelNo); + } + + /** + * テンポラリのレンダーテクスチャのアドレスを取得する + * FrameBufferObjectが存在しない場合、新しく生成する + * + * @return レンダーテクスチャのアドレス + */ + public getMaskRenderTexture(): WebGLFramebuffer { + let ret: WebGLFramebuffer = 0; + + // テンポラリのRenderTextureを取得する + if (this._maskTexture && this._maskTexture.texture != 0) { + // 前回使ったものを返す + this._maskTexture.frameNo = this._currentFrameNo; + ret = this._maskTexture.texture; + } + + if (ret == 0) { + // FrameBufferObjectが存在しない場合、新しく生成する + + // クリッピングバッファサイズを取得 + const size: number = this._clippingMaskBufferSize; + + this._colorBuffer = this.gl.createTexture(); + this.gl.bindTexture(this.gl.TEXTURE_2D, this._colorBuffer); + this.gl.texImage2D( + this.gl.TEXTURE_2D, + 0, + this.gl.RGBA, + size, + size, + 0, + this.gl.RGBA, + this.gl.UNSIGNED_BYTE, + null + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_WRAP_S, + this.gl.CLAMP_TO_EDGE + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_WRAP_T, + this.gl.CLAMP_TO_EDGE + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_MIN_FILTER, + this.gl.LINEAR + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_MAG_FILTER, + this.gl.LINEAR + ); + this.gl.bindTexture(this.gl.TEXTURE_2D, null); + + ret = this.gl.createFramebuffer(); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, ret); + this.gl.framebufferTexture2D( + this.gl.FRAMEBUFFER, + this.gl.COLOR_ATTACHMENT0, + this.gl.TEXTURE_2D, + this._colorBuffer, + 0 + ); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, s_fbo); + + this._maskTexture = new CubismRenderTextureResource( + this._currentFrameNo, + ret + ); + } + + return ret; + } + + /** + * WebGLレンダリングコンテキストを設定する + * @param gl WebGLレンダリングコンテキスト + */ + public setGL(gl: WebGLRenderingContext): void { + this.gl = gl; + } + + /** + * マスクされる描画オブジェクト群全体を囲む矩形(モデル座標系)を計算する + * @param model モデルのインスタンス + * @param clippingContext クリッピングマスクのコンテキスト + */ + public calcClippedDrawTotalBounds( + model: CubismModel, + clippingContext: CubismClippingContext + ): void { + // 被クリッピングマスク(マスクされる描画オブジェクト)の全体の矩形 + let clippedDrawTotalMinX: number = Number.MAX_VALUE; + let clippedDrawTotalMinY: number = Number.MAX_VALUE; + let clippedDrawTotalMaxX: number = Number.MIN_VALUE; + let clippedDrawTotalMaxY: number = Number.MIN_VALUE; + + // このマスクが実際に必要か判定する + // このクリッピングを利用する「描画オブジェクト」がひとつでも使用可能であればマスクを生成する必要がある + const clippedDrawCount: number = + clippingContext._clippedDrawableIndexList.length; + + for ( + let clippedDrawableIndex = 0; + clippedDrawableIndex < clippedDrawCount; + clippedDrawableIndex++ + ) { + // マスクを使用する描画オブジェクトの描画される矩形を求める + const drawableIndex: number = + clippingContext._clippedDrawableIndexList[clippedDrawableIndex]; + + const drawableVertexCount: number = model.getDrawableVertexCount( + drawableIndex + ); + const drawableVertexes: Float32Array = model.getDrawableVertices( + drawableIndex + ); + + let minX: number = Number.MAX_VALUE; + let minY: number = Number.MAX_VALUE; + let maxX: number = Number.MIN_VALUE; + let maxY: number = Number.MIN_VALUE; + + const loop: number = drawableVertexCount * Constant.vertexStep; + for ( + let pi: number = Constant.vertexOffset; + pi < loop; + pi += Constant.vertexStep + ) { + const x: number = drawableVertexes[pi]; + const y: number = drawableVertexes[pi + 1]; + + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } + if (y > maxY) { + maxY = y; + } + } + + // 有効な点が一つも取れなかったのでスキップ + if (minX == Number.MAX_VALUE) { + continue; + } + + // 全体の矩形に反映 + if (minX < clippedDrawTotalMinX) { + clippedDrawTotalMinX = minX; + } + if (minY < clippedDrawTotalMinY) { + clippedDrawTotalMinY = minY; + } + if (maxX > clippedDrawTotalMaxX) { + clippedDrawTotalMaxX = maxX; + } + if (maxY > clippedDrawTotalMaxY) { + clippedDrawTotalMaxY = maxY; + } + + if (clippedDrawTotalMinX == Number.MAX_VALUE) { + clippingContext._allClippedDrawRect.x = 0.0; + clippingContext._allClippedDrawRect.y = 0.0; + clippingContext._allClippedDrawRect.width = 0.0; + clippingContext._allClippedDrawRect.height = 0.0; + clippingContext._isUsing = false; + } else { + clippingContext._isUsing = true; + const w: number = clippedDrawTotalMaxX - clippedDrawTotalMinX; + const h: number = clippedDrawTotalMaxY - clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.x = clippedDrawTotalMinX; + clippingContext._allClippedDrawRect.y = clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.width = w; + clippingContext._allClippedDrawRect.height = h; + } + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._maskRenderTexture = null; + this._colorBuffer = null; + this._currentFrameNo = 0; + this._clippingMaskBufferSize = 256; + this._clippingContextListForMask = new csmVector(); + this._clippingContextListForDraw = new csmVector(); + this._channelColors = new csmVector(); + this._tmpBoundsOnModel = new csmRect(); + this._tmpMatrix = new CubismMatrix44(); + this._tmpMatrixForMask = new CubismMatrix44(); + this._tmpMatrixForDraw = new CubismMatrix44(); + this._maskTexture = null; + + let tmp: CubismTextureColor = new CubismTextureColor(); + tmp.R = 1.0; + tmp.G = 0.0; + tmp.B = 0.0; + tmp.A = 0.0; + this._channelColors.pushBack(tmp); + + tmp = new CubismTextureColor(); + tmp.R = 0.0; + tmp.G = 1.0; + tmp.B = 0.0; + tmp.A = 0.0; + this._channelColors.pushBack(tmp); + + tmp = new CubismTextureColor(); + tmp.R = 0.0; + tmp.G = 0.0; + tmp.B = 1.0; + tmp.A = 0.0; + this._channelColors.pushBack(tmp); + + tmp = new CubismTextureColor(); + tmp.R = 0.0; + tmp.G = 0.0; + tmp.B = 0.0; + tmp.A = 1.0; + this._channelColors.pushBack(tmp); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._clippingContextListForMask.getSize(); i++) { + if (this._clippingContextListForMask.at(i)) { + this._clippingContextListForMask.at(i).release(); + this._clippingContextListForMask.set(i, void 0); + } + this._clippingContextListForMask.set(i, null); + } + this._clippingContextListForMask = null; + + // _clippingContextListForDrawは_clippingContextListForMaskにあるインスタンスを指している。上記の処理により要素ごとのDELETEは不要。 + for (let i = 0; i < this._clippingContextListForDraw.getSize(); i++) { + this._clippingContextListForDraw.set(i, null); + } + this._clippingContextListForDraw = null; + + if (this._maskTexture) { + this.gl.deleteFramebuffer(this._maskTexture.texture); + this._maskTexture = null; + } + + for (let i = 0; i < this._channelColors.getSize(); i++) { + this._channelColors.set(i, null); + } + + this._channelColors = null; + + // テクスチャ解放 + this.gl.deleteTexture(this._colorBuffer); + this._colorBuffer = null; + } + + /** + * マネージャの初期化処理 + * クリッピングマスクを使う描画オブジェクトの登録を行う + * @param model モデルのインスタンス + * @param drawableCount 描画オブジェクトの数 + * @param drawableMasks 描画オブジェクトをマスクする描画オブジェクトのインデックスのリスト + * @param drawableCounts 描画オブジェクトをマスクする描画オブジェクトの数 + */ + public initialize( + model: CubismModel, + drawableCount: number, + drawableMasks: Int32Array[], + drawableMaskCounts: Int32Array + ): void { + // クリッピングマスクを使う描画オブジェクトをすべて登録する + // クリッピングマスクは、通常数個程度に限定して使うものとする + for (let i = 0; i < drawableCount; i++) { + if (drawableMaskCounts[i] <= 0) { + // クリッピングマスクが使用されていないアートメッシュ(多くの場合使用しない) + this._clippingContextListForDraw.pushBack(null); + continue; + } + + // 既にあるClipContextと同じかチェックする + let clippingContext: CubismClippingContext = this.findSameClip( + drawableMasks[i], + drawableMaskCounts[i] + ); + if (clippingContext == null) { + // 同一のマスクが存在していない場合は生成する + clippingContext = new CubismClippingContext( + this, + drawableMasks[i], + drawableMaskCounts[i] + ); + this._clippingContextListForMask.pushBack(clippingContext); + } + + clippingContext.addClippedDrawable(i); + + this._clippingContextListForDraw.pushBack(clippingContext); + } + } + + /** + * クリッピングコンテキストを作成する。モデル描画時に実行する。 + * @param model モデルのインスタンス + * @param renderer レンダラのインスタンス + */ + public setupClippingContext( + model: CubismModel, + renderer: CubismRenderer_WebGL + ): void { + this._currentFrameNo++; + + // 全てのクリッピングを用意する + // 同じクリップ(複数の場合はまとめて一つのクリップ)を使う場合は1度だけ設定する + let usingClipCount = 0; + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.getSize(); + clipIndex++ + ) { + // 1つのクリッピングマスクに関して + const cc: CubismClippingContext = this._clippingContextListForMask.at( + clipIndex + ); + + // このクリップを利用する描画オブジェクト群全体を囲む矩形を計算 + this.calcClippedDrawTotalBounds(model, cc); + + if (cc._isUsing) { + usingClipCount++; // 使用中としてカウント + } + } + + // マスク作成処理 + if (usingClipCount > 0) { + // 生成したFrameBufferと同じサイズでビューポートを設定 + this.gl.viewport( + 0, + 0, + this._clippingMaskBufferSize, + this._clippingMaskBufferSize + ); + + // マスクをactiveにする + this._maskRenderTexture = this.getMaskRenderTexture(); + + // モデル描画時にDrawMeshNowに渡される変換(モデルtoワールド座標変換) + const modelToWorldF: CubismMatrix44 = renderer.getMvpMatrix(); + + renderer.preDraw(); // バッファをクリアする + + // 各マスクのレイアウトを決定していく + this.setupLayoutBounds(usingClipCount); + + // ---------- マスク描画処理 ---------- + // マスク用RenderTextureをactiveにセット + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this._maskRenderTexture); + + // マスクをクリアする + // (仮仕様) 1が無効(描かれない)領域、0が有効(描かれる)領域。(シェーダーCd*Csで0に近い値をかけてマスクを作る。1をかけると何も起こらない) + this.gl.clearColor(1.0, 1.0, 1.0, 1.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + + // 実際にマスクを生成する + // 全てのマスクをどのようにレイアウトして描くかを決定し、ClipContext, ClippedDrawContextに記憶する + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.getSize(); + clipIndex++ + ) { + // --- 実際に1つのマスクを描く --- + const clipContext: CubismClippingContext = this._clippingContextListForMask.at( + clipIndex + ); + const allClipedDrawRect: csmRect = clipContext._allClippedDrawRect; // このマスクを使う、すべての描画オブジェクトの論理座標上の囲み矩形 + const layoutBoundsOnTex01: csmRect = clipContext._layoutBounds; // この中にマスクを収める + + // モデル座標上の矩形を、適宜マージンを付けて使う + const MARGIN = 0.05; + this._tmpBoundsOnModel.setRect(allClipedDrawRect); + this._tmpBoundsOnModel.expand( + allClipedDrawRect.width * MARGIN, + allClipedDrawRect.height * MARGIN + ); + //########## 本来は割り当てられた領域の全体を使わず必要最低限のサイズがよい + + // シェーダ用の計算式を求める。回転を考慮しない場合は以下のとおり + // movePeriod' = movePeriod * scaleX + offX [[ movePeriod' = (movePeriod - tmpBoundsOnModel.movePeriod)*scale + layoutBoundsOnTex01.movePeriod ]] + const scaleX: number = + layoutBoundsOnTex01.width / this._tmpBoundsOnModel.width; + const scaleY: number = + layoutBoundsOnTex01.height / this._tmpBoundsOnModel.height; + + // マスク生成時に使う行列を求める + { + // シェーダに渡す行列を求める <<<<<<<<<<<<<<<<<<<<<<<< 要最適化(逆順に計算すればシンプルにできる) + this._tmpMatrix.loadIdentity(); + { + // layout0..1 を -1..1に変換 + this._tmpMatrix.translateRelative(-1.0, -1.0); + this._tmpMatrix.scaleRelative(2.0, 2.0); + } + { + // view to layout0..1 + this._tmpMatrix.translateRelative( + layoutBoundsOnTex01.x, + layoutBoundsOnTex01.y + ); + this._tmpMatrix.scaleRelative(scaleX, scaleY); // new = [translate][scale] + this._tmpMatrix.translateRelative( + -this._tmpBoundsOnModel.x, + -this._tmpBoundsOnModel.y + ); + // new = [translate][scale][translate] + } + // tmpMatrixForMaskが計算結果 + this._tmpMatrixForMask.setMatrix(this._tmpMatrix.getArray()); + } + + //--------- draw時の mask 参照用行列を計算 + { + // シェーダに渡す行列を求める <<<<<<<<<<<<<<<<<<<<<<<< 要最適化(逆順に計算すればシンプルにできる) + this._tmpMatrix.loadIdentity(); + { + this._tmpMatrix.translateRelative( + layoutBoundsOnTex01.x, + layoutBoundsOnTex01.y + ); + this._tmpMatrix.scaleRelative(scaleX, scaleY); // new = [translate][scale] + this._tmpMatrix.translateRelative( + -this._tmpBoundsOnModel.x, + -this._tmpBoundsOnModel.y + ); + // new = [translate][scale][translate] + } + this._tmpMatrixForDraw.setMatrix(this._tmpMatrix.getArray()); + } + clipContext._matrixForMask.setMatrix( + this._tmpMatrixForMask.getArray() + ); + clipContext._matrixForDraw.setMatrix( + this._tmpMatrixForDraw.getArray() + ); + + const clipDrawCount: number = clipContext._clippingIdCount; + for (let i = 0; i < clipDrawCount; i++) { + const clipDrawIndex: number = clipContext._clippingIdList[i]; + + // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする + if ( + !model.getDrawableDynamicFlagVertexPositionsDidChange( + clipDrawIndex + ) + ) { + continue; + } + + renderer.setIsCulling( + model.getDrawableCulling(clipDrawIndex) != false + ); + + // 今回専用の変換を適用して描く + // チャンネルも切り替える必要がある(A,R,G,B) + renderer.setClippingContextBufferForMask(clipContext); + renderer.drawMesh( + model.getDrawableTextureIndices(clipDrawIndex), + model.getDrawableVertexIndexCount(clipDrawIndex), + model.getDrawableVertexCount(clipDrawIndex), + model.getDrawableVertexIndices(clipDrawIndex), + model.getDrawableVertices(clipDrawIndex), + model.getDrawableVertexUvs(clipDrawIndex), + model.getDrawableOpacity(clipDrawIndex), + CubismBlendMode.CubismBlendMode_Normal, // クリッピングは通常描画を強制 + false // マスク生成時はクリッピングの反転使用は全く関係がない + ); + } + } + + // --- 後処理 --- + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, s_fbo); // 描画対象を戻す + renderer.setClippingContextBufferForMask(null); + + this.gl.viewport( + s_viewport[0], + s_viewport[1], + s_viewport[2], + s_viewport[3] + ); + } + } + + /** + * 既にマスクを作っているかを確認 + * 作っている様であれば該当するクリッピングマスクのインスタンスを返す + * 作っていなければNULLを返す + * @param drawableMasks 描画オブジェクトをマスクする描画オブジェクトのリスト + * @param drawableMaskCounts 描画オブジェクトをマスクする描画オブジェクトの数 + * @return 該当するクリッピングマスクが存在すればインスタンスを返し、なければNULLを返す + */ + public findSameClip( + drawableMasks: Int32Array, + drawableMaskCounts: number + ): CubismClippingContext { + // 作成済みClippingContextと一致するか確認 + for (let i = 0; i < this._clippingContextListForMask.getSize(); i++) { + const clippingContext: CubismClippingContext = this._clippingContextListForMask.at( + i + ); + const count: number = clippingContext._clippingIdCount; + + // 個数が違う場合は別物 + if (count != drawableMaskCounts) { + continue; + } + + let sameCount = 0; + + // 同じIDを持つか確認。配列の数が同じなので、一致した個数が同じなら同じ物を持つとする + for (let j = 0; j < count; j++) { + const clipId: number = clippingContext._clippingIdList[j]; + + for (let k = 0; k < count; k++) { + if (drawableMasks[k] == clipId) { + sameCount++; + break; + } + } + } + + if (sameCount == count) { + return clippingContext; + } + } + + return null; // 見つからなかった + } + + /** + * クリッピングコンテキストを配置するレイアウト + * 一つのレンダーテクスチャを極力いっぱいに使ってマスクをレイアウトする + * マスクグループの数が4以下ならRGBA各チャンネルに一つずつマスクを配置し、5以上6以下ならRGBAを2,2,1,1と配置する。 + * + * @param usingClipCount 配置するクリッピングコンテキストの数 + */ + public setupLayoutBounds(usingClipCount: number): void { + // ひとつのRenderTextureを極力いっぱいに使ってマスクをレイアウトする + // マスクグループの数が4以下ならRGBA各チャンネルに1つずつマスクを配置し、5以上6以下ならRGBAを2,2,1,1と配置する + + // RGBAを順番に使っていく + let div: number = usingClipCount / ColorChannelCount; // 1チャンネルに配置する基本のマスク + let mod: number = usingClipCount % ColorChannelCount; // 余り、この番号のチャンネルまでに一つずつ配分する + + // 小数点は切り捨てる + div = ~~div; + mod = ~~mod; + + // RGBAそれぞれのチャンネルを用意していく(0:R, 1:G, 2:B, 3:A) + let curClipIndex = 0; // 順番に設定していく + + for (let channelNo = 0; channelNo < ColorChannelCount; channelNo++) { + // このチャンネルにレイアウトする数 + const layoutCount: number = div + (channelNo < mod ? 1 : 0); + + // 分割方法を決定する + if (layoutCount == 0) { + // 何もしない + } else if (layoutCount == 1) { + // 全てをそのまま使う + const clipContext: CubismClippingContext = this._clippingContextListForMask.at( + curClipIndex++ + ); + clipContext._layoutChannelNo = channelNo; + clipContext._layoutBounds.x = 0.0; + clipContext._layoutBounds.y = 0.0; + clipContext._layoutBounds.width = 1.0; + clipContext._layoutBounds.height = 1.0; + } else if (layoutCount == 2) { + for (let i = 0; i < layoutCount; i++) { + let xpos: number = i % 2; + + // 小数点は切り捨てる + xpos = ~~xpos; + + const cc: CubismClippingContext = this._clippingContextListForMask.at( + curClipIndex++ + ); + cc._layoutChannelNo = channelNo; + + cc._layoutBounds.x = xpos * 0.5; + cc._layoutBounds.y = 0.0; + cc._layoutBounds.width = 0.5; + cc._layoutBounds.height = 1.0; + // UVを2つに分解して使う + } + } else if (layoutCount <= 4) { + // 4分割して使う + for (let i = 0; i < layoutCount; i++) { + let xpos: number = i % 2; + let ypos: number = i / 2; + + // 小数点は切り捨てる + xpos = ~~xpos; + ypos = ~~ypos; + + const cc = this._clippingContextListForMask.at(curClipIndex++); + cc._layoutChannelNo = channelNo; + + cc._layoutBounds.x = xpos * 0.5; + cc._layoutBounds.y = ypos * 0.5; + cc._layoutBounds.width = 0.5; + cc._layoutBounds.height = 0.5; + } + } else if (layoutCount <= 9) { + // 9分割して使う + for (let i = 0; i < layoutCount; i++) { + let xpos = i % 3; + let ypos = i / 3; + + // 小数点は切り捨てる + xpos = ~~xpos; + ypos = ~~ypos; + + const cc: CubismClippingContext = this._clippingContextListForMask.at( + curClipIndex++ + ); + cc._layoutChannelNo = channelNo; + + cc._layoutBounds.x = xpos / 3.0; + cc._layoutBounds.y = ypos / 3.0; + cc._layoutBounds.width = 1.0 / 3.0; + cc._layoutBounds.height = 1.0 / 3.0; + } + } else { + CubismLogError('not supported mask count : {0}', layoutCount); + } + } + } + + /** + * カラーバッファを取得する + * @return カラーバッファ + */ + public getColorBuffer(): WebGLTexture { + return this._colorBuffer; + } + + /** + * 画面描画に使用するクリッピングマスクのリストを取得する + * @return 画面描画に使用するクリッピングマスクのリスト + */ + public getClippingContextListForDraw(): csmVector { + return this._clippingContextListForDraw; + } + + /** + * クリッピングマスクバッファのサイズを設定する + * @param size クリッピングマスクバッファのサイズ + */ + public setClippingMaskBufferSize(size: number): void { + this._clippingMaskBufferSize = size; + } + + /** + * クリッピングマスクバッファのサイズを取得する + * @return クリッピングマスクバッファのサイズ + */ + public getClippingMaskBufferSize(): number { + return this._clippingMaskBufferSize; + } + + public _maskRenderTexture: WebGLFramebuffer; // マスク用レンダーテクスチャのアドレス + public _colorBuffer: WebGLTexture; // マスク用カラーバッファーのアドレス + public _currentFrameNo: number; // マスクテクスチャに与えるフレーム番号 + + public _channelColors: csmVector; + public _maskTexture: CubismRenderTextureResource; // マスク用のテクスチャリソースのリスト + public _clippingContextListForMask: csmVector; // マスク用クリッピングコンテキストのリスト + public _clippingContextListForDraw: csmVector; // 描画用クリッピングコンテキストのリスト + public _clippingMaskBufferSize: number; // クリッピングマスクのバッファサイズ(初期値:256) + + private _tmpMatrix: CubismMatrix44; // マスク計算用の行列 + private _tmpMatrixForMask: CubismMatrix44; // マスク計算用の行列 + private _tmpMatrixForDraw: CubismMatrix44; // マスク計算用の行列 + private _tmpBoundsOnModel: csmRect; // マスク配置計算用の矩形 + + gl: WebGLRenderingContext; // WebGLレンダリングコンテキスト + } + + /** + * レンダーテクスチャのリソースを定義する構造体 + * クリッピングマスクで使用する + */ + export class CubismRenderTextureResource { + /** + * 引数付きコンストラクタ + * @param frameNo レンダラーのフレーム番号 + * @param texture テクスチャのアドレス + */ + public constructor(frameNo: number, texture: WebGLFramebuffer) { + this.frameNo = frameNo; + this.texture = texture; + } + + public frameNo: number; // レンダラのフレーム番号 + public texture: WebGLFramebuffer; // テクスチャのアドレス + } + + /** + * クリッピングマスクのコンテキスト + */ + export class CubismClippingContext { + /** + * 引数付きコンストラクタ + */ + public constructor( + manager: CubismClippingManager_WebGL, + clippingDrawableIndices: Int32Array, + clipCount: number + ) { + this._owner = manager; + + // クリップしている(=マスク用の)Drawableのインデックスリスト + this._clippingIdList = clippingDrawableIndices; + + // マスクの数 + this._clippingIdCount = clipCount; + + this._allClippedDrawRect = new csmRect(); + this._layoutBounds = new csmRect(); + + this._clippedDrawableIndexList = []; + + this._matrixForMask = new CubismMatrix44(); + this._matrixForDraw = new CubismMatrix44(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._layoutBounds != null) { + this._layoutBounds = null; + } + + if (this._allClippedDrawRect != null) { + this._allClippedDrawRect = null; + } + + if (this._clippedDrawableIndexList != null) { + this._clippedDrawableIndexList = null; + } + } + + /** + * このマスクにクリップされる描画オブジェクトを追加する + * + * @param drawableIndex クリッピング対象に追加する描画オブジェクトのインデックス + */ + public addClippedDrawable(drawableIndex: number) { + this._clippedDrawableIndexList.push(drawableIndex); + } + + /** + * このマスクを管理するマネージャのインスタンスを取得する + * @return クリッピングマネージャのインスタンス + */ + public getClippingManager(): CubismClippingManager_WebGL { + return this._owner; + } + + public setGl(gl: WebGLRenderingContext): void { + this._owner.setGL(gl); + } + + public _isUsing: boolean; // 現在の描画状態でマスクの準備が必要ならtrue + public readonly _clippingIdList: Int32Array; // クリッピングマスクのIDリスト + public _clippingIdCount: number; // クリッピングマスクの数 + public _layoutChannelNo: number; // RGBAのいずれのチャンネルにこのクリップを配置するか(0:R, 1:G, 2:B, 3:A) + public _layoutBounds: csmRect; // マスク用チャンネルのどの領域にマスクを入れるか(View座標-1~1, UVは0~1に直す) + public _allClippedDrawRect: csmRect; // このクリッピングで、クリッピングされるすべての描画オブジェクトの囲み矩形(毎回更新) + public _matrixForMask: CubismMatrix44; // マスクの位置計算結果を保持する行列 + public _matrixForDraw: CubismMatrix44; // 描画オブジェクトの位置計算結果を保持する行列 + public _clippedDrawableIndexList: number[]; // このマスクにクリップされる描画オブジェクトのリスト + + private _owner: CubismClippingManager_WebGL; // このマスクを管理しているマネージャのインスタンス + } + + /** + * WebGL用のシェーダープログラムを生成・破棄するクラス + * シングルトンなクラスであり、CubismShader_WebGL.getInstanceからアクセスする。 + */ + export class CubismShader_WebGL { + /** + * インスタンスを取得する(シングルトン) + * @return インスタンス + */ + public static getInstance(): CubismShader_WebGL { + if (s_instance == null) { + s_instance = new CubismShader_WebGL(); + + return s_instance; + } + return s_instance; + } + + /** + * インスタンスを開放する(シングルトン) + */ + public static deleteInstance(): void { + if (s_instance) { + s_instance.release(); + s_instance = null; + } + } + + /** + * privateなコンストラクタ + */ + private constructor() { + this._shaderSets = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this.releaseShaderProgram(); + } + + /** + * シェーダープログラムの一連のセットアップを実行する + * @param renderer レンダラのインスタンス + * @param textureId GPUのテクスチャID + * @param vertexCount ポリゴンメッシュの頂点数 + * @param vertexArray ポリゴンメッシュの頂点配列 + * @param indexArray インデックスバッファの頂点配列 + * @param uvArray uv配列 + * @param opacity 不透明度 + * @param colorBlendMode カラーブレンディングのタイプ + * @param baseColor ベースカラー + * @param isPremultipliedAlpha 乗算済みアルファかどうか + * @param matrix4x4 Model-View-Projection行列 + * @param invertedMask マスクを反転して使用するフラグ + */ + public setupShaderProgram( + renderer: CubismRenderer_WebGL, + textureId: WebGLTexture, + vertexCount: number, + vertexArray: Float32Array, + indexArray: Uint16Array, + uvArray: Float32Array, + bufferData: { + vertex: WebGLBuffer; + uv: WebGLBuffer; + index: WebGLBuffer; + }, + opacity: number, + colorBlendMode: CubismBlendMode, + baseColor: CubismTextureColor, + isPremultipliedAlpha: boolean, + matrix4x4: CubismMatrix44, + invertedMask: boolean + ): void { + if (!isPremultipliedAlpha) { + CubismLogError('NoPremultipliedAlpha is not allowed'); + } + + if (this._shaderSets.getSize() == 0) { + this.generateShaders(); + } + + // Blending + let SRC_COLOR: number; + let DST_COLOR: number; + let SRC_ALPHA: number; + let DST_ALPHA: number; + + if (renderer.getClippingContextBufferForMask() != null) { + // マスク生成時 + const shaderSet: CubismShaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_SetupMask + ); + this.gl.useProgram(shaderSet.shaderProgram); + + // テクスチャ設定 + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, textureId); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + // 頂点配列の設定(VBO) + if (bufferData.vertex == null) { + bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.vertex); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + vertexArray, + this.gl.DYNAMIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // テクスチャ頂点の設定 + if (bufferData.uv == null) { + bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.uv); + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // チャンネル + const channelNo: number = renderer.getClippingContextBufferForMask() + ._layoutChannelNo; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForMask() + .getClippingManager() + .getChannelFlagAsColor(channelNo); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.R, + colorChannel.G, + colorChannel.B, + colorChannel.A + ); + + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer.getClippingContextBufferForMask()._matrixForMask.getArray() + ); + + const rect: csmRect = renderer.getClippingContextBufferForMask() + ._layoutBounds; + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + rect.x * 2.0 - 1.0, + rect.y * 2.0 - 1.0, + rect.getRight() * 2.0 - 1.0, + rect.getBottom() * 2.0 - 1.0 + ); + + SRC_COLOR = this.gl.ZERO; + DST_COLOR = this.gl.ONE_MINUS_SRC_COLOR; + SRC_ALPHA = this.gl.ZERO; + DST_ALPHA = this.gl.ONE_MINUS_SRC_ALPHA; + } // マスク生成以外の場合 + else { + const masked: boolean = + renderer.getClippingContextBufferForDraw() != null; // この描画オブジェクトはマスク対象か + const offset: number = masked ? (invertedMask ? 2 : 1) : 0; + + let shaderSet: CubismShaderSet = new CubismShaderSet(); + + switch (colorBlendMode) { + case CubismBlendMode.CubismBlendMode_Normal: + default: + shaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_NormalPremultipliedAlpha + offset + ); + SRC_COLOR = this.gl.ONE; + DST_COLOR = this.gl.ONE_MINUS_SRC_ALPHA; + SRC_ALPHA = this.gl.ONE; + DST_ALPHA = this.gl.ONE_MINUS_SRC_ALPHA; + break; + + case CubismBlendMode.CubismBlendMode_Additive: + shaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_AddPremultipliedAlpha + offset + ); + SRC_COLOR = this.gl.ONE; + DST_COLOR = this.gl.ONE; + SRC_ALPHA = this.gl.ZERO; + DST_ALPHA = this.gl.ONE; + break; + + case CubismBlendMode.CubismBlendMode_Multiplicative: + shaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_MultPremultipliedAlpha + offset + ); + SRC_COLOR = this.gl.DST_COLOR; + DST_COLOR = this.gl.ONE_MINUS_SRC_ALPHA; + SRC_ALPHA = this.gl.ZERO; + DST_ALPHA = this.gl.ONE; + break; + } + + this.gl.useProgram(shaderSet.shaderProgram); + + // 頂点配列の設定 + if (bufferData.vertex == null) { + bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.vertex); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + vertexArray, + this.gl.DYNAMIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // テクスチャ頂点の設定 + if (bufferData.uv == null) { + bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.uv); + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + if (masked) { + this.gl.activeTexture(this.gl.TEXTURE1); + const tex: WebGLTexture = renderer + .getClippingContextBufferForDraw() + .getClippingManager() + .getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex); + this.gl.uniform1i(shaderSet.samplerTexture1Location, 1); + + // view座標をClippingContextの座標に変換するための行列を設定 + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer.getClippingContextBufferForDraw()._matrixForDraw.getArray() + ); + + // 使用するカラーチャンネルを設定 + const channelNo: number = renderer.getClippingContextBufferForDraw() + ._layoutChannelNo; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForDraw() + .getClippingManager() + .getChannelFlagAsColor(channelNo); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.R, + colorChannel.G, + colorChannel.B, + colorChannel.A + ); + } + + // テクスチャ設定 + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, textureId); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + // 座標変換 + this.gl.uniformMatrix4fv( + shaderSet.uniformMatrixLocation, + false, + matrix4x4.getArray() + ); + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + baseColor.R, + baseColor.G, + baseColor.B, + baseColor.A + ); + } + + // IBOを作成し、データを転送 + if (bufferData.index == null) { + bufferData.index = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, bufferData.index); + this.gl.bufferData( + this.gl.ELEMENT_ARRAY_BUFFER, + indexArray, + this.gl.DYNAMIC_DRAW + ); + this.gl.blendFuncSeparate(SRC_COLOR, DST_COLOR, SRC_ALPHA, DST_ALPHA); + } + + /** + * シェーダープログラムを解放する + */ + public releaseShaderProgram(): void { + for (let i = 0; i < this._shaderSets.getSize(); i++) { + this.gl.deleteProgram(this._shaderSets.at(i).shaderProgram); + this._shaderSets.at(i).shaderProgram = 0; + this._shaderSets.set(i, void 0); + this._shaderSets.set(i, null); + } + } + + /** + * シェーダープログラムを初期化する + * @param vertShaderSrc 頂点シェーダのソース + * @param fragShaderSrc フラグメントシェーダのソース + */ + public generateShaders(): void { + for (let i = 0; i < shaderCount; i++) { + this._shaderSets.pushBack(new CubismShaderSet()); + } + + this._shaderSets.at(0).shaderProgram = this.loadShaderProgram( + vertexShaderSrcSetupMask, + fragmentShaderSrcsetupMask + ); + + this._shaderSets.at(1).shaderProgram = this.loadShaderProgram( + vertexShaderSrc, + fragmentShaderSrcPremultipliedAlpha + ); + this._shaderSets.at(2).shaderProgram = this.loadShaderProgram( + vertexShaderSrcMasked, + fragmentShaderSrcMaskPremultipliedAlpha + ); + this._shaderSets.at(3).shaderProgram = this.loadShaderProgram( + vertexShaderSrcMasked, + fragmentShaderSrcMaskInvertedPremultipliedAlpha + ); + + // 加算も通常と同じシェーダーを利用する + this._shaderSets.at(4).shaderProgram = this._shaderSets.at( + 1 + ).shaderProgram; + this._shaderSets.at(5).shaderProgram = this._shaderSets.at( + 2 + ).shaderProgram; + this._shaderSets.at(6).shaderProgram = this._shaderSets.at( + 3 + ).shaderProgram; + + // 乗算も通常と同じシェーダーを利用する + this._shaderSets.at(7).shaderProgram = this._shaderSets.at( + 1 + ).shaderProgram; + this._shaderSets.at(8).shaderProgram = this._shaderSets.at( + 2 + ).shaderProgram; + this._shaderSets.at(9).shaderProgram = this._shaderSets.at( + 3 + ).shaderProgram; + + // SetupMask + this._shaderSets.at( + 0 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(0).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 0 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(0).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 0 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 0 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 0 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 0 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 'u_baseColor' + ); + + // 通常(PremultipliedAlpha) + this._shaderSets.at( + 1 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(1).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 1 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(1).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 1 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(1).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(1).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(1).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 1 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(1).shaderProgram, + 'u_baseColor' + ); + + // 通常(クリッピング、PremultipliedAlpha) + this._shaderSets.at( + 2 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(2).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 2 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(2).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 2 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 2 + ).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(2).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 2 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 2 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 2 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_baseColor' + ); + + // 通常(クリッピング・反転, PremultipliedAlpha) + this._shaderSets.at( + 3 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(3).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 3 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(3).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 3 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 3 + ).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(3).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 3 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 3 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 3 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_baseColor' + ); + + // 加算(PremultipliedAlpha) + this._shaderSets.at( + 4 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(4).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 4 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(4).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 4 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(4).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(4).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(4).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 4 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(4).shaderProgram, + 'u_baseColor' + ); + + // 加算(クリッピング、PremultipliedAlpha) + this._shaderSets.at( + 5 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(5).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 5 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(5).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 5 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 5 + ).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(5).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 5 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 5 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 5 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_baseColor' + ); + + // 加算(クリッピング・反転、PremultipliedAlpha) + this._shaderSets.at( + 6 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(6).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 6 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(6).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 6 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 6 + ).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(6).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 6 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 6 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 6 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_baseColor' + ); + + // 乗算(PremultipliedAlpha) + this._shaderSets.at( + 7 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(7).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 7 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(7).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 7 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(7).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(7).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(7).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 7 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(7).shaderProgram, + 'u_baseColor' + ); + + // 乗算(クリッピング、PremultipliedAlpha) + this._shaderSets.at( + 8 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(8).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 8 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(8).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 8 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 8 + ).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(8).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 8 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 8 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 8 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_baseColor' + ); + + // 乗算(クリッピング・反転、PremultipliedAlpha) + this._shaderSets.at( + 9 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(9).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 9 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(9).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at( + 9 + ).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 9 + ).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(9).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 9 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 9 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 9 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_baseColor' + ); + } + + /** + * シェーダプログラムをロードしてアドレスを返す + * @param vertexShaderSource 頂点シェーダのソース + * @param fragmentShaderSource フラグメントシェーダのソース + * @return シェーダプログラムのアドレス + */ + public loadShaderProgram( + vertexShaderSource: string, + fragmentShaderSource: string + ): WebGLProgram { + // Create Shader Program + let shaderProgram: WebGLProgram = this.gl.createProgram(); + + let vertShader = this.compileShaderSource( + this.gl.VERTEX_SHADER, + vertexShaderSource + ); + + if (!vertShader) { + CubismLogError('Vertex shader compile error!'); + return 0; + } + + let fragShader = this.compileShaderSource( + this.gl.FRAGMENT_SHADER, + fragmentShaderSource + ); + if (!fragShader) { + CubismLogError('Vertex shader compile error!'); + return 0; + } + + // Attach vertex shader to program + this.gl.attachShader(shaderProgram, vertShader); + + // Attach fragment shader to program + this.gl.attachShader(shaderProgram, fragShader); + + // link program + this.gl.linkProgram(shaderProgram); + const linkStatus = this.gl.getProgramParameter( + shaderProgram, + this.gl.LINK_STATUS + ); + + // リンクに失敗したらシェーダーを削除 + if (!linkStatus) { + CubismLogError('Failed to link program: {0}', shaderProgram); + + this.gl.deleteShader(vertShader); + vertShader = 0; + + this.gl.deleteShader(fragShader); + fragShader = 0; + + if (shaderProgram) { + this.gl.deleteProgram(shaderProgram); + shaderProgram = 0; + } + + return 0; + } + + // Release vertex and fragment shaders. + this.gl.deleteShader(vertShader); + this.gl.deleteShader(fragShader); + + return shaderProgram; + } + + /** + * シェーダープログラムをコンパイルする + * @param shaderType シェーダタイプ(Vertex/Fragment) + * @param shaderSource シェーダソースコード + * + * @return コンパイルされたシェーダープログラム + */ + public compileShaderSource( + shaderType: GLenum, + shaderSource: string + ): WebGLProgram { + const source: string = shaderSource; + + const shader: WebGLProgram = this.gl.createShader(shaderType); + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + + if (!shader) { + const log: string = this.gl.getShaderInfoLog(shader); + CubismLogError('Shader compile log: {0} ', log); + } + + const status: any = this.gl.getShaderParameter( + shader, + this.gl.COMPILE_STATUS + ); + if (!status) { + this.gl.deleteShader(shader); + return null; + } + + return shader; + } + + public setGl(gl: WebGLRenderingContext): void { + this.gl = gl; + } + + _shaderSets: csmVector; // ロードしたシェーダープログラムを保持する変数 + gl: WebGLRenderingContext; // webglコンテキスト + } + + /** + * CubismShader_WebGLのインナークラス + */ + export class CubismShaderSet { + shaderProgram: WebGLProgram; // シェーダープログラムのアドレス + attributePositionLocation: GLuint; // シェーダープログラムに渡す変数のアドレス(Position) + attributeTexCoordLocation: GLuint; // シェーダープログラムに渡す変数のアドレス(TexCoord) + uniformMatrixLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Matrix) + uniformClipMatrixLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ClipMatrix) + samplerTexture0Location: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Texture0) + samplerTexture1Location: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Texture1) + uniformBaseColorLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(BaseColor) + uniformChannelFlagLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ChannelFlag) + } + + export enum ShaderNames { + // SetupMask + ShaderNames_SetupMask, + + // Normal + ShaderNames_NormalPremultipliedAlpha, + ShaderNames_NormalMaskedPremultipliedAlpha, + ShaderNames_NomralMaskedInvertedPremultipliedAlpha, + + // Add + ShaderNames_AddPremultipliedAlpha, + ShaderNames_AddMaskedPremultipliedAlpha, + ShaderNames_AddMaskedPremultipliedAlphaInverted, + + // Mult + ShaderNames_MultPremultipliedAlpha, + ShaderNames_MultMaskedPremultipliedAlpha, + ShaderNames_MultMaskedPremultipliedAlphaInverted + } + + export const vertexShaderSrcSetupMask = + 'attribute vec4 a_position;' + + 'attribute vec2 a_texCoord;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_myPos;' + + 'uniform mat4 u_clipMatrix;' + + 'void main()' + + '{' + + ' gl_Position = u_clipMatrix * a_position;' + + ' v_myPos = u_clipMatrix * a_position;' + + ' v_texCoord = a_texCoord;' + + ' v_texCoord.y = 1.0 - v_texCoord.y;' + + '}'; + export const fragmentShaderSrcsetupMask = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_myPos;' + + 'uniform vec4 u_baseColor;' + + 'uniform vec4 u_channelFlag;' + + 'uniform sampler2D s_texture0;' + + 'void main()' + + '{' + + ' float isInside = ' + + ' step(u_baseColor.x, v_myPos.x/v_myPos.w)' + + ' * step(u_baseColor.y, v_myPos.y/v_myPos.w)' + + ' * step(v_myPos.x/v_myPos.w, u_baseColor.z)' + + ' * step(v_myPos.y/v_myPos.w, u_baseColor.w);' + + ' gl_FragColor = u_channelFlag * texture2D(s_texture0, v_texCoord).a * isInside;' + + '}'; + + //----- バーテックスシェーダプログラム ----- + // Normal & Add & Mult 共通 + export const vertexShaderSrc = + 'attribute vec4 a_position;' + //v.vertex + 'attribute vec2 a_texCoord;' + //v.texcoord + 'varying vec2 v_texCoord;' + //v2f.texcoord + 'uniform mat4 u_matrix;' + + 'void main()' + + '{' + + ' gl_Position = u_matrix * a_position;' + + ' v_texCoord = a_texCoord;' + + ' v_texCoord.y = 1.0 - v_texCoord.y;' + + '}'; + + // Normal & Add & Mult 共通(クリッピングされたものの描画用) + export const vertexShaderSrcMasked = + 'attribute vec4 a_position;' + + 'attribute vec2 a_texCoord;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_clipPos;' + + 'uniform mat4 u_matrix;' + + 'uniform mat4 u_clipMatrix;' + + 'void main()' + + '{' + + ' gl_Position = u_matrix * a_position;' + + ' v_clipPos = u_clipMatrix * a_position;' + + ' v_texCoord = a_texCoord;' + + ' v_texCoord.y = 1.0 - v_texCoord.y;' + + '}'; + + //----- フラグメントシェーダプログラム ----- + // Normal & Add & Mult 共通 (PremultipliedAlpha) + export const fragmentShaderSrcPremultipliedAlpha = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + //v2f.texcoord + 'uniform vec4 u_baseColor;' + + 'uniform sampler2D s_texture0;' + //_MainTex + 'void main()' + + '{' + + ' gl_FragColor = texture2D(s_texture0 , v_texCoord) * u_baseColor;' + + '}'; + + // Normal (クリッピングされたものの描画用、PremultipliedAlpha兼用) + export const fragmentShaderSrcMaskPremultipliedAlpha = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_clipPos;' + + 'uniform vec4 u_baseColor;' + + 'uniform vec4 u_channelFlag;' + + 'uniform sampler2D s_texture0;' + + 'uniform sampler2D s_texture1;' + + 'void main()' + + '{' + + ' vec4 col_formask = texture2D(s_texture0 , v_texCoord) * u_baseColor;' + + ' vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;' + + ' float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;' + + ' col_formask = col_formask * maskVal;' + + ' gl_FragColor = col_formask;' + + '}'; + + // Normal & Add & Mult 共通(クリッピングされて反転使用の描画用、PremultipliedAlphaの場合) + export const fragmentShaderSrcMaskInvertedPremultipliedAlpha = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_clipPos;' + + 'uniform sampler2D s_texture0;' + + 'uniform sampler2D s_texture1;' + + 'uniform vec4 u_channelFlag;' + + 'uniform vec4 u_baseColor;' + + 'void main()' + + '{' + + 'vec4 col_formask = texture2D(s_texture0, v_texCoord) * u_baseColor;' + + 'vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;' + + 'float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;' + + 'col_formask = col_formask * (1.0 - maskVal);' + + 'gl_FragColor = col_formask;' + + '}'; + + /** + * WebGL用の描画命令を実装したクラス + */ + export class CubismRenderer_WebGL extends CubismRenderer { + /** + * レンダラの初期化処理を実行する + * 引数に渡したモデルからレンダラの初期化処理に必要な情報を取り出すことができる + * + * @param model モデルのインスタンス + */ + public initialize(model: CubismModel): void { + if (model.isUsingMasking()) { + this._clippingManager = new CubismClippingManager_WebGL(); // クリッピングマスク・バッファ前処理方式を初期化 + this._clippingManager.initialize( + model, + model.getDrawableCount(), + model.getDrawableMasks(), + model.getDrawableMaskCounts() + ); + } + + this._sortedDrawableIndexList.resize(model.getDrawableCount(), 0); + + super.initialize(model); // 親クラスの処理を呼ぶ + } + + /** + * WebGLテクスチャのバインド処理 + * CubismRendererにテクスチャを設定し、CubismRenderer内でその画像を参照するためのIndex値を戻り値とする + * @param modelTextureNo セットするモデルテクスチャの番号 + * @param glTextureNo WebGLテクスチャの番号 + */ + public bindTexture(modelTextureNo: number, glTexture: WebGLTexture): void { + this._textures.setValue(modelTextureNo, glTexture); + } + + /** + * WebGLにバインドされたテクスチャのリストを取得する + * @return テクスチャのリスト + */ + public getBindedTextures(): csmMap { + return this._textures; + } + + /** + * クリッピングマスクバッファのサイズを設定する + * マスク用のFrameBufferを破棄、再作成する為処理コストは高い + * @param size クリッピングマスクバッファのサイズ + */ + public setClippingMaskBufferSize(size: number) { + // FrameBufferのサイズを変更するためにインスタンスを破棄・再作成する + this._clippingManager.release(); + this._clippingManager = void 0; + this._clippingManager = null; + + this._clippingManager = new CubismClippingManager_WebGL(); + + this._clippingManager.setClippingMaskBufferSize(size); + + this._clippingManager.initialize( + this.getModel(), + this.getModel().getDrawableCount(), + this.getModel().getDrawableMasks(), + this.getModel().getDrawableMaskCounts() + ); + } + + /** + * クリッピングマスクバッファのサイズを取得する + * @return クリッピングマスクバッファのサイズ + */ + public getClippingMaskBufferSize(): number { + return this._clippingManager.getClippingMaskBufferSize(); + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + this._clippingContextBufferForMask = null; + this._clippingContextBufferForDraw = null; + this._clippingManager = new CubismClippingManager_WebGL(); + this.firstDraw = true; + this._textures = new csmMap(); + this._sortedDrawableIndexList = new csmVector(); + this._bufferData = { + vertex: WebGLBuffer = null, + uv: WebGLBuffer = null, + index: WebGLBuffer = null + }; + + // テクスチャ対応マップの容量を確保しておく + this._textures.prepareCapacity(32, true); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._clippingManager.release(); + this._clippingManager = void 0; + this._clippingManager = null; + + this.gl.deleteBuffer(this._bufferData.vertex); + this._bufferData.vertex = null; + this.gl.deleteBuffer(this._bufferData.uv); + this._bufferData.uv = null; + this.gl.deleteBuffer(this._bufferData.index); + this._bufferData.index = null; + this._bufferData = null; + + this._textures = null; + } + + /** + * モデルを描画する実際の処理 + */ + public doDrawModel(): void { + //------------ クリッピングマスク・バッファ前処理方式の場合 ------------ + if (this._clippingManager != null) { + this.preDraw(); + this._clippingManager.setupClippingContext(this.getModel(), this); + } + + // 上記クリッピング処理内でも一度PreDrawを呼ぶので注意!! + this.preDraw(); + + const drawableCount: number = this.getModel().getDrawableCount(); + const renderOrder: Int32Array = this.getModel().getDrawableRenderOrders(); + + // インデックスを描画順でソート + for (let i = 0; i < drawableCount; ++i) { + const order: number = renderOrder[i]; + this._sortedDrawableIndexList.set(order, i); + } + + // 描画 + for (let i = 0; i < drawableCount; ++i) { + const drawableIndex: number = this._sortedDrawableIndexList.at(i); + + // Drawableが表示状態でなければ処理をパスする + if (!this.getModel().getDrawableDynamicFlagIsVisible(drawableIndex)) { + continue; + } + + // クリッピングマスクをセットする + this.setClippingContextBufferForDraw( + this._clippingManager != null + ? this._clippingManager + .getClippingContextListForDraw() + .at(drawableIndex) + : null + ); + + this.setIsCulling(this.getModel().getDrawableCulling(drawableIndex)); + + this.drawMesh( + this.getModel().getDrawableTextureIndices(drawableIndex), + this.getModel().getDrawableVertexIndexCount(drawableIndex), + this.getModel().getDrawableVertexCount(drawableIndex), + this.getModel().getDrawableVertexIndices(drawableIndex), + this.getModel().getDrawableVertices(drawableIndex), + this.getModel().getDrawableVertexUvs(drawableIndex), + this.getModel().getDrawableOpacity(drawableIndex), + this.getModel().getDrawableBlendMode(drawableIndex), + this.getModel().getDrawableInvertedMaskBit(drawableIndex) + ); + } + } + + /** + * [オーバーライド] + * 描画オブジェクト(アートメッシュ)を描画する。 + * ポリゴンメッシュとテクスチャ番号をセットで渡す。 + * @param textureNo 描画するテクスチャ番号 + * @param indexCount 描画オブジェクトのインデックス値 + * @param vertexCount ポリゴンメッシュの頂点数 + * @param indexArray ポリゴンメッシュのインデックス配列 + * @param vertexArray ポリゴンメッシュの頂点配列 + * @param uvArray uv配列 + * @param opacity 不透明度 + * @param colorBlendMode カラー合成タイプ + * @param invertedMask マスク使用時のマスクの反転使用 + */ + public drawMesh( + textureNo: number, + indexCount: number, + vertexCount: number, + indexArray: Uint16Array, + vertexArray: Float32Array, + uvArray: Float32Array, + opacity: number, + colorBlendMode: CubismBlendMode, + invertedMask: boolean + ): void { + // 裏面描画の有効・無効 + if (this.isCulling()) { + this.gl.enable(this.gl.CULL_FACE); + } else { + this.gl.disable(this.gl.CULL_FACE); + } + + this.gl.frontFace(this.gl.CCW); // Cubism SDK OpenGLはマスク・アートメッシュ共にCCWが表面 + + const modelColorRGBA: CubismTextureColor = this.getModelColor(); + + if (this.getClippingContextBufferForMask() == null) { + // マスク生成時以外 + modelColorRGBA.A *= opacity; + if (this.isPremultipliedAlpha()) { + modelColorRGBA.R *= modelColorRGBA.A; + modelColorRGBA.G *= modelColorRGBA.A; + modelColorRGBA.B *= modelColorRGBA.A; + } + } + + let drawtexture: WebGLTexture; // シェーダに渡すテクスチャ + + // テクスチャマップからバインド済みテクスチャIDを取得 + // バインドされていなければダミーのテクスチャIDをセットする + if (this._textures.getValue(textureNo) != null) { + drawtexture = this._textures.getValue(textureNo); + } else { + drawtexture = null; + } + + CubismShader_WebGL.getInstance().setupShaderProgram( + this, + drawtexture, + vertexCount, + vertexArray, + indexArray, + uvArray, + this._bufferData, + opacity, + colorBlendMode, + modelColorRGBA, + this.isPremultipliedAlpha(), + this.getMvpMatrix(), + invertedMask + ); + + // ポリゴンメッシュを描画する + this.gl.drawElements( + this.gl.TRIANGLES, + indexCount, + this.gl.UNSIGNED_SHORT, + 0 + ); + + // 後処理 + this.gl.useProgram(null); + this.setClippingContextBufferForDraw(null); + this.setClippingContextBufferForMask(null); + } + + /** + * レンダラが保持する静的なリソースを解放する + * WebGLの静的なシェーダープログラムを解放する + */ + public static doStaticRelease(): void { + CubismShader_WebGL.deleteInstance(); + } + + /** + * レンダーステートを設定する + * @param fbo アプリケーション側で指定しているフレームバッファ + * @param viewport ビューポート + */ + public setRenderState(fbo: WebGLFramebuffer, viewport: number[]): void { + s_fbo = fbo; + s_viewport = viewport; + } + + /** + * 描画開始時の追加処理 + * モデルを描画する前にクリッピングマスクに必要な処理を実装している + */ + public preDraw(): void { + if (this.firstDraw) { + this.firstDraw = false; + + // 拡張機能を有効にする + this._anisortopy = + this.gl.getExtension('EXT_texture_filter_anisotropic') || + this.gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || + this.gl.getExtension('MOZ_EXT_texture_filter_anisotropic'); + } + + this.gl.disable(this.gl.SCISSOR_TEST); + this.gl.disable(this.gl.STENCIL_TEST); + this.gl.disable(this.gl.DEPTH_TEST); + + // カリング(1.0beta3) + this.gl.frontFace(this.gl.CW); + + this.gl.enable(this.gl.BLEND); + this.gl.colorMask(true, true, true, true); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); // 前にバッファがバインドされていたら破棄する必要がある + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null); + } + + /** + * マスクテクスチャに描画するクリッピングコンテキストをセットする + */ + public setClippingContextBufferForMask(clip: CubismClippingContext) { + this._clippingContextBufferForMask = clip; + } + + /** + * マスクテクスチャに描画するクリッピングコンテキストを取得する + * @return マスクテクスチャに描画するクリッピングコンテキスト + */ + public getClippingContextBufferForMask(): CubismClippingContext { + return this._clippingContextBufferForMask; + } + + /** + * 画面上に描画するクリッピングコンテキストをセットする + */ + public setClippingContextBufferForDraw(clip: CubismClippingContext): void { + this._clippingContextBufferForDraw = clip; + } + + /** + * 画面上に描画するクリッピングコンテキストを取得する + * @return 画面上に描画するクリッピングコンテキスト + */ + public getClippingContextBufferForDraw(): CubismClippingContext { + return this._clippingContextBufferForDraw; + } + + /** + * glの設定 + */ + public startUp(gl: WebGLRenderingContext): void { + this.gl = gl; + this._clippingManager.setGL(gl); + CubismShader_WebGL.getInstance().setGl(gl); + } + + _textures: csmMap; // モデルが参照するテクスチャとレンダラでバインドしているテクスチャとのマップ + _sortedDrawableIndexList: csmVector; // 描画オブジェクトのインデックスを描画順に並べたリスト + _clippingManager: CubismClippingManager_WebGL; // クリッピングマスク管理オブジェクト + _clippingContextBufferForMask: CubismClippingContext; // マスクテクスチャに描画するためのクリッピングコンテキスト + _clippingContextBufferForDraw: CubismClippingContext; // 画面上描画するためのクリッピングコンテキスト + firstDraw: boolean; + _bufferData: { + vertex: WebGLBuffer; + uv: WebGLBuffer; + index: WebGLBuffer; + }; // 頂点バッファデータ + gl: WebGLRenderingContext; // webglコンテキスト + } + + /** + * レンダラが保持する静的なリソースを開放する + */ + CubismRenderer.staticRelease = (): void => { + CubismRenderer_WebGL.doStaticRelease(); + }; +} diff --git a/src/type/csmmap.ts b/src/type/csmmap.ts new file mode 100644 index 0000000..64a4200 --- /dev/null +++ b/src/type/csmmap.ts @@ -0,0 +1,307 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismLogDebug } from '../utils/cubismdebug'; + +export namespace Live2DCubismFramework { + /** + * Key-Valueのペアを定義するクラス + * csmMapクラスの内部データで使用する。 + */ + export class csmPair<_KeyT, _ValT> { + /** + * コンストラクタ + * @param key Keyとしてセットする値 + * @param value Valueとしてセットする値 + */ + public constructor(key?: _KeyT, value?: _ValT) { + this.first = key == undefined ? null : key; + + this.second = value == undefined ? null : value; + } + + public first: _KeyT; // keyとして用いる変数 + public second: _ValT; // valueとして用いる変数 + } + + /** + * マップ型 + */ + export class csmMap<_KeyT, _ValT> { + /** + * 引数付きコンストラクタ + * @param size 初期化時点で確保するサイズ + */ + public constructor(size?: number) { + if (size != undefined) { + if (size < 1) { + this._keyValues = []; + this._dummyValue = null; + this._size = 0; + } else { + this._keyValues = new Array(size); + this._size = size; + } + } else { + this._keyValues = []; + this._dummyValue = null; + this._size = 0; + } + } + + /** + * デストラクタ + */ + public release() { + this.clear(); + } + + /** + * キーを追加する + * @param key 新たに追加するキー + */ + public appendKey(key: _KeyT): void { + // 新しくKey/Valueのペアを作る + this.prepareCapacity(this._size + 1, false); // 1つ以上入る隙間を作る + // 新しいkey/valueのインデックスは_size + + this._keyValues[this._size] = new csmPair<_KeyT, _ValT>(key); + this._size += 1; + } + + /** + * 添字演算子[key]のオーバーロード(get) + * @param key 添字から特定されるValue値 + */ + public getValue(key: _KeyT): _ValT { + let found = -1; + + for (let i = 0; i < this._size; i++) { + if (this._keyValues[i].first == key) { + found = i; + break; + } + } + + if (found >= 0) { + return this._keyValues[found].second; + } else { + this.appendKey(key); // 新規キーを追加 + return this._keyValues[this._size - 1].second; + } + } + + /** + * 添字演算子[key]のオーバーロード(set) + * @param key 添字から特定されるValue値 + * @param value 代入するValue値 + */ + public setValue(key: _KeyT, value: _ValT): void { + let found = -1; + + for (let i = 0; i < this._size; i++) { + if (this._keyValues[i].first == key) { + found = i; + break; + } + } + + if (found >= 0) { + this._keyValues[found].second = value; + } else { + this.appendKey(key); // 新規キーを追加 + this._keyValues[this._size - 1].second = value; + } + } + + /** + * 引数で渡したKeyを持つ要素が存在するか + * @param key 存在を確認するkey + * @return true 引数で渡したkeyを持つ要素が存在する + * @return false 引数で渡したkeyを持つ要素が存在しない + */ + public isExist(key: _KeyT): boolean { + for (let i = 0; i < this._size; i++) { + if (this._keyValues[i].first == key) { + return true; + } + } + return false; + } + + /** + * keyValueのポインタを全て解放する + */ + public clear(): void { + this._keyValues = void 0; + this._keyValues = null; + this._keyValues = []; + + this._size = 0; + } + + /** + * コンテナのサイズを取得する + * + * @return コンテナのサイズ + */ + public getSize(): number { + return this._size; + } + + /** + * コンテナのキャパシティを確保する + * @param newSize 新たなキャパシティ。引数の値が現在のサイズ未満の場合は何もしない。 + * @param fitToSize trueなら指定したサイズに合わせる。falseならサイズを2倍確保しておく。 + */ + public prepareCapacity(newSize: number, fitToSize: boolean): void { + if (newSize > this._keyValues.length) { + if (this._keyValues.length == 0) { + if (!fitToSize && newSize < csmMap.DefaultSize) + newSize = csmMap.DefaultSize; + this._keyValues.length = newSize; + } else { + if (!fitToSize && newSize < this._keyValues.length * 2) + newSize = this._keyValues.length * 2; + this._keyValues.length = newSize; + } + } + } + + /** + * コンテナの先頭要素を返す + */ + public begin(): iterator<_KeyT, _ValT> { + const ite: iterator<_KeyT, _ValT> = new iterator<_KeyT, _ValT>(this, 0); + return ite; + } + + /** + * コンテナの終端要素を返す + */ + public end(): iterator<_KeyT, _ValT> { + const ite: iterator<_KeyT, _ValT> = new iterator<_KeyT, _ValT>( + this, + this._size + ); // 終了 + return ite; + } + + /** + * コンテナから要素を削除する + * + * @param ite 削除する要素 + */ + public erase(ite: iterator<_KeyT, _ValT>): iterator<_KeyT, _ValT> { + const index: number = ite._index; + if (index < 0 || this._size <= index) { + return ite; // 削除範囲外 + } + + // 削除 + this._keyValues.splice(index, 1); + --this._size; + + const ite2: iterator<_KeyT, _ValT> = new iterator<_KeyT, _ValT>( + this, + index + ); // 終了 + return ite2; + } + + /** + * コンテナの値を32ビット符号付き整数型でダンプする + */ + public dumpAsInt() { + for (let i = 0; i < this._size; i++) { + CubismLogDebug('{0} ,', this._keyValues[i]); + CubismLogDebug('\n'); + } + } + + public static readonly DefaultSize = 10; // コンテナの初期化のデフォルトサイズ + public _keyValues: csmPair<_KeyT, _ValT>[]; // key-valueペアの配列 + public _dummyValue: _ValT; // 空の値を返す為のダミー + public _size: number; // コンテナの要素数 + } + + /** + * csmMapのイテレータ + */ + export class iterator<_KeyT, _ValT> { + /** + * コンストラクタ + */ + constructor(v?: csmMap<_KeyT, _ValT>, idx?: number) { + this._map = v != undefined ? v : new csmMap<_KeyT, _ValT>(); + + this._index = idx != undefined ? idx : 0; + } + + /** + * =演算子のオーバーロード + */ + public set(ite: iterator<_KeyT, _ValT>): iterator<_KeyT, _ValT> { + this._index = ite._index; + this._map = ite._map; + return this; + } + + /** + * 前置き++演算子のオーバーロード + */ + public preIncrement(): iterator<_KeyT, _ValT> { + ++this._index; + return this; + } + + /** + * 前置き--演算子のオーバーロード + */ + public preDecrement(): iterator<_KeyT, _ValT> { + --this._index; + return this; + } + + /** + * 後置き++演算子のオーバーロード + */ + public increment(): iterator<_KeyT, _ValT> { + const iteold = new iterator<_KeyT, _ValT>(this._map, this._index++); // 古い値を保存 + this._map = iteold._map; + this._index = iteold._index; + return this; + } + + /** + * 後置き--演算子のオーバーロード + */ + public decrement(): iterator<_KeyT, _ValT> { + const iteold = new iterator<_KeyT, _ValT>(this._map, this._index); // 古い値を保存 + this._map = iteold._map; + this._index = iteold._index; + return this; + } + + /** + * *演算子のオーバーロード + */ + public ptr(): csmPair<_KeyT, _ValT> { + return this._map._keyValues[this._index]; + } + + /** + * !=演算 + */ + public notEqual(ite: iterator<_KeyT, _ValT>): boolean { + return this._index != ite._index || this._map != ite._map; + } + + _index: number; // コンテナのインデックス値 + _map: csmMap<_KeyT, _ValT>; // コンテナ + } +} diff --git a/src/type/csmrectf.ts b/src/type/csmrectf.ts new file mode 100644 index 0000000..ff8c330 --- /dev/null +++ b/src/type/csmrectf.ts @@ -0,0 +1,83 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + /** + * 矩形形状(座標・長さはfloat値)を定義するクラス + */ + export class csmRect { + /** + * コンストラクタ + * @param x 左端X座標 + * @param y 上端Y座標 + * @param w 幅 + * @param h 高さ + */ + public constructor(x?: number, y?: number, w?: number, h?: number) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + } + + /** + * 矩形中央のX座標を取得する + */ + public getCenterX(): number { + return this.x + 0.5 * this.width; + } + + /** + * 矩形中央のY座標を取得する + */ + public getCenterY(): number { + return this.y + 0.5 * this.height; + } + + /** + * 右側のX座標を取得する + */ + public getRight(): number { + return this.x + this.width; + } + + /** + * 下端のY座標を取得する + */ + public getBottom(): number { + return this.y + this.height; + } + + /** + * 矩形に値をセットする + * @param r 矩形のインスタンス + */ + public setRect(r: csmRect): void { + this.x = r.x; + this.y = r.y; + this.width = r.width; + this.height = r.height; + } + + /** + * 矩形中央を軸にして縦横を拡縮する + * @param w 幅方向に拡縮する量 + * @param h 高さ方向に拡縮する量 + */ + public expand(w: number, h: number) { + this.x -= w; + this.y -= h; + this.width += w * 2.0; + this.height += h * 2.0; + } + + public x: number; // 左端X座標 + public y: number; // 上端Y座標 + public width: number; // 幅 + public height: number; // 高さ + } +} diff --git a/src/type/csmstring.ts b/src/type/csmstring.ts new file mode 100644 index 0000000..af69faf --- /dev/null +++ b/src/type/csmstring.ts @@ -0,0 +1,101 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + /** + * 文字列クラス。 + */ + export class csmString { + /** + * 文字列を後方に追加する + * + * @param c 追加する文字列 + * @return 更新された文字列 + */ + public append(c: string, length?: number): csmString { + this.s += length !== undefined ? c.substr(0, length) : c; + + return this; + } + + /** + * 文字サイズを拡張して文字を埋める + * @param length 拡張する文字数 + * @param v 埋める文字 + * @return 更新された文字列 + */ + public expansion(length: number, v: string): csmString { + for (let i = 0; i < length; i++) { + this.append(v); + } + + return this; + } + + /** + * 文字列の長さをバイト数で取得する + */ + public getBytes(): number { + return encodeURIComponent(this.s).replace(/%../g, 'x').length; + } + + /** + * 文字列の長さを返す + */ + public getLength(): number { + return this.s.length; + } + + /** + * 文字列比較 < + * @param s 比較する文字列 + * @return true: 比較する文字列より小さい + * @return false: 比較する文字列より大きい + */ + public isLess(s: csmString): boolean { + return this.s < s.s; + } + + /** + * 文字列比較 > + * @param s 比較する文字列 + * @return true: 比較する文字列より大きい + * @return false: 比較する文字列より小さい + */ + public isGreat(s: csmString): boolean { + return this.s > s.s; + } + + /** + * 文字列比較 == + * @param s 比較する文字列 + * @return true: 比較する文字列と等しい + * @return false: 比較する文字列と異なる + */ + public isEqual(s: string): boolean { + return this.s == s; + } + + /** + * 文字列が空かどうか + * @return true: 空の文字列 + * @return false: 値が設定されている + */ + public isEmpty(): boolean { + return this.s.length == 0; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(s: string) { + this.s = s; + } + + s: string; + } +} diff --git a/src/type/csmvector.ts b/src/type/csmvector.ts new file mode 100644 index 0000000..4e105a5 --- /dev/null +++ b/src/type/csmvector.ts @@ -0,0 +1,348 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + /** + * ベクター型(可変配列型) + */ + export class csmVector { + /** + * 引数付きコンストラクタ + * @param iniitalCapacity 初期化後のキャパシティ。データサイズは_capacity * sizeof(T) + * @param zeroClear trueなら初期化時に確保した領域を0で埋める + */ + constructor(initialCapacity = 0) { + if (initialCapacity < 1) { + this._ptr = []; + this._capacity = 0; + this._size = 0; + } else { + this._ptr = new Array(initialCapacity); + this._capacity = initialCapacity; + this._size = 0; + } + } + + /** + * インデックスで指定した要素を返す + */ + public at(index: number): T { + return this._ptr[index]; + } + + /** + * 要素をセット + * @param index 要素をセットするインデックス + * @param value セットする要素 + */ + public set(index: number, value: T): void { + this._ptr[index] = value; + } + + /** + * コンテナを取得する + */ + public get(offset = 0): T[] { + const ret: T[] = new Array(); + for (let i = offset; i < this._size; i++) { + ret.push(this._ptr[i]); + } + return ret; + } + + /** + * pushBack処理、コンテナに新たな要素を追加する + * @param value PushBack処理で追加する値 + */ + public pushBack(value: T): void { + if (this._size >= this._capacity) { + this.prepareCapacity( + this._capacity == 0 ? csmVector.s_defaultSize : this._capacity * 2 + ); + } + + this._ptr[this._size++] = value; + } + + /** + * コンテナの全要素を解放する + */ + public clear(): void { + this._ptr.length = 0; + this._size = 0; + } + + /** + * コンテナの要素数を返す + * @return コンテナの要素数 + */ + public getSize(): number { + return this._size; + } + + /** + * コンテナの全要素に対して代入処理を行う + * @param newSize 代入処理後のサイズ + * @param value 要素に代入する値 + */ + public assign(newSize: number, value: T): void { + const curSize = this._size; + + if (curSize < newSize) { + this.prepareCapacity(newSize); // capacity更新 + } + + for (let i = 0; i < newSize; i++) { + this._ptr[i] = value; + } + + this._size = newSize; + } + + /** + * サイズ変更 + */ + public resize(newSize: number, value: T = null): void { + this.updateSize(newSize, value, true); + } + + /** + * サイズ変更 + */ + public updateSize( + newSize: number, + value: any = null, + callPlacementNew = true + ): void { + const curSize: number = this._size; + + if (curSize < newSize) { + this.prepareCapacity(newSize); // capacity更新 + + if (callPlacementNew) { + for (let i: number = this._size; i < newSize; i++) { + if (typeof value == 'function') { + // new + this._ptr[i] = JSON.parse(JSON.stringify(new value())); + } // プリミティブ型なので値渡し + else { + this._ptr[i] = value; + } + } + } else { + for (let i: number = this._size; i < newSize; i++) { + this._ptr[i] = value; + } + } + } else { + // newSize <= this._size + //--- + const sub = this._size - newSize; + this._ptr.splice(this._size - sub, sub); // 不要なので破棄する + } + this._size = newSize; + } + + /** + * コンテナにコンテナ要素を挿入する + * @param position 挿入する位置 + * @param begin 挿入するコンテナの開始位置 + * @param end 挿入するコンテナの終端位置 + */ + public insert( + position: iterator, + begin: iterator, + end: iterator + ): void { + let dstSi: number = position._index; + const srcSi: number = begin._index; + const srcEi: number = end._index; + + const addCount: number = srcEi - srcSi; + + this.prepareCapacity(this._size + addCount); + + // 挿入用の既存データをシフトして隙間を作る + const addSize = this._size - dstSi; + if (addSize > 0) { + for (let i = 0; i < addSize; i++) { + this._ptr.splice(dstSi + i, 0, null); + } + } + + for (let i: number = srcSi; i < srcEi; i++, dstSi++) { + this._ptr[dstSi] = begin._vector._ptr[i]; + } + + this._size = this._size + addCount; + } + + /** + * コンテナからインデックスで指定した要素を削除する + * @param index インデックス値 + * @return true 削除実行 + * @return false 削除範囲外 + */ + public remove(index: number): boolean { + if (index < 0 || this._size <= index) { + return false; // 削除範囲外 + } + + this._ptr.splice(index, 1); + --this._size; + + return true; + } + + /** + * コンテナから要素を削除して他の要素をシフトする + * @param ite 削除する要素 + */ + public erase(ite: iterator): iterator { + const index: number = ite._index; + if (index < 0 || this._size <= index) { + return ite; // 削除範囲外 + } + + // 削除 + this._ptr.splice(index, 1); + --this._size; + + const ite2: iterator = new iterator(this, index); // 終了 + return ite2; + } + + /** + * コンテナのキャパシティを確保する + * @param newSize 新たなキャパシティ。引数の値が現在のサイズ未満の場合は何もしない. + */ + public prepareCapacity(newSize: number): void { + if (newSize > this._capacity) { + if (this._capacity == 0) { + this._ptr = new Array(newSize); + this._capacity = newSize; + } else { + this._ptr.length = newSize; + this._capacity = newSize; + } + } + } + + /** + * コンテナの先頭要素を返す + */ + public begin(): iterator { + const ite: iterator = + this._size == 0 ? this.end() : new iterator(this, 0); + return ite; + } + + /** + * コンテナの終端要素を返す + */ + public end(): iterator { + const ite: iterator = new iterator(this, this._size); + return ite; + } + + public getOffset(offset: number): csmVector { + const newVector = new csmVector(); + newVector._ptr = this.get(offset); + newVector._size = this.get(offset).length; + newVector._capacity = this.get(offset).length; + + return newVector; + } + + _ptr: T[]; // コンテナの先頭アドレス + _size: number; // コンテナの要素数 + _capacity: number; // コンテナのキャパシティ + + static readonly s_defaultSize = 10; // コンテナ初期化のデフォルトサイズ + } + + export class iterator { + /** + * コンストラクタ + */ + public constructor(v?: csmVector, index?: number) { + this._vector = v != undefined ? v : null; + this._index = index != undefined ? index : 0; + } + + /** + * 代入 + */ + public set(ite: iterator): iterator { + this._index = ite._index; + this._vector = ite._vector; + return this; + } + + /** + * 前置き++演算 + */ + public preIncrement(): iterator { + ++this._index; + return this; + } + + /** + * 前置き--演算 + */ + public preDecrement(): iterator { + --this._index; + return this; + } + + /** + * 後置き++演算子 + */ + public increment(): iterator { + const iteold = new iterator(this._vector, this._index++); + this._vector = iteold._vector; + this._index = iteold._index; + return this; + } + + /** + * 後置き--演算子 + */ + public decrement(): iterator { + const iteold = new iterator(this._vector, this._index--); // 古い値を保存 + this._vector = iteold._vector; + this._index = iteold._index; + return this; + } + + /** + * ptr + */ + public ptr(): T { + return this._vector._ptr[this._index]; + } + + /** + * =演算子のオーバーロード + */ + public substitution(ite: iterator): iterator { + this._index = ite._index; + this._vector = ite._vector; + return this; + } + + /** + * !=演算子のオーバーロード + */ + public notEqual(ite: iterator): boolean { + return this._index != ite._index || this._vector != ite._vector; + } + + _index: number; // コンテナのインデックス値 + _vector: csmVector; // コンテナ + } +} diff --git a/src/utils/cubismdebug.ts b/src/utils/cubismdebug.ts new file mode 100644 index 0000000..fccc4cb --- /dev/null +++ b/src/utils/cubismdebug.ts @@ -0,0 +1,166 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { + Live2DCubismFramework as cubismframework, + LogLevel +} from '../live2dcubismframework'; +import { + CSM_LOG_LEVEL, + CSM_LOG_LEVEL_VERBOSE, + CSM_LOG_LEVEL_DEBUG, + CSM_LOG_LEVEL_INFO, + CSM_LOG_LEVEL_WARNING, + CSM_LOG_LEVEL_ERROR +} from '../cubismframeworkconfig'; + +export const CubismLogPrint = (level: LogLevel, fmt: string, args: any[]) => { + Live2DCubismFramework.CubismDebug.print(level, '[CSM]' + fmt, args); +}; + +export const CubismLogPrintIn = (level: LogLevel, fmt: string, args: any[]) => { + CubismLogPrint(level, fmt + '\n', args); +}; + +export const CSM_ASSERT = (expr: any) => { + console.assert(expr); +}; + +export let CubismLogVerbose: (fmt: string, ...args: any[]) => void; +export let CubismLogDebug: (fmt: string, ...args: any[]) => void; +export let CubismLogInfo: (fmt: string, ...args: any[]) => void; +export let CubismLogWarning: (fmt: string, ...args: any[]) => void; +export let CubismLogError: (fmt: string, ...args: any[]) => void; + +if (CSM_LOG_LEVEL <= CSM_LOG_LEVEL_VERBOSE) { + CubismLogVerbose = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Verbose, '[V]' + fmt, args); + }; + + CubismLogDebug = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Debug, '[D]' + fmt, args); + }; + + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_DEBUG) { + CubismLogDebug = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Debug, '[D]' + fmt, args); + }; + + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_INFO) { + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_WARNING) { + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_ERROR) { + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} + +//------------ LIVE2D NAMESPACE ------------ +export namespace Live2DCubismFramework { + /** + * デバッグ用のユーティリティクラス。 + * ログの出力、バイトのダンプなど + */ + export class CubismDebug { + /** + * ログを出力する。第一引数にログレベルを設定する。 + * CubismFramework.initialize()時にオプションで設定されたログ出力レベルを下回る場合はログに出さない。 + * + * @param logLevel ログレベルの設定 + * @param format 書式付き文字列 + * @param args 可変長引数 + */ + public static print( + logLevel: LogLevel, + format: string, + args?: any[] + ): void { + // オプションで設定されたログ出力レベルを下回る場合はログに出さない + if (logLevel < cubismframework.CubismFramework.getLoggingLevel()) { + return; + } + + const logPrint: Live2DCubismCore.csmLogFunction = + cubismframework.CubismFramework.coreLogFunction; + + if (!logPrint) return; + + const buffer: string = format.replace(/\{(\d+)\}/g, (m, k) => { + return args[k]; + }); + logPrint(buffer); + } + + /** + * データから指定した長さだけダンプ出力する。 + * CubismFramework.initialize()時にオプションで設定されたログ出力レベルを下回る場合はログに出さない。 + * + * @param logLevel ログレベルの設定 + * @param data ダンプするデータ + * @param length ダンプする長さ + */ + public static dumpBytes( + logLevel: LogLevel, + data: Uint8Array, + length: number + ): void { + for (let i = 0; i < length; i++) { + if (i % 16 == 0 && i > 0) this.print(logLevel, '\n'); + else if (i % 8 == 0 && i > 0) this.print(logLevel, ' '); + this.print(logLevel, '{0} ', [data[i] & 0xff]); + } + + this.print(logLevel, '\n'); + } + + /** + * private コンストラクタ + */ + private constructor() {} + } +} + +//------------ LIVE2D NAMESPACE ------------ diff --git a/src/utils/cubismjson.ts b/src/utils/cubismjson.ts new file mode 100644 index 0000000..10e91f6 --- /dev/null +++ b/src/utils/cubismjson.ts @@ -0,0 +1,1246 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Live2DCubismFramework as csmstring } from '../type/csmstring'; +import { Live2DCubismFramework as csmmap } from '../type/csmmap'; +import { Live2DCubismFramework as csmvector } from '../type/csmvector'; +import { CubismLogInfo } from './cubismdebug'; +import { strtod } from '../live2dcubismframework'; +import csmVector = csmvector.csmVector; +import csmVector_iterator = csmvector.iterator; +import csmMap = csmmap.csmMap; +import csmMap_iterator = csmmap.iterator; +import csmString = csmstring.csmString; + +export namespace Live2DCubismFramework { + // StaticInitializeNotForClientCall()で初期化する + const CSM_JSON_ERROR_TYPE_MISMATCH = 'Error: type mismatch'; + const CSM_JSON_ERROR_INDEX_OF_BOUNDS = 'Error: index out of bounds'; + + /** + * パースしたJSONエレメントの要素の基底クラス。 + */ + export abstract class Value { + /** + * コンストラクタ + */ + public constructor() {} + + /** + * 要素を文字列型で返す(csmString型) + */ + public abstract getString(defaultValue?: string, indent?: string): string; + + /** + * 要素を文字列型で返す(string) + */ + public getRawString(defaultValue?: string, indent?: string): string { + return this.getString(defaultValue, indent); + } + + /** + * 要素を数値型で返す(number) + */ + public toInt(defaultValue = 0): number { + return defaultValue; + } + + /** + * 要素を数値型で返す(number) + */ + public toFloat(defaultValue = 0): number { + return defaultValue; + } + + /** + * 要素を真偽値で返す(boolean) + */ + public toBoolean(defaultValue = false): boolean { + return defaultValue; + } + + /** + * サイズを返す + */ + public getSize(): number { + return 0; + } + + /** + * 要素を配列で返す(Value[]) + */ + public getArray(defaultValue: Value[] = null): Value[] { + return defaultValue; + } + + /** + * 要素をコンテナで返す(array) + */ + public getVector(defaultValue?: csmVector): csmVector { + return defaultValue; + } + + /** + * 要素をマップで返す(csmMap) + */ + public getMap(defaultValue?: csmMap): csmMap { + return defaultValue; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 添字演算子[string | csmString] + */ + public getValueByString(s: string | csmString): Value { + return Value.nullValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * マップのキー一覧をコンテナで返す + * + * @return マップのキーの一覧 + */ + public getKeys(): csmVector { + return Value.s_dummyKeys; + } + + /** + * Valueの種類がエラー値ならtrue + */ + public isError(): boolean { + return false; + } + + /** + * Valueの種類がnullならtrue + */ + public isNull(): boolean { + return false; + } + + /** + * Valueの種類が真偽値ならtrue + */ + public isBool(): boolean { + return false; + } + + /** + * Valueの種類が数値型ならtrue + */ + public isFloat(): boolean { + return false; + } + + /** + * Valueの種類が文字列ならtrue + */ + public isString(): boolean { + return false; + } + + /** + * Valueの種類が配列ならtrue + */ + public isArray(): boolean { + return false; + } + + /** + * Valueの種類がマップ型ならtrue + */ + public isMap(): boolean { + return false; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + return false; + } + + /** + * Valueの値が静的ならtrue、静的なら解放しない + */ + public isStatic(): boolean { + return false; + } + + /** + * Valueにエラー値をセットする + */ + public setErrorNotForClientCall(errorStr: string): Value { + return JsonError.errorValue; + } + + /** + * 初期化用メソッド + */ + public static staticInitializeNotForClientCall(): void { + JsonBoolean.trueValue = new JsonBoolean(true); + JsonBoolean.falseValue = new JsonBoolean(false); + + JsonError.errorValue = new JsonError('ERROR', true); + this.nullValue = new JsonNullvalue(); + + Value.s_dummyKeys = new csmVector(); + } + + /** + * リリース用メソッド + */ + public static staticReleaseNotForClientCall(): void { + JsonBoolean.trueValue = null; + JsonBoolean.falseValue = null; + JsonError.errorValue = null; + Value.nullValue = null; + Value.s_dummyKeys = null; + + JsonBoolean.trueValue = null; + JsonBoolean.falseValue = null; + JsonError.errorValue = null; + Value.nullValue = null; + Value.s_dummyKeys = null; + } + + protected _stringBuffer: string; // 文字列バッファ + + private static s_dummyKeys: csmVector; // ダミーキー + + public static errorValue: Value; // 一時的な返り値として返すエラー。 CubismFramework::Disposeするまではdeleteしない + public static nullValue: Value; // 一時的な返り値として返すNULL。 CubismFramework::Disposeするまではdeleteしない + } + + /** + * Ascii文字のみ対応した最小限の軽量JSONパーサ。 + * 仕様はJSONのサブセットとなる。 + * 設定ファイル(model3.json)などのロード用 + * + * [未対応項目] + * ・日本語などの非ASCII文字 + * ・eによる指数表現 + */ + export class CubismJson { + /** + * コンストラクタ + */ + public constructor(buffer?: ArrayBuffer, length?: number) { + this._error = null; + this._lineCount = 0; + this._root = null; + + if (buffer != undefined) { + this.parseBytes(buffer, length); + } + } + + /** + * バイトデータから直接ロードしてパースする + * + * @param buffer バッファ + * @param size バッファサイズ + * @return CubismJsonクラスのインスタンス。失敗したらNULL + */ + public static create(buffer: ArrayBuffer, size: number) { + const json = new CubismJson(); + const succeeded: boolean = json.parseBytes(buffer, size); + + if (!succeeded) { + CubismJson.delete(json); + return null; + } else { + return json; + } + } + + /** + * パースしたJSONオブジェクトの解放処理 + * + * @param instance CubismJsonクラスのインスタンス + */ + public static delete(instance: CubismJson) { + instance = null; + } + + /** + * パースしたJSONのルート要素を返す + */ + public getRoot(): Value { + return this._root; + } + + /** + * UnicodeのバイナリをStringに変換 + * + * @param buffer 変換するバイナリデータ + * @return 変換後の文字列 + */ + public arrayBufferToString(buffer: ArrayBuffer): string { + const uint8Array: Uint8Array = new Uint8Array(buffer); + let str = ''; + + for (let i = 0, len: number = uint8Array.length; i < len; ++i) { + str += '%' + this.pad(uint8Array[i].toString(16)); + } + + str = decodeURIComponent(str); + return str; + } + + /** + * エンコード、パディング + */ + private pad(n: string): string { + return n.length < 2 ? '0' + n : n; + } + + /** + * JSONのパースを実行する + * @param buffer パース対象のデータバイト + * @param size データバイトのサイズ + * return true : 成功 + * return false: 失敗 + */ + public parseBytes(buffer: ArrayBuffer, size: number): boolean { + const endPos: number[] = new Array(1); // 参照渡しにするため配列 + const decodeBuffer: string = this.arrayBufferToString(buffer); + this._root = this.parseValue(decodeBuffer, size, 0, endPos); + + if (this._error) { + let strbuf = '\0'; + strbuf = 'Json parse error : @line ' + (this._lineCount + 1) + '\n'; + this._root = new JsonString(strbuf); + + CubismLogInfo('{0}', this._root.getRawString()); + return false; + } else if (this._root == null) { + this._root = new JsonError(new csmString(this._error), false); // rootは解放されるのでエラーオブジェクトを別途作成する + return false; + } + return true; + } + + /** + * パース時のエラー値を返す + */ + public getParseError(): string { + return this._error; + } + + /** + * ルート要素の次の要素がファイルの終端だったらtrueを返す + */ + public checkEndOfFile(): boolean { + return this._root.getArray()[1].equals('EOF'); + } + + /** + * JSONエレメントからValue(float,String,Value*,Array,null,true,false)をパースする + * エレメントの書式に応じて内部でParseString(), ParseObject(), ParseArray()を呼ぶ + * + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseValue( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ) { + if (this._error) return null; + + let o: Value = null; + let i: number = begin; + let f: number; + + for (; i < length; i++) { + const c: string = buffer[i]; + switch (c) { + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + const afterString: string[] = new Array(1); // 参照渡しにするため + f = strtod(buffer.slice(i), afterString); + outEndPos[0] = buffer.indexOf(afterString[0]); + return new JsonFloat(f); + } + case '"': + return new JsonString( + this.parseString(buffer, length, i + 1, outEndPos) + ); // \"の次の文字から + case '[': + o = this.parseArray(buffer, length, i + 1, outEndPos); + return o; + case '{': + o = this.parseObject(buffer, length, i + 1, outEndPos); + return o; + case 'n': // null以外にない + if (i + 3 < length) { + o = new JsonNullvalue(); // 解放できるようにする + outEndPos[0] = i + 4; + } else { + this._error = 'parse null'; + } + return o; + case 't': // true以外にない + if (i + 3 < length) { + o = JsonBoolean.trueValue; + outEndPos[0] = i + 4; + } else { + this._error = 'parse true'; + } + return o; + case 'f': // false以外にない + if (i + 4 < length) { + o = JsonBoolean.falseValue; + outEndPos[0] = i + 5; + } else { + this._error = "illegal ',' position"; + } + return o; + case ',': // Array separator + this._error = "illegal ',' position"; + return null; + case ']': // 不正な}だがスキップする。配列の最後に不要な , があると思われる + outEndPos[0] = i; // 同じ文字を再処理 + return null; + case '\n': + this._lineCount++; + case ' ': + case '\t': + case '\r': + default: + // スキップ + break; + } + } + + this._error = 'illegal end of value'; + return null; + } + + /** + * 次の「"」までの文字列をパースする。 + * + * @param string -> パース対象の文字列 + * @param length -> パースする長さ + * @param begin -> パースを開始する位置 + * @param outEndPos -> パース終了時の位置 + * @return パースした文F字列要素 + */ + protected parseString( + string: string, + length: number, + begin: number, + outEndPos: number[] + ): string { + if (this._error) return null; + + let i = begin; + let c: string, c2: string; + const ret: csmString = new csmString(''); + let bufStart: number = begin; // sbufに登録されていない文字の開始位置 + + for (; i < length; i++) { + c = string[i]; + + switch (c) { + case '"': { + // 終端の”、エスケープ文字は別に処理されるのでここに来ない + outEndPos[0] = i + 1; // ”の次の文字 + ret.append(string.slice(bufStart), i - bufStart); // 前の文字までを登録する + return ret.s; + } + case '//': { + // エスケープの場合 + i++; // 2文字をセットで扱う + + if (i - 1 > bufStart) { + ret.append(string.slice(bufStart), i - bufStart); // 前の文字までを登録する + } + bufStart = i + 1; // エスケープ(2文字)の次の文字から + + if (i < length) { + c2 = string[i]; + + switch (c2) { + case '\\': + ret.expansion(1, '\\'); + break; + case '"': + ret.expansion(1, '"'); + break; + case '/': + ret.expansion(1, '/'); + break; + case 'b': + ret.expansion(1, '\b'); + break; + case 'f': + ret.expansion(1, '\f'); + break; + case 'n': + ret.expansion(1, '\n'); + break; + case 'r': + ret.expansion(1, '\r'); + break; + case 't': + ret.expansion(1, '\t'); + break; + case 'u': + this._error = 'parse string/unicord escape not supported'; + break; + default: + break; + } + } else { + this._error = 'parse string/escape error'; + } + } + default: { + break; + } + } + } + + this._error = 'parse string/illegal end'; + return null; + } + + /** + * JSONのオブジェクトエレメントをパースしてValueオブジェクトを返す + * + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseObject( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ): Value { + if (this._error) return null; + const ret: JsonMap = new JsonMap(); + + // Key: Value + let key = ''; + let i: number = begin; + let c = ''; + const localRetEndPos2: number[] = Array(1); + let ok = false; + + // , が続く限りループ + for (; i < length; i++) { + FOR_LOOP: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case '"': + key = this.parseString(buffer, length, i + 1, localRetEndPos2); + if (this._error) { + return null; + } + + i = localRetEndPos2[0]; + ok = true; + break FOR_LOOP; //-- loopから出る + case '}': // 閉じカッコ + outEndPos[0] = i + 1; + return ret; // 空 + case ':': + this._error = "illegal ':' position"; + break; + case '\n': + this._lineCount++; + default: + break; // スキップする文字 + } + } + if (!ok) { + this._error = 'key not found'; + return null; + } + + ok = false; + + // : をチェック + FOR_LOOP2: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ':': + ok = true; + i++; + break FOR_LOOP2; + case '}': + this._error = "illegal '}' position"; + break; + case '\n': + this._lineCount++; + // case ' ': case '\t' : case '\r': + default: + break; // スキップする文字 + } + } + + if (!ok) { + this._error = "':' not found"; + return null; + } + + // 値をチェック + const value: Value = this.parseValue( + buffer, + length, + i, + localRetEndPos2 + ); + if (this._error) { + return null; + } + + i = localRetEndPos2[0]; + + // ret.put(key, value); + ret.put(key, value); + + FOR_LOOP3: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ',': + break FOR_LOOP3; + case '}': + outEndPos[0] = i + 1; + return ret; // 正常終了 + case '\n': + this._lineCount++; + default: + break; // スキップ + } + } + } + + this._error = 'illegal end of perseObject'; + return null; + } + + /** + * 次の「"」までの文字列をパースする。 + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseArray( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ): Value { + if (this._error) return null; + let ret: JsonArray = new JsonArray(); + + // key : value + let i: number = begin; + let c: string; + const localRetEndpos2: number[] = new Array(1); + + // , が続く限りループ + for (; i < length; i++) { + // : をチェック + const value: Value = this.parseValue( + buffer, + length, + i, + localRetEndpos2 + ); + + if (this._error) { + return null; + } + i = localRetEndpos2[0]; + + if (value) { + ret.add(value); + } + + // FOR_LOOP3: + // boolean breakflag = false; + FOR_LOOP: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ',': + // breakflag = true; + // break; // 次のKEY, VAlUEへ + break FOR_LOOP; + case ']': + outEndPos[0] = i + 1; + return ret; // 終了 + case '\n': + ++this._lineCount; + //case ' ': case '\t': case '\r': + default: + break; // スキップ + } + } + } + + ret = void 0; + this._error = 'illegal end of parseObject'; + return null; + } + + _error: string; // パース時のエラー + _lineCount: number; // エラー報告に用いる行数カウント + _root: Value; // パースされたルート要素 + } + + /** + * パースしたJSONの要素をfloat値として扱う + */ + export class JsonFloat extends Value { + /** + * コンストラクタ + */ + constructor(v: number) { + super(); + + this._value = v; + } + + /** + * Valueの種類が数値型ならtrue + */ + public isFloat(): boolean { + return true; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + const strbuf = '\0'; + this._value = parseFloat(strbuf); + this._stringBuffer = strbuf; + + return this._stringBuffer; + } + + /** + * 要素を数値型で返す(number) + */ + public toInt(defaultValue = 0): number { + return parseInt(this._value.toString()); + } + + /** + * 要素を数値型で返す(number) + */ + public toFloat(defaultValue = 0.0): number { + return this._value; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('number' === typeof value) { + // int + if (Math.round(value)) { + return false; + } + // float + else { + return value == this._value; + } + } + return false; + } + + private _value: number; // JSON要素の値 + } + + /** + * パースしたJSONの要素を真偽値として扱う + */ + export class JsonBoolean extends Value { + /** + * Valueの種類が真偽値ならtrue + */ + public isBool(): boolean { + return true; + } + + /** + * 要素を真偽値で返す(boolean) + */ + public toBoolean(defaultValue = false): boolean { + return this._boolValue; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + this._stringBuffer = this._boolValue ? 'true' : 'false'; + + return this._stringBuffer; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('boolean' === typeof value) { + return value == this._boolValue; + } + return false; + } + + /** + * Valueの値が静的ならtrue, 静的なら解放しない + */ + public isStatic(): boolean { + return true; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(v: boolean) { + super(); + + this._boolValue = v; + } + + static trueValue: JsonBoolean; // true + static falseValue: JsonBoolean; // false + + private _boolValue: boolean; // JSON要素の値 + } + + /** + * パースしたJSONの要素を文字列として扱う + */ + export class JsonString extends Value { + /** + * 引数付きコンストラクタ + */ + public constructor(s: string); + public constructor(s: csmString); + public constructor(s: any) { + super(); + + if ('string' === typeof s) { + this._stringBuffer = s; + } + + if (s instanceof csmString) { + this._stringBuffer = s.s; + } + } + + /** + * Valueの種類が文字列ならtrue + */ + public isString(): boolean { + return true; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + return this._stringBuffer; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('string' === typeof value) { + return this._stringBuffer == value; + } + + if (value instanceof csmString) { + return this._stringBuffer == value.s; + } + + return false; + } + } + + /** + * JSONパース時のエラー結果。文字列型のようにふるまう + */ + export class JsonError extends JsonString { + /** + * Valueの値が静的ならtrue、静的なら解放しない + */ + public isStatic(): boolean { + return this._isStatic; + } + + /** + * エラー情報をセットする + */ + public setErrorNotForClientCall(s: string): Value { + this._stringBuffer = s; + return this; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(s: csmString | string, isStatic: boolean) { + if ('string' === typeof s) { + super(s); + } else { + super(s); + } + this._isStatic = isStatic; + } + + /** + * Valueの種類がエラー値ならtrue + */ + public isError(): boolean { + return true; + } + + protected _isStatic: boolean; // 静的なValueかどうか + } + + /** + * パースしたJSONの要素をNULL値として持つ + */ + export class JsonNullvalue extends Value { + /** + * Valueの種類がNULL値ならtrue + */ + public isNull(): boolean { + return true; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + return this._stringBuffer; + } + + /** + * Valueの値が静的ならtrue, 静的なら解放しない + */ + public isStatic(): boolean { + return true; + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + + this._stringBuffer = 'NullValue'; + } + } + + /** + * パースしたJSONの要素を配列として持つ + */ + export class JsonArray extends Value { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._array = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for ( + let ite: csmVector_iterator = this._array.begin(); + ite.notEqual(this._array.end()); + ite.preIncrement() + ) { + let v: Value = ite.ptr(); + + if (v && !v.isStatic()) { + v = void 0; + v = null; + } + } + } + + /** + * Valueの種類が配列ならtrue + */ + public isArray(): boolean { + return true; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + if (index < 0 || this._array.getSize() <= index) { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_INDEX_OF_BOUNDS + ); + } + + const v: Value = this._array.at(index); + + if (v == null) { + return Value.nullValue; + } + + return v; + } + + /** + * 添字演算子[string | csmString] + */ + public getValueByString(s: string | csmString): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + const stringBuffer: string = indent + '[\n'; + + for ( + let ite: csmVector_iterator = this._array.begin(); + ite.notEqual(this._array.end()); + ite.increment() + ) { + const v: Value = ite.ptr(); + this._stringBuffer += indent + '' + v.getString(indent + ' ') + '\n'; + } + + this._stringBuffer = stringBuffer + indent + ']\n'; + + return this._stringBuffer; + } + + /** + * 配列要素を追加する + * @param v 追加する要素 + */ + public add(v: Value): void { + this._array.pushBack(v); + } + + /** + * 要素をコンテナで返す(csmVector) + */ + public getVector(defaultValue: csmVector = null): csmVector { + return this._array; + } + + /** + * 要素の数を返す + */ + public getSize(): number { + return this._array.getSize(); + } + + private _array: csmVector; // JSON要素の値 + } + + /** + * パースしたJSONの要素をマップとして持つ + */ + export class JsonMap extends Value { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._map = new csmMap(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + const ite: csmMap_iterator = this._map.begin(); + + while (ite.notEqual(this._map.end())) { + let v: Value = ite.ptr().second; + + if (v && !v.isStatic()) { + v = void 0; + v = null; + } + + ite.preIncrement(); + } + } + + /** + * Valueの値がMap型ならtrue + */ + public isMap(): boolean { + return true; + } + + /** + * 添字演算子[string | csmString] + */ + public getValueByString(s: string | csmString): Value { + if (s instanceof csmString) { + const ret: Value = this._map.getValue(s.s); + if (ret == null) { + return Value.nullValue; + } + return ret; + } + + for ( + let iter: csmMap_iterator = this._map.begin(); + iter.notEqual(this._map.end()); + iter.preIncrement() + ) { + if (iter.ptr().first == s) { + if (iter.ptr().second == null) { + return Value.nullValue; + } + return iter.ptr().second; + } + } + + return Value.nullValue; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string) { + this._stringBuffer = indent + '{\n'; + + const ite: csmMap_iterator = this._map.begin(); + while (ite.notEqual(this._map.end())) { + const key = ite.ptr().first; + const v: Value = ite.ptr().second; + + this._stringBuffer += + indent + ' ' + key + ' : ' + v.getString(indent + ' ') + ' \n'; + ite.preIncrement(); + } + + this._stringBuffer += indent + '}\n'; + + return this._stringBuffer; + } + + /** + * 要素をMap型で返す + */ + public getMap(defaultValue?: csmMap): csmMap { + return this._map; + } + + /** + * Mapに要素を追加する + */ + public put(key: string, v: Value): void { + this._map.setValue(key, v); + } + + /** + * Mapからキーのリストを取得する + */ + public getKeys(): csmVector { + if (!this._keys) { + this._keys = new csmVector(); + + const ite: csmMap_iterator = this._map.begin(); + + while (ite.notEqual(this._map.end())) { + const key: string = ite.ptr().first; + this._keys.pushBack(key); + ite.preIncrement(); + } + } + return this._keys; + } + + /** + * Mapの要素数を取得する + */ + public getSize(): number { + return this._keys.getSize(); + } + + private _map: csmMap; // JSON要素の値 + private _keys: csmVector; // JSON要素の値 + } +} diff --git a/src/utils/cubismstring.ts b/src/utils/cubismstring.ts new file mode 100644 index 0000000..a835347 --- /dev/null +++ b/src/utils/cubismstring.ts @@ -0,0 +1,123 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export namespace Live2DCubismFramework { + export class CubismString { + /** + * 標準出力の書式を適用した文字列を取得する。 + * @param format 標準出力の書式指定文字列 + * @param ...args 書式指定文字列に渡す文字列 + * @return 書式を適用した文字列 + */ + public static getFormatedString(format: string, ...args: any[]): string { + const ret: string = format; + return ret.replace( + /\{(\d+)\}/g, + ( + m, + k // m="{0}", k="0" + ) => { + return args[k]; + } + ); + } + + /** + * textがstartWordで始まっているかどうかを返す + * @param test 検査対象の文字列 + * @param startWord 比較対象の文字列 + * @return true textがstartWordで始まっている + * @return false textがstartWordで始まっていない + */ + public static isStartWith(text: string, startWord: string): boolean { + let textIndex = 0; + let startWordIndex = 0; + while (startWord[startWordIndex] != '\0') { + if ( + text[textIndex] == '\0' || + text[textIndex++] != startWord[startWordIndex++] + ) { + return false; + } + } + return false; + } + + /** + * position位置の文字から数字を解析する。 + * + * @param string 文字列 + * @param length 文字列の長さ + * @param position 解析したい文字の位置 + * @param outEndPos 一文字も読み込まなかった場合はエラー値(-1)が入る + * @return 解析結果の数値 + */ + public static stringToFloat( + string: string, + length: number, + position: number, + outEndPos: number[] + ): number { + let i: number = position; + let minus = false; // マイナスフラグ + let period = false; + let v1 = 0; + + //負号の確認 + let c: number = parseInt(string[i]); + if (c < 0) { + minus = true; + i++; + } + + //整数部の確認 + for (; i < length; i++) { + const c = string[i]; + if (0 <= parseInt(c) && parseInt(c) <= 9) { + v1 = v1 * 10 + (parseInt(c) - 0); + } else if (c == '.') { + period = true; + i++; + break; + } else { + break; + } + } + + //小数部の確認 + if (period) { + let mul = 0.1; + for (; i < length; i++) { + c = parseFloat(string[i]) & 0xff; + if (0 <= c && c <= 9) { + v1 += mul * (c - 0); + } else { + break; + } + mul *= 0.1; //一桁下げる + if (!c) break; + } + } + + if (i == position) { + //一文字も読み込まなかった場合 + outEndPos[0] = -1; //エラー値が入るので呼び出し元で適切な処理を行う + return 0; + } + + if (minus) v1 = -v1; + + outEndPos[0] = i; + return v1; + } + + /** + * コンストラクタ呼び出し不可な静的クラスにする。 + */ + private constructor() {} + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f9f9789 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "moduleResolution": "node", + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "emitDecoratorMetadata": true + }, + "include": [ + "src/**/*.ts", + "../Core/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +}