diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ebc58b4..3c5a3b8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,9 +3,9 @@ contact_links: - name: Discussion Forum url: https://github.com/kjanat/Owen/discussions about: Ask questions, share ideas, and discuss the Owen Animation System with the community - - name: Discord Community - url: https://discord.gg/owen-animation - about: Join our Discord server for real-time chat and support + # - name: Discord Community + # url: https://discord.gg/owen-animation + # about: Join our Discord server for real-time chat and support - name: Documentation url: https://kjanat.github.io/Owen/ about: Check our comprehensive documentation and guides diff --git a/.gitignore b/.gitignore index c3cc1cd..6dd0ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ *.tmp *.temp -# Documentation output -/docs/ +# # Documentation output +# /docs/ # Animation assets (if storing locally) /assets/models/ diff --git a/docs/animation_AnimationClip.js.html b/docs/animation_AnimationClip.js.html new file mode 100644 index 0000000..cb06881 --- /dev/null +++ b/docs/animation_AnimationClip.js.html @@ -0,0 +1,375 @@ + + + + + JSDoc: Source: animation/AnimationClip.js + + + + + + + + + +
+

Source: animation/AnimationClip.js

+ +
+
+
/**
+ * @fileoverview Core animation classes for clip management and creation
+ * @module animation
+ */
+
+import * as THREE from 'three'
+import { ClipTypes, Config } from '../constants.js'
+
+/**
+ * Represents a single animation clip with metadata and Three.js action
+ * @class
+ */
+export class AnimationClip {
+  /**
+   * Create an animation clip
+   * @param {string} name - The name of the animation clip
+   * @param {THREE.AnimationClip} threeAnimation - The Three.js animation clip
+   * @param {Object} metadata - Parsed metadata from animation name
+   */
+  constructor (name, threeAnimation, metadata) {
+    /**
+     * The name of the animation clip
+     * @type {string}
+     */
+    this.name = name
+
+    /**
+     * The Three.js animation clip
+     * @type {THREE.AnimationClip}
+     */
+    this.animation = threeAnimation
+
+    /**
+     * Parsed metadata about the animation
+     * @type {Object}
+     */
+    this.metadata = metadata
+
+    /**
+     * The Three.js animation action
+     * @type {THREE.AnimationAction|null}
+     */
+    this.action = null
+
+    /**
+     * The animation mixer
+     * @type {THREE.AnimationMixer|null}
+     */
+    this.mixer = null
+  }
+
+  /**
+   * Create and configure a Three.js action for this clip
+   * @param {THREE.AnimationMixer} mixer - The animation mixer
+   * @returns {THREE.AnimationAction} The created action
+   */
+  createAction (mixer) {
+    this.mixer = mixer
+    this.action = mixer.clipAction(this.animation)
+
+    // Configure based on type
+    if (
+      this.metadata.type === ClipTypes.LOOP ||
+      this.metadata.type === ClipTypes.NESTED_LOOP
+    ) {
+      this.action.setLoop(THREE.LoopRepeat, Infinity)
+    } else {
+      this.action.setLoop(THREE.LoopOnce)
+      this.action.clampWhenFinished = true
+    }
+
+    return this.action
+  }
+
+  /**
+   * Play the animation with optional fade in
+   * @param {number} [fadeInDuration=0.3] - Fade in duration in seconds
+   * @returns {Promise<void>} Promise that resolves when fade in completes
+   */
+  play (fadeInDuration = Config.DEFAULT_FADE_IN) {
+    if (this.action) {
+      this.action.reset()
+      this.action.fadeIn(fadeInDuration)
+      this.action.play()
+    }
+  }
+
+  /**
+   * Stop the animation with optional fade out
+   * @param {number} [fadeOutDuration=0.3] - Fade out duration in seconds
+   * @returns {Promise<void>} Promise that resolves when fade out completes
+   */
+  stop (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
+    if (this.action) {
+      this.action.fadeOut(fadeOutDuration)
+      setTimeout(() => {
+        if (this.action) {
+          this.action.stop()
+        }
+      }, fadeOutDuration * 1000)
+    }
+  }
+
+  /**
+   * Check if the animation is currently playing
+   * @returns {boolean} True if playing, false otherwise
+   */
+  isPlaying () {
+    return this.action?.isRunning() || false
+  }
+}
+
+/**
+ * Factory for creating animation clips with parsed metadata
+ * @class
+ */
+export class AnimationClipFactory {
+  /**
+   * Create an animation clip factory
+   * @param {AnimationLoader} animationLoader - The animation loader instance
+   */
+  constructor (animationLoader) {
+    /**
+     * The animation loader for loading animation data
+     * @type {AnimationLoader}
+     */
+    this.animationLoader = animationLoader
+
+    /**
+     * Cache for created animation clips
+     * @type {Map<string, AnimationClip>}
+     */
+    this.clipCache = new Map()
+  }
+
+  /**
+   * Parse animation name and create clip metadata
+   * Format: [state]_[action]_[type] or [state]_[action]2[toState]_[emotion]_T
+   * @param {string} name - The animation name to parse
+   * @returns {Object} Parsed metadata object
+   */
+  parseAnimationName (name) {
+    const parts = name.split('_')
+    const state = parts[0]
+    const action = parts[1]
+
+    // Handle transitions with emotions
+    if (parts[2]?.includes('2') && parts[3] === ClipTypes.TRANSITION) {
+      const [, toState] = parts[2].split('2')
+      return {
+        state,
+        action,
+        toState,
+        emotion: parts[2] || '',
+        type: ClipTypes.TRANSITION,
+        isTransition: true,
+        hasEmotion: true
+      }
+    }
+
+    // Handle regular transitions
+    if (parts[2] === ClipTypes.TRANSITION) {
+      return {
+        state,
+        action,
+        type: ClipTypes.TRANSITION,
+        isTransition: true
+      }
+    }
+
+    // Handle nested animations
+    if (parts[2] === ClipTypes.NESTED_IN || parts[2] === ClipTypes.NESTED_OUT) {
+      return {
+        state,
+        action,
+        type: parts[2],
+        nestedType: parts[3],
+        isNested: true
+      }
+    }
+
+    // Handle nested loops and quirks
+    if (
+      parts[3] === ClipTypes.NESTED_LOOP ||
+      parts[3] === ClipTypes.NESTED_QUIRK
+    ) {
+      return {
+        state,
+        action,
+        subAction: parts[2],
+        type: parts[3],
+        isNested: true
+      }
+    }
+
+    // Handle standard loops and quirks
+    return {
+      state,
+      action,
+      type: parts[2],
+      isStandard: true
+    }
+  }
+
+  /**
+   * Create an animation clip from a name
+   * @param {string} name - The animation name
+   * @returns {Promise<AnimationClip>} The created animation clip
+   */
+  async createClip (name) {
+    if (this.clipCache.has(name)) {
+      return this.clipCache.get(name)
+    }
+
+    const metadata = this.parseAnimationName(name)
+    const animation = await this.animationLoader.loadAnimation(name)
+
+    const clip = new AnimationClip(name, animation, metadata)
+    this.clipCache.set(name, clip)
+
+    return clip
+  }
+
+  /**
+   * Create all animation clips from a model's animations
+   * @param {THREE.Object3D} model - The 3D model containing animations
+   * @returns {Promise<Map<string, AnimationClip>>} Map of animation name to clip
+   */
+  async createClipsFromModel (model) {
+    const clips = new Map()
+    const animations = model.animations || []
+
+    for (const animation of animations) {
+      const clip = await this.createClip(animation.name, model)
+      clips.set(animation.name, clip)
+    }
+
+    return clips
+  }
+
+  /**
+   * Clear the clip cache
+   * @returns {void}
+   */
+  clearCache () {
+    this.clipCache.clear()
+  }
+
+  /**
+   * Get cached clip by name
+   * @param {string} name - The animation name
+   * @returns {AnimationClip|undefined} The cached clip or undefined
+   */
+  getCachedClip (name) {
+    return this.clipCache.get(name)
+  }
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/animation_AnimationConstants.js.html b/docs/animation_AnimationConstants.js.html new file mode 100644 index 0000000..e85cb69 --- /dev/null +++ b/docs/animation_AnimationConstants.js.html @@ -0,0 +1,398 @@ + + + + + JSDoc: Source: animation/AnimationConstants.js + + + + + + + + + +
+

Source: animation/AnimationConstants.js

+ +
+
+
/**
+ * @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)
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/animation_AnimationNameMapper.js.html b/docs/animation_AnimationNameMapper.js.html new file mode 100644 index 0000000..7b94f09 --- /dev/null +++ b/docs/animation_AnimationNameMapper.js.html @@ -0,0 +1,717 @@ + + + + + JSDoc: Source: animation/AnimationNameMapper.js + + + + + + + + + +
+

Source: animation/AnimationNameMapper.js

+ +
+
+
/**
+ * @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 }
+  }
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/constants.js.html b/docs/constants.js.html new file mode 100644 index 0000000..1c32662 --- /dev/null +++ b/docs/constants.js.html @@ -0,0 +1,196 @@ + + + + + JSDoc: Source: constants.js + + + + + + + + + +
+

Source: constants.js

+ +
+
+
/**
+ * @fileoverview Animation system constants and enumerations
+ * @module constants
+ */
+
+/**
+ * Animation clip types based on naming convention
+ * @readonly
+ * @enum {string}
+ */
+export const ClipTypes = {
+  /** Loop animation */
+  LOOP: 'L',
+  /** Quirk animation */
+  QUIRK: 'Q',
+  /** Nested loop animation */
+  NESTED_LOOP: 'NL',
+  /** Nested quirk animation */
+  NESTED_QUIRK: 'NQ',
+  /** Nested in transition */
+  NESTED_IN: 'IN_NT',
+  /** Nested out transition */
+  NESTED_OUT: 'OUT_NT',
+  /** Transition animation */
+  TRANSITION: 'T'
+}
+
+/**
+ * Character animation states
+ * @readonly
+ * @enum {string}
+ */
+export const States = {
+  /** Waiting/idle state */
+  WAITING: 'wait',
+  /** Reacting to input state */
+  REACTING: 'react',
+  /** Typing response state */
+  TYPING: 'type',
+  /** Sleep/inactive state */
+  SLEEPING: 'sleep'
+}
+
+/**
+ * Character emotional states
+ * @readonly
+ * @enum {string}
+ */
+export const Emotions = {
+  /** Neutral emotion */
+  NEUTRAL: '',
+  /** Angry emotion */
+  ANGRY: 'an',
+  /** Shocked emotion */
+  SHOCKED: 'sh',
+  /** Happy emotion */
+  HAPPY: 'ha',
+  /** Sad emotion */
+  SAD: 'sa'
+}
+
+/**
+ * Default configuration values
+ * @readonly
+ * @type {Object}
+ */
+export const Config = {
+  /** Default fade in duration for animations (ms) */
+  DEFAULT_FADE_IN: 0.3,
+  /** Default fade out duration for animations (ms) */
+  DEFAULT_FADE_OUT: 0.3,
+  /** Default quirk interval (ms) */
+  QUIRK_INTERVAL: 5000,
+  /** Default inactivity timeout (ms) */
+  INACTIVITY_TIMEOUT: 60000,
+  /** Quirk probability threshold */
+  QUIRK_PROBABILITY: 0.3
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/core_OwenAnimationContext.js.html b/docs/core_OwenAnimationContext.js.html new file mode 100644 index 0000000..96ccdfd --- /dev/null +++ b/docs/core_OwenAnimationContext.js.html @@ -0,0 +1,551 @@ + + + + + JSDoc: Source: core/OwenAnimationContext.js + + + + + + + + + +
+

Source: core/OwenAnimationContext.js

+ +
+
+
/**
+ * @fileoverview Main animation context controller
+ * @module core
+ */
+
+import { States, Emotions, Config } from '../constants.js'
+import { AnimationNameMapper } from '../animation/AnimationNameMapper.js'
+
+/**
+ * Main controller for the Owen animation system
+ * Manages state transitions, animation playback, and user interactions
+ * @class
+ */
+export class OwenAnimationContext {
+  /**
+   * Create an Owen animation context
+   * @param {THREE.Object3D} model - The 3D character model
+   * @param {THREE.AnimationMixer} mixer - The Three.js animation mixer
+   * @param {AnimationClipFactory} animationClipFactory - Factory for creating clips
+   * @param {StateFactory} stateFactory - Factory for creating state handlers
+   */
+  constructor (model, mixer, animationClipFactory, stateFactory) {
+    /**
+     * The 3D character model
+     * @type {THREE.Object3D}
+     */
+    this.model = model
+
+    /**
+     * The Three.js animation mixer
+     * @type {THREE.AnimationMixer}
+     */
+    this.mixer = mixer
+
+    /**
+     * Factory for creating animation clips
+     * @type {AnimationClipFactory}
+     */
+    this.animationClipFactory = animationClipFactory
+
+    /**
+     * Factory for creating state handlers
+     * @type {StateFactory}
+     */
+    this.stateFactory = stateFactory
+
+    /**
+     * Multi-scheme animation name mapper
+     * @type {AnimationNameMapper}
+     */
+    this.nameMapper = new AnimationNameMapper()
+
+    /**
+     * Map of animation clips by name
+     * @type {Map<string, AnimationClip>}
+     */
+    this.clips = new Map()
+
+    /**
+     * Map of state handlers by name
+     * @type {Map<string, StateHandler>}
+     */
+    this.states = new Map()
+
+    /**
+     * Current active state
+     * @type {string}
+     */
+    this.currentState = States.WAITING
+
+    /**
+     * Current active state handler
+     * @type {StateHandler|null}
+     */
+    this.currentStateHandler = null
+
+    /**
+     * Timer for inactivity detection
+     * @type {number}
+     */
+    this.inactivityTimer = 0
+
+    /**
+     * Inactivity timeout in milliseconds
+     * @type {number}
+     */
+    this.inactivityTimeout = Config.INACTIVITY_TIMEOUT
+
+    /**
+     * Whether the system is initialized
+     * @type {boolean}
+     */
+    this.initialized = false
+  }
+
+  /**
+   * Initialize the animation system
+   * @returns {Promise<void>}
+   */
+  async initialize () {
+    if (this.initialized) return
+
+    // Create animation clips from model
+    this.clips = await this.animationClipFactory.createClipsFromModel(this.model)
+
+    // Create actions for all clips
+    for (const [, clip] of this.clips) {
+      clip.createAction(this.mixer)
+    }
+
+    // Initialize state handlers
+    this.initializeStates()
+
+    // Start in wait state
+    await this.transitionTo(States.WAITING)
+
+    this.initialized = true
+    console.log('Owen Animation System initialized')
+  }
+
+  /**
+   * Initialize all state handlers
+   * @private
+   * @returns {void}
+   */
+  initializeStates () {
+    const stateNames = this.stateFactory.getAvailableStates()
+
+    for (const stateName of stateNames) {
+      const handler = this.stateFactory.createStateHandler(stateName, this)
+      this.states.set(stateName, handler)
+    }
+  }
+
+  /**
+   * Transition to a new state
+   * @param {string} newStateName - The name of the state to transition to
+   * @param {string} [emotion=Emotions.NEUTRAL] - The emotion for the transition
+   * @returns {Promise<void>}
+   * @throws {Error} If state is not found or transition is invalid
+   */
+  async transitionTo (newStateName, emotion = Emotions.NEUTRAL) {
+    if (!this.states.has(newStateName)) {
+      throw new Error(`State '${newStateName}' not found`)
+    }
+
+    const oldState = this.currentState
+    const newStateHandler = this.states.get(newStateName)
+
+    console.log(`Transitioning from ${oldState} to ${newStateName}`)
+
+    // Exit current state
+    if (this.currentStateHandler) {
+      await this.currentStateHandler.exit(newStateName, emotion)
+    }
+
+    // Enter new state
+    this.currentState = newStateName
+    this.currentStateHandler = newStateHandler
+    await this.currentStateHandler.enter(oldState, emotion)
+
+    // Reset inactivity timer
+    this.resetActivityTimer()
+  }
+
+  /**
+   * Handle a user message
+   * @param {string} message - The user message
+   * @returns {Promise<void>}
+   */
+  async handleUserMessage (message) {
+    console.log(`Handling user message: "${message}"`)
+
+    this.onUserActivity()
+
+    // If sleeping, wake up first
+    if (this.currentState === States.SLEEPING) {
+      await this.transitionTo(States.REACTING)
+    }
+
+    // Let current state handle the message
+    if (this.currentStateHandler) {
+      await this.currentStateHandler.handleMessage(message)
+    }
+
+    // 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)
+    }
+  }
+
+  /**
+   * Called when user activity is detected
+   * @returns {void}
+   */
+  onUserActivity () {
+    this.resetActivityTimer()
+
+    // Wake up if sleeping
+    if (this.currentState === States.SLEEPING) {
+      this.transitionTo(States.WAITING)
+    }
+  }
+
+  /**
+   * Reset the inactivity timer
+   * @private
+   * @returns {void}
+   */
+  resetActivityTimer () {
+    this.inactivityTimer = 0
+  }
+
+  /**
+   * Handle inactivity timeout
+   * @private
+   * @returns {Promise<void>}
+   */
+  async handleInactivity () {
+    console.log('Inactivity detected, transitioning to sleep')
+    await this.transitionTo(States.SLEEPING)
+  }
+
+  /**
+   * Update the animation system (call every frame)
+   * @param {number} deltaTime - Time elapsed since last update (ms)
+   * @returns {void}
+   */
+  update (deltaTime) {
+    if (!this.initialized) return
+
+    // Update Three.js mixer
+    this.mixer.update(deltaTime / 1000) // Convert to seconds
+
+    // Update current state
+    if (this.currentStateHandler) {
+      this.currentStateHandler.update(deltaTime)
+    }
+
+    // Update inactivity timer
+    this.inactivityTimer += deltaTime
+    if (this.inactivityTimer > this.inactivityTimeout && this.currentState !== States.SLEEPING) {
+      this.handleInactivity()
+    }
+  }
+
+  /**
+   * 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) {
+    // 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
+  }
+
+  /**
+   * Get animation clips matching a pattern
+   * @param {string} pattern - Pattern to match (supports * wildcards)
+   * @returns {AnimationClip[]} Array of matching clips
+   */
+  getClipsByPattern (pattern) {
+    const regex = new RegExp(pattern.replace(/\*/g, '.*'))
+    const matches = []
+
+    for (const [name, clip] of this.clips) {
+      if (regex.test(name)) {
+        matches.push(clip)
+      }
+    }
+
+    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
+   */
+  getCurrentState () {
+    return this.currentState
+  }
+
+  /**
+   * Get the current state handler
+   * @returns {StateHandler|null} The current state handler
+   */
+  getCurrentStateHandler () {
+    return this.currentStateHandler
+  }
+
+  /**
+   * Get available transitions from current state
+   * @returns {string[]} Array of available state transitions
+   */
+  getAvailableTransitions () {
+    if (this.currentStateHandler) {
+      return this.currentStateHandler.getAvailableTransitions()
+    }
+    return []
+  }
+
+  /**
+   * Get all available animation clip names
+   * @returns {string[]} Array of clip names
+   */
+  getAvailableClips () {
+    return Array.from(this.clips.keys())
+  }
+
+  /**
+   * Get all available state names
+   * @returns {string[]} Array of state names
+   */
+  getAvailableStates () {
+    return Array.from(this.states.keys())
+  }
+
+  /**
+   * Dispose of the animation system and clean up resources
+   * @returns {void}
+   */
+  dispose () {
+    // Stop all animations
+    for (const [, clip] of this.clips) {
+      if (clip.action) {
+        clip.action.stop()
+      }
+    }
+
+    // Clear caches
+    this.clips.clear()
+    this.states.clear()
+    this.animationClipFactory.clearCache()
+
+    this.initialized = false
+    console.log('Owen Animation System disposed')
+  }
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/factories_OwenSystemFactory.js.html b/docs/factories_OwenSystemFactory.js.html new file mode 100644 index 0000000..1a48a45 --- /dev/null +++ b/docs/factories_OwenSystemFactory.js.html @@ -0,0 +1,209 @@ + + + + + JSDoc: Source: factories/OwenSystemFactory.js + + + + + + + + + +
+

Source: factories/OwenSystemFactory.js

+ +
+
+
/**
+ * @fileoverview Main system factory for creating the complete Owen system
+ * @module factories
+ */
+
+import * as THREE from 'three'
+import { OwenAnimationContext } from '../core/OwenAnimationContext.js'
+import { AnimationClipFactory } from '../animation/AnimationClip.js'
+import { GLTFAnimationLoader } from '../loaders/AnimationLoader.js'
+import { StateFactory } from '../states/StateFactory.js'
+
+/**
+ * Main factory for creating the complete Owen animation system
+ * @class
+ */
+export class OwenSystemFactory {
+  /**
+     * Create a complete Owen animation system
+     * @param {THREE.Object3D} gltfModel - The loaded GLTF model
+     * @param {THREE.Scene} scene - The Three.js scene
+     * @param {Object} [options={}] - Configuration options
+     * @param {THREE.GLTFLoader} [options.gltfLoader] - Custom GLTF loader
+     * @returns {Promise<OwenAnimationContext>} The configured Owen system
+     */
+  static async createOwenSystem (gltfModel, scene, options = {}) {
+    // Create Three.js animation mixer
+    const mixer = new THREE.AnimationMixer(gltfModel)
+
+    // Create GLTF loader if not provided
+    const gltfLoader = options.gltfLoader || new THREE.GLTFLoader()
+
+    // Create animation loader
+    const animationLoader = new GLTFAnimationLoader(gltfLoader)
+
+    // Preload animations from the model
+    await animationLoader.preloadAnimations(gltfModel)
+
+    // Create animation clip factory
+    const animationClipFactory = new AnimationClipFactory(animationLoader)
+
+    // Create state factory
+    const stateFactory = new StateFactory()
+
+    // Create the main Owen context
+    const owenContext = new OwenAnimationContext(
+      gltfModel,
+      mixer,
+      animationClipFactory,
+      stateFactory
+    )
+
+    // Initialize the system
+    await owenContext.initialize()
+
+    return owenContext
+  }
+
+  /**
+     * Create a basic Owen system with minimal configuration
+     * @param {THREE.Object3D} model - The 3D model
+     * @returns {Promise<OwenAnimationContext>} The configured Owen system
+     */
+  static async createBasicOwenSystem (model) {
+    const scene = new THREE.Scene()
+    scene.add(model)
+
+    return await OwenSystemFactory.createOwenSystem(model, scene)
+  }
+
+  /**
+     * Create an Owen system with custom state handlers
+     * @param {THREE.Object3D} gltfModel - The loaded GLTF model
+     * @param {THREE.Scene} scene - The Three.js scene
+     * @param {Map<string, Function>} customStates - Map of state name to handler class
+     * @returns {Promise<OwenAnimationContext>} The configured Owen system
+     */
+  static async createCustomOwenSystem (gltfModel, scene, customStates) {
+    const system = await OwenSystemFactory.createOwenSystem(gltfModel, scene)
+
+    // Register custom state handlers
+    const stateFactory = system.stateFactory
+    for (const [stateName, handlerClass] of customStates) {
+      stateFactory.registerStateHandler(stateName, handlerClass)
+    }
+
+    // Reinitialize with custom states
+    system.initializeStates()
+
+    return system
+  }
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot new file mode 100644 index 0000000..5d20d91 Binary files /dev/null and b/docs/fonts/OpenSans-Bold-webfont.eot differ diff --git a/docs/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg new file mode 100644 index 0000000..3ed7be4 --- /dev/null +++ b/docs/fonts/OpenSans-Bold-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff new file mode 100644 index 0000000..1205787 Binary files /dev/null and b/docs/fonts/OpenSans-Bold-webfont.woff differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot new file mode 100644 index 0000000..1f639a1 Binary files /dev/null and b/docs/fonts/OpenSans-BoldItalic-webfont.eot differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg new file mode 100644 index 0000000..6a2607b --- /dev/null +++ b/docs/fonts/OpenSans-BoldItalic-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff new file mode 100644 index 0000000..ed760c0 Binary files /dev/null and b/docs/fonts/OpenSans-BoldItalic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot new file mode 100644 index 0000000..0c8a0ae Binary files /dev/null and b/docs/fonts/OpenSans-Italic-webfont.eot differ diff --git a/docs/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg new file mode 100644 index 0000000..e1075dc --- /dev/null +++ b/docs/fonts/OpenSans-Italic-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff new file mode 100644 index 0000000..ff652e6 Binary files /dev/null and b/docs/fonts/OpenSans-Italic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot new file mode 100644 index 0000000..1486840 Binary files /dev/null and b/docs/fonts/OpenSans-Light-webfont.eot differ diff --git a/docs/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 0000000..11a472c --- /dev/null +++ b/docs/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 0000000..e786074 Binary files /dev/null and b/docs/fonts/OpenSans-Light-webfont.woff differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot new file mode 100644 index 0000000..8f44592 Binary files /dev/null and b/docs/fonts/OpenSans-LightItalic-webfont.eot differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg new file mode 100644 index 0000000..431d7e3 --- /dev/null +++ b/docs/fonts/OpenSans-LightItalic-webfont.svg @@ -0,0 +1,1835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff new file mode 100644 index 0000000..43e8b9e Binary files /dev/null and b/docs/fonts/OpenSans-LightItalic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000..6bbc3cf Binary files /dev/null and b/docs/fonts/OpenSans-Regular-webfont.eot differ diff --git a/docs/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg new file mode 100644 index 0000000..25a3952 --- /dev/null +++ b/docs/fonts/OpenSans-Regular-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000..e231183 Binary files /dev/null and b/docs/fonts/OpenSans-Regular-webfont.woff differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..dfc6073 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,633 @@ + + + + + JSDoc: Home + + + + + + + + + +
+

Home

+ +

+ +
+
+

Owen Animation System

+

+ A comprehensive Three.js animation system for character state + management with clean architecture principles, dependency injection, + and factory patterns. +

+

+ Gitea Issues + Gitea Pull Requests + Gitea Release +

+

🎯 Overview

+

+ The Owen Animation System is a sophisticated character animation + framework built for Three.js that manages complex state machines, + emotional responses, and animation transitions. It's designed with + clean architecture principles to be maintainable, extensible, and + testable. +

+

✨ Key Features

+
    +
  • + 🤖 State Machine Implementation - Complete state + management system with Wait, React, + Type, and Sleep states +
  • +
  • + 😊 Emotional Response System - Analyzes user + input to determine appropriate emotional animations +
  • +
  • + 🔄 Animation Transition Management - Smooth + transitions between states with fade in/out support +
  • +
  • + 📝 Multi-Scheme Animation Naming - Supports + legacy, artist-friendly, hierarchical, and semantic naming schemes +
  • +
  • + 🎨 Artist-Friendly Workflow - Blender-compatible + naming for 3D artists (Owen_WaitIdle, + Owen_ReactHappy) +
  • +
  • + 👨‍💻 Developer Experience - Type-safe constants and + semantic naming (OwenWaitIdleLoop, + OwenReactAngryTransition) +
  • +
  • + 🏗️ Clean Architecture - Uses dependency + injection, factory patterns, and separation of concerns +
  • +
  • + ⚡ Performance Optimized - Efficient animation + caching and resource management +
  • +
  • + 🧩 Extensible Design - Easy to add new states, + emotions, and animation types +
  • +
  • + 🔄 Backward Compatibility - Legacy naming scheme + continues to work alongside new schemes +
  • +
+

🚀 Installation

+

Prerequisites

+
    +
  • Node.js 16.0.0 or higher
  • +
  • + Three.js compatible 3D model with animations (GLTF/GLB format + recommended) +
  • +
+

Install Dependencies

+
# Clone the repository
+git clone https://gitea.kajkowalski.nl/kjanat/Owen.git
+cd Owen
+
+# Install dependencies
+npm install
+
+# Install dev dependencies
+npm install --include dev
+
+

📖 Usage

+

Basic Usage

+
import * as THREE from "three";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
+import { OwenSystemFactory } from "owen";
+
+// Load your 3D model
+const loader = new GLTFLoader();
+const gltf = await loader.loadAsync("path/to/your-model.gltf");
+
+// Create a Three.js scene
+const scene = new THREE.Scene();
+scene.add(gltf.scene);
+
+// Create the Owen animation system
+const owenSystem = await OwenSystemFactory.createOwenSystem(gltf, scene);
+
+// Handle user messages
+await owenSystem.handleUserMessage("Hello Owen!");
+
+// Update in your render loop
+function animate() {
+  const deltaTime = clock.getDelta() * 1000; // Convert to milliseconds
+  owenSystem.update(deltaTime);
+
+  renderer.render(scene, camera);
+  requestAnimationFrame(animate);
+}
+
+
+

+ [!NOTE] Replace path/to/your-model.gltf with the + actual path to your 3D character model. The system is designed to + work with any GLTF model that follows the animation naming + convention. +

+
+

Advanced Usage

+
import { OwenSystemFactory, States, Emotions, StateHandler } from "owen";
+
+// Create custom state handler
+class CustomStateHandler extends StateHandler {
+  async enter(fromState, emotion) {
+    console.log(`Entering custom state from ${fromState}`);
+    // Your custom logic here
+  }
+
+  async exit(toState, emotion) {
+    console.log(`Exiting custom state to ${toState}`);
+    // Your custom logic here
+  }
+}
+
+// Register custom states
+const customStates = new Map();
+customStates.set("custom", CustomStateHandler);
+
+// Create system with custom states
+const owenSystem = await OwenSystemFactory.createCustomOwenSystem(gltfModel, scene, customStates);
+
+// Manual state transitions
+await owenSystem.transitionTo(States.REACTING, Emotions.HAPPY);
+
+

🎨 Multi-Scheme Animation Naming

+

+ Owen supports + four different animation naming schemes to + accommodate different workflows and preferences: +

+

Naming Schemes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SchemeFormatExampleUse Case
Legacy{state}_{emotion}_{type}wait_idle_LBackward compatibility
ArtistOwen_{Action}Owen_WaitIdleBlender-friendly for 3D artists
Hierarchicalowen.{category}.{state}...owen.state.wait.idle.loopStructured projects
SemanticOwen{StateAction}{Type}OwenWaitIdleLoopDeveloper-friendly
+

Usage Examples

+
// All of these refer to the same animation:
+const clip1 = owenSystem.getClip('wait_idle_L');                    // Legacy
+const clip2 = owenSystem.getClip('Owen_WaitIdle');                  // Artist  
+const clip3 = owenSystem.getClip('owen.state.wait.idle.loop');      // Hierarchical
+const clip4 = owenSystem.getClip('OwenWaitIdleLoop');               // Semantic
+
+// Convert between schemes
+import { convertAnimationName, SemanticAnimations } from 'owen';
+
+const artistName = convertAnimationName('wait_idle_L', 'artist');
+// Returns: 'Owen_WaitIdle'
+
+// Use type-safe constants
+const animation = SemanticAnimations.WAIT_IDLE_LOOP; // 'OwenWaitIdleLoop'
+
+

For 3D Artists (Blender Workflow)

+
// Use artist-friendly names in Blender:
+// Owen_WaitIdle, Owen_ReactHappy, Owen_TypeFast, etc.
+// System automatically handles conversion!
+
+const clip = owenSystem.getClip('Owen_ReactAngry');  // Just works!
+
+
+

+ [!TIP] See the + Multi-Scheme Guide for + complete documentation and examples. +

+
+

🎮 Animation Naming Convention (Legacy)

+

+ The system maintains backward compatibility with the original naming + convention: +

+

The system expects animations to follow this naming convention:

+
[state]_[action]_[type]
+[state]_[action]2[toState]_[emotion]_T
+
+

Examples

+
    +
  • wait_idle_L - Wait state idle loop
  • +
  • wait_quirk1_Q - Wait state quirk animation
  • +
  • + react_angry2type_an_T - Transition from react to type + with angry emotion +
  • +
  • + type_happy_L - Type state with happy emotion loop +
  • +
  • sleep_wakeup_T - Sleep wake up transition
  • +
+

Animation Types

+
    +
  • L - Loop animation
  • +
  • Q - Quirk animation
  • +
  • T - Transition animation
  • +
  • NL - Nested loop
  • +
  • NQ - Nested quirk
  • +
+

Emotions

+
    +
  • an - Angry
  • +
  • sh - Shocked
  • +
  • ha - Happy
  • +
  • sa - Sad
  • +
+

🏗️ Architecture

+

Dependency Injection

+
    +
  • + OwenAnimationContext receives dependencies through + constructor injection +
  • +
  • State handlers are injected with required context
  • +
  • Animation loaders are injected into factories
  • +
+

Factory Patterns

+
    +
  • + AnimationClipFactory - Creates animation clips with + metadata parsing +
  • +
  • + StateFactory - Creates state handlers dynamically +
  • +
  • + OwenSystemFactory - Main factory that assembles the + complete system +
  • +
+

State Machine

+
    +
  • Each state has its own handler class with entry/exit logic
  • +
  • States manage their own transitions and behaviors
  • +
  • + Emotional transitions are handled with proper animation sequencing +
  • +
+

📁 Project Structure

+
Owen/
+├── src/
+│   ├── constants.js              # Animation types, states, emotions
+│   ├── index.js                  # Main entry point
+│   ├── animation/
+│   │   └── AnimationClip.js      # Core animation classes
+│   ├── core/
+│   │   └── OwenAnimationContext.js # Main system controller
+│   ├── factories/
+│   │   └── OwenSystemFactory.js  # System factory
+│   ├── loaders/
+│   │   └── AnimationLoader.js    # Animation loading interfaces
+│   └── states/
+│       ├── StateHandler.js       # Base state handler
+│       ├── StateFactory.js       # State factory
+│       ├── WaitStateHandler.js   # Wait state implementation
+│       ├── ReactStateHandler.js  # React state implementation
+│       ├── TypeStateHandler.js   # Type state implementation
+│       └── SleepStateHandler.js  # Sleep state implementation
+├── examples/
+│   ├── index.html        # Demo HTML page
+│   └── basic-demo.js     # Basic usage example
+├── package.json
+├── vite.config.js
+├── jsdoc.config.json
+└── README.md
+
+

🛠️ Development

+

Running the Development Server

+
# Start the development server
+npm run dev
+
+

+ This will start a Vite development server and open the basic demo at + http://localhost:3000. +

+

Building for Production

+
# Build the project
+npm run build
+
+

Linting

+
# Run ESLint
+npm run lint
+
+# Fix linting issues automatically
+npm run lint:fix
+
+

Generating Documentation

+
# Generate JSDoc documentation
+npm run docs
+
+

+ Documentation will be generated in the docs/ directory. +

+

Project Scripts

+
    +
  • npm run dev - Start development server
  • +
  • npm run build - Build for production
  • +
  • npm run preview - Preview production build
  • +
  • npm run lint - Run StandardJS linting
  • +
  • npm run lint:fix - Fix StandardJS issues
  • +
  • npm run docs - Generate JSDoc documentation
  • +
  • npm run format - Format code with Prettier
  • +
+

🎮 Demo Controls

+

The basic demo includes these keyboard controls:

+
    +
  • 1 - Transition to Wait state
  • +
  • 2 - Transition to React state
  • +
  • 3 - Transition to Type state
  • +
  • 4 - Transition to Sleep state
  • +
  • Space - Send random test message
  • +
  • Click - Register user activity
  • +
+

🔧 Configuration

+

Customizing Emotions

+

+ You can extend the emotion system by modifying the message analysis: +

+
import { ReactStateHandler } from "owen";
+
+class CustomReactHandler extends ReactStateHandler {
+  analyzeMessageEmotion(message) {
+    // Your custom emotion analysis logic
+    if (message.includes("excited")) {
+      return Emotions.HAPPY;
+    }
+    return super.analyzeMessageEmotion(message);
+  }
+}
+
+

Adjusting Timing

+

Configure timing values in your application:

+
import { Config } from "owen";
+
+// Modify default values
+Config.QUIRK_INTERVAL = 8000; // 8 seconds between quirks
+Config.INACTIVITY_TIMEOUT = 120000; // 2 minutes until sleep
+
+

🐛 Troubleshooting

+

Common Issues

+
    +
  1. "Animation not found" errors
  2. +
+
    +
  • + Ensure your 3D model contains animations with the correct naming + convention +
  • +
  • + Check that animations are properly exported in your GLTF file +
  • +
+
    +
  1. State transitions not working
  2. +
+
    +
  • Verify that transition animations exist in your model
  • +
  • Check console for error messages about missing clips
  • +
+
    +
  1. Performance issues
  2. +
+
    +
  • + Ensure you're calling owenSystem.update() in your + render loop +
  • +
  • Check that unused animations are properly disposed
  • +
+

Debug Mode

+

+ Enable debug logging by opening browser console. The system logs + state transitions and important events. +

+

🤝 Contributing

+
    +
  1. Fork the repository
  2. +
  3. + Create a feature branch: + git checkout -b feature/new-feature +
  4. +
  5. + Commit your changes: git commit -am 'Add new feature' +
  6. +
  7. + Push to the branch: + git push origin feature/new-feature +
  8. +
  9. Submit a pull request
  10. +
+

Code Style

+
    +
  • Follow the existing ESLint configuration
  • +
  • Add JSDoc comments for all public methods
  • +
  • Write unit tests for new features
  • +
  • Maintain the existing architecture patterns
  • +
+

📄 License

+

This project is dual-licensed under your choice of:

+
    +
  • + Open Source/Non-Commercial Use: AGPL-3.0 - see + the LICENSE.AGPL file for details. +
  • +
  • + Commercial/Enterprise Use: Commercial License - + see the LICENSE.COMMERCIAL file + for details. Requires a paid commercial license. Please contact us + at [email] for pricing and terms. +
  • +
+

Quick Guide

+
    +
  • ✅ Personal/educational use → Use under AGPL-3.0
  • +
  • ✅ Open source projects → Use under AGPL-3.0
  • +
  • ✅ Commercial/proprietary use → Purchase commercial license
  • +
  • + ❌ SaaS without source disclosure → Purchase commercial license +
  • +
+

🙏 Acknowledgments

+
    +
  • + Built with + Three.js +
  • +
  • Inspired by modern character animation systems
  • +
  • Uses clean architecture principles from Robert C. Martin
  • +
+ +
+
+
+ + + +
+ + + + + + + diff --git a/docs/index.js.html b/docs/index.js.html new file mode 100644 index 0000000..caca5a7 --- /dev/null +++ b/docs/index.js.html @@ -0,0 +1,176 @@ + + + + + JSDoc: Source: index.js + + + + + + + + + +
+

Source: index.js

+ +
+
+
/**
+ * @fileoverview Main entry point for the Owen Animation System
+ * @module owen
+ */
+
+// Core exports
+// Import for default export
+import { OwenSystemFactory } from './factories/OwenSystemFactory.js'
+import { OwenAnimationContext } from './core/OwenAnimationContext.js'
+import { States, Emotions, ClipTypes, Config } from './constants.js'
+
+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'
+
+// State system exports
+export { StateHandler } from './states/StateHandler.js'
+export { WaitStateHandler } from './states/WaitStateHandler.js'
+export { ReactStateHandler } from './states/ReactStateHandler.js'
+export { TypeStateHandler } from './states/TypeStateHandler.js'
+export { SleepStateHandler } from './states/SleepStateHandler.js'
+export { StateFactory } from './states/StateFactory.js'
+
+// Factory exports
+export { OwenSystemFactory } from './factories/OwenSystemFactory.js'
+
+// Constants exports
+export { ClipTypes, States, Emotions, Config } from './constants.js'
+
+/**
+ * Default export - the main factory for easy usage
+ */
+export default {
+  OwenSystemFactory,
+  OwenAnimationContext,
+  States,
+  Emotions,
+  ClipTypes,
+  Config
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/loaders_AnimationLoader.js.html b/docs/loaders_AnimationLoader.js.html new file mode 100644 index 0000000..4520e46 --- /dev/null +++ b/docs/loaders_AnimationLoader.js.html @@ -0,0 +1,212 @@ + + + + + JSDoc: Source: loaders/AnimationLoader.js + + + + + + + + + +
+

Source: loaders/AnimationLoader.js

+ +
+
+
/**
+ * @fileoverview Animation loader interfaces and implementations
+ * @module loaders
+ */
+
+/**
+ * Abstract base class for animation loaders
+ * @abstract
+ * @class
+ */
+export class AnimationLoader {
+  /**
+     * Load an animation by name
+     * @abstract
+     * @param {string} _name - The animation name to load (unused in base class)
+     * @returns {Promise<THREE.AnimationClip>} The loaded animation clip
+     * @throws {Error} Must be implemented by subclasses
+     */
+  async loadAnimation (_name) {
+    throw new Error('loadAnimation method must be implemented by subclasses')
+  }
+}
+
+/**
+ * GLTF animation loader implementation
+ * @class
+ * @extends AnimationLoader
+ */
+export class GLTFAnimationLoader extends AnimationLoader {
+  /**
+     * Create a GLTF animation loader
+     * @param {THREE.GLTFLoader} gltfLoader - The Three.js GLTF loader instance
+     */
+  constructor (gltfLoader) {
+    super()
+
+    /**
+         * The Three.js GLTF loader
+         * @type {THREE.GLTFLoader}
+         */
+    this.gltfLoader = gltfLoader
+
+    /**
+         * Cache for loaded animations
+         * @type {Map<string, THREE.AnimationClip>}
+         */
+    this.animationCache = new Map()
+  }
+
+  /**
+     * Load an animation from GLTF by name
+     * @param {string} name - The animation name to load
+     * @returns {Promise<THREE.AnimationClip>} The loaded animation clip
+     * @throws {Error} If animation is not found
+     */
+  async loadAnimation (name) {
+    if (this.animationCache.has(name)) {
+      return this.animationCache.get(name)
+    }
+
+    // In a real implementation, this would load from GLTF files
+    // For now, we'll assume animations are already loaded in the model
+    throw new Error(`Animation '${name}' not found. Implement GLTF loading logic.`)
+  }
+
+  /**
+     * Preload animations from a GLTF model
+     * @param {Object} gltfModel - The loaded GLTF model
+     * @returns {Promise<void>}
+     */
+  async preloadAnimations (gltfModel) {
+    if (gltfModel.animations) {
+      for (const animation of gltfModel.animations) {
+        this.animationCache.set(animation.name, animation)
+      }
+    }
+  }
+
+  /**
+     * Clear the animation cache
+     * @returns {void}
+     */
+  clearCache () {
+    this.animationCache.clear()
+  }
+
+  /**
+     * Get all cached animation names
+     * @returns {string[]} Array of cached animation names
+     */
+  getCachedAnimationNames () {
+    return Array.from(this.animationCache.keys())
+  }
+}
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-StateHandler.StateHandler.html b/docs/module-StateHandler.StateHandler.html new file mode 100644 index 0000000..612999d --- /dev/null +++ b/docs/module-StateHandler.StateHandler.html @@ -0,0 +1,839 @@ + + + + + JSDoc: Class: StateHandler + + + + + + + + + +
+

Class: StateHandler

+ +
+
+

+ (abstract) + StateHandler.StateHandler(stateName, context) +

+ +
+

Abstract base class for state handlers

+
+
+ +
+
+

Constructor

+ +

+ (abstract) new + StateHandler(stateName, context) +

+ +
+

Create a state handler

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
stateName + string +

The name of the state

context + OwenAnimationContext +

The animation context

+ +
+
Source:
+
+ +
+
+
+ +

Members

+ +

+ context + :OwenAnimationContext +

+ +
+

The animation context

+
+ +
Type:
+
    +
  • + OwenAnimationContext +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ currentClip + :AnimationClip|null +

+ +
+

Currently playing animation clip

+
+ +
Type:
+
    +
  • + AnimationClip + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ nestedState + :Object|null +

+ +
+

Nested state information

+
+ +
Type:
+
    +
  • + Object + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ stateName + :string +

+ +
+

The name of this state

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ (async, abstract) enter(_fromStateopt, + _emotionopt) → {Promise.<void>} +

+ +
+

Enter this state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
_fromState + string + | + + null + <optional>
null +

The previous state (unused in base class)

+
_emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to enter with (unused in base class)

+
+ +
+
Source:
+
+ +
+
+ +
Throws:
+ +
+
+
+

Must be implemented by subclasses

+
+
+
+
+
+
Type
+
+ Error +
+
+
+
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async, abstract) exit(_toStateopt, + _emotionopt) → {Promise.<void>} +

+ +
+

Exit this state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
_toState + string + | + + null + <optional>
null +

The next state (unused in base class)

+
_emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to exit with (unused in base class)

+
+ +
+
Source:
+
+ +
+
+ +
Throws:
+ +
+
+
+

Must be implemented by subclasses

+
+
+
+
+
+
Type
+
+ Error +
+
+
+
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ getAvailableTransitions() → {Array.<string>} +

+ +
+

Get available transitions from this state

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of state names that can be transitioned to

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ (async) handleMessage(_message) → {Promise.<void>} +

+ +
+

Handle a user message while in this state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
_message + string + +

The user message (unused in base class)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async, protected) stopCurrentClip(fadeOutDurationopt) → {Promise.<void>} +

+ +
+

Stop the currently playing clip

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
fadeOutDuration + number + <optional>

Fade out duration

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ update(_deltaTime) → {void} +

+ +
+

Update this state (called every frame)

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
_deltaTime + number + +

+ Time elapsed since last update (ms, unused in base class) +

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ (async, protected) waitForClipEnd(clip) → {Promise.<void>} +

+ +
+

Wait for an animation clip to finish playing

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
clip + AnimationClip + +

The animation clip to wait for

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Promise that resolves when the clip finishes

+
+ +
+
Type
+
+ Promise.<void> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-StateHandler.html b/docs/module-StateHandler.html new file mode 100644 index 0000000..be994b9 --- /dev/null +++ b/docs/module-StateHandler.html @@ -0,0 +1,146 @@ + + + + + JSDoc: Module: StateHandler + + + + + + + + + +
+

Module: StateHandler

+ +
+
+ + +
+
+ + + +
+ + + + + + + diff --git a/docs/module-animation.AnimationClip.html b/docs/module-animation.AnimationClip.html new file mode 100644 index 0000000..f2f7a82 --- /dev/null +++ b/docs/module-animation.AnimationClip.html @@ -0,0 +1,639 @@ + + + + + JSDoc: Class: AnimationClip + + + + + + + + + +
+

Class: AnimationClip

+ +
+
+

+ + animation.AnimationClip(name, threeAnimation, metadata) +

+ +
+

+ Represents a single animation clip with metadata and Three.js + action +

+
+
+ +
+
+

Constructor

+ +

+ new AnimationClip(name, threeAnimation, metadata) +

+ +
+

Create an animation clip

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The name of the animation clip

+
threeAnimation + THREE.AnimationClip + +

The Three.js animation clip

+
metadata + Object + +

Parsed metadata from animation name

+
+ +
+
Source:
+
+ +
+
+
+ +

Members

+ +

+ action + :THREE.AnimationAction|null +

+ +
+

The Three.js animation action

+
+ +
Type:
+
    +
  • + THREE.AnimationAction + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ animation + :THREE.AnimationClip +

+ +
+

The Three.js animation clip

+
+ +
Type:
+
    +
  • + THREE.AnimationClip +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ metadata + :Object +

+ +
+

Parsed metadata about the animation

+
+ +
Type:
+
    +
  • + Object +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ mixer + :THREE.AnimationMixer|null +

+ +
+

The animation mixer

+
+ +
Type:
+
    +
  • + THREE.AnimationMixer + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ name + :string +

+ +
+

The name of the animation clip

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ createAction(mixer) → {THREE.AnimationAction} +

+ +
+

Create and configure a Three.js action for this clip

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
mixer + THREE.AnimationMixer +

The animation mixer

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The created action

+
+ +
+
Type
+
+ THREE.AnimationAction +
+
+ +

+ isPlaying() → {boolean} +

+ +
+

Check if the animation is currently playing

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

True if playing, false otherwise

+
+ +
+
Type
+
+ boolean +
+
+ +

+ play(fadeInDurationopt) → {Promise.<void>} +

+ +
+

Play the animation with optional fade in

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fadeInDuration + number + <optional>
0.3 +

Fade in duration in seconds

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Promise that resolves when fade in completes

+
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ stop(fadeOutDurationopt) → {Promise.<void>} +

+ +
+

Stop the animation with optional fade out

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fadeOutDuration + number + <optional>
0.3 +

Fade out duration in seconds

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Promise that resolves when fade out completes

+
+ +
+
Type
+
+ Promise.<void> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-animation.AnimationClipFactory.html b/docs/module-animation.AnimationClipFactory.html new file mode 100644 index 0000000..3bbe26e --- /dev/null +++ b/docs/module-animation.AnimationClipFactory.html @@ -0,0 +1,567 @@ + + + + + JSDoc: Class: AnimationClipFactory + + + + + + + + + +
+

Class: AnimationClipFactory

+ +
+
+

+ + animation.AnimationClipFactory(animationLoader) +

+ +
+

Factory for creating animation clips with parsed metadata

+
+
+ +
+
+

Constructor

+ +

+ new AnimationClipFactory(animationLoader) +

+ +
+

Create an animation clip factory

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
animationLoader + AnimationLoader + +

The animation loader instance

+
+ +
+
Source:
+
+ +
+
+
+ +

Members

+ +

+ animationLoader + :AnimationLoader +

+ +
+

The animation loader for loading animation data

+
+ +
Type:
+
    +
  • + AnimationLoader +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ clipCache + :Map.<string, AnimationClip> +

+ +
+

Cache for created animation clips

+
+ +
Type:
+
    +
  • + Map.<string, AnimationClip> +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ clearCache() → {void} +

+ +
+

Clear the clip cache

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ (async) createClip(name) + → {Promise.<AnimationClip>} +

+ +
+

Create an animation clip from a name

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string +

The animation name

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The created animation clip

+
+ +
+
Type
+
+ Promise.<AnimationClip> +
+
+ +

+ (async) createClipsFromModel(model) + → {Promise.<Map.<string, AnimationClip>>} +

+ +
+

Create all animation clips from a model's animations

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
model + THREE.Object3D + +

The 3D model containing animations

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Map of animation name to clip

+
+ +
+
Type
+
+ Promise.<Map.<string, AnimationClip>> +
+
+ +

+ getCachedClip(name) + → {AnimationClip|undefined} +

+ +
+

Get cached clip by name

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string +

The animation name

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The cached clip or undefined

+
+ +
+
Type
+
+ AnimationClip + | + + undefined +
+
+ +

+ parseAnimationName(name) → {Object} +

+ +
+

+ Parse animation name and create clip metadata Format: + [state][action][type] or + [state][action]2[toState][emotion]_T +

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The animation name to parse

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Parsed metadata object

+
+ +
+
Type
+
+ Object +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-animation.html b/docs/module-animation.html new file mode 100644 index 0000000..9025bea --- /dev/null +++ b/docs/module-animation.html @@ -0,0 +1,154 @@ + + + + + JSDoc: Module: animation + + + + + + + + + +
+

Module: animation

+ +
+
+ + +
+
+ + + +
+ + + + + + + diff --git a/docs/module-animation_AnimationConstants.html b/docs/module-animation_AnimationConstants.html new file mode 100644 index 0000000..ccf9528 --- /dev/null +++ b/docs/module-animation_AnimationConstants.html @@ -0,0 +1,576 @@ + + + + + JSDoc: Module: animation/AnimationConstants + + + + + + + + + +
+

Module: animation/AnimationConstants

+ +
+
+ +
+
+
+

+ Animation constants with multi-scheme support for Owen Animation + System +

+
+ +
+
Source:
+
+ +
+
+
+ +

Members

+ +

+ (static, constant) ArtistAnimations +

+ +
+

Artist-friendly animation names (Blender workflow)

+
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) HierarchicalAnimations +

+ +
+

Hierarchical animation names (organized structure)

+
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) LegacyAnimations +

+ +
+

Legacy animation names (backward compatibility)

+
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) NamingSchemes +

+ +
+

Animation naming schemes enumeration

+
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) SemanticAnimations +

+ +
+

Semantic animation names (readable camelCase)

+
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ (static) convertAnimationName(name, targetScheme) → {string} +

+ +
+

Convert animation name between different schemes

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The source animation name

+
targetScheme + string + +

The target naming scheme

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The converted animation name

+
+ +
+
Type
+
+ string +
+
+ +

+ (static) getAllAnimationNames(name) → {Object} +

+ +
+

Get all naming scheme variants for an animation

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The source animation name

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Object with all scheme variants

+
+ +
+
Type
+
+ Object +
+
+ +

+ (static) getAnimationsByStateAndEmotion(state, emotion, scheme) → {Array.<string>} +

+ +
+

Get animations by state and emotion

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
state + string +

The state name

emotion + string + +

The emotion name (optional)

+
scheme + string + +

The naming scheme to return (default: 'semantic')

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of animation names

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ (static) validateAnimationName(name) → {Object} +

+ +
+

Validate an animation name

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The animation name to validate

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Validation result

+
+ +
+
Type
+
+ Object +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-animation_AnimationNameMapper.AnimationNameMapper.html b/docs/module-animation_AnimationNameMapper.AnimationNameMapper.html new file mode 100644 index 0000000..25b2ff4 --- /dev/null +++ b/docs/module-animation_AnimationNameMapper.AnimationNameMapper.html @@ -0,0 +1,533 @@ + + + + + JSDoc: Class: AnimationNameMapper + + + + + + + + + +
+

Class: AnimationNameMapper

+ +
+
+

+ + animation/AnimationNameMapper.AnimationNameMapper() +

+ +
+

+ Multi-scheme animation name mapper for Owen Animation System + Supports legacy, artist-friendly, and hierarchical naming schemes +

+
+
+ +
+
+

Constructor

+ +

+ new AnimationNameMapper() +

+ +
+
Source:
+
+ +
+
+
+ +

Methods

+ +

+ convert(fromName, targetScheme) → {string} +

+ +
+

Convert any animation name to any other scheme

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
fromName + string +

Source animation name

targetScheme + string + hierarchical +

+ Target naming scheme ('legacy', 'artist', 'hierarchical', + 'semantic') +

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Converted animation name

+
+ +
+
Type
+
+ string +
+
+ +

+ convertBatch(animations, targetScheme) → {Object} +

+ +
+

Batch convert multiple animations

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
animations + Array.<string> + +

Array of animation names

+
targetScheme + string +

Target naming scheme

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Mapping of original names to converted names

+
+ +
+
Type
+
+ Object +
+
+ +

+ detectScheme(name) → {Object|null} +

+ +
+

Detect which naming scheme is being used

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

Animation name to analyze

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Detection result with scheme and extracted info

+
+ +
+
Type
+
+ Object + | + + null +
+
+ +

+ getAllNames(animationName) → {Object} +

+ +
+

Get all possible names for an animation

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
animationName + string +

Source animation name

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Object with all naming scheme variants

+
+ +
+
Type
+
+ Object +
+
+ +

+ validateAnimationName(name) → {Object} +

+ +
+

Validate animation name format

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

Animation name to validate

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Validation result with issues and suggestions

+
+ +
+
Type
+
+ Object +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-animation_AnimationNameMapper.html b/docs/module-animation_AnimationNameMapper.html new file mode 100644 index 0000000..e13e9d0 --- /dev/null +++ b/docs/module-animation_AnimationNameMapper.html @@ -0,0 +1,154 @@ + + + + + JSDoc: Module: animation/AnimationNameMapper + + + + + + + + + +
+

Module: animation/AnimationNameMapper

+ +
+
+ + +
+
+ + + +
+ + + + + + + diff --git a/docs/module-constants.html b/docs/module-constants.html new file mode 100644 index 0000000..246297a --- /dev/null +++ b/docs/module-constants.html @@ -0,0 +1,259 @@ + + + + + JSDoc: Module: constants + + + + + + + + + +
+

Module: constants

+ +
+
+ +
+
+
+

Animation system constants and enumerations

+
+ +
+
Source:
+
+ +
+
+
+ +

Members

+ +

+ (static, constant) ClipTypes :string +

+ +
+

Animation clip types based on naming convention

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) Config + :Object +

+ +
+

Default configuration values

+
+ +
Type:
+
    +
  • + Object +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) Emotions + :string +

+ +
+

Character emotional states

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ (static, constant) States + :string +

+ +
+

Character animation states

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-core.OwenAnimationContext.html b/docs/module-core.OwenAnimationContext.html new file mode 100644 index 0000000..6ab2f62 --- /dev/null +++ b/docs/module-core.OwenAnimationContext.html @@ -0,0 +1,1661 @@ + + + + + JSDoc: Class: OwenAnimationContext + + + + + + + + + +
+

Class: OwenAnimationContext

+ +
+
+

+ + core.OwenAnimationContext(model, mixer, animationClipFactory, stateFactory) +

+ +
+

+ Main controller for the Owen animation system Manages state + transitions, animation playback, and user interactions +

+
+
+ +
+
+

Constructor

+ +

+ new OwenAnimationContext(model, mixer, animationClipFactory, stateFactory) +

+ +
+

Create an Owen animation context

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
model + THREE.Object3D + +

The 3D character model

+
mixer + THREE.AnimationMixer + +

The Three.js animation mixer

+
animationClipFactory + AnimationClipFactory + +

Factory for creating clips

+
stateFactory + StateFactory + +

Factory for creating state handlers

+
+ +
+
Source:
+
+ +
+
+
+ +

Members

+ +

+ animationClipFactory + :AnimationClipFactory +

+ +
+

Factory for creating animation clips

+
+ +
Type:
+
    +
  • + AnimationClipFactory +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ clips + :Map.<string, AnimationClip> +

+ +
+

Map of animation clips by name

+
+ +
Type:
+
    +
  • + Map.<string, AnimationClip> +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ currentState + :string +

+ +
+

Current active state

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ currentStateHandler + :StateHandler|null +

+ +
+

Current active state handler

+
+ +
Type:
+
    +
  • + StateHandler + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ inactivityTimeout + :number +

+ +
+

Inactivity timeout in milliseconds

+
+ +
Type:
+
    +
  • + number +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ inactivityTimer + :number +

+ +
+

Timer for inactivity detection

+
+ +
Type:
+
    +
  • + number +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ initialized + :boolean +

+ +
+

Whether the system is initialized

+
+ +
Type:
+
    +
  • + boolean +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ mixer + :THREE.AnimationMixer +

+ +
+

The Three.js animation mixer

+
+ +
Type:
+
    +
  • + THREE.AnimationMixer +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ model + :THREE.Object3D +

+ +
+

The 3D character model

+
+ +
Type:
+
    +
  • + THREE.Object3D +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ nameMapper + :AnimationNameMapper +

+ +
+

Multi-scheme animation name mapper

+
+ +
Type:
+
    +
  • + AnimationNameMapper +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ stateFactory + :StateFactory +

+ +
+

Factory for creating state handlers

+
+ +
Type:
+
    +
  • + StateFactory +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ states + :Map.<string, StateHandler> +

+ +
+

Map of state handlers by name

+
+ +
Type:
+
    +
  • + Map.<string, StateHandler> +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ dispose() → {void} +

+ +
+

Dispose of the animation system and clean up resources

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ getAnimationNames(name) → {Object} +

+ +
+

Get all naming scheme variants for an animation

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The animation name in any scheme

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

+ Object with all scheme variants: {legacy, artist, hierarchical, + semantic} +

+
+ +
+
Type
+
+ Object +
+
+ +

+ getAnimationsByStateAndEmotion(state, emotionopt, + schemeopt) → {Array.<string>} +

+ +
+

Get available animations by state and emotion

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
state + string + +

The state name (wait, react, type, sleep)

+
emotion + string + <optional>
+

The emotion name (angry, happy, sad, shocked, neutral)

+
scheme + string + <optional>
'semantic' +

The naming scheme to return

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of animation names in the specified scheme

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ getAvailableClips() → {Array.<string>} +

+ +
+

Get all available animation clip names

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of clip names

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ getAvailableStates() → {Array.<string>} +

+ +
+

Get all available state names

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of state names

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ getAvailableTransitions() → {Array.<string>} +

+ +
+

Get available transitions from current state

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of available state transitions

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ getClip(name) + → {AnimationClip|undefined} +

+ +
+

Get an animation clip by name (supports all naming schemes)

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The animation clip name in any supported scheme

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The animation clip or undefined if not found

+
+ +
+
Type
+
+ AnimationClip + | + + undefined +
+
+ +

+ getClipByScheme(name, targetSchemeopt) + → {AnimationClip|undefined} +

+ +
+

Get an animation clip by name in a specific naming scheme

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
name + string +

The animation name

targetScheme + string + <optional>
+

+ Target scheme: 'legacy', 'artist', 'hierarchical', + 'semantic' +

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The animation clip or undefined if not found

+
+ +
+
Type
+
+ AnimationClip + | + + undefined +
+
+ +

+ getClipsByPattern(pattern) + → {Array.<AnimationClip>} +

+ +
+

Get animation clips matching a pattern

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pattern + string + +

Pattern to match (supports * wildcards)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of matching clips

+
+ +
+
Type
+
+ Array.<AnimationClip> +
+
+ +

+ getCurrentState() → {string} +

+ +
+

Get the current state name

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The current state name

+
+ +
+
Type
+
+ string +
+
+ +

+ getCurrentStateHandler() → {StateHandler|null} +

+ +
+

Get the current state handler

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The current state handler

+
+ +
+
Type
+
+ StateHandler + | + + null +
+
+ +

+ (async) handleUserMessage(message) → {Promise.<void>} +

+ +
+

Handle a user message

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + string +

The user message

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async) initialize() → {Promise.<void>} +

+ +
+

Initialize the animation system

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ onUserActivity() → {void} +

+ +
+

Called when user activity is detected

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ (async) transitionTo(newStateName, emotionopt) → {Promise.<void>} +

+ +
+

Transition to a new state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
newStateName + string + +

The name of the state to transition to

+
emotion + string + <optional>
Emotions.NEUTRAL +

The emotion for the transition

+
+ +
+
Source:
+
+ +
+
+ +
Throws:
+ +
+
+
+

If state is not found or transition is invalid

+
+
+
+
+
+
Type
+
+ Error +
+
+
+
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ update(deltaTime) → {void} +

+ +
+

Update the animation system (call every frame)

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
deltaTime + number + +

Time elapsed since last update (ms)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ validateAnimationName(name) → {Object} +

+ +
+

Validate an animation name and get suggestions if invalid

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The animation name to validate

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

+ Validation result with isValid, scheme, error, and suggestions +

+
+ +
+
Type
+
+ Object +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-core.html b/docs/module-core.html new file mode 100644 index 0000000..d8c1753 --- /dev/null +++ b/docs/module-core.html @@ -0,0 +1,149 @@ + + + + + JSDoc: Module: core + + + + + + + + + +
+

Module: core

+ +
+
+ + +
+
+ + + +
+ + + + + + + diff --git a/docs/module-factories.OwenSystemFactory.html b/docs/module-factories.OwenSystemFactory.html new file mode 100644 index 0000000..d999c6c --- /dev/null +++ b/docs/module-factories.OwenSystemFactory.html @@ -0,0 +1,455 @@ + + + + + JSDoc: Class: OwenSystemFactory + + + + + + + + + +
+

Class: OwenSystemFactory

+ +
+
+

+ + factories.OwenSystemFactory() +

+ +
+

Main factory for creating the complete Owen animation system

+
+
+ +
+
+

Constructor

+ +

+ new OwenSystemFactory() +

+ +
+
Source:
+
+ +
+
+
+ +

Methods

+ +

+ (async, static) createBasicOwenSystem(model) + → {Promise.<OwenAnimationContext>} +

+ +
+

Create a basic Owen system with minimal configuration

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
model + THREE.Object3D +

The 3D model

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The configured Owen system

+
+ +
+
Type
+
+ Promise.<OwenAnimationContext> +
+
+ +

+ (async, static) createCustomOwenSystem(gltfModel, scene, customStates) + → {Promise.<OwenAnimationContext>} +

+ +
+

Create an Owen system with custom state handlers

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
gltfModel + THREE.Object3D +

The loaded GLTF model

scene + THREE.Scene +

The Three.js scene

customStates + Map.<string, function()> + +

Map of state name to handler class

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The configured Owen system

+
+ +
+
Type
+
+ Promise.<OwenAnimationContext> +
+
+ +

+ (async, static) createOwenSystem(gltfModel, scene, optionsopt) + → {Promise.<OwenAnimationContext>} +

+ +
+

Create a complete Owen animation system

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
gltfModel + THREE.Object3D +

The loaded GLTF model

scene + THREE.Scene +

The Three.js scene

options + Object + <optional>
{} +

Configuration options

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
gltfLoader + THREE.GLTFLoader + <optional>
+

Custom GLTF loader

+
+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

The configured Owen system

+
+ +
+
Type
+
+ Promise.<OwenAnimationContext> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-factories.html b/docs/module-factories.html new file mode 100644 index 0000000..1c095a0 --- /dev/null +++ b/docs/module-factories.html @@ -0,0 +1,151 @@ + + + + + JSDoc: Module: factories + + + + + + + + + +
+

Module: factories

+ +
+
+ + +
+
+ + + +
+ + + + + + + diff --git a/docs/module-loaders.AnimationLoader.html b/docs/module-loaders.AnimationLoader.html new file mode 100644 index 0000000..1b80666 --- /dev/null +++ b/docs/module-loaders.AnimationLoader.html @@ -0,0 +1,245 @@ + + + + + JSDoc: Class: AnimationLoader + + + + + + + + + +
+

Class: AnimationLoader

+ +
+
+

+ (abstract) + loaders.AnimationLoader() +

+ +
+

Abstract base class for animation loaders

+
+
+ +
+
+

Constructor

+ +

+ (abstract) new + AnimationLoader() +

+ +
+
Source:
+
+ +
+
+
+ +

Methods

+ +

+ (async, abstract) loadAnimation(_name) + → {Promise.<THREE.AnimationClip>} +

+ +
+

Load an animation by name

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
_name + string + +

The animation name to load (unused in base class)

+
+ +
+
Source:
+
+ +
+
+ +
Throws:
+ +
+
+
+

Must be implemented by subclasses

+
+
+
+
+
+
Type
+
+ Error +
+
+
+
+
+ +
Returns:
+ +
+

The loaded animation clip

+
+ +
+
Type
+
+ Promise.<THREE.AnimationClip> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-loaders.GLTFAnimationLoader.html b/docs/module-loaders.GLTFAnimationLoader.html new file mode 100644 index 0000000..826c288 --- /dev/null +++ b/docs/module-loaders.GLTFAnimationLoader.html @@ -0,0 +1,485 @@ + + + + + JSDoc: Class: GLTFAnimationLoader + + + + + + + + + +
+

Class: GLTFAnimationLoader

+ +
+
+

+ + loaders.GLTFAnimationLoader(gltfLoader) +

+ +
+

GLTF animation loader implementation

+
+
+ +
+
+

Constructor

+ +

+ new GLTFAnimationLoader(gltfLoader) +

+ +
+

Create a GLTF animation loader

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
gltfLoader + THREE.GLTFLoader + +

The Three.js GLTF loader instance

+
+ +
+
Source:
+
+ +
+
+
+ +

Extends

+ +
    +
  • AnimationLoader
  • +
+ +

Members

+ +

+ animationCache + :Map.<string, THREE.AnimationClip> +

+ +
+

Cache for loaded animations

+
+ +
Type:
+
    +
  • + Map.<string, THREE.AnimationClip> +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ gltfLoader + :THREE.GLTFLoader +

+ +
+

The Three.js GLTF loader

+
+ +
Type:
+
    +
  • + THREE.GLTFLoader +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ clearCache() → {void} +

+ +
+

Clear the animation cache

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ getCachedAnimationNames() → {Array.<string>} +

+ +
+

Get all cached animation names

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of cached animation names

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ (async) loadAnimation(name) + → {Promise.<THREE.AnimationClip>} +

+ +
+

Load an animation from GLTF by name

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + string + +

The animation name to load

+
+ +
+
Source:
+
+ +
+
+ +
Throws:
+ +
+
+
+

If animation is not found

+
+
+
+
+
+
Type
+
+ Error +
+
+
+
+
+ +
Returns:
+ +
+

The loaded animation clip

+
+ +
+
Type
+
+ Promise.<THREE.AnimationClip> +
+
+ +

+ (async) preloadAnimations(gltfModel) → {Promise.<void>} +

+ +
+

Preload animations from a GLTF model

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
gltfModel + Object +

The loaded GLTF model

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-loaders.html b/docs/module-loaders.html new file mode 100644 index 0000000..bea41b0 --- /dev/null +++ b/docs/module-loaders.html @@ -0,0 +1,154 @@ + + + + + JSDoc: Module: loaders + + + + + + + + + +
+

Module: loaders

+ +
+
+ + +
+
+ + + +
+ + + + + + + diff --git a/docs/module-owen.html b/docs/module-owen.html new file mode 100644 index 0000000..a651710 --- /dev/null +++ b/docs/module-owen.html @@ -0,0 +1,158 @@ + + + + + JSDoc: Module: owen + + + + + + + + + +
+

Module: owen

+ +
+
+ +
+
+
+

Main entry point for the Owen Animation System

+
+ +
+
Source:
+
+ +
+
+ +

+ module:owen +

+ +
+

Default export - the main factory for easy usage

+
+ +
+
Source:
+
+ +
+
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-states.ReactStateHandler.html b/docs/module-states.ReactStateHandler.html new file mode 100644 index 0000000..961c17b --- /dev/null +++ b/docs/module-states.ReactStateHandler.html @@ -0,0 +1,508 @@ + + + + + JSDoc: Class: ReactStateHandler + + + + + + + + + +
+

Class: ReactStateHandler

+ +
+
+

+ + states.ReactStateHandler(context) +

+ +
+

Handler for the React state

+
+
+ +
+
+

Constructor

+ +

+ new ReactStateHandler(context) +

+ +
+

Create a react state handler

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
context + OwenAnimationContext +

The animation context

+ +
+
Source:
+
+ +
+
+
+ +

Extends

+ +
    +
  • StateHandler
  • +
+ +

Members

+ +

+ emotion + :string +

+ +
+

Current emotional state

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ (async) enter(_fromStateopt, + emotionopt) → {Promise.<void>} +

+ +
+

Enter the react state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
_fromState + string + | + + null + <optional>
null +

The previous state (unused)

+
emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to enter with

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async) exit(toStateopt, + emotionopt) → {Promise.<void>} +

+ +
+

Exit the react state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
toState + string + | + + null + <optional>
null

The next state

emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to exit with

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ getAvailableTransitions() → {Array.<string>} +

+ +
+

Get available transitions from react state

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of available state transitions

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ (async) handleMessage(message) → {Promise.<void>} +

+ +
+

Handle a user message in react state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + string +

The user message

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-states.SleepStateHandler.html b/docs/module-states.SleepStateHandler.html new file mode 100644 index 0000000..aa27caa --- /dev/null +++ b/docs/module-states.SleepStateHandler.html @@ -0,0 +1,683 @@ + + + + + JSDoc: Class: SleepStateHandler + + + + + + + + + +
+

Class: SleepStateHandler

+ +
+
+

+ + states.SleepStateHandler(context) +

+ +
+

Handler for the Sleep state

+
+
+ +
+
+

Constructor

+ +

+ new SleepStateHandler(context) +

+ +
+

Create a sleep state handler

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
context + OwenAnimationContext +

The animation context

+ +
+
Source:
+
+ +
+
+
+ +

Extends

+ +
    +
  • StateHandler
  • +
+ +

Members

+ +

+ isDeepSleep + :boolean +

+ +
+

Whether the character is in deep sleep

+
+ +
Type:
+
    +
  • + boolean +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ sleepClip + :AnimationClip|null +

+ +
+

Sleep animation clip

+
+ +
Type:
+
    +
  • + AnimationClip + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ (async) enter(fromStateopt, + _emotionopt) → {Promise.<void>} +

+ +
+

Enter the sleep state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fromState + string + | + + null + <optional>
null

The previous state

_emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to enter with (unused)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async) exit(toStateopt, + emotionopt) → {Promise.<void>} +

+ +
+

Exit the sleep state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
toState + string + | + + null + <optional>
null

The next state

emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to exit with

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ getAvailableTransitions() → {Array.<string>} +

+ +
+

Get available transitions from sleep state

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of available state transitions

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ (async) handleMessage(_message) → {Promise.<void>} +

+ +
+

Handle a user message in sleep state (wake up)

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
_message + string + +

The user message (unused, just triggers wake up)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ isInDeepSleep() → {boolean} +

+ +
+

Check if in deep sleep

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

True if in deep sleep, false otherwise

+
+ +
+
Type
+
+ boolean +
+
+ +

+ update(_deltaTime) → {void} +

+ +
+

Update the sleep state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
_deltaTime + number + +

Time elapsed since last update (ms, unused)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ (async) wakeUp() → {Promise.<void>} +

+ +
+

Force wake up from sleep

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-states.StateFactory.html b/docs/module-states.StateFactory.html new file mode 100644 index 0000000..d6af0ac --- /dev/null +++ b/docs/module-states.StateFactory.html @@ -0,0 +1,491 @@ + + + + + JSDoc: Class: StateFactory + + + + + + + + + +
+

Class: StateFactory

+ +
+
+

+ + states.StateFactory() +

+ +
+

+ Factory for creating state handlers using dependency injection +

+
+
+ +
+
+

Constructor

+ +

+ new StateFactory() +

+ +
+

Create a state factory

+
+ +
+
Source:
+
+ +
+
+
+ +

Methods

+ +

+ createStateHandler(stateName, context) → {StateHandler} +

+ +
+

Create a state handler instance

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
stateName + string +

The name of the state

context + OwenAnimationContext +

The animation context

+ +
+
Source:
+
+ +
+
+ +
Throws:
+ +
+
+
+

If state handler is not registered

+
+
+
+
+
+
Type
+
+ Error +
+
+
+
+
+ +
Returns:
+ +
+

The created state handler

+
+ +
+
Type
+
+ StateHandler +
+
+ +

+ getAvailableStates() → {Array.<string>} +

+ +
+

Get all available state names

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of registered state names

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ isStateRegistered(stateName) → {boolean} +

+ +
+

Check if a state is registered

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
stateName + string +

The state name to check

+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

True if registered, false otherwise

+
+ +
+
Type
+
+ boolean +
+
+ +

+ registerStateHandler(stateName, handlerClass) → {void} +

+ +
+

Register a state handler class

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
stateName + string +

The name of the state

handlerClass + function + +

The handler class constructor

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+ +

+ unregisterStateHandler(stateName) → {boolean} +

+ +
+

Unregister a state handler

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
stateName + string + +

The state name to unregister

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

True if removed, false if not found

+
+ +
+
Type
+
+ boolean +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-states.TypeStateHandler.html b/docs/module-states.TypeStateHandler.html new file mode 100644 index 0000000..e3752a2 --- /dev/null +++ b/docs/module-states.TypeStateHandler.html @@ -0,0 +1,608 @@ + + + + + JSDoc: Class: TypeStateHandler + + + + + + + + + +
+

Class: TypeStateHandler

+ +
+
+

+ + states.TypeStateHandler(context) +

+ +

Handler for the Type state

+
+ +
+
+

Constructor

+ +

+ new TypeStateHandler(context) +

+ +
+

Create a type state handler

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
context + OwenAnimationContext +

The animation context

+ +
+
Source:
+
+ +
+
+
+ +

Extends

+ +
    +
  • StateHandler
  • +
+ +

Members

+ +

+ emotion + :string +

+ +
+

Current emotional state

+
+ +
Type:
+
    +
  • + string +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ isTyping + :boolean +

+ +
+

Whether currently typing

+
+ +
Type:
+
    +
  • + boolean +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ (async) enter(_fromStateopt, + emotionopt) → {Promise.<void>} +

+ +
+

Enter the type state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
_fromState + string + | + + null + <optional>
null +

The previous state (unused)

+
emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to enter with

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async) exit(toStateopt, + _emotionopt) → {Promise.<void>} +

+ +
+

Exit the type state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
toState + string + | + + null + <optional>
null

The next state

_emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to exit with (unused)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async) finishTyping() → {Promise.<void>} +

+ +
+

Finish typing and prepare to transition

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ getAvailableTransitions() → {Array.<string>} +

+ +
+

Get available transitions from type state

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of available state transitions

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ getIsTyping() → {boolean} +

+ +
+

Check if currently typing

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

True if typing, false otherwise

+
+ +
+
Type
+
+ boolean +
+
+ +

+ setTyping(typing) → {void} +

+ +
+

Set typing state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
typing + boolean + +

Whether currently typing

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-states.WaitStateHandler.html b/docs/module-states.WaitStateHandler.html new file mode 100644 index 0000000..b0884d5 --- /dev/null +++ b/docs/module-states.WaitStateHandler.html @@ -0,0 +1,605 @@ + + + + + JSDoc: Class: WaitStateHandler + + + + + + + + + +
+

Class: WaitStateHandler

+ +
+
+

+ + states.WaitStateHandler(context) +

+ +
+

Handler for the Wait/Idle state

+
+
+ +
+
+

Constructor

+ +

+ new WaitStateHandler(context) +

+ +
+

Create a wait state handler

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
context + OwenAnimationContext +

The animation context

+ +
+
Source:
+
+ +
+
+
+ +

Extends

+ +
    +
  • StateHandler
  • +
+ +

Members

+ +

+ idleClip + :AnimationClip|null +

+ +
+

The main idle animation clip

+
+ +
Type:
+
    +
  • + AnimationClip + | + + null +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ quirkInterval + :number +

+ +
+

Interval between quirk attempts (ms)

+
+ +
Type:
+
    +
  • + number +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ quirkTimer + :number +

+ +
+

Timer for quirk animations

+
+ +
Type:
+
    +
  • + number +
  • +
+ +
+
Source:
+
+ +
+
+ +

+ quirks + :Array.<AnimationClip> +

+ +
+

Available quirk animations

+
+ +
Type:
+
    +
  • + Array.<AnimationClip> +
  • +
+ +
+
Source:
+
+ +
+
+ +

Methods

+ +

+ (async) enter(fromStateopt, + emotionopt) → {Promise.<void>} +

+ +
+

Enter the wait state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fromState + string + | + + null + <optional>
null

The previous state

emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to enter with

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ (async) exit(toStateopt, + emotionopt) → {Promise.<void>} +

+ +
+

Exit the wait state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
toState + string + | + + null + <optional>
null

The next state

emotion + string + <optional>
Emotions.NEUTRAL +

The emotion to exit with

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ Promise.<void> +
+
+ +

+ getAvailableTransitions() → {Array.<string>} +

+ +
+

Get available transitions from wait state

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+

Array of available state transitions

+
+ +
+
Type
+
+ Array.<string> +
+
+ +

+ update(deltaTime) → {void} +

+ +
+

Update the wait state

+
+ +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
deltaTime + number + +

Time elapsed since last update (ms)

+
+ +
+
Source:
+
+ +
+
+ +
Returns:
+ +
+
Type
+
+ void +
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/module-states.html b/docs/module-states.html new file mode 100644 index 0000000..9334981 --- /dev/null +++ b/docs/module-states.html @@ -0,0 +1,672 @@ + + + + + JSDoc: Module: states + + + + + + + + + +
+

Module: states

+ +
+
+ +
+
+
+

React state handler implementation

+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+
+ +

Classes

+ +
+
+ ReactStateHandler +
+
+ +
+ SleepStateHandler +
+
+ +
StateFactory
+
+ +
+ TypeStateHandler +
+
+ +
+ WaitStateHandler +
+
+
+
+
+ +
+
+ +
+
+
+

Sleep state handler implementation

+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+
+ +

Classes

+ +
+
+ ReactStateHandler +
+
+ +
+ SleepStateHandler +
+
+ +
StateFactory
+
+ +
+ TypeStateHandler +
+
+ +
+ WaitStateHandler +
+
+
+
+
+ +
+
+ +
+
+
+

State factory for creating state handlers

+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+
+ +

Classes

+ +
+
+ ReactStateHandler +
+
+ +
+ SleepStateHandler +
+
+ +
StateFactory
+
+ +
+ TypeStateHandler +
+
+ +
+ WaitStateHandler +
+
+
+
+
+ +
+
+ +
+
+
+

Type state handler implementation

+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+
+ +

Classes

+ +
+
+ ReactStateHandler +
+
+ +
+ SleepStateHandler +
+
+ +
StateFactory
+
+ +
+ TypeStateHandler +
+
+ +
+ WaitStateHandler +
+
+
+
+
+ +
+
+ +
+
+
+

Wait state handler implementation

+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+ +
+
Source:
+
+ +
+
+
+ +

Classes

+ +
+
+ ReactStateHandler +
+
+ +
+ SleepStateHandler +
+
+ +
StateFactory
+
+ +
+ TypeStateHandler +
+
+ +
+ WaitStateHandler +
+
+
+
+
+
+ + + +
+ + + + + + + diff --git a/docs/scripts/linenumber.js b/docs/scripts/linenumber.js new file mode 100644 index 0000000..e0c35fb --- /dev/null +++ b/docs/scripts/linenumber.js @@ -0,0 +1,25 @@ +/* global document */ +(() => { + const source = document.getElementsByClassName('prettyprint source linenums') + let i = 0 + let lineNumber = 0 + let lineId + let lines + let totalLines + let anchorHash + + if (source && source[0]) { + anchorHash = document.location.hash.substring(1) + lines = source[0].getElementsByTagName('li') + totalLines = lines.length + + for (; i < totalLines; i++) { + lineNumber++ + lineId = `line${lineNumber}` + lines[i].id = lineId + if (lineId === anchorHash) { + lines[i].className += ' selected' + } + } + } +})() diff --git a/docs/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/docs/scripts/prettify/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/scripts/prettify/lang-css.js b/docs/scripts/prettify/lang-css.js new file mode 100644 index 0000000..5965904 --- /dev/null +++ b/docs/scripts/prettify/lang-css.js @@ -0,0 +1,2 @@ +PR.registerLangHandler(PR.createSimpleLexer([['pln', /^[\t\n\f\r ]+/, null, ' \t\r\n ']], [['str', /^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/, null], ['str', /^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/, null], ['lang-css-str', /^url\(([^"')]*)\)/i], ['kwd', /^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i, null], ['lang-css-kw', /^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i], ['com', /^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//], ['com', + /^(?:<\!--|--\>)/], ['lit', /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i], ['lit', /^#[\da-f]{3,6}/i], ['pln', /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i], ['pun', /^[^\s\w"']+/]]), ['css']); PR.registerLangHandler(PR.createSimpleLexer([], [['kwd', /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]), ['css-kw']); PR.registerLangHandler(PR.createSimpleLexer([], [['str', /^[^"')]+/]]), ['css-str']) diff --git a/docs/scripts/prettify/prettify.js b/docs/scripts/prettify/prettify.js new file mode 100644 index 0000000..9a3086f --- /dev/null +++ b/docs/scripts/prettify/prettify.js @@ -0,0 +1,118 @@ +const q = null; window.PR_SHOULD_USE_CONTINUATION = !0; +(function () { + function L (a) { + function m (a) { let f = a.charCodeAt(0); if (f !== 92) return f; const b = a.charAt(1); return (f = r[b]) ? f : b >= '0' && b <= '7' ? parseInt(a.substring(1), 8) : b === 'u' || b === 'x' ? parseInt(a.substring(2), 16) : a.charCodeAt(1) } function e (a) { if (a < 32) return (a < 16 ? '\\x0' : '\\x') + a.toString(16); a = String.fromCharCode(a); if (a === '\\' || a === '-' || a === '[' || a === ']')a = '\\' + a; return a } function h (a) { + for (var f = a.substring(1, a.length - 1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g), a = +[], b = [], o = f[0] === '^', c = o ? 1 : 0, i = f.length; c < i; ++c) { var j = f[c]; if (/\\[bdsw]/i.test(j))a.push(j); else { var j = m(j); var d; c + 2 < i && f[c + 1] === '-' ? (d = m(f[c + 2]), c += 2) : d = j; b.push([j, d]); d < 65 || j > 122 || (d < 65 || j > 90 || b.push([Math.max(65, j) | 32, Math.min(d, 90) | 32]), d < 97 || j > 122 || b.push([Math.max(97, j) & -33, Math.min(d, 122) & -33])) } }b.sort(function (a, f) { return a[0] - f[0] || f[1] - a[1] }); f = []; j = [NaN, NaN]; for (c = 0; c < b.length; ++c)i = b[c], i[0] <= j[1] + 1 ? j[1] = Math.max(j[1], i[1]) : f.push(j = i); b = ['[']; o && b.push('^'); b.push.apply(b, a); for (c = 0; c < +f.length; ++c)i = f[c], b.push(e(i[0])), i[1] > i[0] && (i[1] + 1 > i[0] && b.push('-'), b.push(e(i[1]))); b.push(']'); return b.join('') + } function y (a) { + for (var f = a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g), b = f.length, d = [], c = 0, i = 0; c < b; ++c) { var j = f[c]; j === '(' ? ++i : j.charAt(0) === '\\' && (j = +j.substring(1)) && j <= i && (d[j] = -1) } for (c = 1; c < d.length; ++c)d[c] === -1 && (d[c] = ++t); for (i = c = 0; c < b; ++c) { + j = f[c], j === '(' + ? (++i, d[i] === void 0 && (f[c] = '(?:')) + : j.charAt(0) === '\\' && +(j = +j.substring(1)) && j <= i && (f[c] = '\\' + d[i]) + } for (i = c = 0; c < b; ++c)f[c] === '^' && f[c + 1] !== '^' && (f[c] = ''); if (a.ignoreCase && s) for (c = 0; c < b; ++c)j = f[c], a = j.charAt(0), j.length >= 2 && a === '[' ? f[c] = h(j) : a !== '\\' && (f[c] = j.replace(/[A-Za-z]/g, function (a) { a = a.charCodeAt(0); return '[' + String.fromCharCode(a & -33, a | 32) + ']' })); return f.join('') + } for (var t = 0, s = !1, l = !1, p = 0, d = a.length; p < d; ++p) { var g = a[p]; if (g.ignoreCase)l = !0; else if (/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi, ''))) { s = !0; l = !1; break } } for (var r = +{ b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 }, n = [], p = 0, d = a.length; p < d; ++p) { g = a[p]; if (g.global || g.multiline) throw Error('' + g); n.push('(?:' + y(g) + ')') } return RegExp(n.join('|'), l ? 'gi' : 'g') + } function M (a) { + function m (a) { + switch (a.nodeType) { + case 1:if (e.test(a.className)) break; for (var g = a.firstChild; g; g = g.nextSibling)m(g); g = a.nodeName; if (g === 'BR' || g === 'LI')h[s] = '\n', t[s << 1] = y++, t[s++ << 1 | 1] = a; break; case 3:case 4:g = a.nodeValue, g.length && (g = p ? g.replace(/\r\n?/g, '\n') : g.replace(/[\t\n\r ]+/g, ' '), h[s] = g, t[s << 1] = y, y += g.length, + t[s++ << 1 | 1] = a) + } + } var e = /(?:^|\s)nocode(?:\s|$)/; var h = []; var y = 0; var t = []; var s = 0; let l; a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = document.defaultView.getComputedStyle(a, q).getPropertyValue('white-space')); var p = l && l.substring(0, 3) === 'pre'; m(a); return { a: h.join('').replace(/\n$/, ''), c: t } + } function B (a, m, e, h) { m && (a = { a: m, d: a }, e(a), h.push.apply(h, a.e)) } function x (a, m) { + function e (a) { + for (var l = a.d, p = [l, 'pln'], d = 0, g = a.a.match(y) || [], r = {}, n = 0, z = g.length; n < z; ++n) { + const f = g[n]; let b = r[f]; let o = void 0; var c; if (typeof b === +'string')c = !1; else { var i = h[f.charAt(0)]; if (i)o = f.match(i[1]), b = i[0]; else { for (c = 0; c < t; ++c) if (i = m[c], o = f.match(i[1])) { b = i[0]; break }o || (b = 'pln') } if ((c = b.length >= 5 && b.substring(0, 5) === 'lang-') && !(o && typeof o[1] === 'string'))c = !1, b = 'src'; c || (r[f] = b) }i = d; d += f.length; if (c) { c = o[1]; let j = f.indexOf(c); let k = j + c.length; o[2] && (k = f.length - o[2].length, j = k - c.length); b = b.substring(5); B(l + i, f.substring(0, j), e, p); B(l + i + j, c, C(b, c), p); B(l + i + k, f.substring(k), e, p) } else p.push(l + i, b) + }a.e = p + } var h = {}; let y; (function () { + for (var e = a.concat(m), + l = [], p = {}, d = 0, g = e.length; d < g; ++d) { let r = e[d]; let n = r[3]; if (n) for (let k = n.length; --k >= 0;)h[n.charAt(k)] = r; r = r[1]; n = '' + r; p.hasOwnProperty(n) || (l.push(r), p[n] = q) }l.push(/[\S\s]/); y = L(l) + })(); var t = m.length; return e + } function u (a) { + const m = []; const e = []; a.tripleQuotedStrings + ? m.push(['str', /^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/, q, "'\""]) + : a.multiLineStrings + ? m.push(['str', /^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, + q, "'\"`"]) + : m.push(['str', /^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/, q, "\"'"]); a.verbatimStrings && e.push(['str', /^@"(?:[^"]|"")*(?:"|$)/, q]); let h = a.hashComments; h && (a.cStyleComments + ? (h > 1 ? m.push(['com', /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, q, '#']) : m.push(['com', /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/, q, '#']), e.push(['str', /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, q])) + : m.push(['com', /^#[^\n\r]*/, + q, '#'])); a.cStyleComments && (e.push(['com', /^\/\/[^\n\r]*/, q]), e.push(['com', /^\/\*[\S\s]*?(?:\*\/|$)/, q])); a.regexLiterals && e.push(['lang-regex', /^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]); (h = a.types) && e.push(['typ', h]); a = ('' + a.keywords).replace(/^ | $/g, + ''); a.length && e.push(['kwd', RegExp('^(?:' + a.replace(/[\s,]+/g, '|') + ')\\b'), q]); m.push(['pln', /^\s+/, q, ' \r\n\t\xa0']); e.push(['lit', /^@[$_a-z][\w$@]*/i, q], ['typ', /^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/, q], ['pln', /^[$_a-z][\w$@]*/i, q], ['lit', /^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i, q, '0123456789'], ['pln', /^\\[\S\s]?/, q], ['pun', /^.[^\s\w"-$'./@\\`]*/, q]); return x(m, e) + } function D (a, m) { + function e (a) { + switch (a.nodeType) { + case 1:if (k.test(a.className)) break; if (a.nodeName === 'BR') { + h(a), + a.parentNode && a.parentNode.removeChild(a) + } else for (a = a.firstChild; a; a = a.nextSibling)e(a); break; case 3:case 4:if (p) { let b = a.nodeValue; const d = b.match(t); if (d) { const c = b.substring(0, d.index); a.nodeValue = c; (b = b.substring(d.index + d[0].length)) && a.parentNode.insertBefore(s.createTextNode(b), a.nextSibling); h(a); c || a.parentNode.removeChild(a) } } + } + } function h (a) { + function b (a, d) { const e = d ? a.cloneNode(!1) : a; var f = a.parentNode; if (f) { var f = b(f, 1); let g = a.nextSibling; f.appendChild(e); for (let h = g; h; h = g)g = h.nextSibling, f.appendChild(h) } return e } + for (;!a.nextSibling;) if (a = a.parentNode, !a) return; for (var a = b(a.nextSibling, 0), e; (e = a.parentNode) && e.nodeType === 1;)a = e; d.push(a) + } var k = /(?:^|\s)nocode(?:\s|$)/; var t = /\r\n?|\n/; var s = a.ownerDocument; let l; a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = s.defaultView.getComputedStyle(a, q).getPropertyValue('white-space')); var p = l && l.substring(0, 3) === 'pre'; for (l = s.createElement('LI'); a.firstChild;)l.appendChild(a.firstChild); for (var d = [l], g = 0; g < d.length; ++g)e(d[g]); m === (m | 0) && d[0].setAttribute('value', + m); const r = s.createElement('OL'); r.className = 'linenums'; for (var n = Math.max(0, m - 1 | 0) || 0, g = 0, z = d.length; g < z; ++g)l = d[g], l.className = 'L' + (g + n) % 10, l.firstChild || l.appendChild(s.createTextNode('\xa0')), r.appendChild(l); a.appendChild(r) + } function k (a, m) { for (let e = m.length; --e >= 0;) { const h = m[e]; A.hasOwnProperty(h) ? window.console && console.warn('cannot override language handler %s', h) : A[h] = a } } function C (a, m) { if (!a || !A.hasOwnProperty(a))a = /^\s*= o && (h += 2); e >= c && (a += 2) + } + } catch (w) { 'console' in window && console.log(w && w.stack ? w.stack : w) } + } var v = ['break,continue,do,else,for,if,return,while']; var w = [[v, 'auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile'], + 'catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof']; const F = [w, 'alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where']; const G = [w, 'abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient'] + const H = [G, 'as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var']; var w = [w, 'debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN']; const I = [v, 'and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None'] + const J = [v, 'alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END']; var v = [v, 'case,done,elif,esac,eval,fi,function,in,local,set,then,until']; const K = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/; const N = /\S/; const O = u({ + keywords: [F, H, w, 'caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END' + +I, J, v], + hashComments: !0, + cStyleComments: !0, + multiLineStrings: !0, + regexLiterals: !0 + }); var A = {}; k(O, ['default-code']); k(x([], [['pln', /^[^]*(?:>|$)/], ['com', /^<\!--[\S\s]*?(?:--\>|$)/], ['lang-', /^<\?([\S\s]+?)(?:\?>|$)/], ['lang-', /^<%([\S\s]+?)(?:%>|$)/], ['pun', /^(?:<[%?]|[%?]>)/], ['lang-', /^]*>([\S\s]+?)<\/xmp\b[^>]*>/i], ['lang-js', /^]*>([\S\s]*?)(<\/script\b[^>]*>)/i], ['lang-css', /^]*>([\S\s]*?)(<\/style\b[^>]*>)/i], ['lang-in.tag', /^(<\/?[a-z][^<>]*>)/i]]), + ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']); k(x([['pln', /^\s+/, q, ' \t\r\n'], ['atv', /^(?:"[^"]*"?|'[^']*'?)/, q, "\"'"]], [['tag', /^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i], ['atn', /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], ['lang-uq.val', /^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/], ['pun', /^[/<->]+/], ['lang-js', /^on\w+\s*=\s*"([^"]+)"/i], ['lang-js', /^on\w+\s*=\s*'([^']+)'/i], ['lang-js', /^on\w+\s*=\s*([^\s"'>]+)/i], ['lang-css', /^style\s*=\s*"([^"]+)"/i], ['lang-css', /^style\s*=\s*'([^']+)'/i], ['lang-css', + /^style\s*=\s*([^\s"'>]+)/i]]), ['in.tag']); k(x([], [['atv', /^[\S\s]+/]]), ['uq.val']); k(u({ keywords: F, hashComments: !0, cStyleComments: !0, types: K }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']); k(u({ keywords: 'null,true,false' }), ['json']); k(u({ keywords: H, hashComments: !0, cStyleComments: !0, verbatimStrings: !0, types: K }), ['cs']); k(u({ keywords: G, cStyleComments: !0 }), ['java']); k(u({ keywords: v, hashComments: !0, multiLineStrings: !0 }), ['bsh', 'csh', 'sh']); k(u({ keywords: I, hashComments: !0, multiLineStrings: !0, tripleQuotedStrings: !0 }), + ['cv', 'py']); k(u({ keywords: 'caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END', hashComments: !0, multiLineStrings: !0, regexLiterals: !0 }), ['perl', 'pl', 'pm']); k(u({ keywords: J, hashComments: !0, multiLineStrings: !0, regexLiterals: !0 }), ['rb']); k(u({ keywords: w, cStyleComments: !0, regexLiterals: !0 }), ['js']); k(u({ + keywords: 'all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes', + hashComments: 3, + cStyleComments: !0, + multilineStrings: !0, + tripleQuotedStrings: !0, + regexLiterals: !0 + }), ['coffee']); k(x([], [['str', /^[\S\s]+/]]), ['regex']); window.prettyPrintOne = function (a, m, e) { const h = document.createElement('PRE'); h.innerHTML = a; e && D(h, e); E({ g: m, i: e, h }); return h.innerHTML }; window.prettyPrint = function (a) { + function m () { + for (let e = window.PR_SHOULD_USE_CONTINUATION ? l.now() + 250 : Infinity; p < h.length && l.now() < e; p++) { + const n = h[p]; var k = n.className; if (k.indexOf('prettyprint') >= 0) { + var k = k.match(g); var f; var b; if (b = +!k) { b = n; for (var o = void 0, c = b.firstChild; c; c = c.nextSibling) var i = c.nodeType, o = i === 1 ? o ? b : c : i === 3 ? N.test(c.nodeValue) ? b : o : o; b = (f = o === b ? void 0 : o) && f.tagName === 'CODE' }b && (k = f.className.match(g)); k && (k = k[1]); b = !1; for (o = n.parentNode; o; o = o.parentNode) if ((o.tagName === 'pre' || o.tagName === 'code' || o.tagName === 'xmp') && o.className && o.className.indexOf('prettyprint') >= 0) { b = !0; break }b || ((b = (b = n.className.match(/\blinenums\b(?::(\d+))?/)) ? b[1] && b[1].length ? +b[1] : !0 : !1) && D(n, b), d = { g: k, h: n, i: b }, E(d)) + } + }p < h.length + ? setTimeout(m, + 250) + : a && a() + } for (var e = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')], h = [], k = 0; k < e.length; ++k) for (let t = 0, s = e[k].length; t < s; ++t)h.push(e[k][t]); var e = q; var l = Date; l.now || (l = { now: function () { return +new Date() } }); var p = 0; let d; var g = /\blang(?:uage)?-([\w.]+)(?!\S)/; m() + }; window.PR = { + createSimpleLexer: x, + registerLangHandler: k, + sourceDecorator: u, + PR_ATTRIB_NAME: 'atn', + PR_ATTRIB_VALUE: 'atv', + PR_COMMENT: 'com', + PR_DECLARATION: 'dec', + PR_KEYWORD: 'kwd', + PR_LITERAL: 'lit', + PR_NOCODE: 'nocode', + PR_PLAIN: 'pln', + PR_PUNCTUATION: 'pun', + PR_SOURCE: 'src', + PR_STRING: 'str', + PR_TAG: 'tag', + PR_TYPE: 'typ' + } +})() diff --git a/docs/states_ReactStateHandler.js.html b/docs/states_ReactStateHandler.js.html new file mode 100644 index 0000000..c9715c8 --- /dev/null +++ b/docs/states_ReactStateHandler.js.html @@ -0,0 +1,277 @@ + + + + + JSDoc: Source: states/ReactStateHandler.js + + + + + + + + + +
+

Source: states/ReactStateHandler.js

+ +
+
+
/**
+ * @fileoverview React state handler implementation
+ * @module states
+ */
+
+import { StateHandler } from './StateHandler.js'
+import { States, Emotions } from '../constants.js'
+
+/**
+ * Handler for the React state
+ * @class
+ * @extends StateHandler
+ */
+export class ReactStateHandler extends StateHandler {
+  /**
+     * Create a react state handler
+     * @param {OwenAnimationContext} context - The animation context
+     */
+  constructor (context) {
+    super(States.REACTING, context)
+
+    /**
+         * Current emotional state
+         * @type {string}
+         */
+    this.emotion = Emotions.NEUTRAL
+  }
+
+  /**
+     * Enter the react 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 REACTING state with emotion: ${emotion}`)
+    this.emotion = emotion
+
+    // Play appropriate reaction
+    const reactionClip = this.context.getClip('react_idle_L')
+    if (reactionClip) {
+      await reactionClip.play()
+      this.currentClip = reactionClip
+    }
+  }
+
+  /**
+     * Exit the react state
+     * @param {string|null} [toState=null] - The next state
+     * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with
+     * @returns {Promise<void>}
+     */
+  async exit (toState = null, emotion = Emotions.NEUTRAL) {
+    console.log(`Exiting REACTING state to ${toState} with emotion: ${emotion}`)
+
+    if (this.currentClip) {
+      await this.stopCurrentClip()
+    }
+
+    // Play emotional transition if available
+    let transitionName
+    if (emotion !== Emotions.NEUTRAL) {
+      transitionName = `react_${this.emotion}2${toState}_${emotion}_T`
+    } else {
+      transitionName = `react_2${toState}_T`
+    }
+
+    const transition = this.context.getClip(transitionName)
+    if (transition) {
+      await transition.play()
+      await this.waitForClipEnd(transition)
+    }
+  }
+
+  /**
+     * Handle a user message in react state
+     * @param {string} message - The user message
+     * @returns {Promise<void>}
+     */
+  async handleMessage (message) {
+    // Analyze message sentiment to determine emotion
+    const emotion = this.analyzeMessageEmotion(message)
+    this.emotion = emotion
+
+    // Play emotional reaction if needed
+    if (emotion !== Emotions.NEUTRAL) {
+      const emotionalReaction = this.context.getClip(`react_${emotion}_Q`)
+      if (emotionalReaction) {
+        if (this.currentClip) {
+          await this.stopCurrentClip(0.2)
+        }
+        await emotionalReaction.play()
+        await this.waitForClipEnd(emotionalReaction)
+      }
+    }
+  }
+
+  /**
+     * Analyze message to determine emotional response
+     * @private
+     * @param {string} message - The message to analyze
+     * @returns {string} The determined emotion
+     */
+  analyzeMessageEmotion (message) {
+    const text = message.toLowerCase()
+
+    // Check for urgent/angry indicators
+    if (
+      text.includes('!') ||
+            text.includes('urgent') ||
+            text.includes('asap') ||
+            text.includes('hurry')
+    ) {
+      return Emotions.ANGRY
+    }
+
+    // Check for error/shocked indicators
+    if (
+      text.includes('error') ||
+            text.includes('problem') ||
+            text.includes('issue') ||
+            text.includes('bug') ||
+            text.includes('broken')
+    ) {
+      return Emotions.SHOCKED
+    }
+
+    // Check for positive/happy indicators
+    if (
+      text.includes('great') ||
+            text.includes('awesome') ||
+            text.includes('good') ||
+            text.includes('excellent') ||
+            text.includes('perfect')
+    ) {
+      return Emotions.HAPPY
+    }
+
+    // Check for sad indicators
+    if (
+      text.includes('sad') ||
+            text.includes('disappointed') ||
+            text.includes('failed') ||
+            text.includes('wrong')
+    ) {
+      return Emotions.SAD
+    }
+
+    return Emotions.NEUTRAL
+  }
+
+  /**
+     * Get available transitions from react state
+     * @returns {string[]} Array of available state transitions
+     */
+  getAvailableTransitions () {
+    return [States.TYPING, States.WAITING]
+  }
+}
+
+
+
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.4 on Sat May 24 + 2025 12:29:38 GMT+0200 (Midden-Europese zomertijd) +
+ + + + + diff --git a/docs/states_SleepStateHandler.js.html b/docs/states_SleepStateHandler.js.html new file mode 100644 index 0000000..da2d585 --- /dev/null +++ b/docs/states_SleepStateHandler.js.html @@ -0,0 +1,258 @@ + + + + + JSDoc: Source: states/SleepStateHandler.js + + + + + + + + + +
+

Source: states/SleepStateHandler.js

+ +
+
+
/**
+ * @fileoverview Sleep state handler implementation
+ * @module states
+ */
+
+import { StateHandler } from './StateHandler.js'
+import { States, Emotions } from '../constants.js'
+
+/**
+ * Handler for the Sleep state
+ * @class
+ * @extends StateHandler
+ */
+export class SleepStateHandler extends StateHandler {
+  /**
+   * Create a sleep state handler
+   * @param {OwenAnimationContext} context - The animation context
+   */
+  constructor (context) {
+    super(States.SLEEPING, context)
+
+    /**
+     * Sleep animation clip
+     * @type {AnimationClip|null}
+     */
+    this.sleepClip = null
+
+    /**
+     * Whether the character is in deep sleep
+     * @type {boolean}
+     */
+    this.isDeepSleep = false
+  }
+
+  /**
+   * Enter the sleep state
+   * @param {string|null} [fromState=null] - The previous state
+   * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to enter with (unused)
+   * @returns {Promise<void>}
+   */
+  async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
+    console.log(`Entering SLEEPING state from ${fromState}`)
+
+    // Play sleep transition if available
+    const sleepTransition = this.context.getClip('wait_2sleep_T')
+    if (sleepTransition) {
+      await sleepTransition.play()
+      await this.waitForClipEnd(sleepTransition)
+    }
+
+    // Start sleep loop
+    this.sleepClip = this.context.getClip('sleep_idle_L')
+    if (this.sleepClip) {
+      await this.sleepClip.play()
+      this.currentClip = this.sleepClip
+    }
+
+    this.isDeepSleep = true
+  }
+
+  /**
+   * Exit the sleep state
+   * @param {string|null} [toState=null] - The next state
+   * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with
+   * @returns {Promise<void>}
+   */
+  async exit (toState = null, _emotion = Emotions.NEUTRAL) {
+    console.log(`Exiting SLEEPING state to ${toState}`)
+    this.isDeepSleep = false
+
+    if (this.currentClip) {
+      await this.stopCurrentClip()
+    }
+
+    // Play wake up animation
+    const wakeUpClip = this.context.getClip('sleep_wakeup_T')
+    if (wakeUpClip) {
+      await wakeUpClip.play()
+      await this.waitForClipEnd(wakeUpClip)
+    }
+
+    // Play transition to next state if available
+    const transitionName = `sleep_2${toState}_T`
+    const transition = this.context.getClip(transitionName)
+    if (transition) {
+      await transition.play()
+      await this.waitForClipEnd(transition)
+    }
+  }
+
+  /**
+   * Update the sleep state
+   * @param {number} _deltaTime - Time elapsed since last update (ms, unused)
+   * @returns {void}
+   */
+  update (_deltaTime) {
+    // Sleep state doesn't need regular updates
+    // Character remains asleep until external stimulus
+  }
+
+  /**
+   * Handle a user message in sleep state (wake up)
+   * @param {string} _message - The user message (unused, just triggers wake up)
+   * @returns {Promise<void>}
+   */
+  async handleMessage (_message) {
+    // 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)
+    }
+  }
+
+  /**
+   * Get available transitions from sleep state
+   * @returns {string[]} Array of available state transitions
+   */
+  getAvailableTransitions () {
+    return [States.WAITING, States.REACTING]
+  }
+
+  /**
+   * Check if in deep sleep
+   * @returns {boolean} True if in deep sleep, false otherwise
+   */
+  isInDeepSleep () {
+    return this.isDeepSleep
+  }
+
+  /**
+   * Force wake up from sleep
+   * @returns {Promise<void>}
+   */
+  async wakeUp () {
+    if (this.isDeepSleep) {
+      await this.context.transitionTo(States.WAITING)
+    }
+  }
+}
+
+
+
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.4 on Sat May 24 + 2025 12:29:38 GMT+0200 (Midden-Europese zomertijd) +
+ + + + + diff --git a/docs/states_StateFactory.js.html b/docs/states_StateFactory.js.html new file mode 100644 index 0000000..3b2fe57 --- /dev/null +++ b/docs/states_StateFactory.js.html @@ -0,0 +1,204 @@ + + + + + JSDoc: Source: states/StateFactory.js + + + + + + + + + +
+

Source: states/StateFactory.js

+ +
+
+
/**
+ * @fileoverview State factory for creating state handlers
+ * @module states
+ */
+
+import { WaitStateHandler } from './WaitStateHandler.js'
+import { ReactStateHandler } from './ReactStateHandler.js'
+import { TypeStateHandler } from './TypeStateHandler.js'
+import { SleepStateHandler } from './SleepStateHandler.js'
+import { States } from '../constants.js'
+
+/**
+ * Factory for creating state handlers using dependency injection
+ * @class
+ */
+export class StateFactory {
+  /**
+   * Create a state factory
+   */
+  constructor () {
+    /**
+     * Registry of state handler classes
+     * @type {Map<string, Function>}
+     * @private
+     */
+    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)
+  }
+
+  /**
+   * Register a state handler class
+   * @param {string} stateName - The name of the state
+   * @param {Function} handlerClass - The handler class constructor
+   * @returns {void}
+   */
+  registerStateHandler (stateName, handlerClass) {
+    this.stateHandlers.set(stateName, handlerClass)
+  }
+
+  /**
+   * Create a state handler instance
+   * @param {string} stateName - The name of the state
+   * @param {OwenAnimationContext} context - The animation context
+   * @returns {StateHandler} The created state handler
+   * @throws {Error} If state handler is not registered
+   */
+  createStateHandler (stateName, context) {
+    const HandlerClass = this.stateHandlers.get(stateName)
+    if (!HandlerClass) {
+      throw new Error(`No handler registered for state: ${stateName}`)
+    }
+
+    return new HandlerClass(context)
+  }
+
+  /**
+   * Get all available state names
+   * @returns {string[]} Array of registered state names
+   */
+  getAvailableStates () {
+    return Array.from(this.stateHandlers.keys())
+  }
+
+  /**
+   * Check if a state is registered
+   * @param {string} stateName - The state name to check
+   * @returns {boolean} True if registered, false otherwise
+   */
+  isStateRegistered (stateName) {
+    return this.stateHandlers.has(stateName)
+  }
+
+  /**
+   * Unregister a state handler
+   * @param {string} stateName - The state name to unregister
+   * @returns {boolean} True if removed, false if not found
+   */
+  unregisterStateHandler (stateName) {
+    return this.stateHandlers.delete(stateName)
+  }
+}
+
+
+
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.4 on Sat May 24 + 2025 12:29:38 GMT+0200 (Midden-Europese zomertijd) +
+ + + + + diff --git a/docs/states_StateHandler.js.html b/docs/states_StateHandler.js.html new file mode 100644 index 0000000..ef75b75 --- /dev/null +++ b/docs/states_StateHandler.js.html @@ -0,0 +1,244 @@ + + + + + JSDoc: Source: states/StateHandler.js + + + + + + + + + +
+

Source: states/StateHandler.js

+ +
+
+
/**
+ * @fileoverview Base state handler class and utilities
+ * @module StateHandler
+ */
+
+import { Emotions, Config } from '../constants.js'
+
+/**
+ * Abstract base class for state handlers
+ * @abstract
+ * @class
+ */
+export class StateHandler {
+  /**
+   * Create a state handler
+   * @param {string} stateName - The name of the state
+   * @param {OwenAnimationContext} context - The animation context
+   */
+  constructor (stateName, context) {
+    /**
+     * The name of this state
+     * @type {string}
+     */
+    this.stateName = stateName
+
+    /**
+     * The animation context
+     * @type {OwenAnimationContext}
+     */
+    this.context = context
+
+    /**
+     * Currently playing animation clip
+     * @type {AnimationClip|null}
+     */
+    this.currentClip = null
+
+    /**
+     * Nested state information
+     * @type {Object|null}
+     */
+    this.nestedState = null
+  }
+
+  /**
+   * Enter this state
+   * @abstract
+   * @param {string|null} [_fromState=null] - The previous state (unused in base class)
+   * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to enter with (unused in base class)
+   * @returns {Promise<void>}
+   * @throws {Error} Must be implemented by subclasses
+   */
+  async enter (_fromState = null, _emotion = Emotions.NEUTRAL) {
+    throw new Error('enter method must be implemented by subclasses')
+  }
+
+  /**
+   * Exit this state
+   * @abstract
+   * @param {string|null} [_toState=null] - The next state (unused in base class)
+   * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to exit with (unused in base class)
+   * @returns {Promise<void>}
+   * @throws {Error} Must be implemented by subclasses
+   */
+  async exit (_toState = null, _emotion = Emotions.NEUTRAL) {
+    throw new Error('exit method must be implemented by subclasses')
+  }
+
+  /**
+   * Update this state (called every frame)
+   * @param {number} _deltaTime - Time elapsed since last update (ms, unused in base class)
+   * @returns {void}
+   */
+  update (_deltaTime) {
+    // Override in subclasses if needed
+  }
+
+  /**
+   * Handle a user message while in this state
+   * @param {string} _message - The user message (unused in base class)
+   * @returns {Promise<void>}
+   */
+  async handleMessage (_message) {
+    // Override in subclasses if needed
+  }
+
+  /**
+   * Get available transitions from this state
+   * @returns {string[]} Array of state names that can be transitioned to
+   */
+  getAvailableTransitions () {
+    return []
+  }
+
+  /**
+   * Wait for an animation clip to finish playing
+   * @protected
+   * @param {AnimationClip} clip - The animation clip to wait for
+   * @returns {Promise<void>} Promise that resolves when the clip finishes
+   */
+  async waitForClipEnd (clip) {
+    return new Promise((resolve) => {
+      const checkFinished = () => {
+        if (!clip.isPlaying()) {
+          resolve()
+        } else {
+          requestAnimationFrame(checkFinished)
+        }
+      }
+      checkFinished()
+    })
+  }
+
+  /**
+   * Stop the currently playing clip
+   * @protected
+   * @param {number} [fadeOutDuration] - Fade out duration
+   * @returns {Promise<void>}
+   */
+  async stopCurrentClip (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
+    if (this.currentClip) {
+      await this.currentClip.stop(fadeOutDuration)
+      this.currentClip = null
+    }
+  }
+}
+
+
+
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.4 on Sat May 24 + 2025 12:29:38 GMT+0200 (Midden-Europese zomertijd) +
+ + + + + diff --git a/docs/states_TypeStateHandler.js.html b/docs/states_TypeStateHandler.js.html new file mode 100644 index 0000000..570e851 --- /dev/null +++ b/docs/states_TypeStateHandler.js.html @@ -0,0 +1,246 @@ + + + + + JSDoc: Source: states/TypeStateHandler.js + + + + + + + + + +
+

Source: states/TypeStateHandler.js

+ +
+
+
/**
+ * @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.TYPING, 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 TYPING 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 TYPING 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.WAITING, States.REACTING]
+  }
+
+  /**
+   * 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
+  }
+}
+
+
+
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.4 on Sat May 24 + 2025 12:29:38 GMT+0200 (Midden-Europese zomertijd) +
+ + + + + diff --git a/docs/states_WaitStateHandler.js.html b/docs/states_WaitStateHandler.js.html new file mode 100644 index 0000000..61437e9 --- /dev/null +++ b/docs/states_WaitStateHandler.js.html @@ -0,0 +1,256 @@ + + + + + JSDoc: Source: states/WaitStateHandler.js + + + + + + + + + +
+

Source: states/WaitStateHandler.js

+ +
+
+
/**
+ * @fileoverview Wait state handler implementation
+ * @module states
+ */
+
+import { StateHandler } from './StateHandler.js'
+import { States, Emotions, Config } from '../constants.js'
+
+/**
+ * Handler for the Wait/Idle state
+ * @class
+ * @extends StateHandler
+ */
+export class WaitStateHandler extends StateHandler {
+  /**
+     * Create a wait state handler
+     * @param {OwenAnimationContext} context - The animation context
+     */
+  constructor (context) {
+    super(States.WAITING, context)
+
+    /**
+         * The main idle animation clip
+         * @type {AnimationClip|null}
+         */
+    this.idleClip = null
+
+    /**
+         * Available quirk animations
+         * @type {AnimationClip[]}
+         */
+    this.quirks = []
+
+    /**
+         * Timer for quirk animations
+         * @type {number}
+         */
+    this.quirkTimer = 0
+
+    /**
+         * Interval between quirk attempts (ms)
+         * @type {number}
+         */
+    this.quirkInterval = Config.QUIRK_INTERVAL
+  }
+
+  /**
+     * Enter the wait state
+     * @param {string|null} [fromState=null] - The previous state
+     * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with
+     * @returns {Promise<void>}
+     */
+  async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
+    console.log(`Entering WAITING state from ${fromState}`)
+
+    // Play idle loop
+    this.idleClip = this.context.getClip('wait_idle_L')
+    if (this.idleClip) {
+      await this.idleClip.play()
+      this.currentClip = this.idleClip
+    }
+
+    // Collect available quirks
+    this.quirks = this.context.getClipsByPattern('wait_*_Q')
+    this.quirkTimer = 0
+  }
+
+  /**
+     * Exit the wait state
+     * @param {string|null} [toState=null] - The next state
+     * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with
+     * @returns {Promise<void>}
+     */
+  async exit (toState = null, _emotion = Emotions.NEUTRAL) {
+    console.log(`Exiting WAITING state to ${toState}`)
+
+    if (this.currentClip) {
+      await this.stopCurrentClip()
+    }
+
+    // Play transition if available
+    const transitionName = `wait_2${toState}_T`
+    const transition = this.context.getClip(transitionName)
+    if (transition) {
+      await transition.play()
+      await this.waitForClipEnd(transition)
+    }
+  }
+
+  /**
+     * Update the wait state
+     * @param {number} deltaTime - Time elapsed since last update (ms)
+     * @returns {void}
+     */
+  update (deltaTime) {
+    this.quirkTimer += deltaTime
+
+    // Randomly play quirks
+    if (this.quirkTimer > this.quirkInterval && Math.random() < Config.QUIRK_PROBABILITY) {
+      this.playRandomQuirk()
+      this.quirkTimer = 0
+    }
+  }
+
+  /**
+     * Play a random quirk animation
+     * @private
+     * @returns {Promise<void>}
+     */
+  async playRandomQuirk () {
+    if (this.quirks.length === 0) return
+
+    const quirk = this.quirks[Math.floor(Math.random() * this.quirks.length)]
+
+    // Fade out idle
+    if (this.idleClip) {
+      await this.idleClip.stop(0.2)
+    }
+
+    // Play quirk
+    await quirk.play()
+    await this.waitForClipEnd(quirk)
+
+    // Return to idle
+    if (this.idleClip) {
+      await this.idleClip.play()
+      this.currentClip = this.idleClip
+    }
+  }
+
+  /**
+     * Get available transitions from wait state
+     * @returns {string[]} Array of available state transitions
+     */
+  getAvailableTransitions () {
+    return [States.REACTING, States.SLEEPING]
+  }
+}
+
+
+
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.4 on Sat May 24 + 2025 12:29:38 GMT+0200 (Midden-Europese zomertijd) +
+ + + + + diff --git a/docs/styles/jsdoc-default.css b/docs/styles/jsdoc-default.css new file mode 100644 index 0000000..2110577 --- /dev/null +++ b/docs/styles/jsdoc-default.css @@ -0,0 +1,377 @@ +@font-face { + font-family: "Open Sans"; + font-weight: normal; + font-style: normal; + src: url("../fonts/OpenSans-Regular-webfont.eot"); + src: + local("Open Sans"), + local("OpenSans"), + url("../fonts/OpenSans-Regular-webfont.eot?#iefix") + format("embedded-opentype"), + url("../fonts/OpenSans-Regular-webfont.woff") format("woff"), + url("../fonts/OpenSans-Regular-webfont.svg#open_sansregular") format("svg"); +} + +@font-face { + font-family: "Open Sans Light"; + font-weight: normal; + font-style: normal; + src: url("../fonts/OpenSans-Light-webfont.eot"); + src: + local("Open Sans Light"), + local("OpenSans Light"), + url("../fonts/OpenSans-Light-webfont.eot?#iefix") + format("embedded-opentype"), + url("../fonts/OpenSans-Light-webfont.woff") format("woff"), + url("../fonts/OpenSans-Light-webfont.svg#open_sanslight") format("svg"); +} + +html { + overflow: auto; + background-color: #fff; + font-size: 14px; +} + +body { + font-family: "Open Sans", sans-serif; + line-height: 1.5; + color: #4d4e53; + background-color: white; +} + +a, +a:visited, +a:active { + color: #0095dd; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +header { + display: block; + padding: 0px 4px; +} + +tt, +code, +kbd, +samp { + font-family: Consolas, Monaco, "Andale Mono", monospace; +} + +.class-description { + font-size: 130%; + line-height: 140%; + margin-bottom: 1em; + margin-top: 1em; +} + +.class-description:empty { + margin: 0; +} + +#main { + float: left; + width: 70%; +} + +article dl { + margin-bottom: 40px; +} + +article img { + max-width: 100%; +} + +section { + display: block; + background-color: #fff; + padding: 12px 24px; + border-bottom: 1px solid #ccc; + margin-right: 30px; +} + +.variation { + display: none; +} + +.signature-attributes { + font-size: 60%; + color: #aaa; + font-style: italic; + font-weight: lighter; +} + +nav { + display: block; + float: right; + margin-top: 28px; + width: 30%; + box-sizing: border-box; + border-left: 1px solid #ccc; + padding-left: 16px; +} + +nav ul { + font-family: "Lucida Grande", "Lucida Sans Unicode", arial, sans-serif; + font-size: 100%; + line-height: 17px; + padding: 0; + margin: 0; + list-style-type: none; +} + +nav ul a, +nav ul a:visited, +nav ul a:active { + font-family: Consolas, Monaco, "Andale Mono", monospace; + line-height: 18px; + color: #4d4e53; +} + +nav h3 { + margin-top: 12px; +} + +nav li { + margin-top: 6px; +} + +footer { + display: block; + padding: 6px; + margin-top: 12px; + font-style: italic; + font-size: 90%; +} + +h1, +h2, +h3, +h4 { + font-weight: 200; + margin: 0; +} + +h1 { + font-family: "Open Sans Light", sans-serif; + font-size: 48px; + letter-spacing: -2px; + margin: 12px 24px 20px; +} + +h2, +h3.subsection-title { + font-size: 30px; + font-weight: 700; + letter-spacing: -1px; + margin-bottom: 12px; +} + +h3 { + font-size: 24px; + letter-spacing: -0.5px; + margin-bottom: 12px; +} + +h4 { + font-size: 18px; + letter-spacing: -0.33px; + margin-bottom: 12px; + color: #4d4e53; +} + +h5, +.container-overview .subsection-title { + font-size: 120%; + font-weight: bold; + letter-spacing: -0.01em; + margin: 8px 0 3px 0; +} + +h6 { + font-size: 100%; + letter-spacing: -0.01em; + margin: 6px 0 3px 0; + font-style: italic; +} + +table { + border-spacing: 0; + border: 0; + border-collapse: collapse; +} + +td, +th { + border: 1px solid #ddd; + margin: 0px; + text-align: left; + vertical-align: top; + padding: 4px 6px; + display: table-cell; +} + +thead tr { + background-color: #ddd; + font-weight: bold; +} + +th { + border-right: 1px solid #aaa; +} +tr > th:last-child { + border-right: 1px solid #ddd; +} + +.ancestors, +.attribs { + color: #999; +} +.ancestors a, +.attribs a { + color: #999 !important; + text-decoration: none; +} + +.clear { + clear: both; +} + +.important { + font-weight: bold; + color: #950b02; +} + +.yes-def { + text-indent: -1000px; +} + +.type-signature { + color: #aaa; +} + +.name, +.signature { + font-family: Consolas, Monaco, "Andale Mono", monospace; +} + +.details { + margin-top: 14px; + border-left: 2px solid #ddd; +} +.details dt { + width: 120px; + float: left; + padding-left: 10px; + padding-top: 6px; +} +.details dd { + margin-left: 70px; +} +.details ul { + margin: 0; +} +.details ul { + list-style-type: none; +} +.details li { + margin-left: 30px; + padding-top: 6px; +} +.details pre.prettyprint { + margin: 0; +} +.details .object-value { + padding-top: 0; +} + +.description { + margin-bottom: 1em; + margin-top: 1em; +} + +.code-caption { + font-style: italic; + font-size: 107%; + margin: 0; +} + +.source { + border: 1px solid #ddd; + width: 80%; + overflow: auto; +} + +.prettyprint.source { + width: inherit; +} + +.source code { + font-size: 100%; + line-height: 18px; + display: block; + padding: 4px 12px; + margin: 0; + background-color: #fff; + color: #4d4e53; +} + +.prettyprint code span.line { + display: inline-block; +} + +.prettyprint.linenums { + padding-left: 70px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.prettyprint.linenums ol { + padding-left: 0; +} + +.prettyprint.linenums li { + border-left: 3px #ddd solid; +} + +.prettyprint.linenums li.selected, +.prettyprint.linenums li.selected * { + background-color: lightyellow; +} + +.prettyprint.linenums li * { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.params .name, +.props .name, +.name code { + color: #4d4e53; + font-family: Consolas, Monaco, "Andale Mono", monospace; + font-size: 100%; +} + +.params td.description > p:first-child, +.props td.description > p:first-child { + margin-top: 0; + padding-top: 0; +} + +.params td.description > p:last-child, +.props td.description > p:last-child { + margin-bottom: 0; + padding-bottom: 0; +} + +.disabled { + color: #454545; +} diff --git a/docs/styles/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css new file mode 100644 index 0000000..5a2526e --- /dev/null +++ b/docs/styles/prettify-jsdoc.css @@ -0,0 +1,111 @@ +/* JSDoc prettify.js theme */ + +/* plain text */ +.pln { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* string content */ +.str { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a keyword */ +.kwd { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a comment */ +.com { + font-weight: normal; + font-style: italic; +} + +/* a type name */ +.typ { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a literal value */ +.lit { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* punctuation */ +.pun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp open bracket */ +.opn { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp close bracket */ +.clo { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a markup tag name */ +.tag { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute name */ +.atn { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute value */ +.atv { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a declaration */ +.dec { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a variable name */ +.var { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a function name */ +.fun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; +} diff --git a/docs/styles/prettify-tomorrow.css b/docs/styles/prettify-tomorrow.css new file mode 100644 index 0000000..3c8d71e --- /dev/null +++ b/docs/styles/prettify-tomorrow.css @@ -0,0 +1,163 @@ +/* Tomorrow Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* Pretty printing styles. Used with prettify.js. */ +/* SPAN elements with the classes below are added by prettyprint. */ +/* plain text */ +.pln { + color: #4d4d4c; +} + +@media screen { + /* string content */ + .str { + color: #718c00; + } + + /* a keyword */ + .kwd { + color: #8959a8; + } + + /* a comment */ + .com { + color: #8e908c; + } + + /* a type name */ + .typ { + color: #4271ae; + } + + /* a literal value */ + .lit { + color: #f5871f; + } + + /* punctuation */ + .pun { + color: #4d4d4c; + } + + /* lisp open bracket */ + .opn { + color: #4d4d4c; + } + + /* lisp close bracket */ + .clo { + color: #4d4d4c; + } + + /* a markup tag name */ + .tag { + color: #c82829; + } + + /* a markup attribute name */ + .atn { + color: #f5871f; + } + + /* a markup attribute value */ + .atv { + color: #3e999f; + } + + /* a declaration */ + .dec { + color: #f5871f; + } + + /* a variable name */ + .var { + color: #c82829; + } + + /* a function name */ + .fun { + color: #4271ae; + } +} +/* Use higher contrast and text-weight for printable form. */ +@media print, projection { + .str { + color: #060; + } + + .kwd { + color: #006; + font-weight: bold; + } + + .com { + color: #600; + font-style: italic; + } + + .typ { + color: #404; + font-weight: bold; + } + + .lit { + color: #044; + } + + .pun, + .opn, + .clo { + color: #440; + } + + .tag { + color: #006; + font-weight: bold; + } + + .atn { + color: #404; + } + + .atv { + color: #060; + } +} +/* Style */ +/* +pre.prettyprint { + background: white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 12px; + line-height: 1.5; + border: 1px solid #ccc; + padding: 10px; } +*/ + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; +} + +/* IE indents via margin-left */ +li.L0, +li.L1, +li.L2, +li.L3, +li.L4, +li.L5, +li.L6, +li.L7, +li.L8, +li.L9 { + /* */ +} + +/* Alternate shading for lines */ +li.L1, +li.L3, +li.L5, +li.L7, +li.L9 { + /* */ +} diff --git a/package.json b/package.json index b1648bd..2a2cf4c 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,9 @@ "standard": { "globals": [ "requestAnimationFrame" + ], + "ignore": [ + "scripts/" ] }, "pre-commit": [