pixi-live2d
Version:
Display live2D model as a sprite in pixi.js.
532 lines (387 loc) • 16.8 kB
JavaScript
//============================================================
//============================================================
// class LAppModel extends L2DBaseModel
//============================================================
//============================================================
import { L2DBaseModel, L2DEyeBlink } from './Live2DFramework';
import MatrixStack from './MatrixStack';
import ModelSettingJson from './ModelSettingJson';
export default function LAppModel(options)
{
//L2DBaseModel.apply(this, arguments);
L2DBaseModel.prototype.constructor.call(this);
this.options = options;
this.randomMotion = this.options.randomMotion;
this.randomMotionGroup = null;
this.randomMotionPriority = null;
this.modelHomeDir = "";
this.modelSetting = null;
this.tmpMatrix = [];
this.audioElement = null;
this.audioContext = null;
this.audioAnalyser = null;
}
LAppModel.prototype = new L2DBaseModel();
LAppModel.prototype.load = function(gl, modelDefine, callback)
{
this.setUpdating(true);
this.setInitialized(false);
this.modelHomeDir = './';
this.modelSetting = new ModelSettingJson(modelDefine);
var thisRef = this;
// this.modelSetting.loadModelSetting(modelSettingPath, function(){
var path = thisRef.modelHomeDir + thisRef.modelSetting.getModelFile();
thisRef.loadModelData(path, function(model){
for (var i = 0; i < thisRef.modelSetting.getTextureNum(); i++)
{
var texPaths = thisRef.modelHomeDir +
thisRef.modelSetting.getTextureFile(i);
thisRef.loadTexture(gl, i, texPaths, function() {
if( thisRef.isTexLoaded ) {
if (thisRef.modelSetting.getExpressionNum() > 0)
{
thisRef.expressions = {};
for (var j = 0; j < thisRef.modelSetting.getExpressionNum(); j++)
{
var expName = thisRef.modelSetting.getExpressionName(j);
var expFilePath = thisRef.modelHomeDir +
thisRef.modelSetting.getExpressionFile(j);
thisRef.loadExpression(expName, expFilePath);
}
}
else
{
thisRef.expressionManager = null;
thisRef.expressions = {};
}
if (!thisRef.eyeBlink)
{
thisRef.eyeBlink = new L2DEyeBlink();
}
if (thisRef.modelSetting.getPhysicsFile())
{
thisRef.loadPhysics(thisRef.modelHomeDir +
thisRef.modelSetting.getPhysicsFile());
}
else
{
thisRef.physics = null;
}
if (thisRef.modelSetting.getPoseFile())
{
thisRef.loadPose(
thisRef.modelHomeDir +
thisRef.modelSetting.getPoseFile(),
function() {
thisRef.pose.updateParam(thisRef.live2DModel);
}
);
}
else
{
thisRef.pose = null;
}
if (thisRef.modelSetting.getLayout())
{
var layout = thisRef.modelSetting.getLayout();
if (layout["width"] != null)
thisRef.modelMatrix.setWidth(layout["width"]);
if (layout["height"] != null)
thisRef.modelMatrix.setHeight(layout["height"]);
if (layout["x"] != null)
thisRef.modelMatrix.setX(layout["x"]);
if (layout["y"] != null)
thisRef.modelMatrix.setY(layout["y"]);
if (layout["center_x"] != null)
thisRef.modelMatrix.centerX(layout["center_x"]);
if (layout["center_y"] != null)
thisRef.modelMatrix.centerY(layout["center_y"]);
if (layout["top"] != null)
thisRef.modelMatrix.top(layout["top"]);
if (layout["bottom"] != null)
thisRef.modelMatrix.bottom(layout["bottom"]);
if (layout["left"] != null)
thisRef.modelMatrix.left(layout["left"]);
if (layout["right"] != null)
thisRef.modelMatrix.right(layout["right"]);
}
for (var j = 0; j < thisRef.modelSetting.getInitParamNum(); j++)
{
thisRef.live2DModel.setParamFloat(
thisRef.modelSetting.getInitParamID(j),
thisRef.modelSetting.getInitParamValue(j)
);
}
for (var j = 0; j < thisRef.modelSetting.getInitPartsVisibleNum(); j++)
{
thisRef.live2DModel.setPartsOpacity(
thisRef.modelSetting.getInitPartsVisibleID(j),
thisRef.modelSetting.getInitPartsVisibleValue(j)
);
}
thisRef.live2DModel.saveParam();
// thisRef.live2DModel.setGL(gl);
thisRef.preloadMotionGroup(thisRef.options.defaultMotionGroup);
thisRef.mainMotionManager.stopAllMotions();
thisRef.setUpdating(false);
thisRef.setInitialized(true);
if (typeof callback == "function") callback();
}
});
}
});
// });
};
LAppModel.prototype.release = function(gl)
{
// this.live2DModel.deleteTextures();
// var pm = Live2DFramework.getPlatformManager();
//
// gl.deleteTexture(pm.texture);
}
LAppModel.prototype.preloadMotionGroup = function(name)
{
var thisRef = this;
for (var i = 0; i < this.modelSetting.getMotionNum(name); i++)
{
var file = this.modelSetting.getMotionFile(name, i);
this.loadMotion(file, this.modelHomeDir + file, function(motion) {
motion.setFadeIn(thisRef.modelSetting.getMotionFadeIn(name, i));
motion.setFadeOut(thisRef.modelSetting.getMotionFadeOut(name, i));
});
}
}
LAppModel.prototype.update = function()
{
// console.log("--> LAppModel.update()");
if(!this.live2DModel)
{
if (this.options.debugLog) console.error("Failed to update.");
return;
}
var timeMSec = UtSystem.getUserTimeMSec() - this.startTimeMSec;
var timeSec = timeMSec / 1000.0;
var t = timeSec * 2 * Math.PI;
if (this.mainMotionManager.isFinished() && this.randomMotion)
{
this.startRandomMotion(this.randomMotionGroup || this.options.defaultMotionGroup, this.randomMotionPriority || this.options.priorityDefault);
}
//-----------------------------------------------------------------
this.live2DModel.loadParam();
var update = this.mainMotionManager.updateParam(this.live2DModel);
if (!update) {
if(this.eyeBlink && this.options.eyeBlink) {
this.eyeBlink.updateParam(this.live2DModel);
}
}
this.live2DModel.saveParam();
//-----------------------------------------------------------------
if (this.expressionManager &&
this.expressions &&
!this.expressionManager.isFinished())
{
this.expressionManager.updateParam(this.live2DModel);
}
this.live2DModel.addToParamFloat("PARAM_ANGLE_X", this.dragX * 30, 1);
this.live2DModel.addToParamFloat("PARAM_ANGLE_Y", this.dragY * 30, 1);
this.live2DModel.addToParamFloat("PARAM_ANGLE_Z", (this.dragX * this.dragY) * -30, 1);
this.live2DModel.addToParamFloat("PARAM_BODY_ANGLE_X", this.dragX*10, 1);
this.live2DModel.addToParamFloat("PARAM_EYE_BALL_X", this.dragX, 1);
this.live2DModel.addToParamFloat("PARAM_EYE_BALL_Y", this.dragY, 1);
// this.live2DModel.addToParamFloat("PARAM_ANGLE_X",
// Number((15 * Math.sin(t / 6.5345))), 0.5);
// this.live2DModel.addToParamFloat("PARAM_ANGLE_Y",
// Number((8 * Math.sin(t / 3.5345))), 0.5);
// this.live2DModel.addToParamFloat("PARAM_ANGLE_Z",
// Number((10 * Math.sin(t / 5.5345))), 0.5);
// this.live2DModel.addToParamFloat("PARAM_BODY_ANGLE_X",
// Number((4 * Math.sin(t / 15.5345))), 0.5);
// this.live2DModel.setParamFloat("PARAM_BREATH",
// Number((0.5 + 0.5 * Math.sin(t / 3.2345))), 1);
if (this.physics)
{
this.physics.updateParam(this.live2DModel);
}
if (this.lipSync)
{
this.live2DModel.setParamFloat("PARAM_MOUTH_OPEN_Y",
this.lipSyncValue);
}
if( this.pose ) {
this.pose.updateParam(this.live2DModel);
}
this.live2DModel.update();
};
LAppModel.prototype.setRandomExpression = function()
{
var tmp = [];
for (var name in this.expressions)
{
tmp.push(name);
}
var no = parseInt(Math.random() * tmp.length);
this.setExpression(tmp[no]);
}
LAppModel.prototype.startRandomMotion = function(name, priority)
{
var max = this.modelSetting.getMotionNum(name);
var no = parseInt(Math.random() * max);
this.startMotion(name, no, priority);
this.randomMotion = true;
this.randomMotionGroup = name;
this.randomMotionPriority = priority;
}
LAppModel.prototype.startRandomMotionOnce = function(name, priority)
{
var max = this.modelSetting.getMotionNum(name);
var no = parseInt(Math.random() * max);
this.startMotion(name, no, priority);
}
LAppModel.prototype.stopRandomMotion = function()
{
this.randomMotion = false;
this.randomMotionGroup = null;
this.randomMotionPriority = null;
}
LAppModel.prototype.startMotion = function(name, no, priority)
{
// console.log("startMotion : " + name + " " + no + " " + priority);
var motionName = this.modelSetting.getMotionFile(name, no);
if (motionName == null || motionName == "")
{
if (this.options.debugLog)
console.warn("Failed to motion.");
return;
}
if (priority == this.options.priorityForce)
{
this.mainMotionManager.setReservePriority(priority);
}
else if (!this.mainMotionManager.reserveMotion(priority))
{
if (this.options.debugLog)
console.log("Motion is running.")
return;
}
var thisRef = this;
var motion;
if (this.motions[name] == null)
{
this.loadMotion(null, this.modelHomeDir + motionName, function(mtn) {
motion = mtn;
thisRef.setFadeInFadeOut(name, no, priority, motion);
});
}
else
{
motion = this.motions[name];
thisRef.setFadeInFadeOut(name, no, priority, motion);
}
}
LAppModel.prototype.setFadeInFadeOut = function(name, no, priority, motion)
{
var motionName = this.modelSetting.getMotionFile(name, no);
motion.setFadeIn(this.modelSetting.getMotionFadeIn(name, no));
motion.setFadeOut(this.modelSetting.getMotionFadeOut(name, no));
if (this.options.debugLog)
console.log("Start motion : " + motionName);
if (this.modelSetting.getMotionSound(name, no) == null)
{
this.mainMotionManager.startMotionPrio(motion, priority);
}
else
{
var soundName = this.modelSetting.getMotionSound(name, no);
// var player = new Sound(this.modelHomeDir + soundName);
if (this.options.debugLog)
console.log("Start sound : " + soundName);
this.playSound(soundName, this.modelHomeDir);
this.mainMotionManager.startMotionPrio(motion, priority);
}
}
LAppModel.prototype.playSound = function(filename, host)
{
if (this.options.audioPlayer) {
this.options.audioPlayer(filename, host);
} else {
const audio = this.audioElement || document.createElement("audio");
!this.audioElement && (this.audioElement = audio);
audio.src = host + filename;
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext && this.options.lipSyncWithSound) {
const context = this.audioContext || new AudioContext();
if (!this.audioContext) {
this.audioContext = context;
this.audioElementSource = context.createMediaElementSource(audio);
}
const source = this.audioElementSource;
const analyser = this.audioAnalyser || context.createAnalyser();
!this.audioAnalyser && (this.audioAnalyser = analyser);
analyser.fftSize = 32;
var bufferLength = analyser.frequencyBinCount;
let cache = [];
let lastTime = Date.now();
const intervalId = setInterval(() => {
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
const value = (dataArray[9] + dataArray[10] + dataArray[11]) / 3;
if (Date.now() - lastTime < 33) {
cache.push(value);
} else {
const lipValue = cache.length ?
(cache.reduce((previous, current) => current += previous) / cache.length / 100)
: this.lipSyncValue;
this.lipSync = true;
this.lipSyncValue = lipValue;
lastTime = Date.now();
cache = [];
}
}, 0);
audio.addEventListener('ended', () => {
clearInterval(intervalId);
this.lipSyncValue = 0;
});
source.connect(analyser);
analyser.connect(context.destination);
}
audio.play();
}
}
LAppModel.prototype.setExpression = function(name)
{
var motion = this.expressions[name];
if (this.options.debugLog)
console.log("Expression : " + name);
this.expressionManager.startMotion(motion, false);
}
LAppModel.prototype.draw = function(gl)
{
//console.log("--> LAppModel.draw()");
// if(this.live2DModel == null) return;
MatrixStack.push();
MatrixStack.multMatrix(this.modelMatrix.getArray());
this.tmpMatrix = MatrixStack.getMatrix()
this.live2DModel.setMatrix(this.tmpMatrix);
this.live2DModel.draw();
MatrixStack.pop();
};
LAppModel.prototype.hitTest = function(id, testX, testY)
{
var len = this.modelSetting.getHitAreaNum();
let hit = false;
for (var i = 0; i < len; i++)
{
// NOTE: id == null means to test all ids.
if (id == null) {
var drawID = this.modelSetting.getHitAreaID(i);
hit = this.hitTestSimple(drawID, testX, testY);
if (hit) {
return hit;
}
} else if (id == this.modelSetting.getHitAreaName(i)) {
var drawID = this.modelSetting.getHitAreaID(i);
return this.hitTestSimple(drawID, testX, testY);
}
}
return false;
}