mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 00:48:06 +00:00
fix(stage-ui-live2d): better blink interval
This commit is contained in:
@@ -135,4 +135,48 @@ describe('live2d motion manager plugins', () => {
|
||||
expect(context.internalModel.eyeBlink?.updateParameters).not.toHaveBeenCalled()
|
||||
expect(context.handled).toBe(true)
|
||||
})
|
||||
|
||||
/**
|
||||
* @example
|
||||
* expect(context.model.getParameterValueById('ParamEyeLOpen')).toBeLessThan(1)
|
||||
*/
|
||||
it('opens force blink over a randomized 150ms to 300ms duration', () => {
|
||||
const randomSpy = vi.spyOn(Math, 'random')
|
||||
.mockReturnValueOnce(0)
|
||||
.mockReturnValueOnce(0.5)
|
||||
|
||||
const context = createContext({
|
||||
live2dAutoBlinkEnabled: ref(true),
|
||||
live2dForceAutoBlinkEnabled: ref(true),
|
||||
timeDelta: 3,
|
||||
})
|
||||
const plugin = useMotionUpdatePluginAutoEyeBlink(ref(false))
|
||||
|
||||
// ROOT CAUSE:
|
||||
//
|
||||
// Force blink previously reopened at one fixed speed.
|
||||
// That made closed eyes feel either too quick or too slow depending on
|
||||
// the model's eye-open parameter range.
|
||||
//
|
||||
// We fixed this by randomizing each opening phase between 150ms and 300ms.
|
||||
plugin(context)
|
||||
context.timeDelta = 0.075
|
||||
context.handled = false
|
||||
plugin(context)
|
||||
context.timeDelta = 0.15
|
||||
context.handled = false
|
||||
plugin(context)
|
||||
|
||||
expect(context.model.getParameterValueById('ParamEyeLOpen')).toBeCloseTo(4 / 9)
|
||||
expect(context.model.getParameterValueById('ParamEyeROpen')).toBeCloseTo(4 / 9)
|
||||
|
||||
context.timeDelta = 0.075
|
||||
context.handled = false
|
||||
plugin(context)
|
||||
|
||||
expect(context.model.getParameterValueById('ParamEyeLOpen')).toBe(1)
|
||||
expect(context.model.getParameterValueById('ParamEyeROpen')).toBe(1)
|
||||
|
||||
randomSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -255,6 +255,7 @@ export function useMotionUpdatePluginAutoEyeBlink(
|
||||
startLeft: 1,
|
||||
startRight: 1,
|
||||
delayMs: 0,
|
||||
openDurationMs: 300,
|
||||
}
|
||||
|
||||
// Eye values captured at blink start. Used as the base during
|
||||
@@ -263,11 +264,13 @@ export function useMotionUpdatePluginAutoEyeBlink(
|
||||
let preBlinkLeft = 1.0
|
||||
let preBlinkRight = 1.0
|
||||
const blinkCloseDuration = 75 // ms
|
||||
const blinkOpenDuration = 75 // ms
|
||||
const minBlinkOpenDuration = 150 // ms
|
||||
const maxBlinkOpenDuration = 300 // ms
|
||||
const minDelay = 3000
|
||||
const maxDelay = 8000
|
||||
|
||||
const clamp01 = (value: number) => Math.min(1, Math.max(0, value))
|
||||
const randomBlinkOpenDuration = () => minBlinkOpenDuration + Math.random() * (maxBlinkOpenDuration - minBlinkOpenDuration)
|
||||
|
||||
function resetBlinkState() {
|
||||
blinkState.phase = 'idle'
|
||||
@@ -307,13 +310,14 @@ export function useMotionUpdatePluginAutoEyeBlink(
|
||||
if (blinkState.progress >= 1) {
|
||||
blinkState.phase = 'opening'
|
||||
blinkState.progress = 0
|
||||
blinkState.openDurationMs = randomBlinkOpenDuration()
|
||||
}
|
||||
|
||||
return { eyeLOpen, eyeROpen }
|
||||
}
|
||||
|
||||
// Opening: move back to the base with ease-in.
|
||||
blinkState.progress = Math.min(1, blinkState.progress + dt / blinkOpenDuration)
|
||||
blinkState.progress = Math.min(1, blinkState.progress + dt / blinkState.openDurationMs)
|
||||
const eased = easeInQuad(blinkState.progress)
|
||||
const eyeLOpen = clamp01(blinkState.startLeft * eased)
|
||||
const eyeROpen = clamp01(blinkState.startRight * eased)
|
||||
|
||||
Reference in New Issue
Block a user