Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change: modify behavior of expressions, interaction between isBinary and override #1489

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Contributor Author

@0b5vr 0b5vr Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VRMExpressionManager is only referred from a doc comment


// 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);
});
});
});
Loading