Implement Owen Animation System with core classes, loaders, and state handlers

- Added OwenSystemFactory for creating the animation system.
- Introduced OwenAnimationContext to manage animations and states.
- Created AnimationLoader and GLTFAnimationLoader for loading animations.
- Developed state handlers: WaitStateHandler, ReactStateHandler, TypeStateHandler, SleepStateHandler.
- Implemented StateFactory for managing state handlers.
- Defined constants for clip types, states, and emotions.
- Added type definitions for TypeScript support.
- Configured Vite for building and serving the project.
- Added licenses (dual) to project.
This commit is contained in:
2025-05-23 21:36:52 +02:00
parent 9e5f576b68
commit 658e1e64b2
29 changed files with 6902 additions and 907 deletions

View File

@ -0,0 +1,128 @@
/**
* @fileoverview Type state handler implementation
* @module states
*/
import { StateHandler } from './StateHandler.js';
import { States, Emotions } from '../constants.js';
/**
* Handler for the Type state
* @class
* @extends StateHandler
*/
export class TypeStateHandler extends StateHandler {
/**
* Create a type state handler
* @param {OwenAnimationContext} context - The animation context
*/
constructor(context) {
super(States.TYPE, context);
/**
* Current emotional state
* @type {string}
*/
this.emotion = Emotions.NEUTRAL;
/**
* Whether currently typing
* @type {boolean}
*/
this.isTyping = false;
}
/**
* Enter the type state
* @param {string|null} [_fromState=null] - The previous state (unused)
* @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with
* @returns {Promise<void>}
*/
async enter(_fromState = null, emotion = Emotions.NEUTRAL) {
console.log(`Entering TYPE state with emotion: ${emotion}`);
this.emotion = emotion;
this.isTyping = true;
// Play appropriate typing animation
let typingClipName = 'type_idle_L';
if (emotion !== Emotions.NEUTRAL) {
typingClipName = `type_${emotion}_L`;
}
const typingClip = this.context.getClip(typingClipName);
if (typingClip) {
await typingClip.play();
this.currentClip = typingClip;
}
}
/**
* Exit the type state
* @param {string|null} [toState=null] - The next state
* @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to exit with (unused)
* @returns {Promise<void>}
*/
async exit(toState = null, _emotion = Emotions.NEUTRAL) {
console.log(`Exiting TYPE state to ${toState}`);
this.isTyping = false;
if (this.currentClip) {
await this.stopCurrentClip();
}
// Play transition if available
let transitionName = `type_2${toState}_T`;
if (this.emotion !== Emotions.NEUTRAL) {
transitionName = `type_${this.emotion}2${toState}_T`;
}
const transition = this.context.getClip(transitionName);
if (transition) {
await transition.play();
await this.waitForClipEnd(transition);
}
}
/**
* Finish typing and prepare to transition
* @returns {Promise<void>}
*/
async finishTyping() {
if (!this.isTyping) return;
// Play typing finish animation if available
const finishClip = this.context.getClip('type_finish_Q');
if (finishClip && this.currentClip) {
await this.stopCurrentClip(0.2);
await finishClip.play();
await this.waitForClipEnd(finishClip);
}
this.isTyping = false;
}
/**
* Get available transitions from type state
* @returns {string[]} Array of available state transitions
*/
getAvailableTransitions() {
return [ States.WAIT, States.REACT ];
}
/**
* Check if currently typing
* @returns {boolean} True if typing, false otherwise
*/
getIsTyping() {
return this.isTyping;
}
/**
* Set typing state
* @param {boolean} typing - Whether currently typing
* @returns {void}
*/
setTyping(typing) {
this.isTyping = typing;
}
}