Skip to content

Commit

Permalink
Merge pull request #1489 from pixiv/isbinary-override
Browse files Browse the repository at this point in the history
change: modify behavior of expressions, interaction between isBinary and override
  • Loading branch information
0b5vr authored Oct 22, 2024
2 parents 483118b + 68a10ad commit 0384245
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 7 deletions.
35 changes: 28 additions & 7 deletions packages/three-vrm-core/src/expressions/VRMExpression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as THREE from 'three';
import { VRMExpressionBind } from './VRMExpressionBind';
import type { VRMExpressionOverrideType } from './VRMExpressionOverrideType';
import type { VRMExpressionManager } from './VRMExpressionManager';

Check warning on line 4 in packages/three-vrm-core/src/expressions/VRMExpression.ts

View workflow job for this annotation

GitHub Actions / publish

'VRMExpressionManager' is defined but never used

// animationMixer の監視対象は、Scene の中に入っている必要がある。
// そのため、表示オブジェクトではないけれど、Object3D を継承して Scene に投入できるようにする。
Expand All @@ -13,6 +14,10 @@ export class VRMExpression extends THREE.Object3D {

/**
* The current weight of the expression.
*
* You usually want to set the weight via {@link VRMExpressionManager.setValue}.
*
* It might also be controlled by the Three.js animation system.
*/
public weight = 0.0;

Expand Down Expand Up @@ -46,9 +51,9 @@ export class VRMExpression extends THREE.Object3D {
*/
public get overrideBlinkAmount(): number {
if (this.overrideBlink === 'block') {
return 0.0 < this.weight ? 1.0 : 0.0;
return 0.0 < this.outputWeight ? 1.0 : 0.0;
} else if (this.overrideBlink === 'blend') {
return this.weight;
return this.outputWeight;
} else {
return 0.0;
}
Expand All @@ -60,9 +65,9 @@ export class VRMExpression extends THREE.Object3D {
*/
public get overrideLookAtAmount(): number {
if (this.overrideLookAt === 'block') {
return 0.0 < this.weight ? 1.0 : 0.0;
return 0.0 < this.outputWeight ? 1.0 : 0.0;
} else if (this.overrideLookAt === 'blend') {
return this.weight;
return this.outputWeight;
} else {
return 0.0;
}
Expand All @@ -74,14 +79,25 @@ export class VRMExpression extends THREE.Object3D {
*/
public get overrideMouthAmount(): number {
if (this.overrideMouth === 'block') {
return 0.0 < this.weight ? 1.0 : 0.0;
return 0.0 < this.outputWeight ? 1.0 : 0.0;
} else if (this.overrideMouth === 'blend') {
return this.weight;
return this.outputWeight;
} else {
return 0.0;
}
}

/**
* An output weight of this expression, considering the {@link isBinary}.
*/
public get outputWeight(): number {
if (this.isBinary) {
return this.weight > 0.5 ? 1.0 : 0.0;
}

return this.weight;
}

constructor(expressionName: string) {
super();

Expand Down Expand Up @@ -112,9 +128,14 @@ export class VRMExpression extends THREE.Object3D {
*/
multiplier?: number;
}): void {
let actualWeight = this.isBinary ? (this.weight <= 0.5 ? 0.0 : 1.0) : this.weight;
let actualWeight = this.outputWeight;
actualWeight *= options?.multiplier ?? 1.0;

// if the expression is binary, the override value must be also treated as binary
if (this.isBinary && actualWeight < 1.0) {
actualWeight = 0.0;
}

this._binds.forEach((bind) => bind.applyWeight(actualWeight));
}

Expand Down
190 changes: 190 additions & 0 deletions packages/three-vrm-core/src/expressions/tests/VRMExpression.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { VRMExpression } from '../VRMExpression';
import { VRMExpressionBind } from '../VRMExpressionBind';

class VRMExpressionMockBind implements VRMExpressionBind {
public weight = 0.0;

public applyWeight(weight: number): void {
this.weight += weight;
}

public clearAppliedWeight(): void {
this.weight = 0.0;
}
}

describe('VRMExpression', () => {
let expression: VRMExpression;

beforeEach(() => {
expression = new VRMExpression('aa');
});

describe('outputWeight', () => {
it('returns the weight if the expression is not binary', () => {
expression.weight = 0.64;
expect(expression.outputWeight).toBe(0.64);
});

it('returns 0.0 if the expression is binary and the weight is less than 0.5', () => {
expression.isBinary = true;
expression.weight = 0.3;
expect(expression.outputWeight).toBe(0.0);
});

it('returns 1.0 if the expression is binary and the weight is more than 0.5', () => {
expression.isBinary = true;
expression.weight = 0.7;
expect(expression.outputWeight).toBe(1.0);
});

it('returns 0.0 if the expression is binary and the weight is exactly 0.5', () => {
expression.isBinary = true;
expression.weight = 0.5;
expect(expression.outputWeight).toBe(0.0);
});
});

describe('overrideBlinkAmount', () => {
it('returns 0.0 when the overrideBlink is none', () => {
expression.overrideBlink = 'none';
expression.weight = 0.75;
expect(expression.overrideBlinkAmount).toBe(0.0);
});

it('returns the override amount when the overrideBlink is blend', () => {
expression.overrideBlink = 'blend';
expression.weight = 0.75;
expect(expression.overrideBlinkAmount).toBe(0.75);
});

it('returns 1.0 when the overrideBlink is block and the weight is not zero', () => {
expression.overrideBlink = 'block';
expression.weight = 0.1;
expect(expression.overrideBlinkAmount).toBe(1.0);
});

it('returns 0.0 when the overrideBlink is block and the weight is exactly zero', () => {
expression.overrideBlink = 'block';
expression.weight = 0.0;
expect(expression.overrideBlinkAmount).toBe(0.0);
});

it('returns 0.0 when the expression is binary, the overrideBlink is blend, and the weight is less than 0.5', () => {
expression.overrideBlink = 'blend';
expression.isBinary = true;
expression.weight = 0.3;
expect(expression.overrideBlinkAmount).toBe(0.0);
});

it('returns 1.0 when the expression is binary, the overrideBlink is blend, and the weight is more than 0.5', () => {
expression.overrideBlink = 'blend';
expression.isBinary = true;
expression.weight = 0.7;
expect(expression.overrideBlinkAmount).toBe(1.0);
});

it('returns 0.0 when the expression is binary, the overrideBlink is block, and the weight is less than 0.5', () => {
expression.overrideBlink = 'block';
expression.isBinary = true;
expression.weight = 0.3;
expect(expression.overrideBlinkAmount).toBe(0.0);
});

it('returns 1.0 when the expression is binary, the overrideBlink is block, and the weight is more than 0.5', () => {
expression.overrideBlink = 'block';
expression.isBinary = true;
expression.weight = 0.7;
expect(expression.overrideBlinkAmount).toBe(1.0);
});
});

describe('applyWeight', () => {
it('applies the weight to the binds', () => {
const bind1 = new VRMExpressionMockBind();
const bind2 = new VRMExpressionMockBind();
expression.addBind(bind1);
expression.addBind(bind2);

expression.weight = 0.64;
expression.applyWeight();

expect(bind1.weight).toBe(0.64);
expect(bind2.weight).toBe(0.64);
});

it('applies the 0.0 if the expression is binary and the weight is less than 0.5', () => {
expression.isBinary = true;

const bind = new VRMExpressionMockBind();
expression.addBind(bind);

expression.weight = 0.3;
expression.applyWeight();

expect(bind.weight).toBe(0.0);
});

it('applies the 1.0 if the expression is binary and the weight is more than 0.5', () => {
expression.isBinary = true;

const bind = new VRMExpressionMockBind();
expression.addBind(bind);

expression.weight = 0.7;
expression.applyWeight();

expect(bind.weight).toBe(1.0);
});

it('applies the 0.0 if the expression is binary and the weight is exactly 0.5', () => {
expression.isBinary = true;

const bind = new VRMExpressionMockBind();
expression.addBind(bind);

expression.weight = 0.5;
expression.applyWeight();

expect(bind.weight).toBe(0.0);
});

it('applies the weight with the override multiplier', () => {
const bind = new VRMExpressionMockBind();
expression.addBind(bind);

expression.weight = 0.75;
expression.applyWeight({ multiplier: 0.5 });

expect(bind.weight).toBe(0.375);
});

it('applies the 0.0 if the expression is binary and the override multiplier is less than 1.0', () => {
expression.isBinary = true;

const bind = new VRMExpressionMockBind();
expression.addBind(bind);

expression.weight = 0.7;
expression.applyWeight({ multiplier: 0.99 });

expect(bind.weight).toBe(0.0);
});
});

describe('clearAppliedWeight', () => {
it('clears the applied weight from the binds', () => {
const bind1 = new VRMExpressionMockBind();
const bind2 = new VRMExpressionMockBind();
bind1.applyWeight(0.82);
bind2.applyWeight(0.48);
expression.addBind(bind1);
expression.addBind(bind2);

expression.clearAppliedWeight();

expect(bind1.weight).toBe(0.0);
expect(bind2.weight).toBe(0.0);
});
});
});

0 comments on commit 0384245

Please sign in to comment.