Refactor code for consistency and readability
- Updated import statements to use consistent formatting across files. - Adjusted method definitions and class constructors for uniform spacing and style. - Simplified promise handling and error messages in state handlers. - Enhanced state transition logic in various state handlers. - Improved quirk animation handling in WaitStateHandler. - Streamlined animation loading and caching mechanisms in AnimationLoader. - Updated Vite configuration for aliasing.
This commit is contained in:
@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2022": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2022,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"single"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"no-unused-vars": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"argsIgnorePattern": "^_"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-console": [
|
|
||||||
"warn"
|
|
||||||
],
|
|
||||||
"max-len": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"code": 120
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"examples/**/*.js"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"src/**/*.js"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ignorePatterns": [
|
|
||||||
"node_modules/",
|
|
||||||
"dist/",
|
|
||||||
"docs/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
41
.gitignore
vendored
41
.gitignore
vendored
@ -1,3 +1,30 @@
|
|||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
|
||||||
|
# Documentation output
|
||||||
|
/docs/
|
||||||
|
|
||||||
|
# Animation assets (if storing locally)
|
||||||
|
/assets/models/
|
||||||
|
/assets/animations/
|
||||||
|
|
||||||
|
# Example build outputs
|
||||||
|
/examples/dist/
|
||||||
|
|
||||||
|
# 3D Models (optional - remove if you want to commit models)
|
||||||
|
*.gltf
|
||||||
|
*.glb
|
||||||
|
*.fbx
|
||||||
|
*.obj
|
||||||
|
*.dae
|
||||||
|
|
||||||
|
# Three.js cache
|
||||||
|
.three-cache/
|
||||||
|
|
||||||
|
# Editor (optional - remove if you want to commit editor files)
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node
|
# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node
|
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node
|
||||||
|
|
||||||
@ -418,17 +445,3 @@ $RECYCLE.BIN/
|
|||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node
|
# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node
|
||||||
|
|
||||||
# Build outputs
|
|
||||||
docs/
|
|
||||||
|
|
||||||
# Temporary files
|
|
||||||
*.tmp
|
|
||||||
*.temp
|
|
||||||
|
|
||||||
# 3D Models (optional - remove if you want to commit models)
|
|
||||||
*.gltf
|
|
||||||
*.glb
|
|
||||||
*.fbx
|
|
||||||
*.obj
|
|
||||||
*.dae
|
|
||||||
|
|||||||
17
CHANGELOG.md
17
CHANGELOG.md
@ -5,9 +5,18 @@ All notable changes to the Owen Animation System will be documented in this file
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.1] - 2025-05-24
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 🎨 Standardized code style throughout the codebase
|
||||||
|
- 🔧 Converted semicolons to non-semicolons according to JavaScript Standard Style
|
||||||
|
- 📝 Improved code consistency and readability
|
||||||
|
|
||||||
## [1.0.0] - 2025-05-23
|
## [1.0.0] - 2025-05-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- 🎉 Initial release of Owen Animation System
|
- 🎉 Initial release of Owen Animation System
|
||||||
- ✨ Complete state machine implementation with Wait, React, Type, and Sleep states
|
- ✨ Complete state machine implementation with Wait, React, Type, and Sleep states
|
||||||
- 🤖 Emotional response system for character animations
|
- 🤖 Emotional response system for character animations
|
||||||
@ -21,7 +30,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- 📦 TypeScript type definitions
|
- 📦 TypeScript type definitions
|
||||||
- 🛠️ Development tooling (ESLint, Vite, JSDoc)
|
- 🛠️ Development tooling (ESLint, Vite, JSDoc)
|
||||||
|
|
||||||
|
[1.0.1]: https://gitea.kajkowalski.nl/kjanat/Owen/releases/tag/v1.0.1
|
||||||
|
|
||||||
### Architecture
|
### Architecture
|
||||||
|
|
||||||
- **Core Classes:**
|
- **Core Classes:**
|
||||||
- `OwenAnimationContext` - Main system controller
|
- `OwenAnimationContext` - Main system controller
|
||||||
- `AnimationClip` - Individual animation management
|
- `AnimationClip` - Individual animation management
|
||||||
@ -43,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- `OwenSystemFactory` - Main system assembly factory
|
- `OwenSystemFactory` - Main system assembly factory
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- **Animation System:**
|
- **Animation System:**
|
||||||
- Support for Loop (L), Quirk (Q), Transition (T), and Nested animations
|
- Support for Loop (L), Quirk (Q), Transition (T), and Nested animations
|
||||||
- Automatic metadata parsing from animation names
|
- Automatic metadata parsing from animation names
|
||||||
@ -63,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Vite development server setup
|
- Vite development server setup
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- Complete README with installation and usage instructions
|
- Complete README with installation and usage instructions
|
||||||
- API documentation via JSDoc
|
- API documentation via JSDoc
|
||||||
- Code examples for basic and advanced usage
|
- Code examples for basic and advanced usage
|
||||||
@ -70,9 +84,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Troubleshooting section
|
- Troubleshooting section
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
- Basic browser demo with Three.js integration
|
- Basic browser demo with Three.js integration
|
||||||
- Simple Node.js example for testing
|
- Simple Node.js example for testing
|
||||||
- Interactive controls for state transitions
|
- Interactive controls for state transitions
|
||||||
- Mock model implementation for development
|
- Mock model implementation for development
|
||||||
|
|
||||||
[1.0.0]: https://github.com/your-username/owen-animation-system/releases/tag/v1.0.0
|
[1.0.0]: https://gitea.kajkowalski.nl/kjanat/Owen/releases/tag/v1.0.0
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
A comprehensive Three.js animation system for character state management with clean architecture principles, dependency injection, and factory patterns.
|
A comprehensive Three.js animation system for character state management with clean architecture principles, dependency injection, and factory patterns.
|
||||||
|
|
||||||
|
[](https://gitea.kajkowalski.nl/kjanat/Owen/issues)
|
||||||
|
[](https://gitea.kajkowalski.nl/kjanat/Owen/pulls)
|
||||||
|
[](https://gitea.kajkowalski.nl/kjanat/Owen/tags)
|
||||||
|
|
||||||
## 🎯 Overview
|
## 🎯 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.
|
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.
|
||||||
@ -181,7 +185,6 @@ Owen/
|
|||||||
│ └── basic-demo.js # Basic usage example
|
│ └── basic-demo.js # Basic usage example
|
||||||
├── package.json
|
├── package.json
|
||||||
├── vite.config.js
|
├── vite.config.js
|
||||||
├── .eslintrc.json
|
|
||||||
├── jsdoc.config.json
|
├── jsdoc.config.json
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
@ -228,8 +231,8 @@ Documentation will be generated in the `docs/` directory.
|
|||||||
- `npm run dev` - Start development server
|
- `npm run dev` - Start development server
|
||||||
- `npm run build` - Build for production
|
- `npm run build` - Build for production
|
||||||
- `npm run preview` - Preview production build
|
- `npm run preview` - Preview production build
|
||||||
- `npm run lint` - Run ESLint
|
- `npm run lint` - Run StandardJS linting
|
||||||
- `npm run lint:fix` - Fix ESLint issues
|
- `npm run lint:fix` - Fix StandardJS issues
|
||||||
- `npm run docs` - Generate JSDoc documentation
|
- `npm run docs` - Generate JSDoc documentation
|
||||||
|
|
||||||
## 🎮 Demo Controls
|
## 🎮 Demo Controls
|
||||||
|
|||||||
@ -1,332 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview Basic example of using the Owen Animation System
|
|
||||||
* @author Owen Animation System
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as THREE from 'three';
|
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
||||||
import { OwenSystemFactory, States } from '../src/index.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic Owen Animation System demo
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
class OwenDemo {
|
|
||||||
/**
|
|
||||||
* Create the demo
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* The Three.js scene
|
|
||||||
* @type {THREE.Scene}
|
|
||||||
*/
|
|
||||||
this.scene = new THREE.Scene();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Three.js camera
|
|
||||||
* @type {THREE.PerspectiveCamera}
|
|
||||||
*/
|
|
||||||
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Three.js renderer
|
|
||||||
* @type {THREE.WebGLRenderer}
|
|
||||||
*/
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Owen animation system
|
|
||||||
* @type {OwenAnimationContext|null}
|
|
||||||
*/
|
|
||||||
this.owenSystem = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock for tracking time
|
|
||||||
* @type {THREE.Clock}
|
|
||||||
*/
|
|
||||||
this.clock = new THREE.Clock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the demo
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async init() {
|
|
||||||
// Setup renderer
|
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
this.renderer.setClearColor(0x1a1a1a);
|
|
||||||
this.renderer.shadowMap.enabled = true;
|
|
||||||
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
||||||
document.body.appendChild(this.renderer.domElement);
|
|
||||||
|
|
||||||
// Setup camera
|
|
||||||
this.camera.position.set(0, 1.6, 3);
|
|
||||||
this.camera.lookAt(0, 1, 0);
|
|
||||||
|
|
||||||
// Add lighting
|
|
||||||
this.setupLighting();
|
|
||||||
|
|
||||||
// Load Owen model (replace with your model path)
|
|
||||||
await this.loadOwenModel();
|
|
||||||
|
|
||||||
// Setup event listeners
|
|
||||||
this.setupEventListeners();
|
|
||||||
|
|
||||||
// Start render loop
|
|
||||||
this.animate();
|
|
||||||
|
|
||||||
console.log('Owen Demo initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup scene lighting
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
setupLighting() {
|
|
||||||
// Ambient light
|
|
||||||
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
|
|
||||||
this.scene.add(ambientLight);
|
|
||||||
|
|
||||||
// Directional light
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
||||||
directionalLight.position.set(5, 10, 5);
|
|
||||||
directionalLight.castShadow = true;
|
|
||||||
directionalLight.shadow.mapSize.width = 2048;
|
|
||||||
directionalLight.shadow.mapSize.height = 2048;
|
|
||||||
this.scene.add(directionalLight);
|
|
||||||
|
|
||||||
// Fill light
|
|
||||||
const fillLight = new THREE.DirectionalLight(0x8bb7f0, 0.3);
|
|
||||||
fillLight.position.set(-5, 5, -5);
|
|
||||||
this.scene.add(fillLight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the Owen character model
|
|
||||||
* @private
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async loadOwenModel() {
|
|
||||||
try {
|
|
||||||
const loader = new GLTFLoader();
|
|
||||||
|
|
||||||
// Replace 'path/to/owen.gltf' with your actual model path
|
|
||||||
const gltf = await new Promise((resolve, reject) => {
|
|
||||||
loader.load(
|
|
||||||
'path/to/owen.gltf', // Update this path
|
|
||||||
resolve,
|
|
||||||
(progress) => console.log('Loading progress:', progress.loaded / progress.total * 100 + '%'),
|
|
||||||
reject
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const model = gltf.scene;
|
|
||||||
model.position.set(0, 0, 0);
|
|
||||||
model.scale.setScalar(1);
|
|
||||||
|
|
||||||
// Enable shadows
|
|
||||||
model.traverse((child) => {
|
|
||||||
if (child.isMesh) {
|
|
||||||
child.castShadow = true;
|
|
||||||
child.receiveShadow = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scene.add(model);
|
|
||||||
|
|
||||||
// Create Owen animation system
|
|
||||||
this.owenSystem = await OwenSystemFactory.createOwenSystem(gltf, this.scene);
|
|
||||||
|
|
||||||
console.log('Owen model loaded and animation system created');
|
|
||||||
this.logSystemInfo();
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading Owen model:', error);
|
|
||||||
|
|
||||||
// Create a placeholder cube for demo purposes
|
|
||||||
this.createPlaceholderModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a placeholder model for demo purposes
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
createPlaceholderModel() {
|
|
||||||
const geometry = new THREE.BoxGeometry(1, 2, 1);
|
|
||||||
const material = new THREE.MeshPhongMaterial({ color: 0x6699ff });
|
|
||||||
const cube = new THREE.Mesh(geometry, material);
|
|
||||||
cube.position.set(0, 1, 0);
|
|
||||||
cube.castShadow = true;
|
|
||||||
cube.receiveShadow = true;
|
|
||||||
this.scene.add(cube);
|
|
||||||
|
|
||||||
console.log('Created placeholder model (cube)');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup event listeners for user interaction
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
setupEventListeners() {
|
|
||||||
// Keyboard controls
|
|
||||||
document.addEventListener('keydown', (event) => {
|
|
||||||
if (!this.owenSystem) return;
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case '1':
|
|
||||||
this.owenSystem.transitionTo(States.WAIT);
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
this.owenSystem.transitionTo(States.REACT);
|
|
||||||
break;
|
|
||||||
case '3':
|
|
||||||
this.owenSystem.transitionTo(States.TYPE);
|
|
||||||
break;
|
|
||||||
case '4':
|
|
||||||
this.owenSystem.transitionTo(States.SLEEP);
|
|
||||||
break;
|
|
||||||
case ' ':
|
|
||||||
this.sendTestMessage();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mouse interaction
|
|
||||||
document.addEventListener('click', () => {
|
|
||||||
if (this.owenSystem) {
|
|
||||||
this.owenSystem.onUserActivity();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Window resize
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
this.camera.updateProjectionMatrix();
|
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add instructions to the page
|
|
||||||
this.addInstructions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add on-screen instructions
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addInstructions() {
|
|
||||||
const instructions = document.createElement('div');
|
|
||||||
instructions.innerHTML = `
|
|
||||||
<div style="position: absolute; top: 10px; left: 10px; color: white; ` +
|
|
||||||
`font-family: monospace; font-size: 14px; line-height: 1.4;">
|
|
||||||
<h3>Owen Animation System Demo</h3>
|
|
||||||
<p><strong>Controls:</strong></p>
|
|
||||||
<p>1 - Wait State</p>
|
|
||||||
<p>2 - React State</p>
|
|
||||||
<p>3 - Type State</p>
|
|
||||||
<p>4 - Sleep State</p>
|
|
||||||
<p>Space - Send Test Message</p>
|
|
||||||
<p>Click - User Activity</p>
|
|
||||||
<br>
|
|
||||||
<p><strong>Current State:</strong> <span id="current-state">-</span></p>
|
|
||||||
<p><strong>Available Transitions:</strong> <span id="transitions">-</span></p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(instructions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a test message to Owen
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
sendTestMessage() {
|
|
||||||
if (!this.owenSystem) return;
|
|
||||||
|
|
||||||
const testMessages = [
|
|
||||||
'Hello Owen!',
|
|
||||||
'How are you doing?',
|
|
||||||
'This is urgent!',
|
|
||||||
'Great work!',
|
|
||||||
'Error in the system!',
|
|
||||||
'I\'m feeling sad today'
|
|
||||||
];
|
|
||||||
|
|
||||||
const randomMessage = testMessages[Math.floor(Math.random() * testMessages.length)];
|
|
||||||
console.log(`Sending message: "${randomMessage}"`);
|
|
||||||
this.owenSystem.handleUserMessage(randomMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log system information
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
logSystemInfo() {
|
|
||||||
if (!this.owenSystem) return;
|
|
||||||
|
|
||||||
console.log('=== Owen System Info ===');
|
|
||||||
console.log('Available States:', this.owenSystem.getAvailableStates());
|
|
||||||
console.log('Available Clips:', this.owenSystem.getAvailableClips());
|
|
||||||
console.log('Current State:', this.owenSystem.getCurrentState());
|
|
||||||
console.log('========================');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update UI with current system state
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
updateUI() {
|
|
||||||
if (!this.owenSystem) return;
|
|
||||||
|
|
||||||
const currentStateElement = document.getElementById('current-state');
|
|
||||||
const transitionsElement = document.getElementById('transitions');
|
|
||||||
|
|
||||||
if (currentStateElement) {
|
|
||||||
currentStateElement.textContent = this.owenSystem.getCurrentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transitionsElement) {
|
|
||||||
transitionsElement.textContent = this.owenSystem.getAvailableTransitions().join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main animation loop
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
animate() {
|
|
||||||
requestAnimationFrame(() => this.animate());
|
|
||||||
|
|
||||||
const deltaTime = this.clock.getDelta() * 1000; // Convert to milliseconds
|
|
||||||
|
|
||||||
// Update Owen system
|
|
||||||
if (this.owenSystem) {
|
|
||||||
this.owenSystem.update(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update UI
|
|
||||||
this.updateUI();
|
|
||||||
|
|
||||||
// Render scene
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the demo when the page loads
|
|
||||||
window.addEventListener('load', async () => {
|
|
||||||
const demo = new OwenDemo();
|
|
||||||
try {
|
|
||||||
await demo.init();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize Owen demo:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OwenDemo;
|
|
||||||
@ -3,9 +3,9 @@
|
|||||||
* @author Owen Animation System
|
* @author Owen Animation System
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three'
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||||
import { OwenSystemFactory, States } from '../src/index.js';
|
import { OwenSystemFactory, States } from '../src/index.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic Owen Animation System demo
|
* Basic Owen Animation System demo
|
||||||
@ -20,31 +20,31 @@ class OwenDemo {
|
|||||||
* The Three.js scene
|
* The Three.js scene
|
||||||
* @type {THREE.Scene}
|
* @type {THREE.Scene}
|
||||||
*/
|
*/
|
||||||
this.scene = new THREE.Scene();
|
this.scene = new THREE.Scene()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Three.js camera
|
* The Three.js camera
|
||||||
* @type {THREE.PerspectiveCamera}
|
* @type {THREE.PerspectiveCamera}
|
||||||
*/
|
*/
|
||||||
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Three.js renderer
|
* The Three.js renderer
|
||||||
* @type {THREE.WebGLRenderer}
|
* @type {THREE.WebGLRenderer}
|
||||||
*/
|
*/
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
this.renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Owen animation system
|
* The Owen animation system
|
||||||
* @type {OwenAnimationContext|null}
|
* @type {OwenAnimationContext|null}
|
||||||
*/
|
*/
|
||||||
this.owenSystem = null;
|
this.owenSystem = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clock for tracking time
|
* Clock for tracking time
|
||||||
* @type {THREE.Clock}
|
* @type {THREE.Clock}
|
||||||
*/
|
*/
|
||||||
this.clock = new THREE.Clock();
|
this.clock = new THREE.Clock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,29 +53,29 @@ class OwenDemo {
|
|||||||
*/
|
*/
|
||||||
async init () {
|
async init () {
|
||||||
// Setup renderer
|
// Setup renderer
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
this.renderer.setClearColor(0x1a1a1a);
|
this.renderer.setClearColor(0x1a1a1a)
|
||||||
this.renderer.shadowMap.enabled = true;
|
this.renderer.shadowMap.enabled = true
|
||||||
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||||
document.body.appendChild(this.renderer.domElement);
|
document.body.appendChild(this.renderer.domElement)
|
||||||
|
|
||||||
// Setup camera
|
// Setup camera
|
||||||
this.camera.position.set(0, 1.6, 3);
|
this.camera.position.set(0, 1.6, 3)
|
||||||
this.camera.lookAt(0, 1, 0);
|
this.camera.lookAt(0, 1, 0)
|
||||||
|
|
||||||
// Add lighting
|
// Add lighting
|
||||||
this.setupLighting();
|
this.setupLighting()
|
||||||
|
|
||||||
// Load Owen model (replace with your model path)
|
// Load Owen model (replace with your model path)
|
||||||
await this.loadOwenModel();
|
await this.loadOwenModel()
|
||||||
|
|
||||||
// Setup event listeners
|
// Setup event listeners
|
||||||
this.setupEventListeners();
|
this.setupEventListeners()
|
||||||
|
|
||||||
// Start render loop
|
// Start render loop
|
||||||
this.animate();
|
this.animate()
|
||||||
|
|
||||||
console.log('Owen Demo initialized');
|
console.log('Owen Demo initialized')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,21 +85,21 @@ class OwenDemo {
|
|||||||
*/
|
*/
|
||||||
setupLighting () {
|
setupLighting () {
|
||||||
// Ambient light
|
// Ambient light
|
||||||
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
|
const ambientLight = new THREE.AmbientLight(0x404040, 0.4)
|
||||||
this.scene.add(ambientLight);
|
this.scene.add(ambientLight)
|
||||||
|
|
||||||
// Directional light
|
// Directional light
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
|
||||||
directionalLight.position.set(5, 10, 5);
|
directionalLight.position.set(5, 10, 5)
|
||||||
directionalLight.castShadow = true;
|
directionalLight.castShadow = true
|
||||||
directionalLight.shadow.mapSize.width = 2048;
|
directionalLight.shadow.mapSize.width = 2048
|
||||||
directionalLight.shadow.mapSize.height = 2048;
|
directionalLight.shadow.mapSize.height = 2048
|
||||||
this.scene.add(directionalLight);
|
this.scene.add(directionalLight)
|
||||||
|
|
||||||
// Fill light
|
// Fill light
|
||||||
const fillLight = new THREE.DirectionalLight(0x8bb7f0, 0.3);
|
const fillLight = new THREE.DirectionalLight(0x8bb7f0, 0.3)
|
||||||
fillLight.position.set(-5, 5, -5);
|
fillLight.position.set(-5, 5, -5)
|
||||||
this.scene.add(fillLight);
|
this.scene.add(fillLight)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,7 +109,7 @@ class OwenDemo {
|
|||||||
*/
|
*/
|
||||||
async loadOwenModel () {
|
async loadOwenModel () {
|
||||||
try {
|
try {
|
||||||
const loader = new GLTFLoader();
|
const loader = new GLTFLoader()
|
||||||
|
|
||||||
// Replace 'path/to/owen.gltf' with your actual model path
|
// Replace 'path/to/owen.gltf' with your actual model path
|
||||||
const gltf = await new Promise((resolve, reject) => {
|
const gltf = await new Promise((resolve, reject) => {
|
||||||
@ -118,34 +118,33 @@ class OwenDemo {
|
|||||||
resolve,
|
resolve,
|
||||||
(progress) => console.log('Loading progress:', progress.loaded / progress.total * 100 + '%'),
|
(progress) => console.log('Loading progress:', progress.loaded / progress.total * 100 + '%'),
|
||||||
reject
|
reject
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
const model = gltf.scene;
|
const model = gltf.scene
|
||||||
model.position.set(0, 0, 0);
|
model.position.set(0, 0, 0)
|
||||||
model.scale.setScalar(1);
|
model.scale.setScalar(1)
|
||||||
|
|
||||||
// Enable shadows
|
// Enable shadows
|
||||||
model.traverse((child) => {
|
model.traverse((child) => {
|
||||||
if (child.isMesh) {
|
if (child.isMesh) {
|
||||||
child.castShadow = true;
|
child.castShadow = true
|
||||||
child.receiveShadow = true;
|
child.receiveShadow = true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
this.scene.add(model);
|
this.scene.add(model)
|
||||||
|
|
||||||
// Create Owen animation system
|
// Create Owen animation system
|
||||||
this.owenSystem = await OwenSystemFactory.createOwenSystem(gltf, this.scene);
|
this.owenSystem = await OwenSystemFactory.createOwenSystem(gltf, this.scene)
|
||||||
|
|
||||||
console.log('Owen model loaded and animation system created');
|
|
||||||
this.logSystemInfo();
|
|
||||||
|
|
||||||
|
console.log('Owen model loaded and animation system created')
|
||||||
|
this.logSystemInfo()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading Owen model:', error);
|
console.error('Error loading Owen model:', error)
|
||||||
|
|
||||||
// Create a placeholder cube for demo purposes
|
// Create a placeholder cube for demo purposes
|
||||||
this.createPlaceholderModel();
|
this.createPlaceholderModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,15 +154,15 @@ class OwenDemo {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
createPlaceholderModel () {
|
createPlaceholderModel () {
|
||||||
const geometry = new THREE.BoxGeometry(1, 2, 1);
|
const geometry = new THREE.BoxGeometry(1, 2, 1)
|
||||||
const material = new THREE.MeshPhongMaterial({ color: 0x6699ff });
|
const material = new THREE.MeshPhongMaterial({ color: 0x6699ff })
|
||||||
const cube = new THREE.Mesh(geometry, material);
|
const cube = new THREE.Mesh(geometry, material)
|
||||||
cube.position.set(0, 1, 0);
|
cube.position.set(0, 1, 0)
|
||||||
cube.castShadow = true;
|
cube.castShadow = true
|
||||||
cube.receiveShadow = true;
|
cube.receiveShadow = true
|
||||||
this.scene.add(cube);
|
this.scene.add(cube)
|
||||||
|
|
||||||
console.log('Created placeholder model (cube)');
|
console.log('Created placeholder model (cube)')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -174,43 +173,43 @@ class OwenDemo {
|
|||||||
setupEventListeners () {
|
setupEventListeners () {
|
||||||
// Keyboard controls
|
// Keyboard controls
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
if (!this.owenSystem) return;
|
if (!this.owenSystem) return
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case '1':
|
case '1':
|
||||||
this.owenSystem.transitionTo(States.WAIT);
|
this.owenSystem.transitionTo(States.WAIT)
|
||||||
break;
|
break
|
||||||
case '2':
|
case '2':
|
||||||
this.owenSystem.transitionTo(States.REACT);
|
this.owenSystem.transitionTo(States.REACT)
|
||||||
break;
|
break
|
||||||
case '3':
|
case '3':
|
||||||
this.owenSystem.transitionTo(States.TYPE);
|
this.owenSystem.transitionTo(States.TYPE)
|
||||||
break;
|
break
|
||||||
case '4':
|
case '4':
|
||||||
this.owenSystem.transitionTo(States.SLEEP);
|
this.owenSystem.transitionTo(States.SLEEP)
|
||||||
break;
|
break
|
||||||
case ' ':
|
case ' ':
|
||||||
this.sendTestMessage();
|
this.sendTestMessage()
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Mouse interaction
|
// Mouse interaction
|
||||||
document.addEventListener('click', () => {
|
document.addEventListener('click', () => {
|
||||||
if (this.owenSystem) {
|
if (this.owenSystem) {
|
||||||
this.owenSystem.onUserActivity();
|
this.owenSystem.onUserActivity()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Window resize
|
// Window resize
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
this.camera.aspect = window.innerWidth / window.innerHeight
|
||||||
this.camera.updateProjectionMatrix();
|
this.camera.updateProjectionMatrix()
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
this.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
});
|
})
|
||||||
|
|
||||||
// Add instructions to the page
|
// Add instructions to the page
|
||||||
this.addInstructions();
|
this.addInstructions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,7 +218,7 @@ class OwenDemo {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
addInstructions () {
|
addInstructions () {
|
||||||
const instructions = document.createElement('div');
|
const instructions = document.createElement('div')
|
||||||
instructions.innerHTML = `
|
instructions.innerHTML = `
|
||||||
<div style="position: absolute; top: 10px; left: 10px; color: white; ` +
|
<div style="position: absolute; top: 10px; left: 10px; color: white; ` +
|
||||||
`font-family: monospace; font-size: 14px; line-height: 1.4;">
|
`font-family: monospace; font-size: 14px; line-height: 1.4;">
|
||||||
@ -235,8 +234,8 @@ class OwenDemo {
|
|||||||
<p><strong>Current State:</strong> <span id="current-state">-</span></p>
|
<p><strong>Current State:</strong> <span id="current-state">-</span></p>
|
||||||
<p><strong>Available Transitions:</strong> <span id="transitions">-</span></p>
|
<p><strong>Available Transitions:</strong> <span id="transitions">-</span></p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
document.body.appendChild(instructions);
|
document.body.appendChild(instructions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -245,7 +244,7 @@ class OwenDemo {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
sendTestMessage () {
|
sendTestMessage () {
|
||||||
if (!this.owenSystem) return;
|
if (!this.owenSystem) return
|
||||||
|
|
||||||
const testMessages = [
|
const testMessages = [
|
||||||
'Hello Owen!',
|
'Hello Owen!',
|
||||||
@ -254,11 +253,11 @@ class OwenDemo {
|
|||||||
'Great work!',
|
'Great work!',
|
||||||
'Error in the system!',
|
'Error in the system!',
|
||||||
'I\'m feeling sad today'
|
'I\'m feeling sad today'
|
||||||
];
|
]
|
||||||
|
|
||||||
const randomMessage = testMessages[ Math.floor(Math.random() * testMessages.length) ];
|
const randomMessage = testMessages[Math.floor(Math.random() * testMessages.length)]
|
||||||
console.log(`Sending message: "${randomMessage}"`);
|
console.log(`Sending message: "${randomMessage}"`)
|
||||||
this.owenSystem.handleUserMessage(randomMessage);
|
this.owenSystem.handleUserMessage(randomMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,13 +266,13 @@ class OwenDemo {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
logSystemInfo () {
|
logSystemInfo () {
|
||||||
if (!this.owenSystem) return;
|
if (!this.owenSystem) return
|
||||||
|
|
||||||
console.log('=== Owen System Info ===');
|
console.log('=== Owen System Info ===')
|
||||||
console.log('Available States:', this.owenSystem.getAvailableStates());
|
console.log('Available States:', this.owenSystem.getAvailableStates())
|
||||||
console.log('Available Clips:', this.owenSystem.getAvailableClips());
|
console.log('Available Clips:', this.owenSystem.getAvailableClips())
|
||||||
console.log('Current State:', this.owenSystem.getCurrentState());
|
console.log('Current State:', this.owenSystem.getCurrentState())
|
||||||
console.log('========================');
|
console.log('========================')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -282,17 +281,17 @@ class OwenDemo {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
updateUI () {
|
updateUI () {
|
||||||
if (!this.owenSystem) return;
|
if (!this.owenSystem) return
|
||||||
|
|
||||||
const currentStateElement = document.getElementById('current-state');
|
const currentStateElement = document.getElementById('current-state')
|
||||||
const transitionsElement = document.getElementById('transitions');
|
const transitionsElement = document.getElementById('transitions')
|
||||||
|
|
||||||
if (currentStateElement) {
|
if (currentStateElement) {
|
||||||
currentStateElement.textContent = this.owenSystem.getCurrentState();
|
currentStateElement.textContent = this.owenSystem.getCurrentState()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transitionsElement) {
|
if (transitionsElement) {
|
||||||
transitionsElement.textContent = this.owenSystem.getAvailableTransitions().join(', ');
|
transitionsElement.textContent = this.owenSystem.getAvailableTransitions().join(', ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,31 +301,31 @@ class OwenDemo {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
animate () {
|
animate () {
|
||||||
requestAnimationFrame(() => this.animate());
|
requestAnimationFrame(() => this.animate())
|
||||||
|
|
||||||
const deltaTime = this.clock.getDelta() * 1000; // Convert to milliseconds
|
const deltaTime = this.clock.getDelta() * 1000 // Convert to milliseconds
|
||||||
|
|
||||||
// Update Owen system
|
// Update Owen system
|
||||||
if (this.owenSystem) {
|
if (this.owenSystem) {
|
||||||
this.owenSystem.update(deltaTime);
|
this.owenSystem.update(deltaTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
this.updateUI();
|
this.updateUI()
|
||||||
|
|
||||||
// Render scene
|
// Render scene
|
||||||
this.renderer.render(this.scene, this.camera);
|
this.renderer.render(this.scene, this.camera)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the demo when the page loads
|
// Initialize the demo when the page loads
|
||||||
window.addEventListener('load', async () => {
|
window.addEventListener('load', async () => {
|
||||||
const demo = new OwenDemo();
|
const demo = new OwenDemo()
|
||||||
try {
|
try {
|
||||||
await demo.init();
|
await demo.init()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize Owen demo:', error);
|
console.error('Failed to initialize Owen demo:', error)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
export default OwenDemo;
|
export default OwenDemo
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Owen Animation System - Basic Demo</title>
|
<title>Owen Animation System - Basic Demo</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
@ -31,13 +31,13 @@
|
|||||||
<div id="loading">Loading Owen Animation System...</div>
|
<div id="loading">Loading Owen Animation System...</div>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import './basic-demo.js';
|
import "./basic-demo.js";
|
||||||
|
|
||||||
// Hide loading screen after a short delay
|
// Hide loading screen after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const loading = document.getElementById('loading');
|
const loading = document.getElementById("loading");
|
||||||
if (loading) {
|
if (loading) {
|
||||||
loading.classList.add('hidden');
|
loading.classList.add("hidden");
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* @author Owen Animation System
|
* @author Owen Animation System
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { OwenSystemFactory, States } from '../src/index.js';
|
import { OwenSystemFactory, States } from '../src/index.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple example of using Owen Animation System
|
* Simple example of using Owen Animation System
|
||||||
@ -11,7 +11,7 @@ import { OwenSystemFactory, States } from '../src/index.js';
|
|||||||
*/
|
*/
|
||||||
class SimpleOwenExample {
|
class SimpleOwenExample {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.owenSystem = null;
|
this.owenSystem = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,21 +21,20 @@ class SimpleOwenExample {
|
|||||||
async init () {
|
async init () {
|
||||||
try {
|
try {
|
||||||
// Create a mock GLTF model for demonstration
|
// Create a mock GLTF model for demonstration
|
||||||
const mockModel = this.createMockModel();
|
const mockModel = this.createMockModel()
|
||||||
|
|
||||||
// Create the Owen system
|
// Create the Owen system
|
||||||
this.owenSystem = await OwenSystemFactory.createBasicOwenSystem(mockModel);
|
this.owenSystem = await OwenSystemFactory.createBasicOwenSystem(mockModel)
|
||||||
|
|
||||||
console.log('✅ Owen Animation System initialized successfully!');
|
console.log('✅ Owen Animation System initialized successfully!')
|
||||||
console.log('📊 System Info:');
|
console.log('📊 System Info:')
|
||||||
console.log(` Available States: ${this.owenSystem.getAvailableStates().join(', ')}`);
|
console.log(` Available States: ${this.owenSystem.getAvailableStates().join(', ')}`)
|
||||||
console.log(` Current State: ${this.owenSystem.getCurrentState()}`);
|
console.log(` Current State: ${this.owenSystem.getCurrentState()}`)
|
||||||
|
|
||||||
// Run some example interactions
|
// Run some example interactions
|
||||||
await this.runExamples();
|
await this.runExamples()
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to initialize Owen system:', error.message);
|
console.error('❌ Failed to initialize Owen system:', error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ class SimpleOwenExample {
|
|||||||
],
|
],
|
||||||
scene: {},
|
scene: {},
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,21 +70,21 @@ class SimpleOwenExample {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async runExamples () {
|
async runExamples () {
|
||||||
console.log('\n🎬 Running example interactions...\n');
|
console.log('\n🎬 Running example interactions...\n')
|
||||||
|
|
||||||
// Example 1: Basic state transitions
|
// Example 1: Basic state transitions
|
||||||
console.log('📝 Example 1: Manual state transitions');
|
console.log('📝 Example 1: Manual state transitions')
|
||||||
await this.demonstrateStateTransitions();
|
await this.demonstrateStateTransitions()
|
||||||
|
|
||||||
// Example 2: Message handling
|
// Example 2: Message handling
|
||||||
console.log('\n📝 Example 2: Message handling with emotions');
|
console.log('\n📝 Example 2: Message handling with emotions')
|
||||||
await this.demonstrateMessageHandling();
|
await this.demonstrateMessageHandling()
|
||||||
|
|
||||||
// Example 3: System update loop
|
// Example 3: System update loop
|
||||||
console.log('\n📝 Example 3: System update simulation');
|
console.log('\n📝 Example 3: System update simulation')
|
||||||
this.demonstrateUpdateLoop();
|
this.demonstrateUpdateLoop()
|
||||||
|
|
||||||
console.log('\n✨ All examples completed!');
|
console.log('\n✨ All examples completed!')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,16 +92,16 @@ class SimpleOwenExample {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async demonstrateStateTransitions () {
|
async demonstrateStateTransitions () {
|
||||||
const states = [ States.REACT, States.TYPE, States.WAIT, States.SLEEP ];
|
const states = [States.REACT, States.TYPE, States.WAIT, States.SLEEP]
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
console.log(`🔄 Transitioning to ${state.toUpperCase()} state...`);
|
console.log(`🔄 Transitioning to ${state.toUpperCase()} state...`)
|
||||||
await this.owenSystem.transitionTo(state);
|
await this.owenSystem.transitionTo(state)
|
||||||
console.log(` ✓ Current state: ${this.owenSystem.getCurrentState()}`);
|
console.log(` ✓ Current state: ${this.owenSystem.getCurrentState()}`)
|
||||||
console.log(` ✓ Available transitions: ${this.owenSystem.getAvailableTransitions().join(', ')}`);
|
console.log(` ✓ Available transitions: ${this.owenSystem.getAvailableTransitions().join(', ')}`)
|
||||||
|
|
||||||
// Simulate some time passing
|
// Simulate some time passing
|
||||||
await this.sleep(500);
|
await this.sleep(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,16 +116,16 @@ class SimpleOwenExample {
|
|||||||
{ text: 'Great work!', expected: 'happy response' },
|
{ text: 'Great work!', expected: 'happy response' },
|
||||||
{ text: 'There\'s an error in the system', expected: 'shocked response' },
|
{ text: 'There\'s an error in the system', expected: 'shocked response' },
|
||||||
{ text: 'I\'m feeling sad today', expected: 'sad response' }
|
{ text: 'I\'m feeling sad today', expected: 'sad response' }
|
||||||
];
|
]
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
console.log(`💬 Sending message: "${message.text}"`);
|
console.log(`💬 Sending message: "${message.text}"`)
|
||||||
console.log(` Expected: ${message.expected}`);
|
console.log(` Expected: ${message.expected}`)
|
||||||
|
|
||||||
await this.owenSystem.handleUserMessage(message.text);
|
await this.owenSystem.handleUserMessage(message.text)
|
||||||
console.log(` ✓ Current state after message: ${this.owenSystem.getCurrentState()}`);
|
console.log(` ✓ Current state after message: ${this.owenSystem.getCurrentState()}`)
|
||||||
|
|
||||||
await this.sleep(300);
|
await this.sleep(300)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,25 +134,25 @@ class SimpleOwenExample {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
demonstrateUpdateLoop () {
|
demonstrateUpdateLoop () {
|
||||||
console.log('⏱️ Simulating update loop for 3 seconds...');
|
console.log('⏱️ Simulating update loop for 3 seconds...')
|
||||||
|
|
||||||
let iterations = 0;
|
let iterations = 0
|
||||||
const startTime = Date.now();
|
const startTime = Date.now()
|
||||||
|
|
||||||
const updateLoop = () => {
|
const updateLoop = () => {
|
||||||
const deltaTime = 16.67; // ~60 FPS
|
const deltaTime = 16.67 // ~60 FPS
|
||||||
this.owenSystem.update(deltaTime);
|
this.owenSystem.update(deltaTime)
|
||||||
iterations++;
|
iterations++
|
||||||
|
|
||||||
if (Date.now() - startTime < 3000) {
|
if (Date.now() - startTime < 3000) {
|
||||||
setTimeout(updateLoop, 16);
|
setTimeout(updateLoop, 16)
|
||||||
} else {
|
} else {
|
||||||
console.log(` ✓ Completed ${iterations} update iterations`);
|
console.log(` ✓ Completed ${iterations} update iterations`)
|
||||||
console.log(` ✓ Final state: ${this.owenSystem.getCurrentState()}`);
|
console.log(` ✓ Final state: ${this.owenSystem.getCurrentState()}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
updateLoop();
|
updateLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,23 +161,23 @@ class SimpleOwenExample {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
sleep (ms) {
|
sleep (ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the example if this file is executed directly
|
// Run the example if this file is executed directly
|
||||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
console.log('🚀 Starting Owen Animation System Example\n');
|
console.log('🚀 Starting Owen Animation System Example\n')
|
||||||
|
|
||||||
const example = new SimpleOwenExample();
|
const example = new SimpleOwenExample()
|
||||||
example.init()
|
example.init()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('\n🎉 Example completed successfully!');
|
console.log('\n🎉 Example completed successfully!')
|
||||||
console.log('💡 Try modifying this example or check out the browser demo in examples/index.html');
|
console.log('💡 Try modifying this example or check out the browser demo in examples/index.html')
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('\n💥 Example failed:', error);
|
console.error('\n💥 Example failed:', error)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SimpleOwenExample;
|
export default SimpleOwenExample
|
||||||
|
|||||||
3311
package-lock.json
generated
3311
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "owen-animation-system",
|
"name": "@kjanat/owen",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "A comprehensive Three.js animation system for character state management with clean architecture principles",
|
"description": "A comprehensive Three.js animation system for character state management with clean architecture principles",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"types": "src/index.d.ts",
|
"types": "src/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint src --ext .js",
|
"lint": "standard",
|
||||||
"lint:fix": "eslint src --ext .js --fix",
|
"lint:fix": "standard --fix",
|
||||||
"docs": "jsdoc -c jsdoc.config.json"
|
"docs": "jsdoc -c jsdoc.config.json",
|
||||||
|
"format": "npx prettier --ignore-path --write '**/*.{html,css}' 'docs/**/*.{html,css}'"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"three.js",
|
"three.js",
|
||||||
@ -21,17 +22,29 @@
|
|||||||
"gltf",
|
"gltf",
|
||||||
"3d"
|
"3d"
|
||||||
],
|
],
|
||||||
"author": "Owen Animation System",
|
"author": "Kaj \"@kjanat\" Kowalski",
|
||||||
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"three": "^0.176.0"
|
"three": "^0.176.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^6.3.5",
|
"jsdoc": "^4.0.2",
|
||||||
"eslint": "^9.27.0",
|
"pre-commit": "^1.2.2",
|
||||||
"jsdoc": "^4.0.2"
|
"standard": "*",
|
||||||
|
"vite": "^6.3.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
},
|
||||||
|
"standard": {
|
||||||
|
"globals": [
|
||||||
|
"requestAnimationFrame"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pre-commit": [
|
||||||
|
"lint:fix",
|
||||||
|
"lint",
|
||||||
|
"docs",
|
||||||
|
"format"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
* @module animation
|
* @module animation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three'
|
||||||
import { ClipTypes, Config } from '../constants.js';
|
import { ClipTypes, Config } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a single animation clip with metadata and Three.js action
|
* Represents a single animation clip with metadata and Three.js action
|
||||||
@ -22,31 +22,31 @@ export class AnimationClip {
|
|||||||
* The name of the animation clip
|
* The name of the animation clip
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.name = name;
|
this.name = name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Three.js animation clip
|
* The Three.js animation clip
|
||||||
* @type {THREE.AnimationClip}
|
* @type {THREE.AnimationClip}
|
||||||
*/
|
*/
|
||||||
this.animation = threeAnimation;
|
this.animation = threeAnimation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsed metadata about the animation
|
* Parsed metadata about the animation
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
this.metadata = metadata;
|
this.metadata = metadata
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Three.js animation action
|
* The Three.js animation action
|
||||||
* @type {THREE.AnimationAction|null}
|
* @type {THREE.AnimationAction|null}
|
||||||
*/
|
*/
|
||||||
this.action = null;
|
this.action = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The animation mixer
|
* The animation mixer
|
||||||
* @type {THREE.AnimationMixer|null}
|
* @type {THREE.AnimationMixer|null}
|
||||||
*/
|
*/
|
||||||
this.mixer = null;
|
this.mixer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,21 +55,21 @@ export class AnimationClip {
|
|||||||
* @returns {THREE.AnimationAction} The created action
|
* @returns {THREE.AnimationAction} The created action
|
||||||
*/
|
*/
|
||||||
createAction (mixer) {
|
createAction (mixer) {
|
||||||
this.mixer = mixer;
|
this.mixer = mixer
|
||||||
this.action = mixer.clipAction(this.animation);
|
this.action = mixer.clipAction(this.animation)
|
||||||
|
|
||||||
// Configure based on type
|
// Configure based on type
|
||||||
if (
|
if (
|
||||||
this.metadata.type === ClipTypes.LOOP ||
|
this.metadata.type === ClipTypes.LOOP ||
|
||||||
this.metadata.type === ClipTypes.NESTED_LOOP
|
this.metadata.type === ClipTypes.NESTED_LOOP
|
||||||
) {
|
) {
|
||||||
this.action.setLoop(THREE.LoopRepeat, Infinity);
|
this.action.setLoop(THREE.LoopRepeat, Infinity)
|
||||||
} else {
|
} else {
|
||||||
this.action.setLoop(THREE.LoopOnce);
|
this.action.setLoop(THREE.LoopOnce)
|
||||||
this.action.clampWhenFinished = true;
|
this.action.clampWhenFinished = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.action;
|
return this.action
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,9 +79,9 @@ export class AnimationClip {
|
|||||||
*/
|
*/
|
||||||
play (fadeInDuration = Config.DEFAULT_FADE_IN) {
|
play (fadeInDuration = Config.DEFAULT_FADE_IN) {
|
||||||
if (this.action) {
|
if (this.action) {
|
||||||
this.action.reset();
|
this.action.reset()
|
||||||
this.action.fadeIn(fadeInDuration);
|
this.action.fadeIn(fadeInDuration)
|
||||||
this.action.play();
|
this.action.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,12 +92,12 @@ export class AnimationClip {
|
|||||||
*/
|
*/
|
||||||
stop (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
|
stop (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
|
||||||
if (this.action) {
|
if (this.action) {
|
||||||
this.action.fadeOut(fadeOutDuration);
|
this.action.fadeOut(fadeOutDuration)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.action) {
|
if (this.action) {
|
||||||
this.action.stop();
|
this.action.stop()
|
||||||
}
|
}
|
||||||
}, fadeOutDuration * 1000);
|
}, fadeOutDuration * 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export class AnimationClip {
|
|||||||
* @returns {boolean} True if playing, false otherwise
|
* @returns {boolean} True if playing, false otherwise
|
||||||
*/
|
*/
|
||||||
isPlaying () {
|
isPlaying () {
|
||||||
return this.action?.isRunning() || false;
|
return this.action?.isRunning() || false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,13 +124,13 @@ export class AnimationClipFactory {
|
|||||||
* The animation loader for loading animation data
|
* The animation loader for loading animation data
|
||||||
* @type {AnimationLoader}
|
* @type {AnimationLoader}
|
||||||
*/
|
*/
|
||||||
this.animationLoader = animationLoader;
|
this.animationLoader = animationLoader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for created animation clips
|
* Cache for created animation clips
|
||||||
* @type {Map<string, AnimationClip>}
|
* @type {Map<string, AnimationClip>}
|
||||||
*/
|
*/
|
||||||
this.clipCache = new Map();
|
this.clipCache = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,13 +140,13 @@ export class AnimationClipFactory {
|
|||||||
* @returns {Object} Parsed metadata object
|
* @returns {Object} Parsed metadata object
|
||||||
*/
|
*/
|
||||||
parseAnimationName (name) {
|
parseAnimationName (name) {
|
||||||
const parts = name.split('_');
|
const parts = name.split('_')
|
||||||
const state = parts[ 0 ];
|
const state = parts[0]
|
||||||
const action = parts[ 1 ];
|
const action = parts[1]
|
||||||
|
|
||||||
// Handle transitions with emotions
|
// Handle transitions with emotions
|
||||||
if (parts[2]?.includes('2') && parts[3] === ClipTypes.TRANSITION) {
|
if (parts[2]?.includes('2') && parts[3] === ClipTypes.TRANSITION) {
|
||||||
const [ , toState ] = parts[ 2 ].split('2');
|
const [, toState] = parts[2].split('2')
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
action,
|
action,
|
||||||
@ -154,8 +154,8 @@ export class AnimationClipFactory {
|
|||||||
emotion: parts[2] || '',
|
emotion: parts[2] || '',
|
||||||
type: ClipTypes.TRANSITION,
|
type: ClipTypes.TRANSITION,
|
||||||
isTransition: true,
|
isTransition: true,
|
||||||
hasEmotion: true,
|
hasEmotion: true
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle regular transitions
|
// Handle regular transitions
|
||||||
@ -164,8 +164,8 @@ export class AnimationClipFactory {
|
|||||||
state,
|
state,
|
||||||
action,
|
action,
|
||||||
type: ClipTypes.TRANSITION,
|
type: ClipTypes.TRANSITION,
|
||||||
isTransition: true,
|
isTransition: true
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle nested animations
|
// Handle nested animations
|
||||||
@ -175,8 +175,8 @@ export class AnimationClipFactory {
|
|||||||
action,
|
action,
|
||||||
type: parts[2],
|
type: parts[2],
|
||||||
nestedType: parts[3],
|
nestedType: parts[3],
|
||||||
isNested: true,
|
isNested: true
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle nested loops and quirks
|
// Handle nested loops and quirks
|
||||||
@ -189,8 +189,8 @@ export class AnimationClipFactory {
|
|||||||
action,
|
action,
|
||||||
subAction: parts[2],
|
subAction: parts[2],
|
||||||
type: parts[3],
|
type: parts[3],
|
||||||
isNested: true,
|
isNested: true
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle standard loops and quirks
|
// Handle standard loops and quirks
|
||||||
@ -198,8 +198,8 @@ export class AnimationClipFactory {
|
|||||||
state,
|
state,
|
||||||
action,
|
action,
|
||||||
type: parts[2],
|
type: parts[2],
|
||||||
isStandard: true,
|
isStandard: true
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -209,16 +209,16 @@ export class AnimationClipFactory {
|
|||||||
*/
|
*/
|
||||||
async createClip (name) {
|
async createClip (name) {
|
||||||
if (this.clipCache.has(name)) {
|
if (this.clipCache.has(name)) {
|
||||||
return this.clipCache.get(name);
|
return this.clipCache.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = this.parseAnimationName(name);
|
const metadata = this.parseAnimationName(name)
|
||||||
const animation = await this.animationLoader.loadAnimation(name);
|
const animation = await this.animationLoader.loadAnimation(name)
|
||||||
|
|
||||||
const clip = new AnimationClip(name, animation, metadata);
|
const clip = new AnimationClip(name, animation, metadata)
|
||||||
this.clipCache.set(name, clip);
|
this.clipCache.set(name, clip)
|
||||||
|
|
||||||
return clip;
|
return clip
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -227,15 +227,15 @@ export class AnimationClipFactory {
|
|||||||
* @returns {Promise<Map<string, AnimationClip>>} Map of animation name to clip
|
* @returns {Promise<Map<string, AnimationClip>>} Map of animation name to clip
|
||||||
*/
|
*/
|
||||||
async createClipsFromModel (model) {
|
async createClipsFromModel (model) {
|
||||||
const clips = new Map();
|
const clips = new Map()
|
||||||
const animations = model.animations || [];
|
const animations = model.animations || []
|
||||||
|
|
||||||
for (const animation of animations) {
|
for (const animation of animations) {
|
||||||
const clip = await this.createClip(animation.name, model);
|
const clip = await this.createClip(animation.name, model)
|
||||||
clips.set(animation.name, clip);
|
clips.set(animation.name, clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
return clips;
|
return clips
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -243,7 +243,7 @@ export class AnimationClipFactory {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
clearCache () {
|
clearCache () {
|
||||||
this.clipCache.clear();
|
this.clipCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -252,6 +252,6 @@ export class AnimationClipFactory {
|
|||||||
* @returns {AnimationClip|undefined} The cached clip or undefined
|
* @returns {AnimationClip|undefined} The cached clip or undefined
|
||||||
*/
|
*/
|
||||||
getCachedClip (name) {
|
getCachedClip (name) {
|
||||||
return this.clipCache.get(name);
|
return this.clipCache.get(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,8 @@ export const ClipTypes = {
|
|||||||
/** Nested out transition */
|
/** Nested out transition */
|
||||||
NESTED_OUT: 'OUT_NT',
|
NESTED_OUT: 'OUT_NT',
|
||||||
/** Transition animation */
|
/** Transition animation */
|
||||||
TRANSITION: 'T',
|
TRANSITION: 'T'
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character animation states
|
* Character animation states
|
||||||
@ -38,8 +38,8 @@ export const States = {
|
|||||||
/** Typing response state */
|
/** Typing response state */
|
||||||
TYPE: 'type',
|
TYPE: 'type',
|
||||||
/** Sleep/inactive state */
|
/** Sleep/inactive state */
|
||||||
SLEEP: 'sleep',
|
SLEEP: 'sleep'
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character emotional states
|
* Character emotional states
|
||||||
@ -56,8 +56,8 @@ export const Emotions = {
|
|||||||
/** Happy emotion */
|
/** Happy emotion */
|
||||||
HAPPY: 'ha',
|
HAPPY: 'ha',
|
||||||
/** Sad emotion */
|
/** Sad emotion */
|
||||||
SAD: 'sa',
|
SAD: 'sa'
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default configuration values
|
* Default configuration values
|
||||||
@ -74,5 +74,5 @@ export const Config = {
|
|||||||
/** Default inactivity timeout (ms) */
|
/** Default inactivity timeout (ms) */
|
||||||
INACTIVITY_TIMEOUT: 60000,
|
INACTIVITY_TIMEOUT: 60000,
|
||||||
/** Quirk probability threshold */
|
/** Quirk probability threshold */
|
||||||
QUIRK_PROBABILITY: 0.3,
|
QUIRK_PROBABILITY: 0.3
|
||||||
};
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* @module core
|
* @module core
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { States, Emotions, Config } from '../constants.js';
|
import { States, Emotions, Config } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main controller for the Owen animation system
|
* Main controller for the Owen animation system
|
||||||
@ -23,67 +23,67 @@ export class OwenAnimationContext {
|
|||||||
* The 3D character model
|
* The 3D character model
|
||||||
* @type {THREE.Object3D}
|
* @type {THREE.Object3D}
|
||||||
*/
|
*/
|
||||||
this.model = model;
|
this.model = model
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Three.js animation mixer
|
* The Three.js animation mixer
|
||||||
* @type {THREE.AnimationMixer}
|
* @type {THREE.AnimationMixer}
|
||||||
*/
|
*/
|
||||||
this.mixer = mixer;
|
this.mixer = mixer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for creating animation clips
|
* Factory for creating animation clips
|
||||||
* @type {AnimationClipFactory}
|
* @type {AnimationClipFactory}
|
||||||
*/
|
*/
|
||||||
this.animationClipFactory = animationClipFactory;
|
this.animationClipFactory = animationClipFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for creating state handlers
|
* Factory for creating state handlers
|
||||||
* @type {StateFactory}
|
* @type {StateFactory}
|
||||||
*/
|
*/
|
||||||
this.stateFactory = stateFactory;
|
this.stateFactory = stateFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of animation clips by name
|
* Map of animation clips by name
|
||||||
* @type {Map<string, AnimationClip>}
|
* @type {Map<string, AnimationClip>}
|
||||||
*/
|
*/
|
||||||
this.clips = new Map();
|
this.clips = new Map()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of state handlers by name
|
* Map of state handlers by name
|
||||||
* @type {Map<string, StateHandler>}
|
* @type {Map<string, StateHandler>}
|
||||||
*/
|
*/
|
||||||
this.states = new Map();
|
this.states = new Map()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current active state
|
* Current active state
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.currentState = States.WAIT;
|
this.currentState = States.WAIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current active state handler
|
* Current active state handler
|
||||||
* @type {StateHandler|null}
|
* @type {StateHandler|null}
|
||||||
*/
|
*/
|
||||||
this.currentStateHandler = null;
|
this.currentStateHandler = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timer for inactivity detection
|
* Timer for inactivity detection
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.inactivityTimer = 0;
|
this.inactivityTimer = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inactivity timeout in milliseconds
|
* Inactivity timeout in milliseconds
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.inactivityTimeout = Config.INACTIVITY_TIMEOUT;
|
this.inactivityTimeout = Config.INACTIVITY_TIMEOUT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the system is initialized
|
* Whether the system is initialized
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.initialized = false;
|
this.initialized = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,24 +91,24 @@ export class OwenAnimationContext {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async initialize () {
|
async initialize () {
|
||||||
if (this.initialized) return;
|
if (this.initialized) return
|
||||||
|
|
||||||
// Create animation clips from model
|
// Create animation clips from model
|
||||||
this.clips = await this.animationClipFactory.createClipsFromModel(this.model);
|
this.clips = await this.animationClipFactory.createClipsFromModel(this.model)
|
||||||
|
|
||||||
// Create actions for all clips
|
// Create actions for all clips
|
||||||
for (const [, clip] of this.clips) {
|
for (const [, clip] of this.clips) {
|
||||||
clip.createAction(this.mixer);
|
clip.createAction(this.mixer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize state handlers
|
// Initialize state handlers
|
||||||
this.initializeStates();
|
this.initializeStates()
|
||||||
|
|
||||||
// Start in wait state
|
// Start in wait state
|
||||||
await this.transitionTo(States.WAIT);
|
await this.transitionTo(States.WAIT)
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true
|
||||||
console.log('Owen Animation System initialized');
|
console.log('Owen Animation System initialized')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,11 +117,11 @@ export class OwenAnimationContext {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
initializeStates () {
|
initializeStates () {
|
||||||
const stateNames = this.stateFactory.getAvailableStates();
|
const stateNames = this.stateFactory.getAvailableStates()
|
||||||
|
|
||||||
for (const stateName of stateNames) {
|
for (const stateName of stateNames) {
|
||||||
const handler = this.stateFactory.createStateHandler(stateName, this);
|
const handler = this.stateFactory.createStateHandler(stateName, this)
|
||||||
this.states.set(stateName, handler);
|
this.states.set(stateName, handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,26 +134,26 @@ export class OwenAnimationContext {
|
|||||||
*/
|
*/
|
||||||
async transitionTo (newStateName, emotion = Emotions.NEUTRAL) {
|
async transitionTo (newStateName, emotion = Emotions.NEUTRAL) {
|
||||||
if (!this.states.has(newStateName)) {
|
if (!this.states.has(newStateName)) {
|
||||||
throw new Error(`State '${newStateName}' not found`);
|
throw new Error(`State '${newStateName}' not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldState = this.currentState;
|
const oldState = this.currentState
|
||||||
const newStateHandler = this.states.get(newStateName);
|
const newStateHandler = this.states.get(newStateName)
|
||||||
|
|
||||||
console.log(`Transitioning from ${oldState} to ${newStateName}`);
|
console.log(`Transitioning from ${oldState} to ${newStateName}`)
|
||||||
|
|
||||||
// Exit current state
|
// Exit current state
|
||||||
if (this.currentStateHandler) {
|
if (this.currentStateHandler) {
|
||||||
await this.currentStateHandler.exit(newStateName, emotion);
|
await this.currentStateHandler.exit(newStateName, emotion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter new state
|
// Enter new state
|
||||||
this.currentState = newStateName;
|
this.currentState = newStateName
|
||||||
this.currentStateHandler = newStateHandler;
|
this.currentStateHandler = newStateHandler
|
||||||
await this.currentStateHandler.enter(oldState, emotion);
|
await this.currentStateHandler.enter(oldState, emotion)
|
||||||
|
|
||||||
// Reset inactivity timer
|
// Reset inactivity timer
|
||||||
this.resetActivityTimer();
|
this.resetActivityTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,25 +162,25 @@ export class OwenAnimationContext {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async handleUserMessage (message) {
|
async handleUserMessage (message) {
|
||||||
console.log(`Handling user message: "${message}"`);
|
console.log(`Handling user message: "${message}"`)
|
||||||
|
|
||||||
this.onUserActivity();
|
this.onUserActivity()
|
||||||
|
|
||||||
// If sleeping, wake up first
|
// If sleeping, wake up first
|
||||||
if (this.currentState === States.SLEEP) {
|
if (this.currentState === States.SLEEP) {
|
||||||
await this.transitionTo(States.REACT);
|
await this.transitionTo(States.REACT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let current state handle the message
|
// Let current state handle the message
|
||||||
if (this.currentStateHandler) {
|
if (this.currentStateHandler) {
|
||||||
await this.currentStateHandler.handleMessage(message);
|
await this.currentStateHandler.handleMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition to appropriate next state based on current state
|
// Transition to appropriate next state based on current state
|
||||||
if (this.currentState === States.WAIT) {
|
if (this.currentState === States.WAIT) {
|
||||||
await this.transitionTo(States.REACT);
|
await this.transitionTo(States.REACT)
|
||||||
} else if (this.currentState === States.REACT) {
|
} else if (this.currentState === States.REACT) {
|
||||||
await this.transitionTo(States.TYPE);
|
await this.transitionTo(States.TYPE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,11 +189,11 @@ export class OwenAnimationContext {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
onUserActivity () {
|
onUserActivity () {
|
||||||
this.resetActivityTimer();
|
this.resetActivityTimer()
|
||||||
|
|
||||||
// Wake up if sleeping
|
// Wake up if sleeping
|
||||||
if (this.currentState === States.SLEEP) {
|
if (this.currentState === States.SLEEP) {
|
||||||
this.transitionTo(States.WAIT);
|
this.transitionTo(States.WAIT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ export class OwenAnimationContext {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
resetActivityTimer () {
|
resetActivityTimer () {
|
||||||
this.inactivityTimer = 0;
|
this.inactivityTimer = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,8 +212,8 @@ export class OwenAnimationContext {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async handleInactivity () {
|
async handleInactivity () {
|
||||||
console.log('Inactivity detected, transitioning to sleep');
|
console.log('Inactivity detected, transitioning to sleep')
|
||||||
await this.transitionTo(States.SLEEP);
|
await this.transitionTo(States.SLEEP)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,20 +222,20 @@ export class OwenAnimationContext {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
update (deltaTime) {
|
update (deltaTime) {
|
||||||
if (!this.initialized) return;
|
if (!this.initialized) return
|
||||||
|
|
||||||
// Update Three.js mixer
|
// Update Three.js mixer
|
||||||
this.mixer.update(deltaTime / 1000); // Convert to seconds
|
this.mixer.update(deltaTime / 1000) // Convert to seconds
|
||||||
|
|
||||||
// Update current state
|
// Update current state
|
||||||
if (this.currentStateHandler) {
|
if (this.currentStateHandler) {
|
||||||
this.currentStateHandler.update(deltaTime);
|
this.currentStateHandler.update(deltaTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update inactivity timer
|
// Update inactivity timer
|
||||||
this.inactivityTimer += deltaTime;
|
this.inactivityTimer += deltaTime
|
||||||
if (this.inactivityTimer > this.inactivityTimeout && this.currentState !== States.SLEEP) {
|
if (this.inactivityTimer > this.inactivityTimeout && this.currentState !== States.SLEEP) {
|
||||||
this.handleInactivity();
|
this.handleInactivity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +245,7 @@ export class OwenAnimationContext {
|
|||||||
* @returns {AnimationClip|undefined} The animation clip or undefined if not found
|
* @returns {AnimationClip|undefined} The animation clip or undefined if not found
|
||||||
*/
|
*/
|
||||||
getClip (name) {
|
getClip (name) {
|
||||||
return this.clips.get(name);
|
return this.clips.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,16 +254,16 @@ export class OwenAnimationContext {
|
|||||||
* @returns {AnimationClip[]} Array of matching clips
|
* @returns {AnimationClip[]} Array of matching clips
|
||||||
*/
|
*/
|
||||||
getClipsByPattern (pattern) {
|
getClipsByPattern (pattern) {
|
||||||
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
const regex = new RegExp(pattern.replace(/\*/g, '.*'))
|
||||||
const matches = [];
|
const matches = []
|
||||||
|
|
||||||
for (const [name, clip] of this.clips) {
|
for (const [name, clip] of this.clips) {
|
||||||
if (regex.test(name)) {
|
if (regex.test(name)) {
|
||||||
matches.push(clip);
|
matches.push(clip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -271,7 +271,7 @@ export class OwenAnimationContext {
|
|||||||
* @returns {string} The current state name
|
* @returns {string} The current state name
|
||||||
*/
|
*/
|
||||||
getCurrentState () {
|
getCurrentState () {
|
||||||
return this.currentState;
|
return this.currentState
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -279,7 +279,7 @@ export class OwenAnimationContext {
|
|||||||
* @returns {StateHandler|null} The current state handler
|
* @returns {StateHandler|null} The current state handler
|
||||||
*/
|
*/
|
||||||
getCurrentStateHandler () {
|
getCurrentStateHandler () {
|
||||||
return this.currentStateHandler;
|
return this.currentStateHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -288,9 +288,9 @@ export class OwenAnimationContext {
|
|||||||
*/
|
*/
|
||||||
getAvailableTransitions () {
|
getAvailableTransitions () {
|
||||||
if (this.currentStateHandler) {
|
if (this.currentStateHandler) {
|
||||||
return this.currentStateHandler.getAvailableTransitions();
|
return this.currentStateHandler.getAvailableTransitions()
|
||||||
}
|
}
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,7 +298,7 @@ export class OwenAnimationContext {
|
|||||||
* @returns {string[]} Array of clip names
|
* @returns {string[]} Array of clip names
|
||||||
*/
|
*/
|
||||||
getAvailableClips () {
|
getAvailableClips () {
|
||||||
return Array.from(this.clips.keys());
|
return Array.from(this.clips.keys())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,7 +306,7 @@ export class OwenAnimationContext {
|
|||||||
* @returns {string[]} Array of state names
|
* @returns {string[]} Array of state names
|
||||||
*/
|
*/
|
||||||
getAvailableStates () {
|
getAvailableStates () {
|
||||||
return Array.from(this.states.keys());
|
return Array.from(this.states.keys())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,16 +317,16 @@ export class OwenAnimationContext {
|
|||||||
// Stop all animations
|
// Stop all animations
|
||||||
for (const [, clip] of this.clips) {
|
for (const [, clip] of this.clips) {
|
||||||
if (clip.action) {
|
if (clip.action) {
|
||||||
clip.action.stop();
|
clip.action.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear caches
|
// Clear caches
|
||||||
this.clips.clear();
|
this.clips.clear()
|
||||||
this.states.clear();
|
this.states.clear()
|
||||||
this.animationClipFactory.clearCache();
|
this.animationClipFactory.clearCache()
|
||||||
|
|
||||||
this.initialized = false;
|
this.initialized = false
|
||||||
console.log('Owen Animation System disposed');
|
console.log('Owen Animation System disposed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* @module factories
|
* @module factories
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three'
|
||||||
import { OwenAnimationContext } from '../core/OwenAnimationContext.js';
|
import { OwenAnimationContext } from '../core/OwenAnimationContext.js'
|
||||||
import { AnimationClipFactory } from '../animation/AnimationClip.js';
|
import { AnimationClipFactory } from '../animation/AnimationClip.js'
|
||||||
import { GLTFAnimationLoader } from '../loaders/AnimationLoader.js';
|
import { GLTFAnimationLoader } from '../loaders/AnimationLoader.js'
|
||||||
import { StateFactory } from '../states/StateFactory.js';
|
import { StateFactory } from '../states/StateFactory.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main factory for creating the complete Owen animation system
|
* Main factory for creating the complete Owen animation system
|
||||||
@ -24,22 +24,22 @@ export class OwenSystemFactory {
|
|||||||
*/
|
*/
|
||||||
static async createOwenSystem (gltfModel, scene, options = {}) {
|
static async createOwenSystem (gltfModel, scene, options = {}) {
|
||||||
// Create Three.js animation mixer
|
// Create Three.js animation mixer
|
||||||
const mixer = new THREE.AnimationMixer(gltfModel);
|
const mixer = new THREE.AnimationMixer(gltfModel)
|
||||||
|
|
||||||
// Create GLTF loader if not provided
|
// Create GLTF loader if not provided
|
||||||
const gltfLoader = options.gltfLoader || new THREE.GLTFLoader();
|
const gltfLoader = options.gltfLoader || new THREE.GLTFLoader()
|
||||||
|
|
||||||
// Create animation loader
|
// Create animation loader
|
||||||
const animationLoader = new GLTFAnimationLoader(gltfLoader);
|
const animationLoader = new GLTFAnimationLoader(gltfLoader)
|
||||||
|
|
||||||
// Preload animations from the model
|
// Preload animations from the model
|
||||||
await animationLoader.preloadAnimations(gltfModel);
|
await animationLoader.preloadAnimations(gltfModel)
|
||||||
|
|
||||||
// Create animation clip factory
|
// Create animation clip factory
|
||||||
const animationClipFactory = new AnimationClipFactory(animationLoader);
|
const animationClipFactory = new AnimationClipFactory(animationLoader)
|
||||||
|
|
||||||
// Create state factory
|
// Create state factory
|
||||||
const stateFactory = new StateFactory();
|
const stateFactory = new StateFactory()
|
||||||
|
|
||||||
// Create the main Owen context
|
// Create the main Owen context
|
||||||
const owenContext = new OwenAnimationContext(
|
const owenContext = new OwenAnimationContext(
|
||||||
@ -47,12 +47,12 @@ export class OwenSystemFactory {
|
|||||||
mixer,
|
mixer,
|
||||||
animationClipFactory,
|
animationClipFactory,
|
||||||
stateFactory
|
stateFactory
|
||||||
);
|
)
|
||||||
|
|
||||||
// Initialize the system
|
// Initialize the system
|
||||||
await owenContext.initialize();
|
await owenContext.initialize()
|
||||||
|
|
||||||
return owenContext;
|
return owenContext
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,10 +61,10 @@ export class OwenSystemFactory {
|
|||||||
* @returns {Promise<OwenAnimationContext>} The configured Owen system
|
* @returns {Promise<OwenAnimationContext>} The configured Owen system
|
||||||
*/
|
*/
|
||||||
static async createBasicOwenSystem (model) {
|
static async createBasicOwenSystem (model) {
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene()
|
||||||
scene.add(model);
|
scene.add(model)
|
||||||
|
|
||||||
return await OwenSystemFactory.createOwenSystem(model, scene);
|
return await OwenSystemFactory.createOwenSystem(model, scene)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,17 +75,17 @@ export class OwenSystemFactory {
|
|||||||
* @returns {Promise<OwenAnimationContext>} The configured Owen system
|
* @returns {Promise<OwenAnimationContext>} The configured Owen system
|
||||||
*/
|
*/
|
||||||
static async createCustomOwenSystem (gltfModel, scene, customStates) {
|
static async createCustomOwenSystem (gltfModel, scene, customStates) {
|
||||||
const system = await OwenSystemFactory.createOwenSystem(gltfModel, scene);
|
const system = await OwenSystemFactory.createOwenSystem(gltfModel, scene)
|
||||||
|
|
||||||
// Register custom state handlers
|
// Register custom state handlers
|
||||||
const stateFactory = system.stateFactory;
|
const stateFactory = system.stateFactory
|
||||||
for (const [stateName, handlerClass] of customStates) {
|
for (const [stateName, handlerClass] of customStates) {
|
||||||
stateFactory.registerStateHandler(stateName, handlerClass);
|
stateFactory.registerStateHandler(stateName, handlerClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reinitialize with custom states
|
// Reinitialize with custom states
|
||||||
system.initializeStates();
|
system.initializeStates()
|
||||||
|
|
||||||
return system;
|
return system
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/index.js
34
src/index.js
@ -4,32 +4,32 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Core exports
|
// Core exports
|
||||||
export { OwenAnimationContext } from './core/OwenAnimationContext.js';
|
// 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
|
// Animation system exports
|
||||||
export { AnimationClip, AnimationClipFactory } from './animation/AnimationClip.js';
|
export { AnimationClip, AnimationClipFactory } from './animation/AnimationClip.js'
|
||||||
|
|
||||||
// Loader exports
|
// Loader exports
|
||||||
export { AnimationLoader, GLTFAnimationLoader } from './loaders/AnimationLoader.js';
|
export { AnimationLoader, GLTFAnimationLoader } from './loaders/AnimationLoader.js'
|
||||||
|
|
||||||
// State system exports
|
// State system exports
|
||||||
export { StateHandler } from './states/StateHandler.js';
|
export { StateHandler } from './states/StateHandler.js'
|
||||||
export { WaitStateHandler } from './states/WaitStateHandler.js';
|
export { WaitStateHandler } from './states/WaitStateHandler.js'
|
||||||
export { ReactStateHandler } from './states/ReactStateHandler.js';
|
export { ReactStateHandler } from './states/ReactStateHandler.js'
|
||||||
export { TypeStateHandler } from './states/TypeStateHandler.js';
|
export { TypeStateHandler } from './states/TypeStateHandler.js'
|
||||||
export { SleepStateHandler } from './states/SleepStateHandler.js';
|
export { SleepStateHandler } from './states/SleepStateHandler.js'
|
||||||
export { StateFactory } from './states/StateFactory.js';
|
export { StateFactory } from './states/StateFactory.js'
|
||||||
|
|
||||||
// Factory exports
|
// Factory exports
|
||||||
export { OwenSystemFactory } from './factories/OwenSystemFactory.js';
|
export { OwenSystemFactory } from './factories/OwenSystemFactory.js'
|
||||||
|
|
||||||
// Constants exports
|
// Constants exports
|
||||||
export { ClipTypes, States, Emotions, Config } from './constants.js';
|
export { ClipTypes, States, Emotions, Config } from './constants.js'
|
||||||
|
|
||||||
// Import for default export
|
|
||||||
import { OwenSystemFactory } from './factories/OwenSystemFactory.js';
|
|
||||||
import { OwenAnimationContext } from './core/OwenAnimationContext.js';
|
|
||||||
import { States, Emotions, ClipTypes, Config } from './constants.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default export - the main factory for easy usage
|
* Default export - the main factory for easy usage
|
||||||
@ -41,4 +41,4 @@ export default {
|
|||||||
Emotions,
|
Emotions,
|
||||||
ClipTypes,
|
ClipTypes,
|
||||||
Config
|
Config
|
||||||
};
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export class AnimationLoader {
|
|||||||
* @throws {Error} Must be implemented by subclasses
|
* @throws {Error} Must be implemented by subclasses
|
||||||
*/
|
*/
|
||||||
async loadAnimation (_name) {
|
async loadAnimation (_name) {
|
||||||
throw new Error('loadAnimation method must be implemented by subclasses');
|
throw new Error('loadAnimation method must be implemented by subclasses')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,19 +32,19 @@ export class GLTFAnimationLoader extends AnimationLoader {
|
|||||||
* @param {THREE.GLTFLoader} gltfLoader - The Three.js GLTF loader instance
|
* @param {THREE.GLTFLoader} gltfLoader - The Three.js GLTF loader instance
|
||||||
*/
|
*/
|
||||||
constructor (gltfLoader) {
|
constructor (gltfLoader) {
|
||||||
super();
|
super()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Three.js GLTF loader
|
* The Three.js GLTF loader
|
||||||
* @type {THREE.GLTFLoader}
|
* @type {THREE.GLTFLoader}
|
||||||
*/
|
*/
|
||||||
this.gltfLoader = gltfLoader;
|
this.gltfLoader = gltfLoader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for loaded animations
|
* Cache for loaded animations
|
||||||
* @type {Map<string, THREE.AnimationClip>}
|
* @type {Map<string, THREE.AnimationClip>}
|
||||||
*/
|
*/
|
||||||
this.animationCache = new Map();
|
this.animationCache = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,12 +55,12 @@ export class GLTFAnimationLoader extends AnimationLoader {
|
|||||||
*/
|
*/
|
||||||
async loadAnimation (name) {
|
async loadAnimation (name) {
|
||||||
if (this.animationCache.has(name)) {
|
if (this.animationCache.has(name)) {
|
||||||
return this.animationCache.get(name);
|
return this.animationCache.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a real implementation, this would load from GLTF files
|
// In a real implementation, this would load from GLTF files
|
||||||
// For now, we'll assume animations are already loaded in the model
|
// For now, we'll assume animations are already loaded in the model
|
||||||
throw new Error(`Animation '${name}' not found. Implement GLTF loading logic.`);
|
throw new Error(`Animation '${name}' not found. Implement GLTF loading logic.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,7 +71,7 @@ export class GLTFAnimationLoader extends AnimationLoader {
|
|||||||
async preloadAnimations (gltfModel) {
|
async preloadAnimations (gltfModel) {
|
||||||
if (gltfModel.animations) {
|
if (gltfModel.animations) {
|
||||||
for (const animation of gltfModel.animations) {
|
for (const animation of gltfModel.animations) {
|
||||||
this.animationCache.set(animation.name, animation);
|
this.animationCache.set(animation.name, animation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ export class GLTFAnimationLoader extends AnimationLoader {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
clearCache () {
|
clearCache () {
|
||||||
this.animationCache.clear();
|
this.animationCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,6 +89,6 @@ export class GLTFAnimationLoader extends AnimationLoader {
|
|||||||
* @returns {string[]} Array of cached animation names
|
* @returns {string[]} Array of cached animation names
|
||||||
*/
|
*/
|
||||||
getCachedAnimationNames () {
|
getCachedAnimationNames () {
|
||||||
return Array.from(this.animationCache.keys());
|
return Array.from(this.animationCache.keys())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
* @module states
|
* @module states
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StateHandler } from './StateHandler.js';
|
import { StateHandler } from './StateHandler.js'
|
||||||
import { States, Emotions } from '../constants.js';
|
import { States, Emotions } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for the React state
|
* Handler for the React state
|
||||||
@ -17,13 +17,13 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
* @param {OwenAnimationContext} context - The animation context
|
* @param {OwenAnimationContext} context - The animation context
|
||||||
*/
|
*/
|
||||||
constructor (context) {
|
constructor (context) {
|
||||||
super(States.REACT, context);
|
super(States.REACT, context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current emotional state
|
* Current emotional state
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.emotion = Emotions.NEUTRAL;
|
this.emotion = Emotions.NEUTRAL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,14 +33,14 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Entering REACT state with emotion: ${emotion}`);
|
console.log(`Entering REACT state with emotion: ${emotion}`)
|
||||||
this.emotion = emotion;
|
this.emotion = emotion
|
||||||
|
|
||||||
// Play appropriate reaction
|
// Play appropriate reaction
|
||||||
const reactionClip = this.context.getClip('react_idle_L');
|
const reactionClip = this.context.getClip('react_idle_L')
|
||||||
if (reactionClip) {
|
if (reactionClip) {
|
||||||
await reactionClip.play();
|
await reactionClip.play()
|
||||||
this.currentClip = reactionClip;
|
this.currentClip = reactionClip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,24 +51,24 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async exit (toState = null, emotion = Emotions.NEUTRAL) {
|
async exit (toState = null, emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Exiting REACT state to ${toState} with emotion: ${emotion}`);
|
console.log(`Exiting REACT state to ${toState} with emotion: ${emotion}`)
|
||||||
|
|
||||||
if (this.currentClip) {
|
if (this.currentClip) {
|
||||||
await this.stopCurrentClip();
|
await this.stopCurrentClip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play emotional transition if available
|
// Play emotional transition if available
|
||||||
let transitionName;
|
let transitionName
|
||||||
if (emotion !== Emotions.NEUTRAL) {
|
if (emotion !== Emotions.NEUTRAL) {
|
||||||
transitionName = `react_${this.emotion}2${toState}_${emotion}_T`;
|
transitionName = `react_${this.emotion}2${toState}_${emotion}_T`
|
||||||
} else {
|
} else {
|
||||||
transitionName = `react_2${toState}_T`;
|
transitionName = `react_2${toState}_T`
|
||||||
}
|
}
|
||||||
|
|
||||||
const transition = this.context.getClip(transitionName);
|
const transition = this.context.getClip(transitionName)
|
||||||
if (transition) {
|
if (transition) {
|
||||||
await transition.play();
|
await transition.play()
|
||||||
await this.waitForClipEnd(transition);
|
await this.waitForClipEnd(transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,18 +79,18 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
*/
|
*/
|
||||||
async handleMessage (message) {
|
async handleMessage (message) {
|
||||||
// Analyze message sentiment to determine emotion
|
// Analyze message sentiment to determine emotion
|
||||||
const emotion = this.analyzeMessageEmotion(message);
|
const emotion = this.analyzeMessageEmotion(message)
|
||||||
this.emotion = emotion;
|
this.emotion = emotion
|
||||||
|
|
||||||
// Play emotional reaction if needed
|
// Play emotional reaction if needed
|
||||||
if (emotion !== Emotions.NEUTRAL) {
|
if (emotion !== Emotions.NEUTRAL) {
|
||||||
const emotionalReaction = this.context.getClip(`react_${emotion}_Q`);
|
const emotionalReaction = this.context.getClip(`react_${emotion}_Q`)
|
||||||
if (emotionalReaction) {
|
if (emotionalReaction) {
|
||||||
if (this.currentClip) {
|
if (this.currentClip) {
|
||||||
await this.stopCurrentClip(0.2);
|
await this.stopCurrentClip(0.2)
|
||||||
}
|
}
|
||||||
await emotionalReaction.play();
|
await emotionalReaction.play()
|
||||||
await this.waitForClipEnd(emotionalReaction);
|
await this.waitForClipEnd(emotionalReaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
* @returns {string} The determined emotion
|
* @returns {string} The determined emotion
|
||||||
*/
|
*/
|
||||||
analyzeMessageEmotion (message) {
|
analyzeMessageEmotion (message) {
|
||||||
const text = message.toLowerCase();
|
const text = message.toLowerCase()
|
||||||
|
|
||||||
// Check for urgent/angry indicators
|
// Check for urgent/angry indicators
|
||||||
if (
|
if (
|
||||||
@ -111,7 +111,7 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
text.includes('asap') ||
|
text.includes('asap') ||
|
||||||
text.includes('hurry')
|
text.includes('hurry')
|
||||||
) {
|
) {
|
||||||
return Emotions.ANGRY;
|
return Emotions.ANGRY
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for error/shocked indicators
|
// Check for error/shocked indicators
|
||||||
@ -122,7 +122,7 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
text.includes('bug') ||
|
text.includes('bug') ||
|
||||||
text.includes('broken')
|
text.includes('broken')
|
||||||
) {
|
) {
|
||||||
return Emotions.SHOCKED;
|
return Emotions.SHOCKED
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for positive/happy indicators
|
// Check for positive/happy indicators
|
||||||
@ -133,7 +133,7 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
text.includes('excellent') ||
|
text.includes('excellent') ||
|
||||||
text.includes('perfect')
|
text.includes('perfect')
|
||||||
) {
|
) {
|
||||||
return Emotions.HAPPY;
|
return Emotions.HAPPY
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for sad indicators
|
// Check for sad indicators
|
||||||
@ -143,10 +143,10 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
text.includes('failed') ||
|
text.includes('failed') ||
|
||||||
text.includes('wrong')
|
text.includes('wrong')
|
||||||
) {
|
) {
|
||||||
return Emotions.SAD;
|
return Emotions.SAD
|
||||||
}
|
}
|
||||||
|
|
||||||
return Emotions.NEUTRAL;
|
return Emotions.NEUTRAL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +154,6 @@ export class ReactStateHandler extends StateHandler {
|
|||||||
* @returns {string[]} Array of available state transitions
|
* @returns {string[]} Array of available state transitions
|
||||||
*/
|
*/
|
||||||
getAvailableTransitions () {
|
getAvailableTransitions () {
|
||||||
return [ States.TYPE, States.WAIT ];
|
return [States.TYPE, States.WAIT]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
* @module states
|
* @module states
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StateHandler } from './StateHandler.js';
|
import { StateHandler } from './StateHandler.js'
|
||||||
import { States, Emotions } from '../constants.js';
|
import { States, Emotions } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for the Sleep state
|
* Handler for the Sleep state
|
||||||
@ -17,19 +17,19 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
* @param {OwenAnimationContext} context - The animation context
|
* @param {OwenAnimationContext} context - The animation context
|
||||||
*/
|
*/
|
||||||
constructor (context) {
|
constructor (context) {
|
||||||
super(States.SLEEP, context);
|
super(States.SLEEP, context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sleep animation clip
|
* Sleep animation clip
|
||||||
* @type {AnimationClip|null}
|
* @type {AnimationClip|null}
|
||||||
*/
|
*/
|
||||||
this.sleepClip = null;
|
this.sleepClip = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the character is in deep sleep
|
* Whether the character is in deep sleep
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.isDeepSleep = false;
|
this.isDeepSleep = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,23 +39,23 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Entering SLEEP state from ${fromState}`);
|
console.log(`Entering SLEEP state from ${fromState}`)
|
||||||
|
|
||||||
// Play sleep transition if available
|
// Play sleep transition if available
|
||||||
const sleepTransition = this.context.getClip('wait_2sleep_T');
|
const sleepTransition = this.context.getClip('wait_2sleep_T')
|
||||||
if (sleepTransition) {
|
if (sleepTransition) {
|
||||||
await sleepTransition.play();
|
await sleepTransition.play()
|
||||||
await this.waitForClipEnd(sleepTransition);
|
await this.waitForClipEnd(sleepTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start sleep loop
|
// Start sleep loop
|
||||||
this.sleepClip = this.context.getClip('sleep_idle_L');
|
this.sleepClip = this.context.getClip('sleep_idle_L')
|
||||||
if (this.sleepClip) {
|
if (this.sleepClip) {
|
||||||
await this.sleepClip.play();
|
await this.sleepClip.play()
|
||||||
this.currentClip = this.sleepClip;
|
this.currentClip = this.sleepClip
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isDeepSleep = true;
|
this.isDeepSleep = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,26 +65,26 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Exiting SLEEP state to ${toState}`);
|
console.log(`Exiting SLEEP state to ${toState}`)
|
||||||
this.isDeepSleep = false;
|
this.isDeepSleep = false
|
||||||
|
|
||||||
if (this.currentClip) {
|
if (this.currentClip) {
|
||||||
await this.stopCurrentClip();
|
await this.stopCurrentClip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play wake up animation
|
// Play wake up animation
|
||||||
const wakeUpClip = this.context.getClip('sleep_wakeup_T');
|
const wakeUpClip = this.context.getClip('sleep_wakeup_T')
|
||||||
if (wakeUpClip) {
|
if (wakeUpClip) {
|
||||||
await wakeUpClip.play();
|
await wakeUpClip.play()
|
||||||
await this.waitForClipEnd(wakeUpClip);
|
await this.waitForClipEnd(wakeUpClip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play transition to next state if available
|
// Play transition to next state if available
|
||||||
const transitionName = `sleep_2${toState}_T`;
|
const transitionName = `sleep_2${toState}_T`
|
||||||
const transition = this.context.getClip(transitionName);
|
const transition = this.context.getClip(transitionName)
|
||||||
if (transition) {
|
if (transition) {
|
||||||
await transition.play();
|
await transition.play()
|
||||||
await this.waitForClipEnd(transition);
|
await this.waitForClipEnd(transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +106,9 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
async handleMessage (_message) {
|
async handleMessage (_message) {
|
||||||
// Any message should wake up the character
|
// Any message should wake up the character
|
||||||
if (this.isDeepSleep) {
|
if (this.isDeepSleep) {
|
||||||
console.log('Waking up due to user message');
|
console.log('Waking up due to user message')
|
||||||
// This will trigger a state transition to REACT
|
// This will trigger a state transition to REACT
|
||||||
await this.context.transitionTo(States.REACT);
|
await this.context.transitionTo(States.REACT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
* @returns {string[]} Array of available state transitions
|
* @returns {string[]} Array of available state transitions
|
||||||
*/
|
*/
|
||||||
getAvailableTransitions () {
|
getAvailableTransitions () {
|
||||||
return [ States.WAIT, States.REACT ];
|
return [States.WAIT, States.REACT]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +125,7 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
* @returns {boolean} True if in deep sleep, false otherwise
|
* @returns {boolean} True if in deep sleep, false otherwise
|
||||||
*/
|
*/
|
||||||
isInDeepSleep () {
|
isInDeepSleep () {
|
||||||
return this.isDeepSleep;
|
return this.isDeepSleep
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +134,7 @@ export class SleepStateHandler extends StateHandler {
|
|||||||
*/
|
*/
|
||||||
async wakeUp () {
|
async wakeUp () {
|
||||||
if (this.isDeepSleep) {
|
if (this.isDeepSleep) {
|
||||||
await this.context.transitionTo(States.WAIT);
|
await this.context.transitionTo(States.WAIT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* @module states
|
* @module states
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { WaitStateHandler } from './WaitStateHandler.js';
|
import { WaitStateHandler } from './WaitStateHandler.js'
|
||||||
import { ReactStateHandler } from './ReactStateHandler.js';
|
import { ReactStateHandler } from './ReactStateHandler.js'
|
||||||
import { TypeStateHandler } from './TypeStateHandler.js';
|
import { TypeStateHandler } from './TypeStateHandler.js'
|
||||||
import { SleepStateHandler } from './SleepStateHandler.js';
|
import { SleepStateHandler } from './SleepStateHandler.js'
|
||||||
import { States } from '../constants.js';
|
import { States } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for creating state handlers using dependency injection
|
* Factory for creating state handlers using dependency injection
|
||||||
@ -23,13 +23,13 @@ export class StateFactory {
|
|||||||
* @type {Map<string, Function>}
|
* @type {Map<string, Function>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.stateHandlers = new Map();
|
this.stateHandlers = new Map()
|
||||||
|
|
||||||
// Register default state handlers
|
// Register default state handlers
|
||||||
this.registerStateHandler(States.WAIT, WaitStateHandler);
|
this.registerStateHandler(States.WAIT, WaitStateHandler)
|
||||||
this.registerStateHandler(States.REACT, ReactStateHandler);
|
this.registerStateHandler(States.REACT, ReactStateHandler)
|
||||||
this.registerStateHandler(States.TYPE, TypeStateHandler);
|
this.registerStateHandler(States.TYPE, TypeStateHandler)
|
||||||
this.registerStateHandler(States.SLEEP, SleepStateHandler);
|
this.registerStateHandler(States.SLEEP, SleepStateHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +39,7 @@ export class StateFactory {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
registerStateHandler (stateName, handlerClass) {
|
registerStateHandler (stateName, handlerClass) {
|
||||||
this.stateHandlers.set(stateName, handlerClass);
|
this.stateHandlers.set(stateName, handlerClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,12 +50,12 @@ export class StateFactory {
|
|||||||
* @throws {Error} If state handler is not registered
|
* @throws {Error} If state handler is not registered
|
||||||
*/
|
*/
|
||||||
createStateHandler (stateName, context) {
|
createStateHandler (stateName, context) {
|
||||||
const HandlerClass = this.stateHandlers.get(stateName);
|
const HandlerClass = this.stateHandlers.get(stateName)
|
||||||
if (!HandlerClass) {
|
if (!HandlerClass) {
|
||||||
throw new Error(`No handler registered for state: ${stateName}`);
|
throw new Error(`No handler registered for state: ${stateName}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HandlerClass(context);
|
return new HandlerClass(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +63,7 @@ export class StateFactory {
|
|||||||
* @returns {string[]} Array of registered state names
|
* @returns {string[]} Array of registered state names
|
||||||
*/
|
*/
|
||||||
getAvailableStates () {
|
getAvailableStates () {
|
||||||
return Array.from(this.stateHandlers.keys());
|
return Array.from(this.stateHandlers.keys())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +72,7 @@ export class StateFactory {
|
|||||||
* @returns {boolean} True if registered, false otherwise
|
* @returns {boolean} True if registered, false otherwise
|
||||||
*/
|
*/
|
||||||
isStateRegistered (stateName) {
|
isStateRegistered (stateName) {
|
||||||
return this.stateHandlers.has(stateName);
|
return this.stateHandlers.has(stateName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,6 +81,6 @@ export class StateFactory {
|
|||||||
* @returns {boolean} True if removed, false if not found
|
* @returns {boolean} True if removed, false if not found
|
||||||
*/
|
*/
|
||||||
unregisterStateHandler (stateName) {
|
unregisterStateHandler (stateName) {
|
||||||
return this.stateHandlers.delete(stateName);
|
return this.stateHandlers.delete(stateName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* @module StateHandler
|
* @module StateHandler
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Emotions, Config } from '../constants.js';
|
import { Emotions, Config } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for state handlers
|
* Abstract base class for state handlers
|
||||||
@ -21,25 +21,25 @@ export class StateHandler {
|
|||||||
* The name of this state
|
* The name of this state
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.stateName = stateName;
|
this.stateName = stateName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The animation context
|
* The animation context
|
||||||
* @type {OwenAnimationContext}
|
* @type {OwenAnimationContext}
|
||||||
*/
|
*/
|
||||||
this.context = context;
|
this.context = context
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently playing animation clip
|
* Currently playing animation clip
|
||||||
* @type {AnimationClip|null}
|
* @type {AnimationClip|null}
|
||||||
*/
|
*/
|
||||||
this.currentClip = null;
|
this.currentClip = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nested state information
|
* Nested state information
|
||||||
* @type {Object|null}
|
* @type {Object|null}
|
||||||
*/
|
*/
|
||||||
this.nestedState = null;
|
this.nestedState = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,7 +51,7 @@ export class StateHandler {
|
|||||||
* @throws {Error} Must be implemented by subclasses
|
* @throws {Error} Must be implemented by subclasses
|
||||||
*/
|
*/
|
||||||
async enter (_fromState = null, _emotion = Emotions.NEUTRAL) {
|
async enter (_fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
throw new Error('enter method must be implemented by subclasses');
|
throw new Error('enter method must be implemented by subclasses')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +63,7 @@ export class StateHandler {
|
|||||||
* @throws {Error} Must be implemented by subclasses
|
* @throws {Error} Must be implemented by subclasses
|
||||||
*/
|
*/
|
||||||
async exit (_toState = null, _emotion = Emotions.NEUTRAL) {
|
async exit (_toState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
throw new Error('exit method must be implemented by subclasses');
|
throw new Error('exit method must be implemented by subclasses')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +89,7 @@ export class StateHandler {
|
|||||||
* @returns {string[]} Array of state names that can be transitioned to
|
* @returns {string[]} Array of state names that can be transitioned to
|
||||||
*/
|
*/
|
||||||
getAvailableTransitions () {
|
getAvailableTransitions () {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,13 +102,13 @@ export class StateHandler {
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const checkFinished = () => {
|
const checkFinished = () => {
|
||||||
if (!clip.isPlaying()) {
|
if (!clip.isPlaying()) {
|
||||||
resolve();
|
resolve()
|
||||||
} else {
|
} else {
|
||||||
requestAnimationFrame(checkFinished);
|
requestAnimationFrame(checkFinished)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
checkFinished();
|
checkFinished()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,8 +119,8 @@ export class StateHandler {
|
|||||||
*/
|
*/
|
||||||
async stopCurrentClip (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
|
async stopCurrentClip (fadeOutDuration = Config.DEFAULT_FADE_OUT) {
|
||||||
if (this.currentClip) {
|
if (this.currentClip) {
|
||||||
await this.currentClip.stop(fadeOutDuration);
|
await this.currentClip.stop(fadeOutDuration)
|
||||||
this.currentClip = null;
|
this.currentClip = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
* @module states
|
* @module states
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StateHandler } from './StateHandler.js';
|
import { StateHandler } from './StateHandler.js'
|
||||||
import { States, Emotions } from '../constants.js';
|
import { States, Emotions } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for the Type state
|
* Handler for the Type state
|
||||||
@ -17,19 +17,19 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @param {OwenAnimationContext} context - The animation context
|
* @param {OwenAnimationContext} context - The animation context
|
||||||
*/
|
*/
|
||||||
constructor (context) {
|
constructor (context) {
|
||||||
super(States.TYPE, context);
|
super(States.TYPE, context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current emotional state
|
* Current emotional state
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.emotion = Emotions.NEUTRAL;
|
this.emotion = Emotions.NEUTRAL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether currently typing
|
* Whether currently typing
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.isTyping = false;
|
this.isTyping = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,20 +39,20 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
async enter (_fromState = null, emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Entering TYPE state with emotion: ${emotion}`);
|
console.log(`Entering TYPE state with emotion: ${emotion}`)
|
||||||
this.emotion = emotion;
|
this.emotion = emotion
|
||||||
this.isTyping = true;
|
this.isTyping = true
|
||||||
|
|
||||||
// Play appropriate typing animation
|
// Play appropriate typing animation
|
||||||
let typingClipName = 'type_idle_L';
|
let typingClipName = 'type_idle_L'
|
||||||
if (emotion !== Emotions.NEUTRAL) {
|
if (emotion !== Emotions.NEUTRAL) {
|
||||||
typingClipName = `type_${emotion}_L`;
|
typingClipName = `type_${emotion}_L`
|
||||||
}
|
}
|
||||||
|
|
||||||
const typingClip = this.context.getClip(typingClipName);
|
const typingClip = this.context.getClip(typingClipName)
|
||||||
if (typingClip) {
|
if (typingClip) {
|
||||||
await typingClip.play();
|
await typingClip.play()
|
||||||
this.currentClip = typingClip;
|
this.currentClip = typingClip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,23 +63,23 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Exiting TYPE state to ${toState}`);
|
console.log(`Exiting TYPE state to ${toState}`)
|
||||||
this.isTyping = false;
|
this.isTyping = false
|
||||||
|
|
||||||
if (this.currentClip) {
|
if (this.currentClip) {
|
||||||
await this.stopCurrentClip();
|
await this.stopCurrentClip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play transition if available
|
// Play transition if available
|
||||||
let transitionName = `type_2${toState}_T`;
|
let transitionName = `type_2${toState}_T`
|
||||||
if (this.emotion !== Emotions.NEUTRAL) {
|
if (this.emotion !== Emotions.NEUTRAL) {
|
||||||
transitionName = `type_${this.emotion}2${toState}_T`;
|
transitionName = `type_${this.emotion}2${toState}_T`
|
||||||
}
|
}
|
||||||
|
|
||||||
const transition = this.context.getClip(transitionName);
|
const transition = this.context.getClip(transitionName)
|
||||||
if (transition) {
|
if (transition) {
|
||||||
await transition.play();
|
await transition.play()
|
||||||
await this.waitForClipEnd(transition);
|
await this.waitForClipEnd(transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,17 +88,17 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async finishTyping () {
|
async finishTyping () {
|
||||||
if (!this.isTyping) return;
|
if (!this.isTyping) return
|
||||||
|
|
||||||
// Play typing finish animation if available
|
// Play typing finish animation if available
|
||||||
const finishClip = this.context.getClip('type_finish_Q');
|
const finishClip = this.context.getClip('type_finish_Q')
|
||||||
if (finishClip && this.currentClip) {
|
if (finishClip && this.currentClip) {
|
||||||
await this.stopCurrentClip(0.2);
|
await this.stopCurrentClip(0.2)
|
||||||
await finishClip.play();
|
await finishClip.play()
|
||||||
await this.waitForClipEnd(finishClip);
|
await this.waitForClipEnd(finishClip)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isTyping = false;
|
this.isTyping = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,7 +106,7 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @returns {string[]} Array of available state transitions
|
* @returns {string[]} Array of available state transitions
|
||||||
*/
|
*/
|
||||||
getAvailableTransitions () {
|
getAvailableTransitions () {
|
||||||
return [ States.WAIT, States.REACT ];
|
return [States.WAIT, States.REACT]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,7 +114,7 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @returns {boolean} True if typing, false otherwise
|
* @returns {boolean} True if typing, false otherwise
|
||||||
*/
|
*/
|
||||||
getIsTyping () {
|
getIsTyping () {
|
||||||
return this.isTyping;
|
return this.isTyping
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,6 +123,6 @@ export class TypeStateHandler extends StateHandler {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
setTyping (typing) {
|
setTyping (typing) {
|
||||||
this.isTyping = typing;
|
this.isTyping = typing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
* @module states
|
* @module states
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StateHandler } from './StateHandler.js';
|
import { StateHandler } from './StateHandler.js'
|
||||||
import { States, Emotions, Config } from '../constants.js';
|
import { States, Emotions, Config } from '../constants.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for the Wait/Idle state
|
* Handler for the Wait/Idle state
|
||||||
@ -17,31 +17,31 @@ export class WaitStateHandler extends StateHandler {
|
|||||||
* @param {OwenAnimationContext} context - The animation context
|
* @param {OwenAnimationContext} context - The animation context
|
||||||
*/
|
*/
|
||||||
constructor (context) {
|
constructor (context) {
|
||||||
super(States.WAIT, context);
|
super(States.WAIT, context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main idle animation clip
|
* The main idle animation clip
|
||||||
* @type {AnimationClip|null}
|
* @type {AnimationClip|null}
|
||||||
*/
|
*/
|
||||||
this.idleClip = null;
|
this.idleClip = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Available quirk animations
|
* Available quirk animations
|
||||||
* @type {AnimationClip[]}
|
* @type {AnimationClip[]}
|
||||||
*/
|
*/
|
||||||
this.quirks = [];
|
this.quirks = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timer for quirk animations
|
* Timer for quirk animations
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.quirkTimer = 0;
|
this.quirkTimer = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interval between quirk attempts (ms)
|
* Interval between quirk attempts (ms)
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.quirkInterval = Config.QUIRK_INTERVAL;
|
this.quirkInterval = Config.QUIRK_INTERVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,18 +51,18 @@ export class WaitStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
async enter (fromState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Entering WAIT state from ${fromState}`);
|
console.log(`Entering WAIT state from ${fromState}`)
|
||||||
|
|
||||||
// Play idle loop
|
// Play idle loop
|
||||||
this.idleClip = this.context.getClip('wait_idle_L');
|
this.idleClip = this.context.getClip('wait_idle_L')
|
||||||
if (this.idleClip) {
|
if (this.idleClip) {
|
||||||
await this.idleClip.play();
|
await this.idleClip.play()
|
||||||
this.currentClip = this.idleClip;
|
this.currentClip = this.idleClip
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect available quirks
|
// Collect available quirks
|
||||||
this.quirks = this.context.getClipsByPattern('wait_*_Q');
|
this.quirks = this.context.getClipsByPattern('wait_*_Q')
|
||||||
this.quirkTimer = 0;
|
this.quirkTimer = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,18 +72,18 @@ export class WaitStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
async exit (toState = null, _emotion = Emotions.NEUTRAL) {
|
||||||
console.log(`Exiting WAIT state to ${toState}`);
|
console.log(`Exiting WAIT state to ${toState}`)
|
||||||
|
|
||||||
if (this.currentClip) {
|
if (this.currentClip) {
|
||||||
await this.stopCurrentClip();
|
await this.stopCurrentClip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play transition if available
|
// Play transition if available
|
||||||
const transitionName = `wait_2${toState}_T`;
|
const transitionName = `wait_2${toState}_T`
|
||||||
const transition = this.context.getClip(transitionName);
|
const transition = this.context.getClip(transitionName)
|
||||||
if (transition) {
|
if (transition) {
|
||||||
await transition.play();
|
await transition.play()
|
||||||
await this.waitForClipEnd(transition);
|
await this.waitForClipEnd(transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,12 +93,12 @@ export class WaitStateHandler extends StateHandler {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
update (deltaTime) {
|
update (deltaTime) {
|
||||||
this.quirkTimer += deltaTime;
|
this.quirkTimer += deltaTime
|
||||||
|
|
||||||
// Randomly play quirks
|
// Randomly play quirks
|
||||||
if (this.quirkTimer > this.quirkInterval && Math.random() < Config.QUIRK_PROBABILITY) {
|
if (this.quirkTimer > this.quirkInterval && Math.random() < Config.QUIRK_PROBABILITY) {
|
||||||
this.playRandomQuirk();
|
this.playRandomQuirk()
|
||||||
this.quirkTimer = 0;
|
this.quirkTimer = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,23 +108,23 @@ export class WaitStateHandler extends StateHandler {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async playRandomQuirk () {
|
async playRandomQuirk () {
|
||||||
if (this.quirks.length === 0) return;
|
if (this.quirks.length === 0) return
|
||||||
|
|
||||||
const quirk = this.quirks[ Math.floor(Math.random() * this.quirks.length) ];
|
const quirk = this.quirks[Math.floor(Math.random() * this.quirks.length)]
|
||||||
|
|
||||||
// Fade out idle
|
// Fade out idle
|
||||||
if (this.idleClip) {
|
if (this.idleClip) {
|
||||||
await this.idleClip.stop(0.2);
|
await this.idleClip.stop(0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play quirk
|
// Play quirk
|
||||||
await quirk.play();
|
await quirk.play()
|
||||||
await this.waitForClipEnd(quirk);
|
await this.waitForClipEnd(quirk)
|
||||||
|
|
||||||
// Return to idle
|
// Return to idle
|
||||||
if (this.idleClip) {
|
if (this.idleClip) {
|
||||||
await this.idleClip.play();
|
await this.idleClip.play()
|
||||||
this.currentClip = this.idleClip;
|
this.currentClip = this.idleClip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +133,6 @@ export class WaitStateHandler extends StateHandler {
|
|||||||
* @returns {string[]} Array of available state transitions
|
* @returns {string[]} Array of available state transitions
|
||||||
*/
|
*/
|
||||||
getAvailableTransitions () {
|
getAvailableTransitions () {
|
||||||
return [ States.REACT, States.SLEEP ];
|
return [States.REACT, States.SLEEP]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
root: 'examples',
|
root: 'examples',
|
||||||
@ -17,7 +17,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'owen': '/src/index.js'
|
owen: '/src/index.js'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user