Implement multi-scheme animation name mapper for Owen Animation System
Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled
- Added AnimationNameMapper class to handle conversion between different animation naming schemes (legacy, artist, hierarchical, semantic). - Included methods for initialization, pattern matching, conversion, and validation of animation names. - Developed comprehensive unit tests for the animation name converter and demo pages using Playwright. - Created a Vite configuration for the demo application, including asset handling and optimization settings. - Enhanced the demo with features for batch conversion, performance metrics, and responsive design.
This commit is contained in:
280
src/animation/AnimationConstants.js
Normal file
280
src/animation/AnimationConstants.js
Normal file
@ -0,0 +1,280 @@
|
||||
/**
|
||||
* @fileoverview Animation constants with multi-scheme support for Owen Animation System
|
||||
* @module animation/AnimationConstants
|
||||
*/
|
||||
|
||||
import { AnimationNameMapper } from './AnimationNameMapper.js'
|
||||
|
||||
// Create a singleton instance of the name mapper
|
||||
const nameMapper = new AnimationNameMapper()
|
||||
|
||||
/**
|
||||
* Legacy animation names (backward compatibility)
|
||||
* @constant
|
||||
*/
|
||||
export const LegacyAnimations = {
|
||||
// Wait state animations
|
||||
WAIT_IDLE_LOOP: 'wait_idle_L',
|
||||
WAIT_PICK_NOSE_QUIRK: 'wait_pickNose_Q',
|
||||
WAIT_STRETCH_QUIRK: 'wait_stretch_Q',
|
||||
WAIT_YAWN_QUIRK: 'wait_yawn_Q',
|
||||
|
||||
// React state animations - neutral
|
||||
REACT_IDLE_LOOP: 'react_idle_L',
|
||||
REACT_ACKNOWLEDGE_TRANSITION: 'react_acknowledge_T',
|
||||
REACT_NOD_TRANSITION: 'react_nod_T',
|
||||
REACT_LISTENING_LOOP: 'react_listening_L',
|
||||
|
||||
// React state animations - angry
|
||||
REACT_ANGRY_IDLE_LOOP: 'react_angry_L',
|
||||
REACT_ANGRY_FROWN_TRANSITION: 'react_an2frown_T',
|
||||
REACT_ANGRY_GRUMBLE_QUIRK: 'react_an2grumble_Q',
|
||||
REACT_ANGRY_TO_TYPE_TRANSITION: 'react_an2type_T',
|
||||
|
||||
// React state animations - happy
|
||||
REACT_HAPPY_IDLE_LOOP: 'react_happy_L',
|
||||
REACT_HAPPY_SMILE_TRANSITION: 'react_hp2smile_T',
|
||||
REACT_HAPPY_BOUNCE_QUIRK: 'react_hp2bounce_Q',
|
||||
REACT_HAPPY_TO_TYPE_TRANSITION: 'react_hp2type_T',
|
||||
|
||||
// React state animations - sad
|
||||
REACT_SAD_IDLE_LOOP: 'react_sad_L',
|
||||
REACT_SAD_SIGH_TRANSITION: 'react_sd2sigh_T',
|
||||
REACT_SAD_SLUMP_QUIRK: 'react_sd2slump_Q',
|
||||
REACT_SAD_TO_TYPE_TRANSITION: 'react_sd2type_T',
|
||||
|
||||
// React state animations - shocked
|
||||
REACT_SHOCKED_IDLE_LOOP: 'react_shocked_L',
|
||||
REACT_SHOCKED_GASP_TRANSITION: 'react_sh2gasp_T',
|
||||
REACT_SHOCKED_JUMP_QUIRK: 'react_sh2jump_Q',
|
||||
REACT_SHOCKED_TO_TYPE_TRANSITION: 'react_sh2type_T',
|
||||
|
||||
// Type state animations
|
||||
TYPE_IDLE_LOOP: 'type_idle_L',
|
||||
TYPE_FAST_LOOP: 'type_fast_L',
|
||||
TYPE_SLOW_LOOP: 'type_slow_L',
|
||||
TYPE_THINKING_LOOP: 'type_thinking_L',
|
||||
TYPE_TO_WAIT_TRANSITION: 'type2wait_T',
|
||||
|
||||
// Sleep state animations
|
||||
SLEEP_LIGHT_LOOP: 'sleep_light_L',
|
||||
SLEEP_DEEP_LOOP: 'sleep_deep_L',
|
||||
SLEEP_DREAM_QUIRK: 'sleep_dream_Q',
|
||||
SLEEP_WAKE_UP_TRANSITION: 'sleep2wake_T'
|
||||
}
|
||||
|
||||
/**
|
||||
* Artist-friendly animation names (Blender workflow)
|
||||
* @constant
|
||||
*/
|
||||
export const ArtistAnimations = {
|
||||
// Wait state animations
|
||||
WAIT_IDLE: 'Owen_WaitIdle',
|
||||
WAIT_PICK_NOSE: 'Owen_PickNose',
|
||||
WAIT_STRETCH: 'Owen_Stretch',
|
||||
WAIT_YAWN: 'Owen_Yawn',
|
||||
|
||||
// React state animations - neutral
|
||||
REACT_IDLE: 'Owen_ReactIdle',
|
||||
REACT_ACKNOWLEDGE: 'Owen_ReactAcknowledge',
|
||||
REACT_NOD: 'Owen_ReactNod',
|
||||
REACT_LISTENING: 'Owen_ReactListening',
|
||||
|
||||
// React state animations - angry
|
||||
REACT_ANGRY_IDLE: 'Owen_ReactAngryIdle',
|
||||
REACT_ANGRY_FROWN: 'Owen_ReactAngryFrown',
|
||||
REACT_ANGRY_GRUMBLE: 'Owen_ReactAngryGrumble',
|
||||
REACT_ANGRY_TO_TYPE: 'Owen_ReactAngryToType',
|
||||
|
||||
// React state animations - happy
|
||||
REACT_HAPPY_IDLE: 'Owen_ReactHappyIdle',
|
||||
REACT_HAPPY_SMILE: 'Owen_ReactHappySmile',
|
||||
REACT_HAPPY_BOUNCE: 'Owen_ReactHappyBounce',
|
||||
REACT_HAPPY_TO_TYPE: 'Owen_ReactHappyToType',
|
||||
|
||||
// React state animations - sad
|
||||
REACT_SAD_IDLE: 'Owen_ReactSadIdle',
|
||||
REACT_SAD_SIGH: 'Owen_ReactSadSigh',
|
||||
REACT_SAD_SLUMP: 'Owen_ReactSadSlump',
|
||||
REACT_SAD_TO_TYPE: 'Owen_ReactSadToType',
|
||||
|
||||
// React state animations - shocked
|
||||
REACT_SHOCKED_IDLE: 'Owen_ReactShockedIdle',
|
||||
REACT_SHOCKED_GASP: 'Owen_ReactShockedGasp',
|
||||
REACT_SHOCKED_JUMP: 'Owen_ReactShockedJump',
|
||||
REACT_SHOCKED_TO_TYPE: 'Owen_ReactShockedToType',
|
||||
|
||||
// Type state animations
|
||||
TYPE_IDLE: 'Owen_TypeIdle',
|
||||
TYPE_FAST: 'Owen_TypeFast',
|
||||
TYPE_SLOW: 'Owen_TypeSlow',
|
||||
TYPE_THINKING: 'Owen_TypeThinking',
|
||||
TYPE_TO_WAIT: 'Owen_TypeToWait',
|
||||
|
||||
// Sleep state animations
|
||||
SLEEP_LIGHT: 'Owen_SleepLight',
|
||||
SLEEP_DEEP: 'Owen_SleepDeep',
|
||||
SLEEP_DREAM: 'Owen_SleepDream',
|
||||
SLEEP_WAKE_UP: 'Owen_SleepWakeUp'
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchical animation names (organized structure)
|
||||
* @constant
|
||||
*/
|
||||
export const HierarchicalAnimations = {
|
||||
// Wait state animations
|
||||
WAIT_IDLE: 'owen.state.wait.idle.loop',
|
||||
WAIT_PICK_NOSE: 'owen.quirk.wait.picknose',
|
||||
WAIT_STRETCH: 'owen.quirk.wait.stretch',
|
||||
WAIT_YAWN: 'owen.quirk.wait.yawn',
|
||||
|
||||
// React state animations - neutral
|
||||
REACT_IDLE: 'owen.state.react.idle.loop',
|
||||
REACT_ACKNOWLEDGE: 'owen.state.react.acknowledge.transition',
|
||||
REACT_NOD: 'owen.state.react.nod.transition',
|
||||
REACT_LISTENING: 'owen.state.react.listening.loop',
|
||||
|
||||
// React state animations - angry
|
||||
REACT_ANGRY_IDLE: 'owen.state.react.angry.idle.loop',
|
||||
REACT_ANGRY_FROWN: 'owen.state.react.angry.frown.transition',
|
||||
REACT_ANGRY_GRUMBLE: 'owen.quirk.react.angry.grumble',
|
||||
REACT_ANGRY_TO_TYPE: 'owen.state.react.angry.totype.transition',
|
||||
|
||||
// React state animations - happy
|
||||
REACT_HAPPY_IDLE: 'owen.state.react.happy.idle.loop',
|
||||
REACT_HAPPY_SMILE: 'owen.state.react.happy.smile.transition',
|
||||
REACT_HAPPY_BOUNCE: 'owen.quirk.react.happy.bounce',
|
||||
REACT_HAPPY_TO_TYPE: 'owen.state.react.happy.totype.transition',
|
||||
|
||||
// React state animations - sad
|
||||
REACT_SAD_IDLE: 'owen.state.react.sad.idle.loop',
|
||||
REACT_SAD_SIGH: 'owen.state.react.sad.sigh.transition',
|
||||
REACT_SAD_SLUMP: 'owen.quirk.react.sad.slump',
|
||||
REACT_SAD_TO_TYPE: 'owen.state.react.sad.totype.transition',
|
||||
|
||||
// React state animations - shocked
|
||||
REACT_SHOCKED_IDLE: 'owen.state.react.shocked.idle.loop',
|
||||
REACT_SHOCKED_GASP: 'owen.state.react.shocked.gasp.transition',
|
||||
REACT_SHOCKED_JUMP: 'owen.quirk.react.shocked.jump',
|
||||
REACT_SHOCKED_TO_TYPE: 'owen.state.react.shocked.totype.transition',
|
||||
|
||||
// Type state animations
|
||||
TYPE_IDLE: 'owen.state.type.idle.loop',
|
||||
TYPE_FAST: 'owen.state.type.fast.loop',
|
||||
TYPE_SLOW: 'owen.state.type.slow.loop',
|
||||
TYPE_THINKING: 'owen.state.type.thinking.loop',
|
||||
TYPE_TO_WAIT: 'owen.state.type.towait.transition',
|
||||
|
||||
// Sleep state animations
|
||||
SLEEP_LIGHT: 'owen.state.sleep.light.loop',
|
||||
SLEEP_DEEP: 'owen.state.sleep.deep.loop',
|
||||
SLEEP_DREAM: 'owen.quirk.sleep.dream',
|
||||
SLEEP_WAKE_UP: 'owen.state.sleep.wakeup.transition'
|
||||
}
|
||||
|
||||
/**
|
||||
* Semantic animation names (readable camelCase)
|
||||
* @constant
|
||||
*/
|
||||
export const SemanticAnimations = {
|
||||
// Wait state animations
|
||||
WAIT_IDLE: 'OwenWaitIdleLoop',
|
||||
WAIT_PICK_NOSE: 'OwenQuirkPickNose',
|
||||
WAIT_STRETCH: 'OwenQuirkStretch',
|
||||
WAIT_YAWN: 'OwenQuirkYawn',
|
||||
|
||||
// React state animations - neutral
|
||||
REACT_IDLE: 'OwenReactIdleLoop',
|
||||
REACT_ACKNOWLEDGE: 'OwenReactAcknowledgeTransition',
|
||||
REACT_NOD: 'OwenReactNodTransition',
|
||||
REACT_LISTENING: 'OwenReactListeningLoop',
|
||||
|
||||
// React state animations - angry
|
||||
REACT_ANGRY_IDLE: 'OwenReactAngryIdleLoop',
|
||||
REACT_ANGRY_FROWN: 'OwenReactAngryFrownTransition',
|
||||
REACT_ANGRY_GRUMBLE: 'OwenQuirkAngryGrumble',
|
||||
REACT_ANGRY_TO_TYPE: 'OwenReactAngryToTypeTransition',
|
||||
|
||||
// React state animations - happy
|
||||
REACT_HAPPY_IDLE: 'OwenReactHappyIdleLoop',
|
||||
REACT_HAPPY_SMILE: 'OwenReactHappySmileTransition',
|
||||
REACT_HAPPY_BOUNCE: 'OwenQuirkHappyBounce',
|
||||
REACT_HAPPY_TO_TYPE: 'OwenReactHappyToTypeTransition',
|
||||
|
||||
// React state animations - sad
|
||||
REACT_SAD_IDLE: 'OwenReactSadIdleLoop',
|
||||
REACT_SAD_SIGH: 'OwenReactSadSighTransition',
|
||||
REACT_SAD_SLUMP: 'OwenQuirkSadSlump',
|
||||
REACT_SAD_TO_TYPE: 'OwenReactSadToTypeTransition',
|
||||
|
||||
// React state animations - shocked
|
||||
REACT_SHOCKED_IDLE: 'OwenReactShockedIdleLoop',
|
||||
REACT_SHOCKED_GASP: 'OwenReactShockedGaspTransition',
|
||||
REACT_SHOCKED_JUMP: 'OwenQuirkShockedJump',
|
||||
REACT_SHOCKED_TO_TYPE: 'OwenReactShockedToTypeTransition',
|
||||
|
||||
// Type state animations
|
||||
TYPE_IDLE: 'OwenTypeIdleLoop',
|
||||
TYPE_FAST: 'OwenTypeFastLoop',
|
||||
TYPE_SLOW: 'OwenTypeSlowLoop',
|
||||
TYPE_THINKING: 'OwenTypeThinkingLoop',
|
||||
TYPE_TO_WAIT: 'OwenTypeToWaitTransition',
|
||||
|
||||
// Sleep state animations
|
||||
SLEEP_LIGHT: 'OwenSleepLightLoop',
|
||||
SLEEP_DEEP: 'OwenSleepDeepLoop',
|
||||
SLEEP_DREAM: 'OwenQuirkSleepDream',
|
||||
SLEEP_WAKE_UP: 'OwenSleepWakeUpTransition'
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation naming schemes enumeration
|
||||
* @constant
|
||||
*/
|
||||
export const NamingSchemes = {
|
||||
LEGACY: 'legacy',
|
||||
ARTIST: 'artist',
|
||||
HIERARCHICAL: 'hierarchical',
|
||||
SEMANTIC: 'semantic'
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert animation name between different schemes
|
||||
* @param {string} name - The source animation name
|
||||
* @param {string} targetScheme - The target naming scheme
|
||||
* @returns {string} The converted animation name
|
||||
*/
|
||||
export function convertAnimationName (name, targetScheme) {
|
||||
return nameMapper.convert(name, targetScheme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all naming scheme variants for an animation
|
||||
* @param {string} name - The source animation name
|
||||
* @returns {Object} Object with all scheme variants
|
||||
*/
|
||||
export function getAllAnimationNames (name) {
|
||||
return nameMapper.getAllNames(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an animation name
|
||||
* @param {string} name - The animation name to validate
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
export function validateAnimationName (name) {
|
||||
return nameMapper.validateAnimationName(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get animations by state and emotion
|
||||
* @param {string} state - The state name
|
||||
* @param {string} emotion - The emotion name (optional)
|
||||
* @param {string} scheme - The naming scheme to return (default: 'semantic')
|
||||
* @returns {string[]} Array of animation names
|
||||
*/
|
||||
export function getAnimationsByStateAndEmotion (state, emotion = '', scheme = 'semantic') {
|
||||
const animations = nameMapper.getAnimationsByFilter({ state, emotion })
|
||||
return animations.map(anim => anim[scheme] || anim.semantic)
|
||||
}
|
||||
599
src/animation/AnimationNameMapper.js
Normal file
599
src/animation/AnimationNameMapper.js
Normal file
@ -0,0 +1,599 @@
|
||||
/**
|
||||
* @fileoverview Multi-scheme animation name mapper for Owen Animation System
|
||||
* @module animation/AnimationNameMapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* Multi-scheme animation name mapper for Owen Animation System
|
||||
* Supports legacy, artist-friendly, and hierarchical naming schemes
|
||||
* @class
|
||||
*/
|
||||
export class AnimationNameMapper {
|
||||
constructor () {
|
||||
// Mapping between different naming schemes
|
||||
this.schemeMappings = new Map()
|
||||
this.reverseMappings = new Map()
|
||||
this.patterns = new Map()
|
||||
|
||||
this.initializeMappings()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all naming scheme mappings and patterns
|
||||
* @private
|
||||
*/
|
||||
initializeMappings () {
|
||||
// Core animation definitions with all naming scheme variants
|
||||
const animations = [
|
||||
// Wait state animations
|
||||
{
|
||||
legacy: 'wait_idle_L',
|
||||
artist: 'Owen_WaitIdle',
|
||||
hierarchical: 'owen.state.wait.idle.loop',
|
||||
semantic: 'OwenWaitIdleLoop',
|
||||
state: 'wait',
|
||||
emotion: '',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'wait_pickNose_Q',
|
||||
artist: 'Owen_PickNose',
|
||||
hierarchical: 'owen.quirk.wait.picknose',
|
||||
semantic: 'OwenQuirkPickNose',
|
||||
state: 'wait',
|
||||
emotion: '',
|
||||
type: 'quirk',
|
||||
category: 'quirk'
|
||||
},
|
||||
{
|
||||
legacy: 'wait_wave_Q',
|
||||
artist: 'Owen_Wave',
|
||||
hierarchical: 'owen.quirk.wait.wave',
|
||||
semantic: 'OwenQuirkWave',
|
||||
state: 'wait',
|
||||
emotion: '',
|
||||
type: 'quirk',
|
||||
category: 'quirk'
|
||||
},
|
||||
// React state animations
|
||||
{
|
||||
legacy: 'react_idle_L',
|
||||
artist: 'Owen_ReactIdle',
|
||||
hierarchical: 'owen.state.react.idle.loop',
|
||||
semantic: 'OwenReactIdleLoop',
|
||||
state: 'react',
|
||||
emotion: '',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'react_an_L',
|
||||
artist: 'Owen_ReactAngry',
|
||||
hierarchical: 'owen.state.react.emotion.angry.loop',
|
||||
semantic: 'OwenReactAngryLoop',
|
||||
state: 'react',
|
||||
emotion: 'angry',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'react_sh_L',
|
||||
artist: 'Owen_ReactShocked',
|
||||
hierarchical: 'owen.state.react.emotion.shocked.loop',
|
||||
semantic: 'OwenReactShockedLoop',
|
||||
state: 'react',
|
||||
emotion: 'shocked',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'react_ha_L',
|
||||
artist: 'Owen_ReactHappy',
|
||||
hierarchical: 'owen.state.react.emotion.happy.loop',
|
||||
semantic: 'OwenReactHappyLoop',
|
||||
state: 'react',
|
||||
emotion: 'happy',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'react_sa_L',
|
||||
artist: 'Owen_ReactSad',
|
||||
hierarchical: 'owen.state.react.emotion.sad.loop',
|
||||
semantic: 'OwenReactSadLoop',
|
||||
state: 'react',
|
||||
emotion: 'sad',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
// Type state animations
|
||||
{
|
||||
legacy: 'type_idle_L',
|
||||
artist: 'Owen_TypeIdle',
|
||||
hierarchical: 'owen.state.type.idle.loop',
|
||||
semantic: 'OwenTypeIdleLoop',
|
||||
state: 'type',
|
||||
emotion: '',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'type_an_L',
|
||||
artist: 'Owen_TypeAngry',
|
||||
hierarchical: 'owen.state.type.emotion.angry.loop',
|
||||
semantic: 'OwenTypeAngryLoop',
|
||||
state: 'type',
|
||||
emotion: 'angry',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
{
|
||||
legacy: 'type_sh_L',
|
||||
artist: 'Owen_TypeShocked',
|
||||
hierarchical: 'owen.state.type.emotion.shocked.loop',
|
||||
semantic: 'OwenTypeShockedLoop',
|
||||
state: 'type',
|
||||
emotion: 'shocked',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
// Sleep state animations
|
||||
{
|
||||
legacy: 'sleep_idle_L',
|
||||
artist: 'Owen_SleepIdle',
|
||||
hierarchical: 'owen.state.sleep.idle.loop',
|
||||
semantic: 'OwenSleepIdleLoop',
|
||||
state: 'sleep',
|
||||
emotion: '',
|
||||
type: 'loop',
|
||||
category: 'state'
|
||||
},
|
||||
// Transition animations
|
||||
{
|
||||
legacy: 'wait_2react_T',
|
||||
artist: 'Owen_WaitToReact',
|
||||
hierarchical: 'owen.transition.wait.to.react',
|
||||
semantic: 'OwenTransitionWaitToReact',
|
||||
fromState: 'wait',
|
||||
toState: 'react',
|
||||
emotion: '',
|
||||
type: 'transition',
|
||||
category: 'transition'
|
||||
},
|
||||
{
|
||||
legacy: 'react_2type_T',
|
||||
artist: 'Owen_ReactToType',
|
||||
hierarchical: 'owen.transition.react.to.type',
|
||||
semantic: 'OwenTransitionReactToType',
|
||||
fromState: 'react',
|
||||
toState: 'type',
|
||||
emotion: '',
|
||||
type: 'transition',
|
||||
category: 'transition'
|
||||
},
|
||||
{
|
||||
legacy: 'react_an2type_T',
|
||||
artist: 'Owen_ReactAngryToType',
|
||||
hierarchical: 'owen.transition.react.to.type.emotion.angry',
|
||||
semantic: 'OwenTransitionReactToTypeAngry',
|
||||
fromState: 'react',
|
||||
toState: 'type',
|
||||
emotion: 'angry',
|
||||
type: 'transition',
|
||||
category: 'transition'
|
||||
},
|
||||
{
|
||||
legacy: 'type_2wait_T',
|
||||
artist: 'Owen_TypeToWait',
|
||||
hierarchical: 'owen.transition.type.to.wait',
|
||||
semantic: 'OwenTransitionTypeToWait',
|
||||
fromState: 'type',
|
||||
toState: 'wait',
|
||||
emotion: '',
|
||||
type: 'transition',
|
||||
category: 'transition'
|
||||
},
|
||||
{
|
||||
legacy: 'sleep_2wait_T',
|
||||
artist: 'Owen_SleepToWait',
|
||||
hierarchical: 'owen.transition.sleep.to.wait',
|
||||
semantic: 'OwenTransitionSleepToWait',
|
||||
fromState: 'sleep',
|
||||
toState: 'wait',
|
||||
emotion: '',
|
||||
type: 'transition',
|
||||
category: 'transition'
|
||||
}
|
||||
]
|
||||
|
||||
// Build bidirectional mappings
|
||||
animations.forEach(anim => {
|
||||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||||
|
||||
schemes.forEach(scheme1 => {
|
||||
schemes.forEach(scheme2 => {
|
||||
if (scheme1 !== scheme2) {
|
||||
this.schemeMappings.set(anim[scheme1], anim[scheme2])
|
||||
}
|
||||
})
|
||||
|
||||
// Also map to animation definition
|
||||
this.schemeMappings.set(anim[scheme1], anim)
|
||||
})
|
||||
})
|
||||
|
||||
// Initialize pattern matchers for auto-detection
|
||||
this.initializePatterns()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize pattern matchers for different naming schemes
|
||||
* @private
|
||||
*/
|
||||
initializePatterns () {
|
||||
// Pattern matchers for different naming schemes
|
||||
this.patterns.set('legacy', [
|
||||
{
|
||||
regex: /^(\w+)_(\w+)_([LTQ])$/,
|
||||
extract: (match) => ({
|
||||
state: match[1],
|
||||
action: match[2],
|
||||
type: match[3] === 'L' ? 'loop' : match[3] === 'T' ? 'transition' : 'quirk'
|
||||
})
|
||||
},
|
||||
{
|
||||
regex: /^(\w+)_(\w{2})_([LTQ])$/,
|
||||
extract: (match) => ({
|
||||
state: match[1],
|
||||
emotion: this.mapEmotionCode(match[2]),
|
||||
type: match[3] === 'L' ? 'loop' : match[3] === 'T' ? 'transition' : 'quirk'
|
||||
})
|
||||
},
|
||||
{
|
||||
regex: /^(\w+)_(\w{2})?2(\w+)_T$/,
|
||||
extract: (match) => ({
|
||||
fromState: match[1],
|
||||
emotion: match[2] ? this.mapEmotionCode(match[2]) : '',
|
||||
toState: match[3],
|
||||
type: 'transition'
|
||||
})
|
||||
},
|
||||
{
|
||||
regex: /^(\w+)_2(\w+)_T$/,
|
||||
extract: (match) => ({
|
||||
fromState: match[1],
|
||||
toState: match[2],
|
||||
type: 'transition'
|
||||
})
|
||||
}
|
||||
])
|
||||
|
||||
this.patterns.set('artist', [
|
||||
{
|
||||
regex: /^Owen_(\w+)$/,
|
||||
extract: (match) => ({
|
||||
action: match[1],
|
||||
scheme: 'artist'
|
||||
})
|
||||
},
|
||||
{
|
||||
regex: /^Owen_(\w+)To(\w+)$/,
|
||||
extract: (match) => ({
|
||||
fromState: match[1].toLowerCase(),
|
||||
toState: match[2].toLowerCase(),
|
||||
type: 'transition'
|
||||
})
|
||||
},
|
||||
{
|
||||
regex: /^Owen_(\w+)(Angry|Happy|Sad|Shocked)$/,
|
||||
extract: (match) => ({
|
||||
state: match[1].toLowerCase(),
|
||||
emotion: match[2].toLowerCase(),
|
||||
type: 'loop'
|
||||
})
|
||||
},
|
||||
{
|
||||
regex: /^Owen_(\w+)(Angry|Happy|Sad|Shocked)To(\w+)$/,
|
||||
extract: (match) => ({
|
||||
fromState: match[1].toLowerCase(),
|
||||
emotion: match[2].toLowerCase(),
|
||||
toState: match[3].toLowerCase(),
|
||||
type: 'transition'
|
||||
})
|
||||
}
|
||||
])
|
||||
|
||||
this.patterns.set('hierarchical', [
|
||||
{
|
||||
regex: /^owen\.(\w+)\.(\w+)\.(\w+)(?:\.(\w+))?(?:\.(\w+))?$/,
|
||||
extract: (match) => ({
|
||||
category: match[1],
|
||||
subcategory: match[2],
|
||||
action: match[3],
|
||||
modifier: match[4],
|
||||
type: match[5] || match[4]
|
||||
})
|
||||
}
|
||||
])
|
||||
|
||||
this.patterns.set('semantic', [
|
||||
{
|
||||
regex: /^Owen(\w+)(\w+)(\w+)$/,
|
||||
extract: (match) => ({
|
||||
category: match[1].toLowerCase(),
|
||||
action: match[2].toLowerCase(),
|
||||
type: match[3].toLowerCase()
|
||||
})
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Map emotion codes to full names
|
||||
* @private
|
||||
* @param {string} code - Emotion code
|
||||
* @returns {string} Full emotion name
|
||||
*/
|
||||
mapEmotionCode (code) {
|
||||
const emotionMap = {
|
||||
an: 'angry',
|
||||
sh: 'shocked',
|
||||
ha: 'happy',
|
||||
sa: 'sad',
|
||||
'': 'neutral'
|
||||
}
|
||||
return emotionMap[code] || code
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any animation name to any other scheme
|
||||
* @param {string} fromName - Source animation name
|
||||
* @param {string} targetScheme - Target naming scheme ('legacy', 'artist', 'hierarchical', 'semantic')
|
||||
* @returns {string} Converted animation name
|
||||
*/
|
||||
convert (fromName, targetScheme = 'hierarchical') {
|
||||
// Direct lookup first
|
||||
const directMapping = this.schemeMappings.get(fromName)
|
||||
if (directMapping && typeof directMapping === 'object') {
|
||||
return directMapping[targetScheme] || fromName
|
||||
}
|
||||
|
||||
// Pattern-based conversion
|
||||
const detected = this.detectScheme(fromName)
|
||||
if (detected) {
|
||||
return this.generateName(detected.info, targetScheme)
|
||||
}
|
||||
|
||||
console.warn(`Could not convert animation name: ${fromName}`)
|
||||
return fromName
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect which naming scheme is being used
|
||||
* @param {string} name - Animation name to analyze
|
||||
* @returns {Object|null} Detection result with scheme and extracted info
|
||||
*/
|
||||
detectScheme (name) {
|
||||
for (const [scheme, patterns] of this.patterns) {
|
||||
for (const pattern of patterns) {
|
||||
const match = name.match(pattern.regex)
|
||||
if (match) {
|
||||
return {
|
||||
scheme,
|
||||
info: pattern.extract(match),
|
||||
originalName: name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate animation name in target scheme
|
||||
* @private
|
||||
* @param {Object} info - Animation information
|
||||
* @param {string} targetScheme - Target naming scheme
|
||||
* @returns {string} Generated animation name
|
||||
*/
|
||||
generateName (info, targetScheme) {
|
||||
switch (targetScheme) {
|
||||
case 'legacy':
|
||||
return this.generateLegacyName(info)
|
||||
case 'artist':
|
||||
return this.generateArtistName(info)
|
||||
case 'hierarchical':
|
||||
return this.generateHierarchicalName(info)
|
||||
case 'semantic':
|
||||
return this.generateSemanticName(info)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate legacy format name
|
||||
* @private
|
||||
* @param {Object} info - Animation information
|
||||
* @returns {string} Legacy format name
|
||||
*/
|
||||
generateLegacyName (info) {
|
||||
const typeMap = { loop: 'L', transition: 'T', quirk: 'Q' }
|
||||
const emotionMap = { angry: 'an', shocked: 'sh', happy: 'ha', sad: 'sa' }
|
||||
|
||||
if (info.type === 'transition' && info.fromState && info.toState) {
|
||||
const emotionPart = info.emotion ? emotionMap[info.emotion] || '' : ''
|
||||
return emotionPart
|
||||
? `${info.fromState}_${emotionPart}2${info.toState}_T`
|
||||
: `${info.fromState}_2${info.toState}_T`
|
||||
}
|
||||
|
||||
const state = info.state || info.fromState || 'wait'
|
||||
const action = info.action || (info.emotion ? emotionMap[info.emotion] : 'idle')
|
||||
const type = typeMap[info.type] || 'L'
|
||||
|
||||
return `${state}_${action}_${type}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate artist-friendly format name
|
||||
* @private
|
||||
* @param {Object} info - Animation information
|
||||
* @returns {string} Artist format name
|
||||
*/
|
||||
generateArtistName (info) {
|
||||
const parts = ['Owen']
|
||||
|
||||
if (info.type === 'transition') {
|
||||
const from = this.capitalize(info.fromState || info.state)
|
||||
const to = this.capitalize(info.toState)
|
||||
if (info.emotion) {
|
||||
parts.push(`${from}${this.capitalize(info.emotion)}To${to}`)
|
||||
} else {
|
||||
parts.push(`${from}To${to}`)
|
||||
}
|
||||
} else {
|
||||
if (info.state) parts.push(this.capitalize(info.state))
|
||||
if (info.action && info.action !== 'idle') parts.push(this.capitalize(info.action))
|
||||
if (info.emotion) parts.push(this.capitalize(info.emotion))
|
||||
}
|
||||
|
||||
return parts.join('_')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate hierarchical format name
|
||||
* @private
|
||||
* @param {Object} info - Animation information
|
||||
* @returns {string} Hierarchical format name
|
||||
*/
|
||||
generateHierarchicalName (info) {
|
||||
const parts = ['owen']
|
||||
|
||||
if (info.category) {
|
||||
parts.push(info.category)
|
||||
} else if (info.type === 'transition') {
|
||||
parts.push('transition')
|
||||
} else if (info.type === 'quirk') {
|
||||
parts.push('quirk')
|
||||
} else {
|
||||
parts.push('state')
|
||||
}
|
||||
|
||||
if (info.fromState && info.toState) {
|
||||
// Transition
|
||||
parts.push(info.fromState, 'to', info.toState)
|
||||
} else if (info.state) {
|
||||
parts.push(info.state)
|
||||
}
|
||||
|
||||
if (info.action && info.action !== 'idle') parts.push(info.action)
|
||||
if (info.emotion) parts.push('emotion', info.emotion)
|
||||
if (info.type) parts.push(info.type)
|
||||
|
||||
return parts.join('.')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate semantic format name
|
||||
* @private
|
||||
* @param {Object} info - Animation information
|
||||
* @returns {string} Semantic format name
|
||||
*/
|
||||
generateSemanticName (info) {
|
||||
const parts = ['Owen']
|
||||
|
||||
if (info.type === 'transition') {
|
||||
parts.push('Transition')
|
||||
if (info.fromState) parts.push(this.capitalize(info.fromState))
|
||||
parts.push('To')
|
||||
if (info.toState) parts.push(this.capitalize(info.toState))
|
||||
if (info.emotion) parts.push(this.capitalize(info.emotion))
|
||||
} else {
|
||||
if (info.type === 'quirk') parts.push('Quirk')
|
||||
if (info.state) parts.push(this.capitalize(info.state))
|
||||
if (info.action && info.action !== 'idle') parts.push(this.capitalize(info.action))
|
||||
if (info.emotion) parts.push(this.capitalize(info.emotion))
|
||||
if (info.type && info.type !== 'quirk') parts.push(this.capitalize(info.type))
|
||||
}
|
||||
|
||||
return parts.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalize first letter of string
|
||||
* @private
|
||||
* @param {string} str - String to capitalize
|
||||
* @returns {string} Capitalized string
|
||||
*/
|
||||
capitalize (str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all possible names for an animation
|
||||
* @param {string} animationName - Source animation name
|
||||
* @returns {Object} Object with all naming scheme variants
|
||||
*/
|
||||
getAllNames (animationName) {
|
||||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||||
const names = {}
|
||||
|
||||
schemes.forEach(scheme => {
|
||||
names[scheme] = this.convert(animationName, scheme)
|
||||
})
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch convert multiple animations
|
||||
* @param {string[]} animations - Array of animation names
|
||||
* @param {string} targetScheme - Target naming scheme
|
||||
* @returns {Object} Mapping of original names to converted names
|
||||
*/
|
||||
convertBatch (animations, targetScheme) {
|
||||
const converted = {}
|
||||
animations.forEach(name => {
|
||||
converted[name] = this.convert(name, targetScheme)
|
||||
})
|
||||
return converted
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate animation name format
|
||||
* @param {string} name - Animation name to validate
|
||||
* @returns {Object} Validation result with issues and suggestions
|
||||
*/
|
||||
validateAnimationName (name) {
|
||||
const issues = []
|
||||
const suggestions = []
|
||||
|
||||
// Check for common issues
|
||||
if (name.includes(' ')) {
|
||||
issues.push(`❌ "${name}" contains spaces - may cause issues`)
|
||||
suggestions.push(`💡 Suggestion: "${name.replace(/ /g, '_')}"`)
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
|
||||
issues.push(`❌ "${name}" contains invalid characters`)
|
||||
suggestions.push('💡 Use only letters, numbers, dots, underscores, and hyphens')
|
||||
}
|
||||
|
||||
if (name.length > 50) {
|
||||
issues.push(`⚠️ "${name}" is very long (${name.length} chars)`)
|
||||
suggestions.push('💡 Consider shortening the name')
|
||||
}
|
||||
|
||||
const detected = this.detectScheme(name)
|
||||
if (!detected) {
|
||||
issues.push(`⚠️ "${name}" doesn't match any known naming pattern`)
|
||||
suggestions.push('💡 Consider using one of: legacy, artist, hierarchical, or semantic format')
|
||||
} else {
|
||||
suggestions.push(`✅ Detected as ${detected.scheme} scheme`)
|
||||
}
|
||||
|
||||
return { issues, suggestions, detected }
|
||||
}
|
||||
}
|
||||
@ -32,13 +32,13 @@ export const ClipTypes = {
|
||||
*/
|
||||
export const States = {
|
||||
/** Waiting/idle state */
|
||||
WAITING: 'wait',
|
||||
WAITING: 'wait',
|
||||
/** Reacting to input state */
|
||||
REACTING: 'react',
|
||||
REACTING: 'react',
|
||||
/** Typing response state */
|
||||
TYPING: 'type',
|
||||
TYPING: 'type',
|
||||
/** Sleep/inactive state */
|
||||
SLEEPING: 'sleep'
|
||||
SLEEPING: 'sleep'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { States, Emotions, Config } from '../constants.js'
|
||||
import { AnimationNameMapper } from '../animation/AnimationNameMapper.js'
|
||||
|
||||
/**
|
||||
* Main controller for the Owen animation system
|
||||
@ -43,6 +44,12 @@ export class OwenAnimationContext {
|
||||
*/
|
||||
this.stateFactory = stateFactory
|
||||
|
||||
/**
|
||||
* Multi-scheme animation name mapper
|
||||
* @type {AnimationNameMapper}
|
||||
*/
|
||||
this.nameMapper = new AnimationNameMapper()
|
||||
|
||||
/**
|
||||
* Map of animation clips by name
|
||||
* @type {Map<string, AnimationClip>}
|
||||
@ -59,7 +66,7 @@ export class OwenAnimationContext {
|
||||
* Current active state
|
||||
* @type {string}
|
||||
*/
|
||||
this.currentState = States.WAITING
|
||||
this.currentState = States.WAITING
|
||||
|
||||
/**
|
||||
* Current active state handler
|
||||
@ -105,7 +112,7 @@ export class OwenAnimationContext {
|
||||
this.initializeStates()
|
||||
|
||||
// Start in wait state
|
||||
await this.transitionTo(States.WAITING)
|
||||
await this.transitionTo(States.WAITING)
|
||||
|
||||
this.initialized = true
|
||||
console.log('Owen Animation System initialized')
|
||||
@ -167,8 +174,8 @@ export class OwenAnimationContext {
|
||||
this.onUserActivity()
|
||||
|
||||
// If sleeping, wake up first
|
||||
if (this.currentState === States.SLEEPING) {
|
||||
await this.transitionTo(States.REACTING)
|
||||
if (this.currentState === States.SLEEPING) {
|
||||
await this.transitionTo(States.REACTING)
|
||||
}
|
||||
|
||||
// Let current state handle the message
|
||||
@ -177,10 +184,10 @@ export class OwenAnimationContext {
|
||||
}
|
||||
|
||||
// Transition to appropriate next state based on current state
|
||||
if (this.currentState === States.WAITING) {
|
||||
await this.transitionTo(States.REACTING);
|
||||
} else if (this.currentState === States.REACTING) {
|
||||
await this.transitionTo(States.TYPING)
|
||||
if (this.currentState === States.WAITING) {
|
||||
await this.transitionTo(States.REACTING)
|
||||
} else if (this.currentState === States.REACTING) {
|
||||
await this.transitionTo(States.TYPING)
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,8 +199,8 @@ export class OwenAnimationContext {
|
||||
this.resetActivityTimer()
|
||||
|
||||
// Wake up if sleeping
|
||||
if (this.currentState === States.SLEEPING) {
|
||||
this.transitionTo(States.WAITING)
|
||||
if (this.currentState === States.SLEEPING) {
|
||||
this.transitionTo(States.WAITING)
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +220,7 @@ export class OwenAnimationContext {
|
||||
*/
|
||||
async handleInactivity () {
|
||||
console.log('Inactivity detected, transitioning to sleep')
|
||||
await this.transitionTo(States.SLEEPING)
|
||||
await this.transitionTo(States.SLEEPING)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -234,18 +241,38 @@ export class OwenAnimationContext {
|
||||
|
||||
// Update inactivity timer
|
||||
this.inactivityTimer += deltaTime
|
||||
if (this.inactivityTimer > this.inactivityTimeout && this.currentState !== States.SLEEPING) {
|
||||
if (this.inactivityTimer > this.inactivityTimeout && this.currentState !== States.SLEEPING) {
|
||||
this.handleInactivity()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an animation clip by name
|
||||
* @param {string} name - The animation clip name
|
||||
* Get an animation clip by name (supports all naming schemes)
|
||||
* @param {string} name - The animation clip name in any supported scheme
|
||||
* @returns {AnimationClip|undefined} The animation clip or undefined if not found
|
||||
*/
|
||||
getClip (name) {
|
||||
return this.clips.get(name)
|
||||
// First try direct lookup
|
||||
let clip = this.clips.get(name)
|
||||
if (clip) return clip
|
||||
|
||||
// Try to find clip using name mapper
|
||||
try {
|
||||
const allNames = this.nameMapper.getAllNames(name)
|
||||
|
||||
// Try each possible name variant
|
||||
for (const variant of Object.values(allNames)) {
|
||||
clip = this.clips.get(variant)
|
||||
if (clip) return clip
|
||||
}
|
||||
} catch (error) {
|
||||
// If name mapping fails, continue with pattern search
|
||||
console.debug(`Name mapping failed for "${name}":`, error.message)
|
||||
}
|
||||
|
||||
// Fall back to pattern matching for legacy compatibility
|
||||
const exactMatches = this.getClipsByPattern(`^${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`)
|
||||
return exactMatches.length > 0 ? exactMatches[0] : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@ -266,6 +293,80 @@ export class OwenAnimationContext {
|
||||
return matches
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an animation clip by name in a specific naming scheme
|
||||
* @param {string} name - The animation name
|
||||
* @param {string} [targetScheme] - Target scheme: 'legacy', 'artist', 'hierarchical', 'semantic'
|
||||
* @returns {AnimationClip|undefined} The animation clip or undefined if not found
|
||||
*/
|
||||
getClipByScheme (name, targetScheme) {
|
||||
try {
|
||||
if (targetScheme) {
|
||||
const convertedName = this.nameMapper.convert(name, targetScheme)
|
||||
return this.clips.get(convertedName)
|
||||
} else {
|
||||
return this.getClip(name)
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(`Scheme conversion failed for "${name}" to "${targetScheme}":`, error.message)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all naming scheme variants for an animation
|
||||
* @param {string} name - The animation name in any scheme
|
||||
* @returns {Object} Object with all scheme variants: {legacy, artist, hierarchical, semantic}
|
||||
*/
|
||||
getAnimationNames (name) {
|
||||
try {
|
||||
return this.nameMapper.getAllNames(name)
|
||||
} catch (error) {
|
||||
console.warn(`Could not get animation name variants for "${name}":`, error.message)
|
||||
return {
|
||||
legacy: name,
|
||||
artist: name,
|
||||
hierarchical: name,
|
||||
semantic: name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an animation name and get suggestions if invalid
|
||||
* @param {string} name - The animation name to validate
|
||||
* @returns {Object} Validation result with isValid, scheme, error, and suggestions
|
||||
*/
|
||||
validateAnimationName (name) {
|
||||
try {
|
||||
return this.nameMapper.validateAnimationName(name)
|
||||
} catch (error) {
|
||||
return {
|
||||
isValid: false,
|
||||
scheme: 'unknown',
|
||||
error: error.message,
|
||||
suggestions: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available animations by state and emotion
|
||||
* @param {string} state - The state name (wait, react, type, sleep)
|
||||
* @param {string} [emotion] - The emotion name (angry, happy, sad, shocked, neutral)
|
||||
* @param {string} [scheme='semantic'] - The naming scheme to return
|
||||
* @returns {string[]} Array of animation names in the specified scheme
|
||||
*/
|
||||
getAnimationsByStateAndEmotion (state, emotion = '', scheme = 'semantic') {
|
||||
try {
|
||||
const animations = this.nameMapper.getAnimationsByFilter({ state, emotion })
|
||||
return animations.map(anim => anim[scheme] || anim.semantic)
|
||||
} catch (error) {
|
||||
console.warn(`Could not filter animations by state "${state}" and emotion "${emotion}":`, error.message)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state name
|
||||
* @returns {string} The current state name
|
||||
|
||||
14
src/index.js
14
src/index.js
@ -14,6 +14,20 @@ export { OwenAnimationContext } from './core/OwenAnimationContext.js'
|
||||
// Animation system exports
|
||||
export { AnimationClip, AnimationClipFactory } from './animation/AnimationClip.js'
|
||||
|
||||
// Multi-scheme animation naming exports
|
||||
export { AnimationNameMapper } from './animation/AnimationNameMapper.js'
|
||||
export {
|
||||
LegacyAnimations,
|
||||
ArtistAnimations,
|
||||
HierarchicalAnimations,
|
||||
SemanticAnimations,
|
||||
NamingSchemes,
|
||||
convertAnimationName,
|
||||
getAllAnimationNames,
|
||||
validateAnimationName,
|
||||
getAnimationsByStateAndEmotion
|
||||
} from './animation/AnimationConstants.js'
|
||||
|
||||
// Loader exports
|
||||
export { AnimationLoader, GLTFAnimationLoader } from './loaders/AnimationLoader.js'
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ export class ReactStateHandler extends StateHandler {
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor (context) {
|
||||
super(States.REACTING, context)
|
||||
super(States.REACTING, context)
|
||||
|
||||
/**
|
||||
* Current emotional state
|
||||
@ -33,7 +33,7 @@ export class ReactStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering REACTING state with emotion: ${emotion}`)
|
||||
console.log(`Entering REACTING state with emotion: ${emotion}`)
|
||||
this.emotion = emotion
|
||||
|
||||
// Play appropriate reaction
|
||||
@ -51,7 +51,7 @@ export class ReactStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit (toState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting REACTING state to ${toState} with emotion: ${emotion}`)
|
||||
console.log(`Exiting REACTING state to ${toState} with emotion: ${emotion}`)
|
||||
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip()
|
||||
@ -154,6 +154,6 @@ export class ReactStateHandler extends StateHandler {
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions () {
|
||||
return [ States.TYPING, States.WAITING ]
|
||||
return [States.TYPING, States.WAITING]
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor (context) {
|
||||
super(States.SLEEPING, context)
|
||||
super(States.SLEEPING, context)
|
||||
|
||||
/**
|
||||
* Sleep animation clip
|
||||
@ -39,7 +39,7 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering SLEEPING state from ${fromState}`)
|
||||
console.log(`Entering SLEEPING state from ${fromState}`)
|
||||
|
||||
// Play sleep transition if available
|
||||
const sleepTransition = this.context.getClip('wait_2sleep_T')
|
||||
@ -65,7 +65,7 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting SLEEPING state to ${toState}`)
|
||||
console.log(`Exiting SLEEPING state to ${toState}`)
|
||||
this.isDeepSleep = false
|
||||
|
||||
if (this.currentClip) {
|
||||
@ -107,8 +107,8 @@ export class SleepStateHandler extends StateHandler {
|
||||
// Any message should wake up the character
|
||||
if (this.isDeepSleep) {
|
||||
console.log('Waking up due to user message')
|
||||
// This will trigger a state transition to REACTING
|
||||
await this.context.transitionTo(States.REACTING)
|
||||
// This will trigger a state transition to REACTING
|
||||
await this.context.transitionTo(States.REACTING)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ export class SleepStateHandler extends StateHandler {
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions () {
|
||||
return [ States.WAITING, States.REACTING ]
|
||||
return [States.WAITING, States.REACTING]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +134,7 @@ export class SleepStateHandler extends StateHandler {
|
||||
*/
|
||||
async wakeUp () {
|
||||
if (this.isDeepSleep) {
|
||||
await this.context.transitionTo(States.WAITING)
|
||||
await this.context.transitionTo(States.WAITING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,10 +26,10 @@ export class StateFactory {
|
||||
this.stateHandlers = new Map()
|
||||
|
||||
// Register default state handlers
|
||||
this.registerStateHandler(States.WAITING, WaitStateHandler);
|
||||
this.registerStateHandler(States.REACTING, ReactStateHandler);
|
||||
this.registerStateHandler(States.TYPING, TypeStateHandler);
|
||||
this.registerStateHandler(States.SLEEPING, SleepStateHandler)
|
||||
this.registerStateHandler(States.WAITING, WaitStateHandler)
|
||||
this.registerStateHandler(States.REACTING, ReactStateHandler)
|
||||
this.registerStateHandler(States.TYPING, TypeStateHandler)
|
||||
this.registerStateHandler(States.SLEEPING, SleepStateHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -17,7 +17,7 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor (context) {
|
||||
super(States.TYPING, context)
|
||||
super(States.TYPING, context)
|
||||
|
||||
/**
|
||||
* Current emotional state
|
||||
@ -39,7 +39,7 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering TYPING state with emotion: ${emotion}`)
|
||||
console.log(`Entering TYPING state with emotion: ${emotion}`)
|
||||
this.emotion = emotion
|
||||
this.isTyping = true
|
||||
|
||||
@ -63,7 +63,7 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting TYPING state to ${toState}`)
|
||||
console.log(`Exiting TYPING state to ${toState}`)
|
||||
this.isTyping = false
|
||||
|
||||
if (this.currentClip) {
|
||||
@ -106,7 +106,7 @@ export class TypeStateHandler extends StateHandler {
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions () {
|
||||
return [ States.WAITING, States.REACTING ]
|
||||
return [States.WAITING, States.REACTING]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -17,7 +17,7 @@ export class WaitStateHandler extends StateHandler {
|
||||
* @param {OwenAnimationContext} context - The animation context
|
||||
*/
|
||||
constructor (context) {
|
||||
super(States.WAITING, context)
|
||||
super(States.WAITING, context)
|
||||
|
||||
/**
|
||||
* The main idle animation clip
|
||||
@ -51,7 +51,7 @@ export class WaitStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Entering WAITING state from ${fromState}`)
|
||||
console.log(`Entering WAITING state from ${fromState}`)
|
||||
|
||||
// Play idle loop
|
||||
this.idleClip = this.context.getClip('wait_idle_L')
|
||||
@ -72,7 +72,7 @@ export class WaitStateHandler extends StateHandler {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||
console.log(`Exiting WAITING state to ${toState}`)
|
||||
console.log(`Exiting WAITING state to ${toState}`)
|
||||
|
||||
if (this.currentClip) {
|
||||
await this.stopCurrentClip()
|
||||
@ -133,6 +133,6 @@ export class WaitStateHandler extends StateHandler {
|
||||
* @returns {string[]} Array of available state transitions
|
||||
*/
|
||||
getAvailableTransitions () {
|
||||
return [ States.REACTING, States.SLEEPING ]
|
||||
return [States.REACTING, States.SLEEPING]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user