Skip to content

Commit

Permalink
v0.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
guansss committed Sep 21, 2020
1 parent babdcd4 commit f65f1c8
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pixi-live2d-display",
"version": "0.2.1",
"version": "0.2.2",
"description": "Live2D plugin for PixiJS",
"main": "lib/index.js",
"repository": {
Expand Down
175 changes: 175 additions & 0 deletions scripts/Cubism2SuperModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { NormalizedLayoutDefinition, SuperModel } from '@/cubism-common/SuperModel';
import { Matrix } from '@pixi/math';
import { Cubism2ModelSettings } from './Cubism2ModelSettings';
import { Cubism2MotionManager } from './Cubism2MotionManager';
import { Live2DEyeBlink } from './Live2DEyeBlink';
import { Live2DPhysics } from './Live2DPhysics';
import { Live2DPose } from './Live2DPose';

// prettier-ignore
const tempMatrixArray = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]);

/**
* A wrapper of core model, which is `Live2DModelWebGL` from Live2D runtime library.
*/
export class Live2DInternalModel extends SuperModel<Live2DModelWebGL, Cubism2ModelSettings, Cubism2MotionManager> {
eyeBlink: Live2DEyeBlink;
physics?: Live2DPhysics;
pose?: Live2DPose;

/**
* Live2D parameter index, cached for better performance.
*/
eyeballXParamIndex: number;
eyeballYParamIndex: number;
angleXParamIndex: number;
angleYParamIndex: number;
angleZParamIndex: number;
bodyAngleXParamIndex: number;
breathParamIndex: number;

constructor(model: Live2DModelWebGL, modelSettings: Cubism2ModelSettings) {
super(model, modelSettings, new Cubism2MotionManager(model, modelSettings));

this.eyeBlink = new Live2DEyeBlink(model);

if (modelSettings.initParams) {
modelSettings.initParams.forEach(({ id, value }) => model.setParamFloat(id, value));
}
if (modelSettings.initOpacities) {
modelSettings.initOpacities.forEach(({ id, value }) => model.setPartsOpacity(id, value));
}

model.saveParam();

this.eyeballXParamIndex = model.getParamIndex('PARAM_EYE_BALL_X');
this.eyeballYParamIndex = model.getParamIndex('PARAM_EYE_BALL_Y');
this.angleXParamIndex = model.getParamIndex('PARAM_ANGLE_X');
this.angleYParamIndex = model.getParamIndex('PARAM_ANGLE_Y');
this.angleZParamIndex = model.getParamIndex('PARAM_ANGLE_Z');
this.bodyAngleXParamIndex = model.getParamIndex('PARAM_BODY_ANGLE_X');
this.breathParamIndex = model.getParamIndex('PARAM_BREATH');
}

protected getSize(): [number, number] {
return [this.model.getCanvasWidth(), this.model.getCanvasHeight()];
}

protected getLayout(): NormalizedLayoutDefinition {
const layout = this.modelSettings.layout || {};

return {
...layout,
centerX: layout.center_x,
centerY: layout.center_y,
};
}

/** @override */
updateWebGLContext(gl: WebGLRenderingContext, glContextID: number): void {
const drawParamWebGL = this.model.drawParamWebGL;

drawParamWebGL.firstDraw = true;
drawParamWebGL.setGL(gl);
drawParamWebGL.glno = glContextID;

// reset WebGL buffers
for (const prop in drawParamWebGL) {
if (drawParamWebGL.hasOwnProperty(prop) && (drawParamWebGL as any)[prop] instanceof WebGLBuffer) {
(drawParamWebGL as any)[prop] = null;
}
}

// a temporary workaround for the frame buffers bound to WebGL context in Live2D
const clipManager = this.model.getModelContext().clipManager;
clipManager.curFrameNo = glContextID;
clipManager.getMaskRenderTexture();
}

/** @override */
bindTexture(index: number, texture: WebGLTexture): void {
this.model.setTexture(index, texture);
}

/** @override */
hitTest(x: number, y: number): string[] {
if (this.model && this.modelSettings.hitAreas) {
return this.modelSettings.hitAreas
.filter(({ name, id }) => {
const drawIndex = this.model.getDrawDataIndex(id);

if (drawIndex >= 0) {
const points = this.model.getTransformedPoints(drawIndex);
let left = this.originalWidth;
let right = 0;
let top = this.originalHeight;
let bottom = 0;

for (let i = 0; i < points.length; i += 2) {
const px = points[i];
const py = points[i + 1];

if (px < left) left = px;
if (px > right) right = px;
if (py < top) top = py;
if (py > bottom) bottom = py;
}

return left <= x && x <= right && top <= y && y <= bottom;
}
})
.map(hitArea => hitArea.name);
}

return [];
}

update(dt: DOMHighResTimeStamp, now: DOMHighResTimeStamp): void {
const model = this.model;

model.loadParam();

const updated = this.motionManager.update(this.model, dt);
if (!updated) {
this.eyeBlink.update(dt);
}

model.saveParam();

// update focus and natural movements
this.focusController.update(dt);
const focusX = this.focusController.x;
const focusY = this.focusController.y;
const t = (now / 1000) * 2 * Math.PI;
model.addToParamFloat(this.eyeballXParamIndex, focusX);
model.addToParamFloat(this.eyeballYParamIndex, focusY);
model.addToParamFloat(this.angleXParamIndex, focusX * 30 + 15 * Math.sin(t / 6.5345) * 0.5);
model.addToParamFloat(this.angleYParamIndex, focusY * 30 + 8 * Math.sin(t / 3.5345) * 0.5);
model.addToParamFloat(this.angleZParamIndex, focusX * focusY * -30 + 10 * Math.sin(t / 5.5345) * 0.5);
model.addToParamFloat(this.bodyAngleXParamIndex, focusX * 10 + 4 * Math.sin(t / 15.5345) * 0.5);
model.setParamFloat(this.breathParamIndex, 0.5 + 0.5 * Math.sin(t / 3.2345));

this.physics && this.physics.update(now);
this.pose && this.pose.update(dt);

model.update();
}

draw(matrix: Matrix): void {
// set given 3x3 matrix into a 4x4 matrix, with Y inverted
tempMatrixArray[0] = matrix.a;
tempMatrixArray[1] = -matrix.c;
tempMatrixArray[4] = matrix.b;
tempMatrixArray[5] = -matrix.d;
tempMatrixArray[12] = matrix.tx;
tempMatrixArray[13] = -matrix.ty;

this.model.setMatrix(tempMatrixArray);
this.model.draw();
}
}
57 changes: 57 additions & 0 deletions scripts/FocusController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const EPSILON = 0.01; // Minimum distance to respond

const MAX_SPEED = 40 / 7.5;
const ACCELERATION_TIME = 1 / (0.15 * 1000);

export class FocusController {
targetX = 0;
targetY = 0;
x = 0;
y = 0;

vx = 0;
vy = 0;

/**
* Focus in range [-1, 1].
*/
focus(x: number, y: number) {
this.targetX = x;
this.targetY = y;
}

update(dt: DOMHighResTimeStamp) {
const dx = this.targetX - this.x;
const dy = this.targetY - this.y;

if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) return;

const d = Math.sqrt(dx ** 2 + dy ** 2);
const maxSpeed = MAX_SPEED / (1000 / dt);

let ax = maxSpeed * (dx / d) - this.vx;
let ay = maxSpeed * (dy / d) - this.vy;

const a = Math.sqrt(ax ** 2 + ay ** 2);
const maxA = maxSpeed * ACCELERATION_TIME * dt;

if (a > maxA) {
ax *= maxA / a;
ay *= maxA / a;
}

this.vx += ax;
this.vy += ay;

const v = Math.sqrt(this.vx ** 2 + this.vy ** 2);
const maxV = 0.5 * (Math.sqrt(maxA ** 2 + 8 * maxA * d) - maxA);

if (v > maxV) {
this.vx *= maxV / v;
this.vy *= maxV / v;
}

this.x += this.vx;
this.y += this.vy;
}
}

0 comments on commit f65f1c8

Please sign in to comment.