├── .gitignore ├── .npmignore ├── menus └── live-2d.cson ├── package.json ├── keymaps └── live-2d.cson ├── styles └── atom-live2d.less ├── index.html ├── src ├── LAppDefine.js ├── utils │ ├── MatrixStack.js │ └── ModelSettingJson.js ├── PlatformManager.js ├── LAppLive2DManager.js ├── AtomLive2D.js ├── index.js └── LAppModel.js ├── README.md ├── LICENSE.md └── lib └── live2d_framework.js /.gitignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /menus/live-2d.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'menu': [ 3 | { 4 | 'label': 'Packages' 5 | 'submenu': [ 6 | 'label': 'Live2D' 7 | 'submenu': [ 8 | { 'label': 'Toggle', 'command': 'atom-live2d:toggle' }, 9 | ] 10 | ] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-live2d", 3 | "main": "./src/index.js", 4 | "version": "0.1.0", 5 | "description": "A package which adds live2d models to your atom!", 6 | "repository": "https://github.com/if-Team/atom-live2d", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": ">1.47.0" 10 | }, 11 | "dependencies": { 12 | "cron": "^1.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /keymaps/live-2d.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | 'atom-text-editor': 11 | 'ctrl-alt-k': 'atom-live2d:toggle' 12 | -------------------------------------------------------------------------------- /styles/atom-live2d.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .live-2d iframe#live2d { 8 | opacity: 0.3; 9 | bottom: 0; 10 | right: 0; 11 | position: absolute; 12 | z-index: 2; 13 | border: none; 14 | display: inline-block; 15 | pointer-events: none; 16 | } 17 | /*.live-2d { 18 | .item-views > :not(div) { 19 | /deep/ .editor--private:not(.mini) .scroll-view { 20 | #glcanvas { 21 | opacity: 0.3; 22 | bottom: 0; 23 | right: 0; 24 | position: absolute; 25 | z-index: 2; 26 | } 27 | } 28 | } 29 | }*/ 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL 5 | 12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/LAppDefine.js: -------------------------------------------------------------------------------- 1 | var LAppDefine = { 2 | 3 | // デバッグ。trueのときにログを表示する。 4 | DEBUG_LOG : false, 5 | DEBUG_MOUSE_LOG : false, // マウス関連の冗長なログ 6 | // DEBUG_DRAW_HIT_AREA : false, // 当たり判定の可視化 7 | // DEBUG_DRAW_ALPHA_MODEL : false, // 半透明のモデル描画を行うかどうか。 8 | 9 | // 全体の設定 10 | 11 | // 画面 12 | VIEW_MAX_SCALE : 2, 13 | VIEW_MIN_SCALE : 0.8, 14 | 15 | VIEW_LOGICAL_LEFT : -1, 16 | VIEW_LOGICAL_RIGHT : 1, 17 | 18 | VIEW_LOGICAL_MAX_LEFT : -2, 19 | VIEW_LOGICAL_MAX_RIGHT : 2, 20 | VIEW_LOGICAL_MAX_BOTTOM : -2, 21 | VIEW_LOGICAL_MAX_TOP : 2, 22 | 23 | // モーションの優先度定数 24 | PRIORITY_NONE : 0, 25 | PRIORITY_IDLE : 1, 26 | PRIORITY_NORMAL : 2, 27 | PRIORITY_FORCE : 3, 28 | 29 | // モデルの後ろにある背景の画像ファイル 30 | // モデル定義 31 | 32 | // 外部定義ファイル(json)と合わせる 33 | MOTION_GROUP_IDLE : "idle", // アイドリング 34 | MOTION_GROUP_TAP_BODY : "tap_body", // 体をタップしたとき 35 | MOTION_GROUP_FLICK_HEAD : "flick_head", // 頭を撫でた時 36 | MOTION_GROUP_PINCH_IN : "pinch_in", // 拡大した時 37 | MOTION_GROUP_PINCH_OUT : "pinch_out", // 縮小した時 38 | MOTION_GROUP_SHAKE : "shake", // シェイク 39 | 40 | // 外部定義ファイル(json)と合わせる 41 | HIT_AREA_HEAD : "head", 42 | HIT_AREA_BODY : "body" 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atom-live2d 2 | A package which adds live2d models to your atom! 3 | 4 | ## Features 5 | - [x] Shows a Live2D Model. 6 | - [x] Watches your cursor. 7 | - [x] Supports voice. 8 | - [x] Plays a motion on notification. 9 | 10 | ## How to Install atom-live2d 11 | ```bash 12 | $ apm install atom-live2d 13 | ``` 14 | 15 | ## Set the Live2D model 16 | Put the `model.json` of live 2d model address in the settings. 17 | 18 | - Example : `~/assets/model_name/model_name.model.json`, type `model_name/model_name.model.json`. 19 | 20 | ### Example: [`hibiki.model.json`](http://sandwichproject.com) 21 | ```json 22 | { 23 | "version":"Sample 1.0.0", 24 | "model":"hibiki.moc", 25 | "textures":[ 26 | "hibiki.1024/texture_00.png", 27 | "hibiki.1024/texture_01.png", 28 | "hibiki.1024/texture_02.png" 29 | ], 30 | "motions":{ 31 | "null":[ 32 | { 33 | "file":"motions/hibiki_01.mtn", 34 | "sound":"voice/hibiki_01.mp3" 35 | }, 36 | { 37 | "file":"motions/hibiki_02.mtn", 38 | "sound":"voice/hibiki_02.mp3" 39 | }, 40 | { 41 | "file":"motions/hibiki_03.mtn", 42 | "sound":"voice/hibiki_03.mp3" 43 | }, 44 | { 45 | "file":"motions/hibiki_04.mtn", 46 | "sound":"voice/hibiki_04.mp3" 47 | }, 48 | { 49 | "file":"motions/hibiki_05.mtn", 50 | "sound":"voice/hibiki_05.mp3" 51 | } 52 | ], 53 | "idle":[ 54 | { 55 | "file":"motions/idle_01.mtn" 56 | }, 57 | { 58 | "file":"motions/idle_02.mtn" 59 | }, 60 | { 61 | "file":"motions/idle_03.mtn" 62 | }, 63 | { 64 | "file":"motions/idle_04.mtn" 65 | } 66 | ] 67 | }, 68 | "expressions":[ 69 | {"name":"ANGRY","file":"expressions/f01.exp.json"}, 70 | {"name":"DOWN","file":"expressions/f02.exp.json"}, 71 | {"name":"FUN","file":"expressions/f03.exp.json"}, 72 | {"name":"NOMAL.mtn","file":"expressions/f04.exp.json"}, 73 | {"name":"SAD","file":"expressions/f05.exp.json"}, 74 | {"name":"F_SURPRISE","file":"expressions/f06.exp.json"} 75 | ], 76 | "notification":{ 77 | "angry": [ 78 | {"file":"motions/hibiki_01.mtn"} 79 | ], 80 | "fun": [ 81 | {"file":"motions/hibiki_02.mtn"} 82 | ], 83 | "sad": [ 84 | {"file":"motions/hibiki_03.mtn"} 85 | ], 86 | "surprise": [ 87 | {"file":"motions/hibiki_04.mtn"}, 88 | {"file":"motions/hibiki_05.mtn"} 89 | ], 90 | "": [ 91 | {"file":"motions/idle_01.mtn"}, 92 | {"file":"motions/idle_02.mtn"}, 93 | {"file":"motions/idle_03.mtn"}, 94 | {"file":"motions/idle_04.mtn"} 95 | ], 96 | "idle": [ 97 | {"file":"motions/idle_01.mtn"} 98 | ] 99 | }, 100 | "physics":"hibiki.physics.json" 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ``` 2 | Copyright (c) 2016 HelloWorld017 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | ``` 23 | 24 | ---- 25 | ## Open Source License: 26 | 27 | ### Forked atom-pronama-chan by hurumeki 28 | License: 29 | ``` 30 | Copyright (c) 2014 hurumeki 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining 33 | a copy of this software and associated documentation files (the 34 | "Software"), to deal in the Software without restriction, including 35 | without limitation the rights to use, copy, modify, merge, publish, 36 | distribute, sublicense, and/or sell copies of the Software, and to 37 | permit persons to whom the Software is furnished to do so, subject to 38 | the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be 41 | included in all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 44 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 45 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 46 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 47 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 48 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 49 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 50 | ``` 51 | 52 | ### Live2D WebGL 53 | Please refer to [License](http://live2d.com/sdk_license_cubism) 54 | 55 | ### Used Live2D Sample 56 | License: 57 | ``` 58 | Sample.js 59 | 60 | You can modify and use this source freely 61 | only for the development of application related Live2D. 62 | 63 | (c) Live2D Inc. All rights reserved. 64 | ``` 65 | -------------------------------------------------------------------------------- /src/utils/MatrixStack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * You can modify and use this source freely 4 | * only for the development of application related Live2D. 5 | * 6 | * (c) Live2D Inc. All rights reserved. 7 | */ 8 | 9 | function MatrixStack() {} 10 | 11 | // 行列スタック。4x4行列を基本とするので、16ごとの区切りの配列。 12 | MatrixStack.matrixStack = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 13 | 14 | // 現在のスタックの深さ。初期は0でpushするごとに+1。 15 | MatrixStack.depth = 0; 16 | 17 | // 現在の行列 18 | MatrixStack.currentMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 19 | 20 | // 計算用 21 | MatrixStack.tmp = new Array(16); 22 | 23 | 24 | /* 25 | * スタックをリセット 26 | */ 27 | MatrixStack.reset = function() 28 | { 29 | this.depth = 0; 30 | } 31 | 32 | 33 | /* 34 | * 単位行列にする 35 | */ 36 | MatrixStack.loadIdentity = function() 37 | { 38 | for (var i = 0; i < 16; i++) 39 | { 40 | this.currentMatrix[i] = (i % 5 == 0) ? 1 : 0; 41 | } 42 | } 43 | 44 | 45 | /* 46 | * 現在の行列を保存 47 | */ 48 | MatrixStack.push = function() 49 | { 50 | var offset = this.depth * 16; 51 | var nextOffset = (this.depth + 1) * 16; 52 | 53 | if (this.matrixStack.length < nextOffset + 16) 54 | { 55 | this.matrixStack.length = nextOffset + 16; 56 | } 57 | 58 | for (var i = 0; i < 16; i++) 59 | { 60 | this.matrixStack[nextOffset + i] = this.currentMatrix[i]; 61 | } 62 | 63 | this.depth++; 64 | } 65 | 66 | 67 | /* 68 | * 一つ前の行列へ 69 | */ 70 | MatrixStack.pop = function() 71 | { 72 | this.depth--; 73 | if (this.depth < 0) 74 | { 75 | myError("Invalid matrix stack."); 76 | this.depth = 0; 77 | } 78 | 79 | var offset = this.depth * 16; 80 | for (var i = 0; i < 16; i++) 81 | { 82 | this.currentMatrix[i] = this.matrixStack[offset + i]; 83 | } 84 | } 85 | 86 | 87 | /* 88 | * 現在の行列を取得 89 | */ 90 | MatrixStack.getMatrix = function() 91 | { 92 | return this.currentMatrix; 93 | } 94 | 95 | 96 | /* 97 | * 行列を掛け合わせる 98 | */ 99 | MatrixStack.multMatrix = function(matNew) 100 | { 101 | var i, j, k; 102 | 103 | for (i = 0; i < 16; i++) 104 | { 105 | this.tmp[i] = 0; 106 | } 107 | 108 | for (i = 0; i < 4; i++) 109 | { 110 | for (j = 0; j < 4; j++) 111 | { 112 | for (k = 0; k < 4; k++) 113 | { 114 | this.tmp[i + j * 4] += this.currentMatrix[i + k * 4] * matNew[k + j * 4]; 115 | } 116 | } 117 | } 118 | for (i = 0; i < 16; i++) 119 | { 120 | this.currentMatrix[i] = this.tmp[i]; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/PlatformManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * You can modify and use this source freely 4 | * only for the development of application related Live2D. 5 | * 6 | * (c) Live2D Inc. All rights reserved. 7 | */ 8 | 9 | //============================================================ 10 | //============================================================ 11 | // class PlatformManager extend IPlatformManager 12 | //============================================================ 13 | //============================================================ 14 | function PlatformManager() 15 | { 16 | 17 | } 18 | 19 | //============================================================ 20 | // PlatformManager # loadBytes() 21 | //============================================================ 22 | PlatformManager.prototype.loadBytes = function(path/*String*/, callback) 23 | { 24 | var request = new XMLHttpRequest(); 25 | request.open("GET", path, true); 26 | request.responseType = "arraybuffer"; 27 | request.onload = function(){ 28 | switch(request.status){ 29 | case 200: 30 | callback(request.response); 31 | break; 32 | default: 33 | console.error("Failed to load (" + request.status + ") : " + path); 34 | break; 35 | } 36 | } 37 | request.send(null); 38 | //return request; 39 | } 40 | 41 | //============================================================ 42 | // PlatformManager # loadString() 43 | //============================================================ 44 | PlatformManager.prototype.loadString = function(path/*String*/) 45 | { 46 | 47 | this.loadBytes(path, function(buf) { 48 | return buf; 49 | }); 50 | 51 | } 52 | 53 | //============================================================ 54 | // PlatformManager # loadLive2DModel() 55 | //============================================================ 56 | PlatformManager.prototype.loadLive2DModel = function(path/*String*/, callback) 57 | { 58 | var model = null; 59 | 60 | // load moc 61 | this.loadBytes(path, function(buf){ 62 | model = Live2DModelWebGL.loadModel(buf); 63 | callback(model); 64 | }); 65 | 66 | } 67 | 68 | //============================================================ 69 | // PlatformManager # loadTexture() 70 | //============================================================ 71 | PlatformManager.prototype.loadTexture = function(model/*ALive2DModel*/, no/*int*/, path/*String*/, callback) 72 | { 73 | // load textures 74 | var loadedImage = new Image(); 75 | loadedImage.src = path; 76 | 77 | var thisRef = this; 78 | loadedImage.onload = function() { 79 | 80 | // create texture 81 | var canvas = document.getElementById("glcanvas"); 82 | var gl = getWebGLContext(canvas, {premultipliedAlpha : true}); 83 | var texture = gl.createTexture(); // テクスチャオブジェクトを作成する 84 | if (!texture){ console.error("Failed to generate gl texture name."); return -1; } 85 | 86 | if(model.isPremultipliedAlpha() == false){ 87 | // 乗算済アルファテクスチャ以外の場合 88 | gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); 89 | } 90 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // imageを上下反転 91 | gl.activeTexture(gl.TEXTURE0); 92 | gl.bindTexture(gl.TEXTURE_2D, texture); 93 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, 94 | gl.UNSIGNED_BYTE, loadedImage); 95 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 96 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); 97 | gl.generateMipmap(gl.TEXTURE_2D); 98 | 99 | 100 | // 画像からWebGLテクスチャ化を生成し、モデルに登録 101 | model.setTexture(no, texture);// モデルにテクスチャをセット 102 | 103 | // テクスチャオブジェクトを解放 104 | texture = null; 105 | 106 | if (typeof callback == "function") callback(); 107 | }; 108 | 109 | loadedImage.onerror = function() { 110 | console.error("Failed to load image : " + path); 111 | } 112 | } 113 | 114 | 115 | //============================================================ 116 | // PlatformManager # parseFromBytes(buf) 117 | // ArrayBuffer から JSON に変換する 118 | //============================================================ 119 | PlatformManager.prototype.jsonParseFromBytes = function(buf){ 120 | 121 | var jsonStr; 122 | 123 | // BOMの有無に応じて処理を分ける 124 | // UTF-8のBOMは0xEF 0xBB 0xBF(10進数:239 187 191) 125 | var bomCode = new Uint8Array(buf, 0, 3); 126 | if (bomCode[0] == 239 && bomCode[1] == 187 && bomCode[2] == 191) { 127 | jsonStr = String.fromCharCode.apply(null, new Uint8Array(buf, 3)); 128 | } else { 129 | jsonStr = String.fromCharCode.apply(null, new Uint8Array(buf)); 130 | } 131 | 132 | var jsonObj = JSON.parse(jsonStr); 133 | 134 | return jsonObj; 135 | }; 136 | 137 | 138 | //============================================================ 139 | // PlatformManager # log() 140 | //============================================================ 141 | PlatformManager.prototype.log = function(txt/*String*/) 142 | { 143 | console.log(txt); 144 | } 145 | 146 | -------------------------------------------------------------------------------- /src/LAppLive2DManager.js: -------------------------------------------------------------------------------- 1 | function LAppLive2DManager() 2 | { 3 | // console.log("--> LAppLive2DManager()"); 4 | 5 | // モデルデータ 6 | this.models = []; // LAppModel 7 | 8 | // サンプル機能 9 | this.count = -1; 10 | this.reloadFlg = false; // モデル再読み込みのフラグ 11 | 12 | Live2D.init(); 13 | Live2DFramework.setPlatformManager(new PlatformManager); 14 | 15 | } 16 | 17 | LAppLive2DManager.prototype.createModel = function() 18 | { 19 | // console.log("--> LAppLive2DManager.createModel()"); 20 | 21 | var model = new LAppModel(); 22 | this.models.push(model); 23 | 24 | return model; 25 | } 26 | 27 | 28 | LAppLive2DManager.prototype.changeModel = function(gl, model) 29 | { 30 | // console.log("--> LAppLive2DManager.update(gl)"); 31 | 32 | if (this.reloadFlg) 33 | { 34 | // モデル切り替えボタンが押された時、モデルを再読み込みする 35 | this.reloadFlg = false; 36 | var no = parseInt(this.count % 5); 37 | 38 | var thisRef = this; 39 | /*switch (no) 40 | { 41 | case 0: // ハル 42 | this.releaseModel(1, gl); 43 | this.releaseModel(0, gl); 44 | // OpenGLのコンテキストをセット 45 | this.createModel(); 46 | this.models[0].load(gl, LAppDefine.MODEL_HARU); 47 | break; 48 | case 1: // しずく 49 | this.releaseModel(0, gl); 50 | this.createModel(); 51 | this.models[0].load(gl, LAppDefine.MODEL_SHIZUKU); 52 | break; 53 | case 2: // わんこ 54 | this.releaseModel(0, gl); 55 | this.createModel(); 56 | this.models[0].load(gl, LAppDefine.MODEL_WANKO); 57 | break; 58 | case 3: // Epsilon2.1モデル 59 | this.releaseModel(0, gl); 60 | this.createModel(); 61 | this.models[0].load(gl, LAppDefine.MODEL_EPSILON); 62 | break; 63 | case 4: // 複数モデル 64 | this.releaseModel(0, gl); 65 | // 一体目のモデル 66 | this.createModel(); 67 | this.models[0].load(gl, LAppDefine.MODEL_HARU_A, function() { 68 | // 二体目のモデル 69 | thisRef.createModel(); 70 | thisRef.models[1].load(gl, LAppDefine.MODEL_HARU_B); 71 | }); 72 | break; 73 | default: 74 | break; 75 | }*/ 76 | 77 | this.releaseModel(0, gl); 78 | this.createModel(); 79 | this.models[0].load(gl, model); 80 | } 81 | }; 82 | 83 | 84 | LAppLive2DManager.prototype.getModel = function(no) 85 | { 86 | // console.log("--> LAppLive2DManager.getModel(" + no + ")"); 87 | 88 | if (no >= this.models.length) return null; 89 | 90 | return this.models[no]; 91 | }; 92 | 93 | 94 | /* 95 | * モデルを解放する 96 | * ないときはなにもしない 97 | */ 98 | LAppLive2DManager.prototype.releaseModel = function(no, gl) 99 | { 100 | // console.log("--> LAppLive2DManager.releaseModel(" + no + ")"); 101 | 102 | if (this.models.length <= no) return; 103 | 104 | this.models[no].release(gl); 105 | 106 | delete this.models[no]; 107 | this.models.splice(no, 1); 108 | }; 109 | 110 | 111 | /* 112 | * モデルの数 113 | */ 114 | LAppLive2DManager.prototype.numModels = function() 115 | { 116 | return this.models.length; 117 | }; 118 | 119 | 120 | /* 121 | * ドラッグしたとき、その方向を向く設定する 122 | */ 123 | LAppLive2DManager.prototype.setDrag = function(x, y) 124 | { 125 | for (var i = 0; i < this.models.length; i++) 126 | { 127 | this.models[i].setDrag(x, y); 128 | } 129 | } 130 | 131 | 132 | /* 133 | * 画面が最大になったときのイベント 134 | */ 135 | LAppLive2DManager.prototype.maxScaleEvent = function() 136 | { 137 | if (LAppDefine.DEBUG_LOG) 138 | console.log("Max scale event."); 139 | for (var i = 0; i < this.models.length; i++) 140 | { 141 | this.models[i].startRandomMotion(LAppDefine.MOTION_GROUP_PINCH_IN, 142 | LAppDefine.PRIORITY_NORMAL); 143 | } 144 | } 145 | 146 | 147 | /* 148 | * 画面が最小になったときのイベント 149 | */ 150 | LAppLive2DManager.prototype.minScaleEvent = function() 151 | { 152 | if (LAppDefine.DEBUG_LOG) 153 | console.log("Min scale event."); 154 | for (var i = 0; i < this.models.length; i++) 155 | { 156 | this.models[i].startRandomMotion(LAppDefine.MOTION_GROUP_PINCH_OUT, 157 | LAppDefine.PRIORITY_NORMAL); 158 | } 159 | } 160 | 161 | 162 | /* 163 | * タップしたときのイベント 164 | */ 165 | LAppLive2DManager.prototype.tapEvent = function(x, y) 166 | { 167 | if (LAppDefine.DEBUG_LOG) 168 | console.log("tapEvent view x:" + x + " y:" + y); 169 | 170 | for (var i = 0; i < this.models.length; i++) 171 | { 172 | 173 | if (this.models[i].hitTest(LAppDefine.HIT_AREA_HEAD, x, y)) 174 | { 175 | // 顔をタップしたら表情切り替え 176 | if (LAppDefine.DEBUG_LOG) 177 | console.log("Tap face."); 178 | 179 | this.models[i].setRandomExpression(); 180 | } 181 | else if (this.models[i].hitTest(LAppDefine.HIT_AREA_BODY, x, y)) 182 | { 183 | // 体をタップしたらモーション 184 | if (LAppDefine.DEBUG_LOG) 185 | console.log("Tap body." + " models[" + i + "]"); 186 | 187 | this.models[i].startRandomMotion(LAppDefine.MOTION_GROUP_TAP_BODY, 188 | LAppDefine.PRIORITY_NORMAL); 189 | } 190 | } 191 | 192 | return true; 193 | }; 194 | -------------------------------------------------------------------------------- /src/utils/ModelSettingJson.js: -------------------------------------------------------------------------------- 1 | function ModelSettingJson() 2 | { 3 | this.NAME = "name"; 4 | this.ID = "id"; 5 | this.MODEL = "model"; 6 | this.TEXTURES = "textures"; 7 | this.HIT_AREAS = "hit_areas"; 8 | this.PHYSICS = "physics"; 9 | this.POSE = "pose"; 10 | this.EXPRESSIONS = "expressions"; 11 | this.MOTION_GROUPS = "motions"; 12 | this.SOUND = "sound"; 13 | this.FADE_IN = "fade_in"; 14 | this.FADE_OUT = "fade_out"; 15 | this.LAYOUT = "layout"; 16 | this.INIT_PARAM = "init_param"; 17 | this.INIT_PARTS_VISIBLE = "init_parts_visible"; 18 | this.VALUE = "val"; 19 | this.FILE = "file"; 20 | 21 | this.json = {}; 22 | } 23 | 24 | 25 | ModelSettingJson.prototype.loadModelSetting = function(path, callback) 26 | { 27 | var thisRef = this; 28 | var pm = Live2DFramework.getPlatformManager(); 29 | pm.loadBytes(path, function(buf) { 30 | var str = String.fromCharCode.apply(null,new Uint8Array(buf)); 31 | thisRef.json = JSON.parse(str); 32 | callback(); 33 | }); 34 | }; 35 | 36 | 37 | ModelSettingJson.prototype.getTextureFile = function(n) 38 | { 39 | if (this.json[this.TEXTURES] == null || this.json[this.TEXTURES][n] == null) 40 | return null; 41 | 42 | return this.json[this.TEXTURES][n]; 43 | } 44 | 45 | 46 | ModelSettingJson.prototype.getModelFile = function() 47 | { 48 | return this.json[this.MODEL]; 49 | }; 50 | 51 | 52 | ModelSettingJson.prototype.getTextureNum = function() 53 | { 54 | if (this.json[this.TEXTURES] == null) return 0; 55 | 56 | return this.json[this.TEXTURES].length; 57 | } 58 | 59 | 60 | ModelSettingJson.prototype.getHitAreaNum = function() 61 | { 62 | if (this.json[this.HIT_AREAS] == null) 63 | return 0; 64 | 65 | return this.json[this.HIT_AREAS].length; 66 | } 67 | 68 | 69 | ModelSettingJson.prototype.getHitAreaID = function(n) 70 | { 71 | if (this.json[this.HIT_AREAS] == null || 72 | this.json[this.HIT_AREAS][n] == null) 73 | return null; 74 | 75 | return this.json[this.HIT_AREAS][n][this.ID]; 76 | } 77 | 78 | 79 | ModelSettingJson.prototype.getHitAreaName = function(n) 80 | { 81 | if (this.json[this.HIT_AREAS] == null || 82 | this.json[this.HIT_AREAS][n] == null) 83 | return null; 84 | 85 | return this.json[this.HIT_AREAS][n][this.NAME]; 86 | } 87 | 88 | 89 | ModelSettingJson.prototype.getPhysicsFile = function() 90 | { 91 | return this.json[this.PHYSICS]; 92 | } 93 | 94 | 95 | ModelSettingJson.prototype.getPoseFile = function() 96 | { 97 | return this.json[this.POSE]; 98 | } 99 | 100 | 101 | ModelSettingJson.prototype.getExpressionNum = function() 102 | { 103 | return (this.json[this.EXPRESSIONS] == null) ? 0 : this.json[this.EXPRESSIONS].length; 104 | } 105 | 106 | 107 | ModelSettingJson.prototype.getExpressionFile = function(n) 108 | { 109 | if (this.json[this.EXPRESSIONS] == null) 110 | return null; 111 | return this.json[this.EXPRESSIONS][n][this.FILE]; 112 | } 113 | 114 | 115 | ModelSettingJson.prototype.getExpressionName = function(n) 116 | { 117 | if (this.json[this.EXPRESSIONS] == null) 118 | return null; 119 | return this.json[this.EXPRESSIONS][n][this.NAME]; 120 | } 121 | 122 | 123 | ModelSettingJson.prototype.getLayout = function() 124 | { 125 | return this.json[this.LAYOUT]; 126 | } 127 | 128 | 129 | ModelSettingJson.prototype.getInitParamNum = function() 130 | { 131 | return (this.json[this.INIT_PARAM] == null) ? 0 : this.json[this.INIT_PARAM].length; 132 | } 133 | 134 | 135 | ModelSettingJson.prototype.getMotionNum = function(name) 136 | { 137 | if (this.json[this.MOTION_GROUPS] == null || 138 | this.json[this.MOTION_GROUPS][name] == null) 139 | return 0; 140 | 141 | return this.json[this.MOTION_GROUPS][name].length; 142 | } 143 | 144 | 145 | ModelSettingJson.prototype.getMotionFile = function(name, n) 146 | { 147 | if (this.json[this.MOTION_GROUPS] == null || 148 | this.json[this.MOTION_GROUPS][name] == null || 149 | this.json[this.MOTION_GROUPS][name][n] == null) 150 | return null; 151 | 152 | return this.json[this.MOTION_GROUPS][name][n][this.FILE]; 153 | } 154 | 155 | 156 | ModelSettingJson.prototype.getMotionSound = function(name, n) 157 | { 158 | if (this.json[this.MOTION_GROUPS] == null || 159 | this.json[this.MOTION_GROUPS][name] == null || 160 | this.json[this.MOTION_GROUPS][name][n] == null || 161 | this.json[this.MOTION_GROUPS][name][n][this.SOUND] == null) 162 | return null; 163 | 164 | return this.json[this.MOTION_GROUPS][name][n][this.SOUND]; 165 | } 166 | 167 | 168 | ModelSettingJson.prototype.getMotionFadeIn = function(name, n) 169 | { 170 | if (this.json[this.MOTION_GROUPS] == null || 171 | this.json[this.MOTION_GROUPS][name] == null || 172 | this.json[this.MOTION_GROUPS][name][n] == null || 173 | this.json[this.MOTION_GROUPS][name][n][this.FADE_IN] == null) 174 | return 1000; 175 | 176 | return this.json[this.MOTION_GROUPS][name][n][this.FADE_IN]; 177 | } 178 | 179 | 180 | ModelSettingJson.prototype.getMotionFadeOut = function(name, n) 181 | { 182 | if (this.json[this.MOTION_GROUPS] == null || 183 | this.json[this.MOTION_GROUPS][name] == null || 184 | this.json[this.MOTION_GROUPS][name][n] == null || 185 | this.json[this.MOTION_GROUPS][name][n][this.FADE_OUT] == null) 186 | return 1000; 187 | 188 | return this.json[this.MOTION_GROUPS][name][n][this.FADE_OUT]; 189 | } 190 | 191 | 192 | ModelSettingJson.prototype.getInitParamID = function(n) 193 | { 194 | if (this.json[this.INIT_PARAM] == null || 195 | this.json[this.INIT_PARAM][n] == null) 196 | return null; 197 | 198 | return this.json[this.INIT_PARAM][n][this.ID]; 199 | } 200 | 201 | 202 | ModelSettingJson.prototype.getInitParamValue = function(n) 203 | { 204 | if (this.json[this.INIT_PARAM] == null || this.json[this.INIT_PARAM][n] == null) 205 | return NaN; 206 | 207 | return this.json[this.INIT_PARAM][n][this.VALUE]; 208 | } 209 | 210 | 211 | ModelSettingJson.prototype.getInitPartsVisibleNum = function() 212 | { 213 | return (this.json[this.INIT_PARTS_VISIBLE] == null) ? 0 : this.json[this.INIT_PARTS_VISIBLE].length; 214 | } 215 | 216 | 217 | ModelSettingJson.prototype.getInitPartsVisibleID = function(n) 218 | { 219 | if (this.json[this.INIT_PARTS_VISIBLE] == null || this.json[this.INIT_PARTS_VISIBLE][n] == null) 220 | return null; 221 | return this.json[this.INIT_PARTS_VISIBLE][n][this.ID]; 222 | } 223 | 224 | 225 | ModelSettingJson.prototype.getInitPartsVisibleValue = function(n) 226 | { 227 | if (this.json[this.INIT_PARTS_VISIBLE] == null || this.json[this.INIT_PARTS_VISIBLE][n] == null) 228 | return NaN; 229 | 230 | return this.json[this.INIT_PARTS_VISIBLE][n][this.VALUE]; 231 | } 232 | -------------------------------------------------------------------------------- /src/AtomLive2D.js: -------------------------------------------------------------------------------- 1 | var l2dthisRef = this; 2 | // JavaScriptで発生したエラーを取得 3 | const EYE_PARAM = 'PARAM_EYE_R_OPEN'; 4 | let drawing = true; 5 | 6 | function atomLive2d(model) 7 | { 8 | console.log("우리의 핵심목표는 이것이다 : " + model); 9 | this.platform = window.navigator.platform.toLowerCase(); 10 | 11 | this.live2DMgr = new LAppLive2DManager(); 12 | 13 | this.isDrawStart = false; 14 | 15 | this.gl = null; 16 | this.canvas = null; 17 | 18 | this.dragMgr = null; /*new L2DTargetPoint();*/ // ドラッグによるアニメーションの管理 19 | this.viewMatrix = null; /*new L2DViewMatrix();*/ 20 | this.projMatrix = null; /*new L2DMatrix44()*/ 21 | this.deviceToScreen = null; /*new L2DMatrix44();*/ 22 | 23 | this.drag = false; // ドラッグ中かどうか 24 | this.oldLen = 0; // 二本指タップした時の二点間の距離 25 | 26 | this.lastMouseX = 0; 27 | this.lastMouseY = 0; 28 | 29 | this.isModelShown = false; 30 | 31 | // モデル描画用canvasの初期化 32 | initL2dCanvas("glcanvas"); 33 | 34 | // モデル用マトリクスの初期化と描画の開始 35 | init(model); 36 | 37 | window.addEventListener('message', () => { 38 | drawing = !drawing; 39 | }, false); 40 | } 41 | 42 | 43 | function initL2dCanvas(canvasId) 44 | { 45 | // canvasオブジェクトを取得 46 | this.canvas = document.getElementById(canvasId); 47 | 48 | // イベントの登録 49 | /*if(this.canvas.addEventListener) { 50 | this.canvas.addEventListener("mousewheel", mouseEvent, false); 51 | this.canvas.addEventListener("click", mouseEvent, false); 52 | 53 | this.canvas.addEventListener("mousedown", mouseEvent, false); 54 | this.canvas.addEventListener("mousemove", mouseEvent, false); 55 | 56 | this.canvas.addEventListener("mouseup", mouseEvent, false); 57 | this.canvas.addEventListener("mouseout", mouseEvent, false); 58 | this.canvas.addEventListener("contextmenu", mouseEvent, false); 59 | 60 | // タッチイベントに対応 61 | this.canvas.addEventListener("touchstart", touchEvent, false); 62 | this.canvas.addEventListener("touchend", touchEvent, false); 63 | this.canvas.addEventListener("touchmove", touchEvent, false); 64 | 65 | }*/ 66 | } 67 | 68 | 69 | function init(model) 70 | { 71 | // 3Dバッファの初期化 72 | var width = this.canvas.width; 73 | var height = this.canvas.height; 74 | 75 | this.dragMgr = new L2DTargetPoint(); 76 | 77 | // ビュー行列 78 | var ratio = height / width; 79 | var left = LAppDefine.VIEW_LOGICAL_LEFT; 80 | var right = LAppDefine.VIEW_LOGICAL_RIGHT; 81 | var bottom = -ratio; 82 | var top = ratio; 83 | 84 | this.viewMatrix = new L2DViewMatrix(); 85 | 86 | // デバイスに対応する画面の範囲。 Xの左端, Xの右端, Yの下端, Yの上端 87 | this.viewMatrix.setScreenRect(left, right, bottom, top); 88 | 89 | // デバイスに対応する画面の範囲。 Xの左端, Xの右端, Yの下端, Yの上端 90 | this.viewMatrix.setMaxScreenRect(LAppDefine.VIEW_LOGICAL_MAX_LEFT, 91 | LAppDefine.VIEW_LOGICAL_MAX_RIGHT, 92 | LAppDefine.VIEW_LOGICAL_MAX_BOTTOM, 93 | LAppDefine.VIEW_LOGICAL_MAX_TOP); 94 | 95 | this.viewMatrix.setMaxScale(LAppDefine.VIEW_MAX_SCALE); 96 | this.viewMatrix.setMinScale(LAppDefine.VIEW_MIN_SCALE); 97 | 98 | this.projMatrix = new L2DMatrix44(); 99 | this.projMatrix.multScale(1, (width / height)); 100 | 101 | // マウス用スクリーン変換行列 102 | this.deviceToScreen = new L2DMatrix44(); 103 | this.deviceToScreen.multTranslate(-width / 2.0, -height / 2.0); 104 | this.deviceToScreen.multScale(2 / width, -2 / width); 105 | 106 | 107 | // WebGLのコンテキストを取得する 108 | this.gl = getWebGLContext(); 109 | if (!this.gl) { 110 | l2dError("Failed to create WebGL context."); 111 | return; 112 | } 113 | // OpenGLのコンテキストをセット 114 | Live2D.setGL(this.gl); 115 | 116 | // 描画エリアを白でクリア 117 | this.gl.clearColor(0.0, 0.0, 0.0, 0.0); 118 | 119 | changeModel(model); 120 | 121 | startDraw(); 122 | } 123 | 124 | 125 | function startDraw() { 126 | if(!this.isDrawStart) { 127 | this.isDrawStart = true; 128 | (function tick() { 129 | if(drawing) draw(); // 1回分描画 130 | 131 | var requestAnimationFrame = 132 | window.requestAnimationFrame || 133 | window.mozRequestAnimationFrame || 134 | window.webkitRequestAnimationFrame || 135 | window.msRequestAnimationFrame; 136 | 137 | // 一定時間後に自身を呼び出す 138 | requestAnimationFrame(tick ,this.canvas); 139 | })(); 140 | } 141 | } 142 | 143 | 144 | function draw() 145 | { 146 | l2dLog("--> draw()"); 147 | 148 | MatrixStack.reset(); 149 | MatrixStack.loadIdentity(); 150 | 151 | this.dragMgr.update(); // ドラッグ用パラメータの更新 152 | this.live2DMgr.setDrag(this.dragMgr.getX(), this.dragMgr.getY()); 153 | 154 | // Canvasをクリアする 155 | this.gl.clear(this.gl.COLOR_BUFFER_BIT); 156 | 157 | MatrixStack.multMatrix(projMatrix.getArray()); 158 | MatrixStack.multMatrix(viewMatrix.getArray()); 159 | MatrixStack.push(); 160 | 161 | for (var i = 0; i < this.live2DMgr.numModels(); i++) 162 | { 163 | var model = this.live2DMgr.getModel(i); 164 | 165 | if(model == null) return; 166 | 167 | if (model.initialized && !model.updating) 168 | { 169 | model.update(); 170 | model.draw(this.gl); 171 | 172 | if (!this.isModelShown && i == this.live2DMgr.numModels()-1) { 173 | this.isModelShown = !this.isModelShown; 174 | } 175 | } 176 | } 177 | 178 | MatrixStack.pop(); 179 | } 180 | 181 | 182 | function changeModel(model) 183 | { 184 | this.isModelShown = false; 185 | 186 | this.live2DMgr.reloadFlg = true; 187 | this.live2DMgr.count++; 188 | 189 | this.live2DMgr.changeModel(this.gl, model); 190 | } 191 | 192 | /* ********** マウスイベント ********** */ 193 | 194 | /* 195 | * マウスホイールによる拡大縮小 196 | */ 197 | 198 | function wink(){ 199 | var waitForNextBlink = () => { 200 | if(live2DMgr.models[0].eyeBlink.nextBlinkTime >= UtSystem.getUserTimeMSec()) setTimeout(waitForNextBlink, 50); 201 | else doWinkImmediate(); 202 | }; 203 | 204 | waitForNextBlink(); 205 | } 206 | 207 | function doWinkImmediate(){ 208 | var currModel = live2DMgr.models[0].live2DModel; 209 | live2DMgr.models[0].eyeBlink.nextBlinkTime += 1000; 210 | live2DMgr.models[0].eyeBlink.eyeState = EYE_STATE.STATE_INTERVAL; 211 | 212 | var startValue = currModel.getParamFloat(EYE_PARAM); 213 | var currValue = startValue; 214 | var animateAmount = startValue / 40; 215 | 216 | var animateOpening = () => { 217 | if(currValue >= startValue) return; 218 | currValue += animateAmount; 219 | currModel.setParamFloat(EYE_PARAM, currValue); 220 | setTimeout(animateOpening, 10); 221 | }; 222 | 223 | var animateClosing = () => { 224 | if(currValue <= 0) return setTimeout(animateOpening, 200); 225 | currValue -= animateAmount; 226 | currModel.setParamFloat(EYE_PARAM, currValue); 227 | setTimeout(animateClosing, 10); 228 | }; 229 | 230 | animateClosing(); 231 | } 232 | 233 | function loadMotionGroup(motionGroup){ 234 | var model = l2dthisRef.live2DMgr.getModel(0); 235 | if(model == null) return; 236 | 237 | model.preloadMotionGroup(motionGroup); 238 | } 239 | 240 | function showExpression(exp){ 241 | var model = l2dthisRef.live2DMgr.getModel(0); 242 | if(model == null) return; 243 | 244 | model.setExpression(exp); 245 | } 246 | 247 | function showRandomMotion(motionGroup){ 248 | var model = l2dthisRef.live2DMgr.getModel(0); 249 | if(model == null) return; 250 | 251 | model.startRandomMotion(motionGroup, LAppDefine.PRIORITY_NORMAL); 252 | } 253 | 254 | function modelScaling(scale) 255 | { 256 | var isMaxScale = l2dthisRef.viewMatrix.isMaxScale(); 257 | var isMinScale = l2dthisRef.viewMatrix.isMinScale(); 258 | 259 | l2dthisRef.viewMatrix.adjustScale(0, 0, scale); 260 | 261 | // 画面が最大になったときのイベント 262 | if (!isMaxScale) 263 | { 264 | if (l2dthisRef.viewMatrix.isMaxScale()) 265 | { 266 | l2dthisRef.live2DMgr.maxScaleEvent(); 267 | } 268 | } 269 | // 画面が最小になったときのイベント 270 | if (!isMinScale) 271 | { 272 | if (l2dthisRef.viewMatrix.isMinScale()) 273 | { 274 | l2dthisRef.live2DMgr.minScaleEvent(); 275 | } 276 | } 277 | } 278 | 279 | 280 | /* 281 | * クリックされた方向を向く 282 | * タップされた場所に応じてモーションを再生 283 | */ 284 | function modelTurnHead(event) 285 | { 286 | l2dthisRef.drag = true; 287 | 288 | var rect = event.target.getBoundingClientRect(); 289 | 290 | var sx = transformScreenX(event.clientX - rect.left); 291 | var sy = transformScreenY(event.clientY - rect.top); 292 | var vx = transformViewX(event.clientX - rect.left); 293 | var vy = transformViewY(event.clientY - rect.top); 294 | 295 | if (LAppDefine.DEBUG_MOUSE_LOG) 296 | l2dLog("onMouseDown device( x:" + event.clientX + " y:" + event.clientY + " ) view( x:" + vx + " y:" + vy + ")"); 297 | 298 | l2dthisRef.lastMouseX = sx; 299 | l2dthisRef.lastMouseY = sy; 300 | 301 | l2dthisRef.dragMgr.setPoint(vx, vy); // その方向を向く 302 | 303 | // タップした場所に応じてモーションを再生 304 | l2dthisRef.live2DMgr.tapEvent(vx, vy); 305 | } 306 | 307 | 308 | /* 309 | * マウスを動かした時のイベント 310 | */ 311 | // Edited by AtomLive2D Project 312 | function followPointer(event, options) 313 | { 314 | var sx, sy, vx, vy; 315 | 316 | if (options) { 317 | vx = event.clientX - options.screenWidth + options.spriteWidth/2 + options.spriteOffsetX; 318 | sx = transformScreenX(vx); 319 | vx = vx / options.screenWidth * options.followCursorCoefficient; 320 | 321 | vy = event.clientY - options.screenHeight + options.spriteHeight/2 + options.spriteOffsetY; 322 | sy = transformScreenY(vy); 323 | vy = vy / -options.screenHeight * options.followCursorCoefficient; 324 | } else { // Fallback original method 325 | var rect = event.target.getBoundingClientRect(); 326 | sx = transformScreenX(event.clientX - rect.left); 327 | sy = transformScreenY(event.clientY - rect.top); 328 | vx = transformViewX(event.clientX - rect.left); 329 | vy = transformViewY(event.clientY - rect.top); 330 | } 331 | 332 | if (LAppDefine.DEBUG_MOUSE_LOG) 333 | l2dLog("onMouseMove device( x:" + event.clientX + " y:" + event.clientY + " ) view( x:" + vx + " y:" + vy + ")"); 334 | 335 | //if (l2dthisRef.drag) 336 | //{ 337 | l2dthisRef.lastMouseX = sx; 338 | l2dthisRef.lastMouseY = sy; 339 | 340 | l2dthisRef.dragMgr.setPoint(vx, vy); // その方向を向く 341 | //} 342 | } 343 | 344 | 345 | /* 346 | * 正面を向く 347 | */ 348 | function lookFront() 349 | { 350 | if (l2dthisRef.drag) 351 | { 352 | l2dthisRef.drag = false; 353 | } 354 | 355 | l2dthisRef.dragMgr.setPoint(0, 0); 356 | } 357 | 358 | 359 | /*function mouseEvent(e) 360 | { 361 | e.preventDefault(); 362 | 363 | if (e.type == "mousedown") { 364 | 365 | // 右クリック以外なら処理を抜ける 366 | if("button" in e && e.button != 0) return; 367 | 368 | modelTurnHead(e); 369 | 370 | } else if (e.type == "mousemove") { 371 | followPointer(e); 372 | 373 | } else if (e.type == "mouseup") { 374 | 375 | // 右クリック以外なら処理を抜ける 376 | if("button" in e && e.button != 0) return; 377 | 378 | lookFront(); 379 | 380 | } else if (e.type == "mouseout") { 381 | 382 | lookFront(); 383 | 384 | } 385 | 386 | } 387 | 388 | 389 | function touchEvent(e) 390 | { 391 | e.preventDefault(); 392 | 393 | var touch = e.touches[0]; 394 | 395 | if (e.type == "touchstart") { 396 | if (e.touches.length == 1) modelTurnHead(touch); 397 | // onClick(touch); 398 | 399 | } else if (e.type == "touchmove") { 400 | followPointer(touch); 401 | 402 | if (e.touches.length == 2) { 403 | var touch1 = e.touches[0]; 404 | var touch2 = e.touches[1]; 405 | 406 | var len = Math.pow(touch1.pageX - touch2.pageX, 2) + Math.pow(touch1.pageY - touch2.pageY, 2); 407 | if (l2dthisRef.oldLen - len < 0) modelScaling(1.025); // 上方向スクロール 拡大 408 | else modelScaling(0.975); // 下方向スクロール 縮小 409 | 410 | thisRef.oldLen = len; 411 | } 412 | 413 | } else if (e.type == "touchend") { 414 | lookFront(); 415 | } 416 | }*/ 417 | 418 | 419 | /* ********** マトリックス操作 ********** */ 420 | 421 | function transformViewX(deviceX) 422 | { 423 | var screenX = this.deviceToScreen.transformX(deviceX); // 論理座標変換した座標を取得。 424 | return viewMatrix.invertTransformX(screenX); // 拡大、縮小、移動後の値。 425 | } 426 | 427 | 428 | function transformViewY(deviceY) 429 | { 430 | var screenY = this.deviceToScreen.transformY(deviceY); // 論理座標変換した座標を取得。 431 | return viewMatrix.invertTransformY(screenY); // 拡大、縮小、移動後の値。 432 | } 433 | 434 | 435 | function transformScreenX(deviceX) 436 | { 437 | return this.deviceToScreen.transformX(deviceX); 438 | } 439 | 440 | 441 | function transformScreenY(deviceY) 442 | { 443 | return this.deviceToScreen.transformY(deviceY); 444 | } 445 | 446 | 447 | /* 448 | * WebGLのコンテキストを取得する 449 | */ 450 | function getWebGLContext() 451 | { 452 | var NAMES = [ "webgl" , "experimental-webgl" , "webkit-3d" , "moz-webgl"]; 453 | 454 | for( var i = 0; i < NAMES.length; i++ ){ 455 | try{ 456 | var ctx = this.canvas.getContext(NAMES[i], {premultipliedAlpha : true}); 457 | if(ctx) return ctx; 458 | } 459 | catch(e){} 460 | } 461 | return null; 462 | }; 463 | 464 | 465 | /* 466 | * 画面ログを出力 467 | */ 468 | function l2dLog(msg) { 469 | if(!LAppDefine.DEBUG_LOG) return; 470 | 471 | console.log(msg); 472 | } 473 | 474 | 475 | /* 476 | * 画面エラーを出力 477 | */ 478 | function l2dError(msg) 479 | { 480 | if(!LAppDefine.DEBUG_LOG) return; 481 | 482 | l2dLog( "" + msg + ""); 483 | 484 | console.error(msg); 485 | }; 486 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const remote = require('remote'); 4 | const url = require('url'); 5 | const browserWindow = remote.BrowserWindow; 6 | const CronJob = require('cron').CronJob; 7 | 8 | const MOTION_REGEX = /\((motion|wink|exp):(.*)\)/; 9 | 10 | module.exports = { 11 | config: { 12 | model: { 13 | type: "string", 14 | "default": "epsilon_2.1/Epsilon2.1.model.json", 15 | description: "model.json file of your model.", 16 | order: 1 17 | }, 18 | expressions: { 19 | type: "object", 20 | properties: { 21 | info: { 22 | type: "string", 23 | "default": "", 24 | description: "Expression or motion to play when info notification is showed. example: (exp:f01);(motion:idle)" 25 | }, 26 | 27 | success: { 28 | type: "string", 29 | "default": "", 30 | description: "Expression or motion to play when success notification is showed." 31 | }, 32 | 33 | warning: { 34 | type: "string", 35 | "default": "", 36 | description: "Expression or motion to play when warning notification is showed." 37 | }, 38 | 39 | error: { 40 | type: "string", 41 | "default": "", 42 | description: "Expression or motion to play when error notification is showed." 43 | }, 44 | 45 | fatal: { 46 | type: "string", 47 | "default": "", 48 | description: "Expression or motion to play when fatal notification is showed." 49 | }, 50 | 51 | wink: { 52 | type: "string", 53 | "default": "", 54 | description: "Expession or motion of wink. example: (motion:fun);(wink:do)" 55 | }, 56 | 57 | time: { 58 | type: "string", 59 | "default": "", 60 | description: "Expression or motion to play with timeSignal voice." 61 | } 62 | }, 63 | order: 2 64 | }, 65 | modelOpacity: { 66 | type: "number", 67 | minimum: 0.0, 68 | maximum: 1.0, 69 | "default": 0.7, 70 | description: "Opacity of model", 71 | order: 3 72 | }, 73 | modelCursorOpacity: { 74 | type: "number", 75 | minimum: 0.0, 76 | maximum: 1.0, 77 | "default": 0.1, 78 | description: "Opacity of model with curser over", 79 | order: 4 80 | }, 81 | startVoice: { 82 | type: "object", 83 | properties: { 84 | morning: { 85 | type: "string", 86 | "default": "", 87 | description: "from 6:00 to 12:00" 88 | }, 89 | afternoon: { 90 | type: "string", 91 | "default": "", 92 | description: "from 12:00 to 18:00" 93 | }, 94 | night: { 95 | type: "string", 96 | "default": "", 97 | description: "from 18:00 to 6:00" 98 | } 99 | }, 100 | order: 5 101 | }, 102 | timeSignal: { 103 | type: "array", 104 | "default": [], 105 | items: { 106 | type: "string" 107 | }, 108 | description: "Time signal voice filename. Array [0 - 23]", 109 | order: 6 110 | }, 111 | voiceVolume: { 112 | type: "number", 113 | "default": 0.3, 114 | minimum: 0.0, 115 | maximum: 1.0, 116 | description: "voice volume. between 0.0 and 1.0", 117 | order: 7 118 | }, 119 | width: { 120 | type: "number", 121 | "default": 360, 122 | minimum: 0, 123 | description: "Width of your canvas.", 124 | order: 8 125 | }, 126 | height: { 127 | type: "number", 128 | "default": 480, 129 | minimum: 0, 130 | description: "Height of your canvas.", 131 | order: 9 132 | }, 133 | assetsDir: { 134 | type: "string", 135 | "default": "~/.atom/packages/atom-live2d/assets/", 136 | description: "Path to assetsdir", 137 | order: 10 138 | }, 139 | followCursor: { 140 | type: "boolean", 141 | "default": true, 142 | description: "If it is true, the model will see your cursor.", 143 | order: 11 144 | }, 145 | followCursorOffsetX: { 146 | type: "number", 147 | "default": 0, 148 | description: "Offset the model's X-axis field of view", 149 | order: 12 150 | }, 151 | followCursorOffsetY: { 152 | type: "number", 153 | "default": 0, 154 | description: "Offset the model's Y-axis field of view", 155 | order: 13 156 | }, 157 | followCursorCoefficient: { 158 | type: "number", 159 | "default": 2, 160 | description: "Focus distance coefficient (closer to greater)", 161 | order: 14 162 | } 163 | }, 164 | timer: null, 165 | winkTimer: null, 166 | audio: null, 167 | activate: function(state) { 168 | var key, pJson, pkg; 169 | 170 | pJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'))); 171 | this.loadConfig(atom.config.get("atom-live2d.model")); 172 | atom.commands.add('atom-text-editor', "atom-live2d:toggle", (function(_this) { 173 | return function() { 174 | return _this.toggle(); 175 | }; 176 | })(this)); 177 | 178 | atom.views.getView(atom.workspace).classList.add("live-2d"); 179 | atom.notifications.onDidAddNotification((function(_this) { 180 | return function(notification) { 181 | return _this.showMotion(notification.type); 182 | }; 183 | })(this)); 184 | 185 | atom.config.onDidChange('atom-live2d.modelOpacity', (function(_this) { 186 | return function(arg) { 187 | var newValue, oldValue; 188 | newValue = arg.newValue, oldValue = arg.oldValue; 189 | return _this.reload(); 190 | }; 191 | })(this)); 192 | 193 | this.init(); 194 | return this.startVoice(new Date); 195 | }, 196 | getFileURL: function(...filePath) { 197 | return url.pathToFileURL(path.join( 198 | atom.packages.getLoadedPackage("atom-live2d").path, 199 | ...filePath 200 | )).toString(); 201 | }, 202 | 203 | deactivate: function() { 204 | var ref; 205 | this.audio = null; 206 | this.element.remove(); 207 | this.iframe.remove(); 208 | if ((ref = this.timer) != null) { 209 | ref.stop(); 210 | } 211 | this.timer = null; 212 | return clearTimeout(this.winkTimer); 213 | }, 214 | serialize: function() {}, 215 | loadConfig: function(model) { 216 | var data; 217 | var p = path.join(this.getAssetsDirPath(), path.dirname(model), 'atom-l2d-conf.json'); 218 | if (fs.existsSync(p)) data = require(p); 219 | 220 | if (data != null) { 221 | return atom.config.setDefaults("atom-live2d", data); 222 | } 223 | }, 224 | init: function() { 225 | this.element = document.createElement('style'); 226 | this.element.textContent = ` /*.live-2d .item-views /deep/ #glcanvas { 227 | display: inline-block; 228 | opacity: ${atom.config.get("atom-live2d.modelOpacity")}; 229 | } 230 | 231 | #glcanvas { 232 | display: none; 233 | }*/ 234 | 235 | iframe#live2d { 236 | display: none; 237 | } 238 | 239 | .live-2d iframe#live2d { 240 | opacity: ${atom.config.get("atom-live2d.modelOpacity")}; 241 | width: ${atom.config.get("atom-live2d.width")}px; 242 | height: ${atom.config.get("atom-live2d.height")}px; 243 | transition: opacity .5s; 244 | } 245 | 246 | .live-2d iframe#live2d.cursor-over { 247 | opacity: ${atom.config.get("atom-live2d.modelCursorOpacity")}; 248 | }`; 249 | 250 | /*this.glcanvas = document.createElement('canvas'); 251 | this.glcanvas.id = 'glcanvas'; 252 | atom.views.getView(atom.workspace).ownerDocument.querySelector('.item-views /deep/ .editor--private:not(.mini) .scroll-view').appendChild(this.glcanvas);*/ 253 | 254 | const workspaceView = atom.views.getView(atom.workspace); 255 | workspaceView.appendChild(this.element); 256 | 257 | this.iframe = document.createElement('iframe'); 258 | this.iframe.src = this.getFileURL('index.html'); 259 | this.iframe.name = `${atom.config.get("atom-live2d.width")};${atom.config.get("atom-live2d.height")}`; 260 | this.iframe.id = 'live2d'; 261 | this.iframe.onload = () => { 262 | this.loadCurrentModel(); 263 | this.loadMotionGroup(); 264 | this.iframe.contentWindow.document.body.appendChild(css); 265 | if(atom.config.get('atom-live2d.followCursor')) { 266 | workspaceView.addEventListener('mousemove', (e) => { 267 | if(atom.config.get('atom-live2d.followCursor')) { 268 | this.callOnWindow('followPointer', e, { 269 | screenWidth: workspaceView.clientWidth, 270 | screenHeight: workspaceView.clientHeight, 271 | spriteWidth: atom.config.get("atom-live2d.width"), 272 | spriteHeight: atom.config.get("atom-live2d.height"), 273 | spriteOffsetX: atom.config.get("atom-live2d.followCursorOffsetX"), 274 | spriteOffsetY: atom.config.get("atom-live2d.followCursorOffsetY"), 275 | followCursorCoefficient: atom.config.get("atom-live2d.followCursorCoefficient") 276 | }); 277 | } 278 | }); 279 | } 280 | 281 | const l2dView = workspaceView.querySelector('#live2d'); 282 | atom.workspace.observeTextEditors(editor => { 283 | const callback = _ => { 284 | const spriteStartX = workspaceView.clientWidth - atom.config.get("atom-live2d.width"); 285 | const spriteStartY = workspaceView.clientHeight - atom.config.get("atom-live2d.height"); 286 | const spriteEndX = workspaceView.clientWidth; 287 | const spriteEndY = workspaceView.clientHeight; 288 | 289 | var detect = false; 290 | atom.views.getView(editor).querySelectorAll('.cursor').forEach(v => { 291 | const rect = v.getBoundingClientRect(); 292 | if (spriteStartX < rect.left && spriteStartY < rect.top 293 | && spriteEndX > rect.left && spriteEndY > rect.top) { 294 | 295 | detect = true; 296 | } 297 | }); 298 | 299 | if (detect && !l2dView.classList.contains('cursor-over')) { 300 | l2dView.classList.add('cursor-over'); 301 | } else if (!detect && l2dView.classList.contains('cursor-over')) { 302 | l2dView.classList.remove('cursor-over'); 303 | } 304 | }; 305 | editor.onDidChangeCursorPosition(callback); 306 | editor.onDidAddCursor(callback); 307 | editor.onDidRemoveCursor(callback); 308 | }); 309 | }; 310 | 311 | var css = document.createElement('style'); 312 | css.innerHTML = ` 313 | #glcanvas { 314 | width: ${atom.config.get("atom-live2d.width")}px; 315 | height: ${atom.config.get("atom-live2d.height")}px; 316 | } 317 | `; 318 | 319 | atom.views.getView(atom.workspace).querySelector('atom-workspace-axis.vertical>atom-pane-container.panes atom-pane.pane').appendChild(this.iframe); 320 | 321 | /*[ 322 | 'lib/live2d.min', 323 | 'lib/live2d_framework', 324 | 'src/utils/MatrixStack', 325 | 'src/utils/ModelSettingJSON', 326 | 'src/PlatformManager', 327 | 'src/LAppDefine', 328 | 'src/LAppModel', 329 | 'src/LAppLive2DManager', 330 | 'src/AtomLive2D' 331 | ].map((v) => path.join(atom.packages.resolvePackagePath('atom-live2d'), v + '.js')).forEach((v) => { 332 | this.evalOnWindow(fs.readFileSync(v)); 333 | });*/ 334 | 335 | if(atom.config.get("atom-live2d.timeSignal").length > 0) { 336 | this.timer = new CronJob('00 00 * * * *', this.timeSignal.bind(this), null, true); 337 | } 338 | 339 | if(atom.config.get("atom-live2d.expressions.wink")) { 340 | return this.winkTimer = () => { 341 | this.showMotion('wink'); 342 | }; 343 | } 344 | }, 345 | reload: function() { 346 | this.deactivate(); 347 | this.loadConfig(atom.config.get("atom-live2d.model")); 348 | return this.init(); 349 | }, 350 | toggle: function() { 351 | this.iframe.contentWindow.postMessage('toggle', '*'); 352 | return atom.views.getView(atom.workspace).classList.toggle("live-2d"); 353 | }, 354 | startVoice: function(d) { 355 | var time; 356 | if (!d.getHours) { 357 | return; 358 | } 359 | time = "night"; 360 | if (d.getHours() >= 6 && d.getHours() < 12) { 361 | time = "morning"; 362 | } else if (d.getHours() >= 12 && d.getHours() < 18) { 363 | time = "afternoon"; 364 | } 365 | return this.speak(atom.config.get("atom-live2d.startVoice." + time)); 366 | }, 367 | timeSignal: function() { 368 | var d; 369 | d = new Date; 370 | this.showMotion('time'); 371 | return this.speak(atom.config.get("atom-live2d.timeSignal")[d.getHours()]); 372 | }, 373 | speak: function(filename) { 374 | if(!filename) return; 375 | var filepath, fileurl, windows; 376 | windows = browserWindow.getAllWindows(); 377 | if (windows[0].id !== atom.getCurrentWindow().id) { 378 | return; 379 | } 380 | if (!atom.views.getView(atom.workspace).classList.contains("live-2d")) { 381 | return; 382 | } 383 | filepath = path.join(this.getThemeDirPath(), filename); 384 | fileurl = this.getThemeDirUrl() + filename; 385 | if (!fs.existsSync(filepath)) { 386 | if (atom.inDevMode) { 387 | console.warn("Atom Live2D: no voice file:" + filepath); 388 | } 389 | return; 390 | } 391 | this.audio = this.audio || document.createElement("audio"); 392 | this.audio.autoplay = true; 393 | this.audio.volume = atom.config.get("atom-live2d.voiceVolume"); 394 | return this.audio.src = fileurl; 395 | }, 396 | showMotion: function(motionString){ 397 | var exp = atom.config.get("atom-live2d.expressions"); 398 | var index = {}; 399 | exp[motionString].split(';').forEach((v) => { 400 | var regex = v.match(MOTION_REGEX); 401 | 402 | if(regex === null) return; 403 | if(index[regex[1]] === undefined) index[regex[1]] = 0; 404 | else index[regex[1]]++; 405 | 406 | setTimeout(() => { 407 | switch(regex[1]){ 408 | case 'motion': 409 | this.callOnWindow('showRandomMotion', regex[2]); 410 | break; 411 | 412 | case 'exp': 413 | this.callOnWindow('showExpression', regex[2]); 414 | break; 415 | 416 | case 'wink': 417 | this.callOnWindow('wink'); 418 | break; 419 | } 420 | }, index[regex[1]]); 421 | }); 422 | }, 423 | loadMotionGroup: function(){ 424 | var exp = atom.config.get("atom-live2d.expressions"); 425 | Object.keys(exp).forEach((k) => { 426 | exp[k].split(';').forEach((v) => { 427 | var regex = v.match(MOTION_REGEX); 428 | 429 | if(regex === null) return; 430 | if(regex[1] === 'motion') this.callOnWindow('loadMotionGroup', regex[2]); 431 | }); 432 | }); 433 | }, 434 | loadCurrentModel: function() { 435 | this.callOnWindow('atomLive2d', this.getFileURL('assets', atom.config.get('atom-live2d.model'))); 436 | console.log('Evaluating init script...'); 437 | }, 438 | callOnWindow: function(funcname, ...args) { 439 | if(this.iframe.contentWindow[funcname] === undefined) 440 | return console.log('Skipped undefined function (plz wait for a sec!): ', funcname) 441 | //return atom.getCurrentWindow().webContents.executeJavaScript(source); 442 | return this.iframe.contentWindow[funcname](...args); 443 | }, 444 | getThemeDir: function() { 445 | return path.dirname(atom.config.get('atom-live2d.model')); 446 | }, 447 | getThemeDirUrl: function() { 448 | return this.trailingslash(this.getFileURL('assets', this.getThemeDir())); 449 | }, 450 | getThemeDirPath: function() { 451 | return path.join(this.getAssetsDirPath(), this.getThemeDir()); 452 | }, 453 | getAssetsDirPath: function() { 454 | var path; 455 | path = atom.config.get("atom-live2d.assetsDir"); 456 | if (path[0] === "~") { 457 | path = this.getUserHome() + path.substr(1); 458 | } 459 | return this.trailingslash(path); 460 | }, 461 | getUserHome: function() { 462 | if (process.platform === 'win32') { 463 | return process.env.USERPROFILE; 464 | } 465 | return process.env.HOME; 466 | }, 467 | trailingslash: function(path) { 468 | if (path[path.length - 1] === "/") { 469 | path = path.slice(0, -1); 470 | } 471 | return path + "/"; 472 | } 473 | }; 474 | -------------------------------------------------------------------------------- /src/LAppModel.js: -------------------------------------------------------------------------------- 1 | //============================================================ 2 | //============================================================ 3 | // class LAppModel extends L2DBaseModel 4 | //============================================================ 5 | //============================================================ 6 | function LAppModel() 7 | { 8 | //L2DBaseModel.apply(this, arguments); 9 | L2DBaseModel.prototype.constructor.call(this); 10 | 11 | this.modelHomeDir = ""; 12 | this.modelSetting = null; 13 | this.tmpMatrix = []; 14 | } 15 | 16 | LAppModel.prototype = new L2DBaseModel(); 17 | 18 | /* 19 | * モデルを初期化する 20 | */ 21 | LAppModel.prototype.load = function(gl, modelSettingPath, callback) 22 | { 23 | this.setUpdating(true); 24 | this.setInitialized(false); 25 | 26 | this.modelHomeDir = modelSettingPath.substring(0, modelSettingPath.lastIndexOf("/") + 1); 27 | 28 | this.modelSetting = new ModelSettingJson(); 29 | 30 | var thisRef = this; 31 | 32 | this.modelSetting.loadModelSetting(modelSettingPath, function(){ 33 | // モデルデータを読み込む 34 | var path = thisRef.modelHomeDir + thisRef.modelSetting.getModelFile(); 35 | thisRef.loadModelData(path, function(model){ 36 | 37 | for (var i = 0; i < thisRef.modelSetting.getTextureNum(); i++) 38 | { 39 | // テクスチャを読み込む 40 | var texPaths = thisRef.modelHomeDir + 41 | thisRef.modelSetting.getTextureFile(i); 42 | 43 | thisRef.loadTexture(i, texPaths, function() { 44 | // すべてのテクスチャを読み込んだ後の処理 45 | if( thisRef.isTexLoaded ) { 46 | // 表情 47 | if (thisRef.modelSetting.getExpressionNum() > 0) 48 | { 49 | // 古い表情を削除 50 | thisRef.expressions = {}; 51 | 52 | for (var j = 0; j < thisRef.modelSetting.getExpressionNum(); j++) 53 | { 54 | var expName = thisRef.modelSetting.getExpressionName(j); 55 | var expFilePath = thisRef.modelHomeDir + 56 | thisRef.modelSetting.getExpressionFile(j); 57 | 58 | thisRef.loadExpression(expName, expFilePath); 59 | } 60 | } 61 | else 62 | { 63 | thisRef.expressionManager = null; 64 | thisRef.expressions = {}; 65 | } 66 | 67 | 68 | // 自動目パチ 69 | if (thisRef.eyeBlink == null) 70 | { 71 | thisRef.eyeBlink = new L2DEyeBlink(); 72 | } 73 | 74 | // 物理演算 75 | if (thisRef.modelSetting.getPhysicsFile() != null) 76 | { 77 | thisRef.loadPhysics(thisRef.modelHomeDir + 78 | thisRef.modelSetting.getPhysicsFile()); 79 | } 80 | else 81 | { 82 | thisRef.physics = null; 83 | } 84 | 85 | 86 | // パーツ切り替え 87 | if (thisRef.modelSetting.getPoseFile() != null) 88 | { 89 | thisRef.loadPose( 90 | thisRef.modelHomeDir + 91 | thisRef.modelSetting.getPoseFile(), 92 | function() { 93 | thisRef.pose.updateParam(thisRef.live2DModel); 94 | } 95 | ); 96 | } 97 | else 98 | { 99 | thisRef.pose = null; 100 | } 101 | 102 | 103 | // レイアウト 104 | if (thisRef.modelSetting.getLayout() != null) 105 | { 106 | var layout = thisRef.modelSetting.getLayout(); 107 | if (layout["width"] != null) 108 | thisRef.modelMatrix.setWidth(layout["width"]); 109 | if (layout["height"] != null) 110 | thisRef.modelMatrix.setHeight(layout["height"]); 111 | 112 | if (layout["x"] != null) 113 | thisRef.modelMatrix.setX(layout["x"]); 114 | if (layout["y"] != null) 115 | thisRef.modelMatrix.setY(layout["y"]); 116 | if (layout["center_x"] != null) 117 | thisRef.modelMatrix.centerX(layout["center_x"]); 118 | if (layout["center_y"] != null) 119 | thisRef.modelMatrix.centerY(layout["center_y"]); 120 | if (layout["top"] != null) 121 | thisRef.modelMatrix.top(layout["top"]); 122 | if (layout["bottom"] != null) 123 | thisRef.modelMatrix.bottom(layout["bottom"]); 124 | if (layout["left"] != null) 125 | thisRef.modelMatrix.left(layout["left"]); 126 | if (layout["right"] != null) 127 | thisRef.modelMatrix.right(layout["right"]); 128 | } 129 | 130 | for (var j = 0; j < thisRef.modelSetting.getInitParamNum(); j++) 131 | { 132 | // パラメータを上書き 133 | thisRef.live2DModel.setParamFloat( 134 | thisRef.modelSetting.getInitParamID(j), 135 | thisRef.modelSetting.getInitParamValue(j) 136 | ); 137 | } 138 | 139 | for (var j = 0; j < thisRef.modelSetting.getInitPartsVisibleNum(); j++) 140 | { 141 | // パーツの透明度を設定 142 | thisRef.live2DModel.setPartsOpacity( 143 | thisRef.modelSetting.getInitPartsVisibleID(j), 144 | thisRef.modelSetting.getInitPartsVisibleValue(j) 145 | ); 146 | } 147 | 148 | 149 | // パラメータを保存。次回のloadParamで読みだされる 150 | thisRef.live2DModel.saveParam(); 151 | // thisRef.live2DModel.setGL(gl); 152 | 153 | // アイドリングはあらかじめ読み込んでおく。 154 | thisRef.preloadMotionGroup(LAppDefine.MOTION_GROUP_IDLE); 155 | thisRef.mainMotionManager.stopAllMotions(); 156 | 157 | thisRef.setUpdating(false); // 更新状態の完了 158 | thisRef.setInitialized(true); // 初期化完了 159 | 160 | if (typeof callback == "function") callback(); 161 | 162 | } 163 | }); 164 | } 165 | }); 166 | }); 167 | }; 168 | 169 | 170 | /* 171 | * GCだけで解放されないメモリを解放 172 | */ 173 | LAppModel.prototype.release = function(gl) 174 | { 175 | // this.live2DModel.deleteTextures(); 176 | var pm = Live2DFramework.getPlatformManager(); 177 | 178 | gl.deleteTexture(pm.texture); 179 | } 180 | 181 | 182 | /* 183 | * モーションファイルをあらかじめ読み込む 184 | */ 185 | LAppModel.prototype.preloadMotionGroup = function(name) 186 | { 187 | var thisRef = this; 188 | 189 | for (var i = 0; i < this.modelSetting.getMotionNum(name); i++) 190 | { 191 | var file = this.modelSetting.getMotionFile(name, i); 192 | this.loadMotion(file, this.modelHomeDir + file, function(motion) { 193 | motion.setFadeIn(thisRef.modelSetting.getMotionFadeIn(name, i)); 194 | motion.setFadeOut(thisRef.modelSetting.getMotionFadeOut(name, i)); 195 | }); 196 | 197 | } 198 | } 199 | 200 | 201 | LAppModel.prototype.update = function() 202 | { 203 | // console.log("--> LAppModel.update()"); 204 | 205 | if(this.live2DModel == null) 206 | { 207 | if (LAppDefine.DEBUG_LOG) console.error("Failed to update."); 208 | 209 | return; 210 | } 211 | 212 | var timeMSec = UtSystem.getUserTimeMSec() - this.startTimeMSec; 213 | var timeSec = timeMSec / 1000.0; 214 | var t = timeSec * 2 * Math.PI; // 2πt 215 | 216 | // 待機モーション判定 217 | if (this.mainMotionManager.isFinished()) 218 | { 219 | // モーションの再生がない場合、待機モーションの中からランダムで再生する 220 | this.startRandomMotion(LAppDefine.MOTION_GROUP_IDLE, LAppDefine.PRIORITY_IDLE); 221 | } 222 | 223 | //----------------------------------------------------------------- 224 | 225 | // 前回セーブされた状態をロード 226 | this.live2DModel.loadParam(); 227 | 228 | /* インスタンスが作られていたら更新 */ 229 | 230 | var update = this.mainMotionManager.updateParam(this.live2DModel); // モーションを更新 231 | if (!update) { 232 | // 目ぱち 233 | if(this.eyeBlink != null) { 234 | this.eyeBlink.updateParam(this.live2DModel); 235 | } 236 | } 237 | 238 | // 状態を保存 239 | this.live2DModel.saveParam(); 240 | 241 | //----------------------------------------------------------------- 242 | 243 | // 表情でパラメータ更新(相対変化) 244 | if (this.expressionManager != null && 245 | this.expressions != null && 246 | !this.expressionManager.isFinished()) 247 | { 248 | this.expressionManager.updateParam(this.live2DModel); 249 | } 250 | 251 | // ドラッグによる顔の向きの調整 252 | // -30から30の値を加える 253 | this.live2DModel.addToParamFloat("PARAM_ANGLE_X", this.dragX * 30, 1); 254 | this.live2DModel.addToParamFloat("PARAM_ANGLE_Y", this.dragY * 30, 1); 255 | this.live2DModel.addToParamFloat("PARAM_ANGLE_Z", (this.dragX * this.dragY) * -30, 1); 256 | 257 | // ドラッグによる体の向きの調整 258 | // -10から10の値を加える 259 | this.live2DModel.addToParamFloat("PARAM_BODY_ANGLE_X", this.dragX*10, 1); 260 | 261 | // ドラッグによる目の向きの調整 262 | // -1から1の値を加える 263 | this.live2DModel.addToParamFloat("PARAM_EYE_BALL_X", this.dragX, 1); 264 | this.live2DModel.addToParamFloat("PARAM_EYE_BALL_Y", this.dragY, 1); 265 | 266 | 267 | // 呼吸など 268 | this.live2DModel.addToParamFloat("PARAM_ANGLE_X", 269 | Number((15 * Math.sin(t / 6.5345))), 0.5); 270 | this.live2DModel.addToParamFloat("PARAM_ANGLE_Y", 271 | Number((8 * Math.sin(t / 3.5345))), 0.5); 272 | this.live2DModel.addToParamFloat("PARAM_ANGLE_Z", 273 | Number((10 * Math.sin(t / 5.5345))), 0.5); 274 | this.live2DModel.addToParamFloat("PARAM_BODY_ANGLE_X", 275 | Number((4 * Math.sin(t / 15.5345))), 0.5); 276 | this.live2DModel.setParamFloat("PARAM_BREATH", 277 | Number((0.5 + 0.5 * Math.sin(t / 3.2345))), 1); 278 | 279 | // 物理演算 280 | if (this.physics != null) 281 | { 282 | this.physics.updateParam(this.live2DModel); // 物理演算でパラメータ更新 283 | } 284 | 285 | // リップシンクの設定 286 | if (this.lipSync == null) 287 | { 288 | this.live2DModel.setParamFloat("PARAM_MOUTH_OPEN_Y", 289 | this.lipSyncValue); 290 | } 291 | 292 | // ポーズ 293 | if( this.pose != null ) { 294 | this.pose.updateParam(this.live2DModel); 295 | } 296 | 297 | this.live2DModel.update(); 298 | }; 299 | 300 | 301 | /* 302 | * 表情をランダムに切り替える 303 | */ 304 | LAppModel.prototype.setRandomExpression = function() 305 | { 306 | var tmp = []; 307 | for (var name in this.expressions) 308 | { 309 | tmp.push(name); 310 | } 311 | 312 | var no = parseInt(Math.random() * tmp.length); 313 | 314 | this.setExpression(tmp[no]); 315 | } 316 | 317 | 318 | /* 319 | * モーションをランダムで再生する 320 | */ 321 | LAppModel.prototype.startRandomMotion = function(name, priority) 322 | { 323 | var max = this.modelSetting.getMotionNum(name); 324 | var no = parseInt(Math.random() * max); 325 | this.startMotion(name, no, priority); 326 | } 327 | 328 | 329 | /* 330 | * モーションの開始。 331 | * 再生できる状態かチェックして、できなければ何もしない。 332 | * 再生出来る場合は自動でファイルを読み込んで再生。 333 | * 音声付きならそれも再生。 334 | * フェードイン、フェードアウトの情報があればここで設定。なければ初期値。 335 | */ 336 | LAppModel.prototype.startMotion = function(name, no, priority) 337 | { 338 | // console.log("startMotion : " + name + " " + no + " " + priority); 339 | 340 | var motionName = this.modelSetting.getMotionFile(name, no); 341 | 342 | if (motionName == null || motionName == "") 343 | { 344 | if (LAppDefine.DEBUG_LOG) 345 | console.error("Failed to motion."); 346 | return; 347 | } 348 | 349 | if (priority == LAppDefine.PRIORITY_FORCE) 350 | { 351 | this.mainMotionManager.setReservePriority(priority); 352 | } 353 | else if (!this.mainMotionManager.reserveMotion(priority)) 354 | { 355 | if (LAppDefine.DEBUG_LOG) 356 | console.log("Motion is running.") 357 | return; 358 | } 359 | 360 | var thisRef = this; 361 | var motion; 362 | 363 | if (this.motions[name] == null) 364 | { 365 | this.loadMotion(null, this.modelHomeDir + motionName, function(mtn) { 366 | motion = mtn; 367 | 368 | // フェードイン、フェードアウトの設定 369 | thisRef.setFadeInFadeOut(name, no, priority, motion); 370 | 371 | }); 372 | } 373 | else 374 | { 375 | motion = this.motions[name]; 376 | 377 | // フェードイン、フェードアウトの設定 378 | thisRef.setFadeInFadeOut(name, no, priority, motion); 379 | } 380 | } 381 | 382 | 383 | LAppModel.prototype.setFadeInFadeOut = function(name, no, priority, motion) 384 | { 385 | var motionName = this.modelSetting.getMotionFile(name, no); 386 | 387 | motion.setFadeIn(this.modelSetting.getMotionFadeIn(name, no)); 388 | motion.setFadeOut(this.modelSetting.getMotionFadeOut(name, no)); 389 | 390 | 391 | if (LAppDefine.DEBUG_LOG) 392 | console.log("Start motion : " + motionName); 393 | 394 | if (this.modelSetting.getMotionSound(name, no) == null) 395 | { 396 | this.mainMotionManager.startMotionPrio(motion, priority); 397 | } 398 | else 399 | { 400 | var soundName = this.modelSetting.getMotionSound(name, no); 401 | // var player = new Sound(this.modelHomeDir + soundName); 402 | 403 | var snd = document.createElement("audio"); 404 | snd.src = this.modelHomeDir + soundName; 405 | 406 | if (LAppDefine.DEBUG_LOG) 407 | console.log("Start sound : " + soundName); 408 | 409 | snd.play(); 410 | this.mainMotionManager.startMotionPrio(motion, priority); 411 | } 412 | } 413 | 414 | 415 | /* 416 | * 表情を設定する 417 | */ 418 | LAppModel.prototype.setExpression = function(name) 419 | { 420 | var motion = this.expressions[name]; 421 | 422 | if (LAppDefine.DEBUG_LOG) 423 | console.log("Expression : " + name); 424 | 425 | this.expressionManager.startMotion(motion, false); 426 | } 427 | 428 | 429 | /* 430 | * 描画 431 | */ 432 | LAppModel.prototype.draw = function(gl) 433 | { 434 | //console.log("--> LAppModel.draw()"); 435 | 436 | // if(this.live2DModel == null) return; 437 | 438 | // 通常 439 | MatrixStack.push(); 440 | 441 | MatrixStack.multMatrix(this.modelMatrix.getArray()); 442 | 443 | this.tmpMatrix = MatrixStack.getMatrix() 444 | this.live2DModel.setMatrix(this.tmpMatrix); 445 | this.live2DModel.draw(); 446 | 447 | MatrixStack.pop(); 448 | 449 | }; 450 | 451 | 452 | /* 453 | * 当たり判定との簡易テスト。 454 | * 指定IDの頂点リストからそれらを含む最大の矩形を計算し、点がそこに含まれるか判定 455 | */ 456 | LAppModel.prototype.hitTest = function(id, testX, testY) 457 | { 458 | var len = this.modelSetting.getHitAreaNum(); 459 | for (var i = 0; i < len; i++) 460 | { 461 | if (id == this.modelSetting.getHitAreaName(i)) 462 | { 463 | var drawID = this.modelSetting.getHitAreaID(i); 464 | 465 | return this.hitTestSimple(drawID, testX, testY); 466 | } 467 | } 468 | 469 | return false; // 存在しない場合はfalse 470 | } 471 | -------------------------------------------------------------------------------- /lib/live2d_framework.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * You can modify and use this source freely 4 | * only for the development of application related Live2D. 5 | * 6 | * (c) Live2D Inc. All rights reserved. 7 | */ 8 | //============================================================ 9 | //============================================================ 10 | // class L2DBaseModel 11 | //============================================================ 12 | //============================================================ 13 | function L2DBaseModel() 14 | { 15 | this.live2DModel = null; // ALive2DModel 16 | this.modelMatrix = null; // L2DModelMatrix 17 | this.eyeBlink = null; // L2DEyeBlink 18 | this.physics = null; // L2DPhysics 19 | this.pose = null; // L2DPose 20 | this.debugMode = false; 21 | this.initialized = false; 22 | this.updating = false; 23 | this.alpha = 1; 24 | this.accAlpha = 0; 25 | this.lipSync = false; // リップシンクが有効かどうか 26 | this.lipSyncValue = 0; // 基本は0~1 27 | this.accelX = 0; 28 | this.accelY = 0; 29 | this.accelZ = 0; 30 | this.dragX = 0; 31 | this.dragY = 0; 32 | this.startTimeMSec = null; 33 | this.mainMotionManager = new L2DMotionManager(); //L2DMotionManager 34 | this.expressionManager = new L2DMotionManager(); //L2DMotionManager 35 | this.motions = {}; 36 | this.expressions = {}; 37 | 38 | this.isTexLoaded = false; 39 | } 40 | 41 | var texCounter = 0; 42 | 43 | //============================================================ 44 | // L2DBaseModel # getModelMatrix() 45 | //============================================================ 46 | L2DBaseModel.prototype.getModelMatrix = function() 47 | { 48 | return this.modelMatrix; 49 | } 50 | 51 | //============================================================ 52 | // L2DBaseModel # setAlpha() 53 | //============================================================ 54 | L2DBaseModel.prototype.setAlpha = function(a/*float*/) 55 | { 56 | if( a > 0.999 ) a = 1; 57 | if( a < 0.001 ) a = 0; 58 | this.alpha = a; 59 | } 60 | 61 | //============================================================ 62 | // L2DBaseModel # getAlpha() 63 | //============================================================ 64 | L2DBaseModel.prototype.getAlpha = function() 65 | { 66 | return this.alpha; 67 | } 68 | 69 | //============================================================ 70 | // L2DBaseModel # isInitialized() 71 | //============================================================ 72 | L2DBaseModel.prototype.isInitialized = function() 73 | { 74 | return this.initialized; 75 | } 76 | 77 | //============================================================ 78 | // L2DBaseModel # setInitialized() 79 | //============================================================ 80 | L2DBaseModel.prototype.setInitialized = function( v/*boolean*/) 81 | { 82 | this.initialized = v; 83 | } 84 | 85 | //============================================================ 86 | // L2DBaseModel # isUpdating() 87 | //============================================================ 88 | L2DBaseModel.prototype.isUpdating = function() 89 | { 90 | return this.updating; 91 | } 92 | 93 | //============================================================ 94 | // L2DBaseModel # setUpdating() 95 | //============================================================ 96 | L2DBaseModel.prototype.setUpdating = function(v/*boolean*/) 97 | { 98 | this.updating = v; 99 | } 100 | 101 | //============================================================ 102 | // L2DBaseModel # getLive2DModel() 103 | //============================================================ 104 | L2DBaseModel.prototype.getLive2DModel = function() 105 | { 106 | return this.live2DModel; 107 | } 108 | 109 | //============================================================ 110 | // L2DBaseModel # setLipSync() 111 | //============================================================ 112 | L2DBaseModel.prototype.setLipSync = function(v/*boolean*/) 113 | { 114 | this.lipSync = v; 115 | } 116 | 117 | //============================================================ 118 | // L2DBaseModel # setLipSyncValue() 119 | //============================================================ 120 | L2DBaseModel.prototype.setLipSyncValue = function(v/*float*/) 121 | { 122 | this.lipSyncValue = v; 123 | } 124 | 125 | //============================================================ 126 | // L2DBaseModel # setAccel() 127 | //============================================================ 128 | L2DBaseModel.prototype.setAccel = function(x/*float*/, y/*float*/, z/*float*/) 129 | { 130 | this.accelX = x; 131 | this.accelY = y; 132 | this.accelZ = z; 133 | } 134 | 135 | //============================================================ 136 | // L2DBaseModel # setDrag() 137 | //============================================================ 138 | L2DBaseModel.prototype.setDrag = function(x/*float*/, y/*float*/) 139 | { 140 | this.dragX = x; 141 | this.dragY = y; 142 | } 143 | 144 | //============================================================ 145 | // L2DBaseModel # getMainMotionManager() 146 | //============================================================ 147 | L2DBaseModel.prototype.getMainMotionManager = function() 148 | { 149 | return this.mainMotionManager; 150 | } 151 | 152 | //============================================================ 153 | // L2DBaseModel # getExpressionManager() 154 | //============================================================ 155 | L2DBaseModel.prototype.getExpressionManager = function() 156 | { 157 | return this.expressionManager; 158 | } 159 | 160 | //============================================================ 161 | // L2DBaseModel # loadModelData() 162 | //============================================================ 163 | L2DBaseModel.prototype.loadModelData = function(path/*String*/, callback) 164 | { 165 | /* 166 | if( this.live2DModel != null ) { 167 | this.live2DModel.deleteTextures(); 168 | } 169 | */ 170 | var pm = Live2DFramework.getPlatformManager(); //IPlatformManager 171 | if( this.debugMode ) pm.log("Load model : " + path); 172 | 173 | var thisRef = this; 174 | pm.loadLive2DModel(path, function(l2dModel) { 175 | thisRef.live2DModel = l2dModel; 176 | thisRef.live2DModel.saveParam(); 177 | 178 | var _err = Live2D.getError(); 179 | 180 | if( _err != 0 ) { 181 | console.error("Error : Failed to loadModelData()."); 182 | return; 183 | } 184 | 185 | thisRef.modelMatrix = new L2DModelMatrix( 186 | thisRef.live2DModel.getCanvasWidth(), 187 | thisRef.live2DModel.getCanvasHeight()); //L2DModelMatrix 188 | thisRef.modelMatrix.setWidth(2); 189 | thisRef.modelMatrix.setCenterPosition(0, 0); 190 | 191 | callback(thisRef.live2DModel); 192 | }); 193 | } 194 | 195 | 196 | //============================================================ 197 | // L2DBaseModel # loadTexture() 198 | //============================================================ 199 | L2DBaseModel.prototype.loadTexture = function(no/*int*/, path/*String*/, callback) 200 | { 201 | texCounter++; 202 | 203 | var pm = Live2DFramework.getPlatformManager(); //IPlatformManager 204 | 205 | if( this.debugMode ) pm.log("Load Texture : " + path); 206 | 207 | var thisRef = this; 208 | pm.loadTexture(this.live2DModel , no , path, function(){ 209 | texCounter--; 210 | if(texCounter == 0) thisRef.isTexLoaded = true; 211 | if (typeof callback == "function") callback(); 212 | }); 213 | 214 | } 215 | 216 | //============================================================ 217 | // L2DBaseModel # loadMotion() 218 | //============================================================ 219 | L2DBaseModel.prototype.loadMotion = function(name/*String*/, path /*String*/, callback) 220 | { 221 | var pm = Live2DFramework.getPlatformManager(); //IPlatformManager 222 | 223 | if(this.debugMode) pm.log("Load Motion : " + path); 224 | 225 | var motion = null; //Live2DMotion 226 | 227 | var thisRef = this; 228 | pm.loadBytes(path, function(buf) { 229 | motion = Live2DMotion.loadMotion(buf); 230 | if( name != null ) { 231 | thisRef.motions[name] = motion; 232 | } 233 | callback(motion); 234 | }); 235 | 236 | } 237 | 238 | //============================================================ 239 | // L2DBaseModel # loadExpression() 240 | //============================================================ 241 | L2DBaseModel.prototype.loadExpression = function(name/*String*/, path /*String*/, callback) 242 | { 243 | var pm = Live2DFramework.getPlatformManager(); //IPlatformManager 244 | 245 | if( this.debugMode ) pm.log("Load Expression : " + path); 246 | 247 | var thisRef = this; 248 | pm.loadBytes(path, function(buf) { 249 | if(name != null) { 250 | thisRef.expressions[name] = L2DExpressionMotion.loadJson(buf); 251 | } 252 | if (typeof callback == "function") callback(); 253 | }); 254 | } 255 | 256 | //============================================================ 257 | // L2DBaseModel # loadPose() 258 | //============================================================ 259 | L2DBaseModel.prototype.loadPose = function( path /*String*/, callback ) 260 | { 261 | var pm = Live2DFramework.getPlatformManager(); //IPlatformManager 262 | if( this.debugMode ) pm.log("Load Pose : " + path); 263 | var thisRef = this; 264 | try { 265 | pm.loadBytes(path, function(buf) { 266 | thisRef.pose = L2DPose.load(buf); 267 | if (typeof callback == "function") callback(); 268 | }); 269 | } 270 | catch(e) { 271 | console.warn(e); 272 | } 273 | } 274 | 275 | //============================================================ 276 | // L2DBaseModel # loadPhysics() 277 | //============================================================ 278 | L2DBaseModel.prototype.loadPhysics = function(path/*String*/) 279 | { 280 | var pm = Live2DFramework.getPlatformManager(); //IPlatformManager 281 | if( this.debugMode ) pm.log("Load Physics : " + path); 282 | var thisRef = this; 283 | try { 284 | pm.loadBytes(path, function(buf) { 285 | thisRef.physics = L2DPhysics.load(buf); 286 | }); 287 | } 288 | catch(e){ 289 | console.warn(e); 290 | } 291 | } 292 | 293 | //============================================================ 294 | // L2DBaseModel # hitTestSimple() 295 | //============================================================ 296 | L2DBaseModel.prototype.hitTestSimple = function(drawID, testX, testY) 297 | { 298 | var drawIndex = this.live2DModel.getDrawDataIndex(drawID); 299 | 300 | if( drawIndex < 0 ) return false; 301 | 302 | var points = this.live2DModel.getTransformedPoints(drawIndex); 303 | var left = this.live2DModel.getCanvasWidth(); 304 | var right = 0; 305 | var top = this.live2DModel.getCanvasHeight(); 306 | var bottom = 0; 307 | 308 | for( var j = 0; j < points.length; j = j + 2 ) { 309 | var x = points[j]; 310 | var y = points[j + 1]; 311 | 312 | if( x < left ) left = x; 313 | if( x > right ) right = x; 314 | if( y < top ) top = y; 315 | if( y > bottom ) bottom = y; 316 | } 317 | var tx = this.modelMatrix.invertTransformX(testX); 318 | var ty = this.modelMatrix.invertTransformY(testY); 319 | 320 | return ( left <= tx && tx <= right && top <= ty && ty <= bottom ); 321 | } 322 | 323 | /** 324 | * 325 | * You can modify and use this source freely 326 | * only for the development of application related Live2D. 327 | * 328 | * (c) Live2D Inc. All rights reserved. 329 | */ 330 | 331 | //============================================================ 332 | //============================================================ 333 | // class L2DExpressionMotion extends AMotion 334 | //============================================================ 335 | //============================================================ 336 | function L2DExpressionMotion() 337 | { 338 | AMotion.prototype.constructor.call(this); 339 | this.paramList = new Array(); //ArrayList 340 | } 341 | 342 | L2DExpressionMotion.prototype = new AMotion(); // L2DExpressionMotion extends AMotion 343 | 344 | //============================================================ 345 | L2DExpressionMotion.EXPRESSION_DEFAULT = "DEFAULT"; 346 | L2DExpressionMotion.TYPE_SET = 0; 347 | L2DExpressionMotion.TYPE_ADD = 1; 348 | L2DExpressionMotion.TYPE_MULT = 2; 349 | 350 | //============================================================ 351 | // static L2DExpressionMotion.loadJson() 352 | //============================================================ 353 | L2DExpressionMotion.loadJson = function(buf) 354 | { 355 | var ret = new L2DExpressionMotion(); 356 | 357 | var pm = Live2DFramework.getPlatformManager(); 358 | var json = pm.jsonParseFromBytes(buf); 359 | 360 | ret.setFadeIn(parseInt(json.fade_in) > 0 ? parseInt(json.fade_in) : 1000); 361 | ret.setFadeOut(parseInt(json.fade_out) > 0 ? parseInt(json.fade_out) : 1000); 362 | 363 | if(json.params == null) { 364 | return ret; 365 | } 366 | 367 | var params = json.params; 368 | var paramNum = params.length; 369 | ret.paramList = []; //ArrayList 370 | for( var i = 0; i < paramNum; i++) { 371 | var param = params[i]; 372 | var paramID = param.id.toString(); 373 | var value = parseFloat(param.val); 374 | var calcTypeInt = L2DExpressionMotion.TYPE_ADD; 375 | var calc = param.calc != null ? param.calc.toString() : "add"; 376 | if(calc === "add") { 377 | calcTypeInt = L2DExpressionMotion.TYPE_ADD; 378 | } 379 | else if(calc === "mult") { 380 | calcTypeInt = L2DExpressionMotion.TYPE_MULT; 381 | } 382 | else if(calc === "set") { 383 | calcTypeInt = L2DExpressionMotion.TYPE_SET; 384 | } 385 | else { 386 | calcTypeInt = L2DExpressionMotion.TYPE_ADD; 387 | } 388 | if(calcTypeInt == L2DExpressionMotion.TYPE_ADD) { 389 | var defaultValue = param.def == null ? 0 : parseFloat(param.def); 390 | value = value - defaultValue; 391 | } 392 | else if(calcTypeInt == L2DExpressionMotion.TYPE_MULT) { 393 | var defaultValue = param.def == null ? 1 : parseFloat(param.def); 394 | if(defaultValue == 0 ) defaultValue = 1; 395 | value = value / defaultValue; 396 | } 397 | 398 | var item = new L2DExpressionParam( ); 399 | item.id = paramID; 400 | item.type = calcTypeInt; 401 | item.value = value; 402 | 403 | ret.paramList.push(item); 404 | } 405 | 406 | return ret; 407 | } 408 | 409 | 410 | //============================================================ 411 | // L2DExpressionMotion # updateParamExe() 412 | //============================================================ 413 | L2DExpressionMotion.prototype.updateParamExe = function(model /*ALive2DModel*/, timeMSec/*long*/ ,weight /*float*/ ,motionQueueEnt /*MotionQueueEnt*/) 414 | { 415 | for(var i = this.paramList.length - 1; i >= 0; --i) { 416 | var param = this.paramList[i]; //L2DExpressionParam 417 | // if (!param || !param.type) continue; 418 | if(param.type == L2DExpressionMotion.TYPE_ADD) { 419 | model.addToParamFloat(param.id, param.value, weight); 420 | } 421 | else if(param.type == L2DExpressionMotion.TYPE_MULT) { 422 | model.multParamFloat(param.id, param.value, weight); 423 | } 424 | else if(param.type == L2DExpressionMotion.TYPE_SET) { 425 | model.setParamFloat(param.id, param.value, weight); 426 | } 427 | } 428 | } 429 | 430 | //============================================================ 431 | //============================================================ 432 | // class L2DExpressionParam 433 | //============================================================ 434 | //============================================================ 435 | function L2DExpressionParam() 436 | { 437 | this.id = ""; 438 | this.type = -1; 439 | this.value = null; 440 | } 441 | 442 | /** 443 | * 444 | * You can modify and use this source freely 445 | * only for the development of application related Live2D. 446 | * 447 | * (c) Live2D Inc. All rights reserved. 448 | */ 449 | 450 | //============================================================ 451 | //============================================================ 452 | // class L2DEyeBlink 453 | //============================================================ 454 | //============================================================ 455 | function L2DEyeBlink() 456 | { 457 | this.nextBlinkTime = null /* TODO NOT INIT */; // 458 | this.stateStartTime = null /* TODO NOT INIT */; // 459 | this.blinkIntervalMsec = null /* TODO NOT INIT */; // 460 | this.eyeState = EYE_STATE.STATE_FIRST; 461 | this.blinkIntervalMsec = 4000; 462 | this.closingMotionMsec = 100; 463 | this.closedMotionMsec = 50; 464 | this.openingMotionMsec = 150; 465 | this.closeIfZero = true; 466 | this.eyeID_L = "PARAM_EYE_L_OPEN"; 467 | this.eyeID_R = "PARAM_EYE_R_OPEN"; 468 | } 469 | 470 | //============================================================ 471 | // L2DEyeBlink # calcNextBlink() 472 | //============================================================ 473 | L2DEyeBlink.prototype.calcNextBlink = function() 474 | { 475 | var time /*long*/ = UtSystem.getUserTimeMSec(); 476 | var r /*Number*/ = Math.random(); 477 | return /*(long)*/ (time + r * (2 * this.blinkIntervalMsec - 1)); 478 | } 479 | 480 | //============================================================ 481 | // L2DEyeBlink # setInterval() 482 | //============================================================ 483 | L2DEyeBlink.prototype.setInterval = function(blinkIntervalMsec /*int*/) 484 | { 485 | this.blinkIntervalMsec = blinkIntervalMsec; 486 | } 487 | 488 | //============================================================ 489 | // L2DEyeBlink # setEyeMotion() 490 | //============================================================ 491 | L2DEyeBlink.prototype.setEyeMotion = function(closingMotionMsec/*int*/ , closedMotionMsec/*int*/ , openingMotionMsec/*int*/) 492 | { 493 | this.closingMotionMsec = closingMotionMsec; 494 | this.closedMotionMsec = closedMotionMsec; 495 | this.openingMotionMsec = openingMotionMsec; 496 | } 497 | 498 | //============================================================ 499 | // L2DEyeBlink # updateParam() 500 | //============================================================ 501 | L2DEyeBlink.prototype.updateParam = function(model/*ALive2DModel*/) 502 | { 503 | var time /*:long*/ = UtSystem.getUserTimeMSec(); 504 | var eyeParamValue /*:Number*/; 505 | var t /*:Number*/ = 0; 506 | switch(this.eyeState){ 507 | case EYE_STATE.STATE_CLOSING: 508 | t = (time - this.stateStartTime) / this.closingMotionMsec; 509 | if(t >= 1) { 510 | t = 1; 511 | this.eyeState = EYE_STATE.STATE_CLOSED; 512 | this.stateStartTime = time; 513 | } 514 | eyeParamValue = 1 - t; 515 | break; 516 | case EYE_STATE.STATE_CLOSED: 517 | t = (time - this.stateStartTime) / this.closedMotionMsec; 518 | if(t >= 1) { 519 | this.eyeState = EYE_STATE.STATE_OPENING; 520 | this.stateStartTime = time; 521 | } 522 | eyeParamValue = 0; 523 | break; 524 | case EYE_STATE.STATE_OPENING: 525 | t = (time - this.stateStartTime) / this.openingMotionMsec; 526 | if(t >= 1) { 527 | t = 1; 528 | this.eyeState = EYE_STATE.STATE_INTERVAL; 529 | this.nextBlinkTime = this.calcNextBlink(); 530 | } 531 | eyeParamValue = t; 532 | break; 533 | case EYE_STATE.STATE_INTERVAL: 534 | if(this.nextBlinkTime < time) { 535 | this.eyeState = EYE_STATE.STATE_CLOSING; 536 | this.stateStartTime = time; 537 | } 538 | eyeParamValue = 1; 539 | break; 540 | case EYE_STATE.STATE_FIRST: 541 | default: 542 | this.eyeState = EYE_STATE.STATE_INTERVAL; 543 | this.nextBlinkTime = this.calcNextBlink(); 544 | eyeParamValue = 1; 545 | break; 546 | } 547 | if(!this.closeIfZero) eyeParamValue = -eyeParamValue; 548 | model.setParamFloat(this.eyeID_L , eyeParamValue); 549 | model.setParamFloat(this.eyeID_R , eyeParamValue); 550 | } 551 | 552 | //== enum EYE_STATE == 553 | var EYE_STATE = function(){}; 554 | 555 | EYE_STATE.STATE_FIRST = "STATE_FIRST" 556 | EYE_STATE.STATE_INTERVAL = "STATE_INTERVAL" 557 | EYE_STATE.STATE_CLOSING = "STATE_CLOSING" 558 | EYE_STATE.STATE_CLOSED = "STATE_CLOSED" 559 | EYE_STATE.STATE_OPENING = "STATE_OPENING" 560 | /** 561 | * 562 | * You can modify and use this source freely 563 | * only for the development of application related Live2D. 564 | * 565 | * (c) Live2D Inc. All rights reserved. 566 | */ 567 | 568 | //============================================================ 569 | //============================================================ 570 | // class L2DMatrix44 571 | //============================================================ 572 | //============================================================ 573 | function L2DMatrix44() 574 | { 575 | this.tr = new Float32Array(16); // 576 | this.identity(); 577 | } 578 | 579 | //============================================================ 580 | // static L2DMatrix44.mul() 581 | //============================================================ 582 | L2DMatrix44.mul = function( a/*float[]*/, b/*float[]*/, dst/*float[]*/ ) 583 | { 584 | var c = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; 585 | var n = 4; 586 | var i, j, k; 587 | for( i = 0; i < n; i++ ) { 588 | for( j = 0; j < n; j++ ) { 589 | for( k = 0; k < n; k++ ) { 590 | c[i + j * 4] += a[i + k * 4] * b[k + j * 4]; 591 | } 592 | } 593 | } 594 | for( i = 0; i < 16; i++ ) { 595 | dst[i] = c[i]; 596 | } 597 | } 598 | 599 | //============================================================ 600 | // L2DMatrix44 # identity() 601 | //============================================================ 602 | L2DMatrix44.prototype.identity = function() 603 | { 604 | for( var i/*:int*/ = 0; i < 16; i++ ) 605 | this.tr[i] = ( ( i % 5 ) == 0 ) ? 1 : 0; 606 | } 607 | 608 | //============================================================ 609 | // L2DMatrix44 # getArray() 610 | //============================================================ 611 | L2DMatrix44.prototype.getArray = function() 612 | { 613 | return this.tr; 614 | } 615 | 616 | //============================================================ 617 | // L2DMatrix44 # getCopyMatrix() 618 | //============================================================ 619 | L2DMatrix44.prototype.getCopyMatrix = function() 620 | { 621 | return new Float32Array(this.tr); // this.tr.clone(); 622 | } 623 | 624 | //============================================================ 625 | // L2DMatrix44 # setMatrix() 626 | //============================================================ 627 | L2DMatrix44.prototype.setMatrix = function( tr/*float[]*/ ) 628 | { 629 | if( this.tr == null || this.tr.length != this.tr.length ) return ; 630 | for( var i/*:int*/ = 0; i < 16; i++ ) this.tr[i] = tr[i]; 631 | } 632 | 633 | //============================================================ 634 | // L2DMatrix44 # getScaleX() 635 | //============================================================ 636 | L2DMatrix44.prototype.getScaleX = function() 637 | { 638 | return this.tr[0]; 639 | } 640 | 641 | //============================================================ 642 | // L2DMatrix44 # getScaleY() 643 | //============================================================ 644 | L2DMatrix44.prototype.getScaleY = function() 645 | { 646 | return this.tr[5]; 647 | } 648 | 649 | //============================================================ 650 | // L2DMatrix44 # transformX() 651 | //============================================================ 652 | L2DMatrix44.prototype.transformX = function( src/*float*/ ) 653 | { 654 | return this.tr[0] * src + this.tr[12]; 655 | } 656 | 657 | //============================================================ 658 | // L2DMatrix44 # transformY() 659 | //============================================================ 660 | L2DMatrix44.prototype.transformY = function( src/*float*/ ) 661 | { 662 | return this.tr[5] * src + this.tr[13]; 663 | } 664 | 665 | //============================================================ 666 | // L2DMatrix44 # invertTransformX() 667 | //============================================================ 668 | L2DMatrix44.prototype.invertTransformX = function( src/*float*/ ) 669 | { 670 | return ( src - this.tr[12] ) / this.tr[0]; 671 | } 672 | 673 | //============================================================ 674 | // L2DMatrix44 # invertTransformY() 675 | //============================================================ 676 | L2DMatrix44.prototype.invertTransformY = function( src/*float*/ ) 677 | { 678 | return ( src - this.tr[13] ) / this.tr[5]; 679 | } 680 | 681 | //============================================================ 682 | // L2DMatrix44 # multTranslate() 683 | //============================================================ 684 | L2DMatrix44.prototype.multTranslate = function( shiftX/*float*/, shiftY/*float*/ ) 685 | { 686 | var tr1 = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, shiftX, shiftY, 0, 1 ]; 687 | L2DMatrix44.mul(tr1, this.tr, this.tr); 688 | } 689 | 690 | //============================================================ 691 | // L2DMatrix44 # translate() 692 | //============================================================ 693 | L2DMatrix44.prototype.translate = function( x/*float*/, y/*float*/ ) 694 | { 695 | this.tr[12] = x; 696 | this.tr[13] = y; 697 | } 698 | 699 | //============================================================ 700 | // L2DMatrix44 # translateX() 701 | //============================================================ 702 | L2DMatrix44.prototype.translateX = function( x/*float*/ ) 703 | { 704 | this.tr[12] = x; 705 | } 706 | 707 | //============================================================ 708 | // L2DMatrix44 # translateY() 709 | //============================================================ 710 | L2DMatrix44.prototype.translateY = function( y/*float*/ ) 711 | { 712 | this.tr[13] = y; 713 | } 714 | 715 | //============================================================ 716 | // L2DMatrix44 # multScale() 717 | //============================================================ 718 | L2DMatrix44.prototype.multScale = function( scaleX/*float*/, scaleY/*float*/ ) 719 | { 720 | var tr1 = [scaleX, 0, 0, 0, 0, scaleY, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 721 | L2DMatrix44.mul(tr1, this.tr, this.tr); 722 | } 723 | 724 | //============================================================ 725 | // L2DMatrix44 # scale() 726 | //============================================================ 727 | L2DMatrix44.prototype.scale = function( scaleX/*float*/, scaleY/*float*/ ) 728 | { 729 | this.tr[0] = scaleX; 730 | this.tr[5] = scaleY; 731 | } 732 | /** 733 | * 734 | * You can modify and use this source freely 735 | * only for the development of application related Live2D. 736 | * 737 | * (c) Live2D Inc. All rights reserved. 738 | */ 739 | 740 | //============================================================ 741 | //============================================================ 742 | // class L2DModelMatrix extends L2DMatrix44 743 | //============================================================ 744 | //============================================================ 745 | function L2DModelMatrix(w/*float*/, h/*float*/){ 746 | L2DMatrix44.prototype.constructor.call(this); 747 | this.width = w; 748 | this.height = h; 749 | } 750 | 751 | //L2DModelMatrix extends L2DMatrix44 752 | L2DModelMatrix.prototype = new L2DMatrix44(); 753 | 754 | //============================================================ 755 | // L2DModelMatrix # setPosition() 756 | //============================================================ 757 | L2DModelMatrix.prototype.setPosition = function(x/*float*/, y/*float*/) 758 | { 759 | this.translate(x, y); 760 | } 761 | 762 | //============================================================ 763 | // L2DModelMatrix # setCenterPosition() 764 | //============================================================ 765 | L2DModelMatrix.prototype.setCenterPosition = function(x/*float*/, y/*float*/) 766 | { 767 | var w = this.width * this.getScaleX(); 768 | var h = this.height * this.getScaleY(); 769 | this.translate(x - w / 2, y - h / 2); 770 | } 771 | 772 | //============================================================ 773 | // L2DModelMatrix # top() 774 | //============================================================ 775 | L2DModelMatrix.prototype.top = function(y/*float*/) 776 | { 777 | this.setY(y); 778 | } 779 | 780 | //============================================================ 781 | // L2DModelMatrix # bottom() 782 | //============================================================ 783 | L2DModelMatrix.prototype.bottom = function(y/*float*/) 784 | { 785 | var h = this.height * this.getScaleY(); 786 | this.translateY(y - h); 787 | } 788 | 789 | //============================================================ 790 | // L2DModelMatrix # left() 791 | //============================================================ 792 | L2DModelMatrix.prototype.left = function(x/*float*/) 793 | { 794 | this.setX(x); 795 | } 796 | 797 | //============================================================ 798 | // L2DModelMatrix # right() 799 | //============================================================ 800 | L2DModelMatrix.prototype.right = function(x/*float*/) 801 | { 802 | var w = this.width * this.getScaleX(); 803 | this.translateX(x - w); 804 | } 805 | 806 | //============================================================ 807 | // L2DModelMatrix # centerX() 808 | //============================================================ 809 | L2DModelMatrix.prototype.centerX = function(x/*float*/) 810 | { 811 | var w = this.width * this.getScaleX(); 812 | this.translateX(x - w / 2); 813 | } 814 | 815 | //============================================================ 816 | // L2DModelMatrix # centerY() 817 | //============================================================ 818 | L2DModelMatrix.prototype.centerY = function(y/*float*/) 819 | { 820 | var h = this.height * this.getScaleY(); 821 | this.translateY(y - h / 2); 822 | } 823 | 824 | //============================================================ 825 | // L2DModelMatrix # setX() 826 | //============================================================ 827 | L2DModelMatrix.prototype.setX = function(x/*float*/) 828 | { 829 | this.translateX(x); 830 | } 831 | 832 | //============================================================ 833 | // L2DModelMatrix # setY() 834 | //============================================================ 835 | L2DModelMatrix.prototype.setY = function(y/*float*/) 836 | { 837 | this.translateY(y); 838 | } 839 | 840 | //============================================================ 841 | // L2DModelMatrix # setHeight() 842 | //============================================================ 843 | L2DModelMatrix.prototype.setHeight = function(h/*float*/) 844 | { 845 | var scaleX = h / this.height; 846 | var scaleY = -scaleX; 847 | this.scale(scaleX, scaleY); 848 | } 849 | 850 | //============================================================ 851 | // L2DModelMatrix # setWidth() 852 | //============================================================ 853 | L2DModelMatrix.prototype.setWidth = function(w/*float*/) 854 | { 855 | var scaleX = w / this.width; 856 | var scaleY = -scaleX; 857 | this.scale(scaleX, scaleY); 858 | } 859 | 860 | /** 861 | * 862 | * You can modify and use this source freely 863 | * only for the development of application related Live2D. 864 | * 865 | * (c) Live2D Inc. All rights reserved. 866 | */ 867 | 868 | //============================================================ 869 | //============================================================ 870 | // class L2DMotionManager extends MotionQueueManager 871 | //============================================================ 872 | //============================================================ 873 | function L2DMotionManager() 874 | { 875 | MotionQueueManager.prototype.constructor.call(this); 876 | this.currentPriority = null; 877 | this.reservePriority = null; 878 | 879 | this.super = MotionQueueManager.prototype; 880 | } 881 | 882 | // MotionQueueManagerクラスを継承 883 | L2DMotionManager.prototype = new MotionQueueManager(); 884 | 885 | //============================================================ 886 | // L2DMotionManager # getCurrentPriority() 887 | //============================================================ 888 | L2DMotionManager.prototype.getCurrentPriority = function() 889 | { 890 | return this.currentPriority; 891 | } 892 | 893 | //============================================================ 894 | // L2DMotionManager # getReservePriority() 895 | //============================================================ 896 | L2DMotionManager.prototype.getReservePriority = function() 897 | { 898 | return this.reservePriority; 899 | } 900 | 901 | //============================================================ 902 | // L2DMotionManager # reserveMotion() 903 | //============================================================ 904 | L2DMotionManager.prototype.reserveMotion = function(priority/*int*/) 905 | { 906 | if(this.reservePriority >= priority) { 907 | return false; 908 | } 909 | if(this.currentPriority >= priority) { 910 | return false; 911 | } 912 | 913 | this.reservePriority = priority; 914 | 915 | return true; 916 | } 917 | 918 | //============================================================ 919 | // L2DMotionManager # setReservePriority() 920 | //============================================================ 921 | L2DMotionManager.prototype.setReservePriority = function(val/*int*/) 922 | { 923 | this.reservePriority = val; 924 | } 925 | 926 | //============================================================ 927 | // L2DMotionManager # updateParam() 928 | //============================================================ 929 | L2DMotionManager.prototype.updateParam = function(model/*ALive2DModel*/) 930 | { 931 | var updated = MotionQueueManager.prototype.updateParam.call(this, model); 932 | 933 | if(this.isFinished()) { 934 | this.currentPriority = 0; 935 | } 936 | 937 | return updated; 938 | } 939 | 940 | //============================================================ 941 | // L2DMotionManager # startMotionPrio() 942 | //============================================================ 943 | L2DMotionManager.prototype.startMotionPrio = function(motion/*AMotion*/, priority/*int*/) 944 | { 945 | if(priority == this.reservePriority) { 946 | this.reservePriority = 0; 947 | } 948 | this.currentPriority = priority; 949 | return this.startMotion(motion, false); 950 | } 951 | 952 | /** 953 | * 954 | * You can modify and use this source freely 955 | * only for the development of application related Live2D. 956 | * 957 | * (c) Live2D Inc. All rights reserved. 958 | */ 959 | 960 | //============================================================ 961 | //============================================================ 962 | // class L2DPhysics 963 | //============================================================ 964 | //============================================================ 965 | function L2DPhysics() 966 | { 967 | this.physicsList = new Array(); //ArrayList 968 | this.startTimeMSec = UtSystem.getUserTimeMSec(); 969 | } 970 | 971 | //============================================================ 972 | // static L2DPhysics.load() 973 | //============================================================ 974 | L2DPhysics.load = function(buf /*byte[]*/ ) 975 | { 976 | var ret = new L2DPhysics(); //L2DPhysicsL2DPhysics 977 | var pm = Live2DFramework.getPlatformManager(); 978 | var json = pm.jsonParseFromBytes(buf); 979 | var params = json.physics_hair; 980 | var paramNum = params.length; 981 | for(var i = 0; i < paramNum; i++) { 982 | var param = params[i]; //Value 983 | var physics = new PhysicsHair(); //PhysicsHairPhysicsHair 984 | var setup = param.setup; //Value 985 | var length = parseFloat(setup.length); 986 | var resist = parseFloat(setup.regist); 987 | var mass = parseFloat(setup.mass); 988 | physics.setup(length, resist, mass); 989 | var srcList = param.src; //Value 990 | var srcNum = srcList.length; 991 | for(var j = 0; j < srcNum; j++) { 992 | var src = srcList[j]; //Value 993 | var id = src.id; //String 994 | var type = PhysicsHair.Src.SRC_TO_X; 995 | var typeStr = src.ptype; //String 996 | if(typeStr === "x") { 997 | type = PhysicsHair.Src.SRC_TO_X; 998 | } 999 | else if(typeStr === "y") { 1000 | type = PhysicsHair.Src.SRC_TO_Y; 1001 | } 1002 | else if(typeStr === "angle") { 1003 | type = PhysicsHair.Src.SRC_TO_G_ANGLE; 1004 | } 1005 | else { 1006 | UtDebug.error("live2d", "Invalid parameter:PhysicsHair.Src"); 1007 | } 1008 | var scale = parseFloat(src.scale); 1009 | var weight = parseFloat(src.weight); 1010 | physics.addSrcParam(type, id, scale, weight); 1011 | } 1012 | var targetList = param.targets; //Value 1013 | var targetNum = targetList.length; 1014 | for(var j = 0; j < targetNum; j++) { 1015 | var target = targetList[j]; //Value 1016 | var id = target.id; //String 1017 | var type = PhysicsHair.Target.TARGET_FROM_ANGLE; 1018 | var typeStr = target.ptype; //String 1019 | if(typeStr === "angle") { 1020 | type = PhysicsHair.Target.TARGET_FROM_ANGLE; 1021 | } 1022 | else if(typeStr === "angle_v") { 1023 | type = PhysicsHair.Target.TARGET_FROM_ANGLE_V; 1024 | } 1025 | else { 1026 | UtDebug.error("live2d", "Invalid parameter:PhysicsHair.Target"); 1027 | } 1028 | var scale = parseFloat(target.scale); 1029 | var weight = parseFloat(target.weight); 1030 | physics.addTargetParam(type, id, scale, weight); 1031 | } 1032 | ret.physicsList.push(physics); 1033 | } 1034 | return ret; 1035 | } 1036 | 1037 | //============================================================ 1038 | // L2DPhysics # updateParam() 1039 | //============================================================ 1040 | L2DPhysics.prototype.updateParam = function(model/*ALive2DModel*/) 1041 | { 1042 | var timeMSec = UtSystem.getUserTimeMSec() - this.startTimeMSec; 1043 | for(var i = 0; i < this.physicsList.length; i++) { 1044 | this.physicsList[i].update(model, timeMSec); 1045 | } 1046 | } 1047 | 1048 | /** 1049 | * 1050 | * You can modify and use this source freely 1051 | * only for the development of application related Live2D. 1052 | * 1053 | * (c) Live2D Inc. All rights reserved. 1054 | */ 1055 | 1056 | //============================================================ 1057 | //============================================================ 1058 | // class L2DPose 1059 | //============================================================ 1060 | //============================================================ 1061 | function L2DPose() 1062 | { 1063 | this.lastTime = 0; 1064 | this.lastModel = null; //ALive2DModel 1065 | this.partsGroups = new Array(); //ArrayList 1066 | } 1067 | 1068 | 1069 | //============================================================ 1070 | // static L2DPose.load() 1071 | //============================================================ 1072 | L2DPose.load = function(buf/*byte[]*/) 1073 | { 1074 | var ret = new L2DPose(); //L2DPose 1075 | var pm = Live2DFramework.getPlatformManager(); 1076 | var json = pm.jsonParseFromBytes(buf); 1077 | var poseListInfo = json.parts_visible; //Value 1078 | var poseNum = poseListInfo.length; 1079 | for(var i_pose = 0; i_pose < poseNum; i_pose++) { 1080 | var poseInfo = poseListInfo[i_pose]; //Value 1081 | var idListInfo = poseInfo.group; //Value 1082 | var idNum = idListInfo.length; 1083 | var partsGroup/*L2DPartsParam*/ = new Array(); 1084 | for(var i_group = 0; i_group < idNum; i_group++) { 1085 | var partsInfo = idListInfo[i_group]; //Value 1086 | var parts = new L2DPartsParam(partsInfo.id); //L2DPartsParamL2DPartsParam 1087 | partsGroup[i_group] = parts; 1088 | if(partsInfo.link == null) continue; 1089 | var linkListInfo = partsInfo.link; //Value 1090 | var linkNum = linkListInfo.length; 1091 | parts.link = new Array(); //ArrayList 1092 | for(var i_link = 0; i_link < linkNum; i_link++) { 1093 | var linkParts = new L2DPartsParam(linkListInfo[i_link]); //L2DPartsParamL2DPartsParam 1094 | parts.link.push(linkParts); 1095 | } 1096 | } 1097 | ret.partsGroups.push(partsGroup); 1098 | } 1099 | 1100 | return ret; 1101 | } 1102 | 1103 | //============================================================ 1104 | // L2DPose # updateParam() 1105 | //============================================================ 1106 | L2DPose.prototype.updateParam = function(model/*ALive2DModel*/) 1107 | { 1108 | if(model == null) return ; 1109 | 1110 | if(!(model == this.lastModel)) { 1111 | this.initParam(model); 1112 | } 1113 | this.lastModel = model; 1114 | 1115 | var curTime = UtSystem.getUserTimeMSec(); 1116 | var deltaTimeSec = ((this.lastTime == 0) ? 0 : (curTime - this.lastTime) / 1000.0); 1117 | this.lastTime = curTime; 1118 | if(deltaTimeSec < 0) deltaTimeSec = 0; 1119 | for(var i = 0; i < this.partsGroups.length; i++) { 1120 | this.normalizePartsOpacityGroup(model, this.partsGroups[i], deltaTimeSec); 1121 | this.copyOpacityOtherParts(model, this.partsGroups[i]); 1122 | } 1123 | } 1124 | 1125 | //============================================================ 1126 | // L2DPose # initParam() 1127 | //============================================================ 1128 | L2DPose.prototype.initParam = function(model/*ALive2DModel*/) 1129 | { 1130 | if(model == null) return ; 1131 | for(var i = 0; i < this.partsGroups.length; i++) { 1132 | var partsGroup = this.partsGroups[i]; //L2DPartsParam 1133 | for(var j = 0; j < partsGroup.length; j++) { 1134 | partsGroup[j].initIndex(model); 1135 | var partsIndex = partsGroup[j].partsIndex; 1136 | var paramIndex = partsGroup[j].paramIndex; 1137 | if(partsIndex < 0) continue; 1138 | var v/*:Boolean*/ = (model.getParamFloat(paramIndex) != 0); 1139 | model.setPartsOpacity(partsIndex, (v ? 1.0 : 0.0)); 1140 | model.setParamFloat(paramIndex, (v ? 1.0 : 0.0)); 1141 | if(partsGroup[j].link == null) continue; 1142 | for(var k = 0; k < partsGroup[j].link.length; k++) { 1143 | partsGroup[j].link[k].initIndex(model); 1144 | } 1145 | } 1146 | } 1147 | } 1148 | 1149 | //============================================================ 1150 | // L2DPose # normalizePartsOpacityGroup() 1151 | //============================================================ 1152 | L2DPose.prototype.normalizePartsOpacityGroup = function(model/*ALive2DModel*/, partsGroup/*L2DPartsParam[]*/, deltaTimeSec/*float*/) 1153 | { 1154 | var visibleParts = -1; 1155 | var visibleOpacity = 1.0; 1156 | var CLEAR_TIME_SEC = 0.5; 1157 | var phi = 0.5; 1158 | var maxBackOpacity = 0.15; 1159 | for(var i = 0; i < partsGroup.length; i++) { 1160 | var partsIndex = partsGroup[i].partsIndex; 1161 | var paramIndex = partsGroup[i].paramIndex; 1162 | if(partsIndex < 0) continue;if(model.getParamFloat(paramIndex) != 0) { 1163 | if(visibleParts >= 0) { 1164 | break; 1165 | } 1166 | visibleParts = i; 1167 | visibleOpacity = model.getPartsOpacity(partsIndex); 1168 | visibleOpacity += deltaTimeSec / CLEAR_TIME_SEC; 1169 | if(visibleOpacity > 1) { 1170 | visibleOpacity = 1; 1171 | } 1172 | } 1173 | } 1174 | if(visibleParts < 0) { 1175 | visibleParts = 0; 1176 | visibleOpacity = 1; 1177 | } 1178 | for(var i = 0; i < partsGroup.length; i++) { 1179 | var partsIndex = partsGroup[i].partsIndex; 1180 | if(partsIndex < 0) continue;if(visibleParts == i) { 1181 | model.setPartsOpacity(partsIndex, visibleOpacity); 1182 | } 1183 | else { 1184 | var opacity = model.getPartsOpacity(partsIndex); 1185 | var a1; 1186 | if(visibleOpacity < phi) { 1187 | a1 = visibleOpacity * (phi - 1) / phi + 1; 1188 | } 1189 | else { 1190 | a1 = (1 - visibleOpacity) * phi / (1 - phi); 1191 | } 1192 | var backOp = (1 - a1) * (1 - visibleOpacity); 1193 | if(backOp > maxBackOpacity) { 1194 | a1 = 1 - maxBackOpacity / (1 - visibleOpacity); 1195 | } 1196 | if(opacity > a1) { 1197 | opacity = a1; 1198 | } 1199 | model.setPartsOpacity(partsIndex, opacity); 1200 | } 1201 | } 1202 | } 1203 | 1204 | //============================================================ 1205 | // L2DPose # copyOpacityOtherParts() 1206 | //============================================================ 1207 | L2DPose.prototype.copyOpacityOtherParts = function(model/*ALive2DModel*/, partsGroup/*L2DPartsParam[]*/) 1208 | { 1209 | for(var i_group = 0; i_group < partsGroup.length; i_group++) { 1210 | var partsParam = partsGroup[i_group]; //L2DPartsParam 1211 | if(partsParam.link == null) continue; 1212 | if(partsParam.partsIndex < 0) continue; 1213 | var opacity = model.getPartsOpacity(partsParam.partsIndex); 1214 | for(var i_link = 0; i_link < partsParam.link.length; i_link++) { 1215 | var linkParts = partsParam.link[i_link]; //L2DPartsParam 1216 | if(linkParts.partsIndex < 0) continue; 1217 | model.setPartsOpacity(linkParts.partsIndex, opacity); 1218 | } 1219 | } 1220 | } 1221 | 1222 | //============================================================ 1223 | //============================================================ 1224 | // class L2DPartsParam 1225 | //============================================================ 1226 | //============================================================ 1227 | function L2DPartsParam(id/*String*/){ 1228 | this.paramIndex = -1; 1229 | this.partsIndex = -1; 1230 | this.link = null; // ArrayList 1231 | this.id = id; 1232 | } 1233 | 1234 | //============================================================ 1235 | // L2DPartsParam # initIndex() 1236 | //============================================================ 1237 | L2DPartsParam.prototype.initIndex = function(model/*ALive2DModel*/) 1238 | { 1239 | this.paramIndex = model.getParamIndex("VISIBLE:" + this.id); 1240 | this.partsIndex = model.getPartsDataIndex(PartsDataID.getID(this.id)); 1241 | model.setParamFloat(this.paramIndex, 1); 1242 | } 1243 | /** 1244 | * 1245 | * You can modify and use this source freely 1246 | * only for the development of application related Live2D. 1247 | * 1248 | * (c) Live2D Inc. All rights reserved. 1249 | */ 1250 | 1251 | //============================================================ 1252 | //============================================================ 1253 | // class L2DTargetPoint 1254 | //============================================================ 1255 | //============================================================ 1256 | function L2DTargetPoint() 1257 | { 1258 | this.EPSILON = 0.01; // 変化の最小値(この値以下は無視される) 1259 | this.faceTargetX = 0; 1260 | this.faceTargetY = 0; 1261 | this.faceX = 0; 1262 | this.faceY = 0; 1263 | this.faceVX = 0; 1264 | this.faceVY = 0; 1265 | this.lastTimeSec = 0; 1266 | } 1267 | 1268 | //============================================================ 1269 | L2DTargetPoint.FRAME_RATE = 30; 1270 | 1271 | //============================================================ 1272 | // L2DTargetPoint # set() 1273 | //============================================================ 1274 | L2DTargetPoint.prototype.setPoint = function(x/*float*/, y/*float*/) 1275 | { 1276 | this.faceTargetX = x; 1277 | this.faceTargetY = y; 1278 | } 1279 | 1280 | //============================================================ 1281 | // L2DTargetPoint # getX() 1282 | //============================================================ 1283 | L2DTargetPoint.prototype.getX = function() 1284 | { 1285 | return this.faceX; 1286 | } 1287 | 1288 | //============================================================ 1289 | // L2DTargetPoint # getY() 1290 | //============================================================ 1291 | L2DTargetPoint.prototype.getY = function() 1292 | { 1293 | return this.faceY; 1294 | } 1295 | 1296 | //============================================================ 1297 | // L2DTargetPoint # update() 1298 | //============================================================ 1299 | L2DTargetPoint.prototype.update = function() 1300 | { 1301 | var TIME_TO_MAX_SPEED = 0.15; 1302 | var FACE_PARAM_MAX_V = 40.0 / 7.5; 1303 | var MAX_V = FACE_PARAM_MAX_V / L2DTargetPoint.FRAME_RATE; 1304 | if(this.lastTimeSec == 0) { 1305 | this.lastTimeSec = UtSystem.getUserTimeMSec(); 1306 | return; 1307 | } 1308 | var curTimeSec = UtSystem.getUserTimeMSec(); 1309 | var deltaTimeWeight = (curTimeSec - this.lastTimeSec) * L2DTargetPoint.FRAME_RATE / 1000.0; 1310 | this.lastTimeSec = curTimeSec; 1311 | var FRAME_TO_MAX_SPEED = TIME_TO_MAX_SPEED * L2DTargetPoint.FRAME_RATE; 1312 | var MAX_A = deltaTimeWeight * MAX_V / FRAME_TO_MAX_SPEED; 1313 | var dx = (this.faceTargetX - this.faceX); 1314 | var dy = (this.faceTargetY - this.faceY); 1315 | // if(dx == 0 && dy == 0) return; 1316 | if( Math.abs(dx) <= this.EPSILON && Math.abs(dy) <= this.EPSILON ) return; 1317 | var d = Math.sqrt(dx * dx + dy * dy); 1318 | var vx = MAX_V * dx / d; 1319 | var vy = MAX_V * dy / d; 1320 | var ax = vx - this.faceVX; 1321 | var ay = vy - this.faceVY; 1322 | var a = Math.sqrt(ax * ax + ay * ay); 1323 | if(a < -MAX_A || a > MAX_A) { 1324 | ax *= MAX_A / a; 1325 | ay *= MAX_A / a; 1326 | a = MAX_A; 1327 | } 1328 | this.faceVX += ax; 1329 | this.faceVY += ay; 1330 | { 1331 | var max_v = 0.5 * ( Math.sqrt(MAX_A * MAX_A + 16 * MAX_A * d - 8 * MAX_A * d) - MAX_A); 1332 | var cur_v = Math.sqrt(this.faceVX * this.faceVX + this.faceVY * this.faceVY); 1333 | if(cur_v > max_v) { 1334 | this.faceVX *= max_v / cur_v; 1335 | this.faceVY *= max_v / cur_v; 1336 | } 1337 | } 1338 | this.faceX += this.faceVX; 1339 | this.faceY += this.faceVY; 1340 | } 1341 | /** 1342 | * 1343 | * You can modify and use this source freely 1344 | * only for the development of application related Live2D. 1345 | * 1346 | * (c) Live2D Inc. All rights reserved. 1347 | */ 1348 | 1349 | //============================================================ 1350 | //============================================================ 1351 | // class L2DViewMatrix extends L2DMatrix44 1352 | //============================================================ 1353 | //============================================================ 1354 | function L2DViewMatrix() 1355 | { 1356 | L2DMatrix44.prototype.constructor.call(this); 1357 | this.screenLeft = null; 1358 | this.screenRight = null; 1359 | this.screenTop = null; 1360 | this.screenBottom = null; 1361 | this.maxLeft = null; 1362 | this.maxRight = null; 1363 | this.maxTop = null; 1364 | this.maxBottom = null; 1365 | this.max = Number.MAX_VALUE; 1366 | this.min = 0; 1367 | } 1368 | 1369 | L2DViewMatrix.prototype = new L2DMatrix44(); //L2DViewMatrix extends L2DMatrix44 1370 | 1371 | //============================================================ 1372 | // L2DViewMatrix # getMaxScale() 1373 | //============================================================ 1374 | L2DViewMatrix.prototype.getMaxScale = function() 1375 | { 1376 | return this.max; 1377 | } 1378 | 1379 | //============================================================ 1380 | // L2DViewMatrix # getMinScale() 1381 | //============================================================ 1382 | L2DViewMatrix.prototype.getMinScale = function() 1383 | { 1384 | return this.min; 1385 | } 1386 | 1387 | //============================================================ 1388 | // L2DViewMatrix # setMaxScale() 1389 | //============================================================ 1390 | L2DViewMatrix.prototype.setMaxScale = function(v/*float*/) 1391 | { 1392 | this.max = v; 1393 | } 1394 | 1395 | //============================================================ 1396 | // L2DViewMatrix # setMinScale() 1397 | //============================================================ 1398 | L2DViewMatrix.prototype.setMinScale = function(v/*float*/) 1399 | { 1400 | this.min = v; 1401 | } 1402 | 1403 | //============================================================ 1404 | // L2DViewMatrix # isMaxScale() 1405 | //============================================================ 1406 | L2DViewMatrix.prototype.isMaxScale = function() 1407 | { 1408 | return this.getScaleX() == this.max; 1409 | } 1410 | 1411 | //============================================================ 1412 | // L2DViewMatrix # isMinScale() 1413 | //============================================================ 1414 | L2DViewMatrix.prototype.isMinScale = function() 1415 | { 1416 | return this.getScaleX() == this.min; 1417 | } 1418 | 1419 | //============================================================ 1420 | // L2DViewMatrix # adjustTranslate() 1421 | //============================================================ 1422 | L2DViewMatrix.prototype.adjustTranslate = function(shiftX/*float*/, shiftY/*float*/) 1423 | { 1424 | if(this.tr[0] * this.maxLeft + (this.tr[12] + shiftX) > this.screenLeft) 1425 | shiftX = this.screenLeft - this.tr[0] * this.maxLeft - this.tr[12]; 1426 | if(this.tr[0] * this.maxRight + (this.tr[12] + shiftX) < this.screenRight) 1427 | shiftX = this.screenRight - this.tr[0] * this.maxRight - this.tr[12]; 1428 | if(this.tr[5] * this.maxTop + (this.tr[13] + shiftY) < this.screenTop) 1429 | shiftY = this.screenTop - this.tr[5] * this.maxTop - this.tr[13]; 1430 | if(this.tr[5] * this.maxBottom + (this.tr[13] + shiftY) > this.screenBottom) 1431 | shiftY = this.screenBottom - this.tr[5] * this.maxBottom - this.tr[13]; 1432 | 1433 | var tr1 = [1, 0, 0, 0, 1434 | 0, 1, 0, 0, 1435 | 0, 0, 1, 0, 1436 | shiftX, shiftY, 0, 1 ]; 1437 | L2DMatrix44.mul(tr1, this.tr, this.tr); 1438 | } 1439 | 1440 | //============================================================ 1441 | // L2DViewMatrix # adjustScale() 1442 | //============================================================ 1443 | L2DViewMatrix.prototype.adjustScale = function(cx/*float*/, cy/*float*/, scale/*float*/) 1444 | { 1445 | var targetScale = scale * this.tr[0]; 1446 | if(targetScale < this.min) { 1447 | if(this.tr[0] > 0) scale = this.min / this.tr[0]; 1448 | } 1449 | else if(targetScale > this.max) { 1450 | if(this.tr[0] > 0) scale = this.max / this.tr[0]; 1451 | } 1452 | var tr1 = [1, 0, 0, 0, 1453 | 0, 1, 0, 0, 1454 | 0, 0, 1, 0, 1455 | cx, cy, 0, 1]; 1456 | var tr2 = [scale, 0, 0, 0, 1457 | 0, scale, 0, 0, 1458 | 0, 0, 1, 0, 1459 | 0, 0, 0, 1 ]; 1460 | var tr3 = [1, 0, 0, 0, 1461 | 0, 1, 0, 0, 1462 | 0, 0, 1, 0, 1463 | -cx, -cy, 0, 1 ]; 1464 | L2DMatrix44.mul(tr3, this.tr, this.tr); 1465 | L2DMatrix44.mul(tr2, this.tr, this.tr); 1466 | L2DMatrix44.mul(tr1, this.tr, this.tr); 1467 | } 1468 | 1469 | //============================================================ 1470 | // L2DViewMatrix # setScreenRect() 1471 | //============================================================ 1472 | L2DViewMatrix.prototype.setScreenRect = function(left/*float*/, right/*float*/, bottom/*float*/, top/*float*/) 1473 | { 1474 | this.screenLeft = left; 1475 | this.screenRight = right; 1476 | this.screenTop = top; 1477 | this.screenBottom = bottom; 1478 | } 1479 | 1480 | //============================================================ 1481 | // L2DViewMatrix # setMaxScreenRect() 1482 | //============================================================ 1483 | L2DViewMatrix.prototype.setMaxScreenRect = function(left/*float*/, right/*float*/, bottom/*float*/, top/*float*/) 1484 | { 1485 | this.maxLeft = left; 1486 | this.maxRight = right; 1487 | this.maxTop = top; 1488 | this.maxBottom = bottom; 1489 | } 1490 | 1491 | //============================================================ 1492 | // L2DViewMatrix # getScreenLeft() 1493 | //============================================================ 1494 | L2DViewMatrix.prototype.getScreenLeft = function() 1495 | { 1496 | return this.screenLeft; 1497 | } 1498 | 1499 | //============================================================ 1500 | // L2DViewMatrix # getScreenRight() 1501 | //============================================================ 1502 | L2DViewMatrix.prototype.getScreenRight = function() 1503 | { 1504 | return this.screenRight; 1505 | } 1506 | 1507 | //============================================================ 1508 | // L2DViewMatrix # getScreenBottom() 1509 | //============================================================ 1510 | L2DViewMatrix.prototype.getScreenBottom = function() 1511 | { 1512 | return this.screenBottom; 1513 | } 1514 | 1515 | //============================================================ 1516 | // L2DViewMatrix # getScreenTop() 1517 | //============================================================ 1518 | L2DViewMatrix.prototype.getScreenTop = function() 1519 | { 1520 | return this.screenTop; 1521 | } 1522 | 1523 | //============================================================ 1524 | // L2DViewMatrix # getMaxLeft() 1525 | //============================================================ 1526 | L2DViewMatrix.prototype.getMaxLeft = function() 1527 | { 1528 | return this.maxLeft; 1529 | } 1530 | 1531 | //============================================================ 1532 | // L2DViewMatrix # getMaxRight() 1533 | //============================================================ 1534 | L2DViewMatrix.prototype.getMaxRight = function() 1535 | { 1536 | return this.maxRight; 1537 | } 1538 | 1539 | //============================================================ 1540 | // L2DViewMatrix # getMaxBottom() 1541 | //============================================================ 1542 | L2DViewMatrix.prototype.getMaxBottom = function() 1543 | { 1544 | return this.maxBottom; 1545 | } 1546 | 1547 | //============================================================ 1548 | // L2DViewMatrix # getMaxTop() 1549 | //============================================================ 1550 | L2DViewMatrix.prototype.getMaxTop = function() 1551 | { 1552 | return this.maxTop; 1553 | } 1554 | 1555 | /** 1556 | * 1557 | * You can modify and use this source freely 1558 | * only for the development of application related Live2D. 1559 | * 1560 | * (c) Live2D Inc. All rights reserved. 1561 | */ 1562 | 1563 | //============================================================ 1564 | //============================================================ 1565 | // class Live2DFramework 1566 | //============================================================ 1567 | //============================================================ 1568 | function Live2DFramework() 1569 | { 1570 | } 1571 | 1572 | //============================================================ 1573 | Live2DFramework.platformManager = null; 1574 | 1575 | //============================================================ 1576 | // static Live2DFramework.getPlatformManager() 1577 | //============================================================ 1578 | Live2DFramework.getPlatformManager = function() 1579 | { 1580 | return Live2DFramework.platformManager; 1581 | } 1582 | 1583 | //============================================================ 1584 | // static Live2DFramework.setPlatformManager() 1585 | //============================================================ 1586 | Live2DFramework.setPlatformManager = function( platformManager /*IPlatformManager*/ ) 1587 | { 1588 | Live2DFramework.platformManager = platformManager; 1589 | } --------------------------------------------------------------------------------