@daidr/minecraft-skin-renderer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -0
- package/dist/animation/AnimationController.d.mts +15 -0
- package/dist/animation/AnimationController.d.mts.map +1 -0
- package/dist/animation/AnimationController.mjs +146 -0
- package/dist/animation/AnimationController.mjs.map +1 -0
- package/dist/animation/easing.mjs +9 -0
- package/dist/animation/easing.mjs.map +1 -0
- package/dist/animation/presets/fly.mjs +176 -0
- package/dist/animation/presets/fly.mjs.map +1 -0
- package/dist/animation/presets/idle.mjs +114 -0
- package/dist/animation/presets/idle.mjs.map +1 -0
- package/dist/animation/presets/index.mjs +7 -0
- package/dist/animation/presets/run.mjs +126 -0
- package/dist/animation/presets/run.mjs.map +1 -0
- package/dist/animation/presets/utils.mjs +166 -0
- package/dist/animation/presets/utils.mjs.map +1 -0
- package/dist/animation/presets/walk.mjs +76 -0
- package/dist/animation/presets/walk.mjs.map +1 -0
- package/dist/animation/types.d.mts +50 -0
- package/dist/animation/types.d.mts.map +1 -0
- package/dist/animation/types.mjs +22 -0
- package/dist/animation/types.mjs.map +1 -0
- package/dist/core/camera/Camera.d.mts +33 -0
- package/dist/core/camera/Camera.d.mts.map +1 -0
- package/dist/core/camera/Camera.mjs +77 -0
- package/dist/core/camera/Camera.mjs.map +1 -0
- package/dist/core/camera/OrbitControls.d.mts +50 -0
- package/dist/core/camera/OrbitControls.d.mts.map +1 -0
- package/dist/core/camera/OrbitControls.mjs +170 -0
- package/dist/core/camera/OrbitControls.mjs.map +1 -0
- package/dist/core/math/index.mjs +6 -0
- package/dist/core/math/mat4.d.mts +61 -0
- package/dist/core/math/mat4.d.mts.map +1 -0
- package/dist/core/math/mat4.mjs +513 -0
- package/dist/core/math/mat4.mjs.map +1 -0
- package/dist/core/math/quat.d.mts +61 -0
- package/dist/core/math/quat.d.mts.map +1 -0
- package/dist/core/math/quat.mjs +360 -0
- package/dist/core/math/quat.mjs.map +1 -0
- package/dist/core/math/utils.d.mts +43 -0
- package/dist/core/math/utils.d.mts.map +1 -0
- package/dist/core/math/utils.mjs +85 -0
- package/dist/core/math/utils.mjs.map +1 -0
- package/dist/core/math/vec3.d.mts +61 -0
- package/dist/core/math/vec3.d.mts.map +1 -0
- package/dist/core/math/vec3.mjs +198 -0
- package/dist/core/math/vec3.mjs.map +1 -0
- package/dist/core/plugins/registry.mjs +29 -0
- package/dist/core/plugins/registry.mjs.map +1 -0
- package/dist/core/plugins/types.d.mts +34 -0
- package/dist/core/plugins/types.d.mts.map +1 -0
- package/dist/core/renderer/registry.d.mts +46 -0
- package/dist/core/renderer/registry.d.mts.map +1 -0
- package/dist/core/renderer/registry.mjs +46 -0
- package/dist/core/renderer/registry.mjs.map +1 -0
- package/dist/core/renderer/shader-composer.mjs +30 -0
- package/dist/core/renderer/shader-composer.mjs.map +1 -0
- package/dist/core/renderer/types.d.mts +172 -0
- package/dist/core/renderer/types.d.mts.map +1 -0
- package/dist/core/renderer/types.mjs +90 -0
- package/dist/core/renderer/types.mjs.map +1 -0
- package/dist/core/renderer/utils.mjs +20 -0
- package/dist/core/renderer/utils.mjs.map +1 -0
- package/dist/core/renderer/webgl/WebGLBuffer.mjs +57 -0
- package/dist/core/renderer/webgl/WebGLBuffer.mjs.map +1 -0
- package/dist/core/renderer/webgl/WebGLPipeline.mjs +259 -0
- package/dist/core/renderer/webgl/WebGLPipeline.mjs.map +1 -0
- package/dist/core/renderer/webgl/WebGLRenderer.mjs +140 -0
- package/dist/core/renderer/webgl/WebGLRenderer.mjs.map +1 -0
- package/dist/core/renderer/webgl/WebGLTexture.mjs +87 -0
- package/dist/core/renderer/webgl/WebGLTexture.mjs.map +1 -0
- package/dist/core/renderer/webgl/plugin.d.mts +8 -0
- package/dist/core/renderer/webgl/plugin.d.mts.map +1 -0
- package/dist/core/renderer/webgl/plugin.mjs +24 -0
- package/dist/core/renderer/webgl/plugin.mjs.map +1 -0
- package/dist/core/renderer/webgl/shaders/index.mjs +81 -0
- package/dist/core/renderer/webgl/shaders/index.mjs.map +1 -0
- package/dist/core/renderer/webgpu/WebGPUBuffer.mjs +52 -0
- package/dist/core/renderer/webgpu/WebGPUBuffer.mjs.map +1 -0
- package/dist/core/renderer/webgpu/WebGPUPipeline.mjs +167 -0
- package/dist/core/renderer/webgpu/WebGPUPipeline.mjs.map +1 -0
- package/dist/core/renderer/webgpu/WebGPURenderer.mjs +299 -0
- package/dist/core/renderer/webgpu/WebGPURenderer.mjs.map +1 -0
- package/dist/core/renderer/webgpu/WebGPUTexture.mjs +126 -0
- package/dist/core/renderer/webgpu/WebGPUTexture.mjs.map +1 -0
- package/dist/core/renderer/webgpu/plugin.d.mts +8 -0
- package/dist/core/renderer/webgpu/plugin.d.mts.map +1 -0
- package/dist/core/renderer/webgpu/plugin.mjs +35 -0
- package/dist/core/renderer/webgpu/plugin.mjs.map +1 -0
- package/dist/core/renderer/webgpu/shaders/index.mjs +84 -0
- package/dist/core/renderer/webgpu/shaders/index.mjs.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +19 -0
- package/dist/model/PlayerModel.d.mts +8 -0
- package/dist/model/PlayerModel.d.mts.map +1 -0
- package/dist/model/PlayerModel.mjs +253 -0
- package/dist/model/PlayerModel.mjs.map +1 -0
- package/dist/model/bone-utils.mjs +3 -0
- package/dist/model/constants.mjs +20 -0
- package/dist/model/constants.mjs.map +1 -0
- package/dist/model/geometry/BoxGeometry.mjs +316 -0
- package/dist/model/geometry/BoxGeometry.mjs.map +1 -0
- package/dist/model/geometry/index.mjs +3 -0
- package/dist/model/index.mjs +10 -0
- package/dist/model/types.d.mts +64 -0
- package/dist/model/types.d.mts.map +1 -0
- package/dist/model/types.mjs +67 -0
- package/dist/model/types.mjs.map +1 -0
- package/dist/model/uv/CapeUV.mjs +25 -0
- package/dist/model/uv/CapeUV.mjs.map +1 -0
- package/dist/model/uv/SkinUV.mjs +104 -0
- package/dist/model/uv/SkinUV.mjs.map +1 -0
- package/dist/model/uv/common.mjs +147 -0
- package/dist/model/uv/common.mjs.map +1 -0
- package/dist/panorama.d.mts +2 -0
- package/dist/panorama.mjs +3 -0
- package/dist/plugins/panorama/PanoramaRenderer.mjs +94 -0
- package/dist/plugins/panorama/PanoramaRenderer.mjs.map +1 -0
- package/dist/plugins/panorama/SkyboxGeometry.mjs +132 -0
- package/dist/plugins/panorama/SkyboxGeometry.mjs.map +1 -0
- package/dist/plugins/panorama/plugin.d.mts +8 -0
- package/dist/plugins/panorama/plugin.d.mts.map +1 -0
- package/dist/plugins/panorama/plugin.mjs +13 -0
- package/dist/plugins/panorama/plugin.mjs.map +1 -0
- package/dist/plugins/panorama/shaders/webgl.mjs +59 -0
- package/dist/plugins/panorama/shaders/webgl.mjs.map +1 -0
- package/dist/plugins/panorama/shaders/webgpu.mjs +70 -0
- package/dist/plugins/panorama/shaders/webgpu.mjs.map +1 -0
- package/dist/texture/TextureLoader.d.mts +22 -0
- package/dist/texture/TextureLoader.d.mts.map +1 -0
- package/dist/texture/TextureLoader.mjs +114 -0
- package/dist/texture/TextureLoader.mjs.map +1 -0
- package/dist/viewer/BoneMatrixComputer.mjs +82 -0
- package/dist/viewer/BoneMatrixComputer.mjs.map +1 -0
- package/dist/viewer/RenderLoop.mjs +45 -0
- package/dist/viewer/RenderLoop.mjs.map +1 -0
- package/dist/viewer/RenderState.mjs +44 -0
- package/dist/viewer/RenderState.mjs.map +1 -0
- package/dist/viewer/ResourceManager.mjs +278 -0
- package/dist/viewer/ResourceManager.mjs.map +1 -0
- package/dist/viewer/SkinViewer.d.mts +98 -0
- package/dist/viewer/SkinViewer.d.mts.map +1 -0
- package/dist/viewer/SkinViewer.mjs +379 -0
- package/dist/viewer/SkinViewer.mjs.map +1 -0
- package/dist/viewer/index.mjs +7 -0
- package/dist/webgl.d.mts +2 -0
- package/dist/webgl.mjs +3 -0
- package/dist/webgpu.d.mts +2 -0
- package/dist/webgpu.mjs +3 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# minecraft-skin-renderer
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/minecraft-skin-renderer)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A high-performance, browser-based 3D Minecraft skin renderer with WebGL and WebGPU support.
|
|
7
|
+
|
|
8
|
+
[Online Demo](https://mcskin.daidr.me)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Dual Rendering Backends** - WebGL2 for broad compatibility, WebGPU for modern performance
|
|
13
|
+
- **Skin Variants** - Classic (4px arms) and slim (3px arms) model support
|
|
14
|
+
- **Back Equipment** - Cape and elytra rendering
|
|
15
|
+
- **Animations** - Built-in presets (idle, walk, run, fly) with custom animation support
|
|
16
|
+
- **Camera Controls** - Orbit controls with zoom, rotation, and auto-rotate
|
|
17
|
+
- **Panorama Backgrounds** - Equirectangular panorama support via plugin
|
|
18
|
+
- **Screenshot Export** - Export renders as PNG or JPEG
|
|
19
|
+
- **Tree-Shakable** - Plugin architecture for minimal bundle size
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# npm
|
|
27
|
+
npm install minecraft-skin-renderer
|
|
28
|
+
|
|
29
|
+
# pnpm
|
|
30
|
+
pnpm add minecraft-skin-renderer
|
|
31
|
+
|
|
32
|
+
# yarn
|
|
33
|
+
yarn add minecraft-skin-renderer
|
|
34
|
+
|
|
35
|
+
# bun
|
|
36
|
+
bun add minecraft-skin-renderer
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Basic Usage
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { use, createSkinViewer } from 'minecraft-skin-renderer'
|
|
43
|
+
import { WebGLRendererPlugin } from 'minecraft-skin-renderer/webgl'
|
|
44
|
+
|
|
45
|
+
// Register renderer plugin (required before creating viewer)
|
|
46
|
+
use(WebGLRendererPlugin)
|
|
47
|
+
|
|
48
|
+
// Create viewer
|
|
49
|
+
const viewer = await createSkinViewer({
|
|
50
|
+
canvas: document.getElementById('canvas') as HTMLCanvasElement,
|
|
51
|
+
skin: 'https://example.com/skin.png',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Start rendering
|
|
55
|
+
viewer.startRenderLoop()
|
|
56
|
+
|
|
57
|
+
// Play animation
|
|
58
|
+
viewer.playAnimation('walk')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API Reference
|
|
62
|
+
|
|
63
|
+
### `use(plugin)`
|
|
64
|
+
|
|
65
|
+
Register a renderer or feature plugin. Must be called before `createSkinViewer()`.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { use } from 'minecraft-skin-renderer'
|
|
69
|
+
import { WebGLRendererPlugin } from 'minecraft-skin-renderer/webgl'
|
|
70
|
+
import { WebGPURendererPlugin } from 'minecraft-skin-renderer/webgpu'
|
|
71
|
+
import { PanoramaPlugin } from 'minecraft-skin-renderer/panorama'
|
|
72
|
+
|
|
73
|
+
use(WebGLRendererPlugin)
|
|
74
|
+
use(WebGPURendererPlugin)
|
|
75
|
+
use(PanoramaPlugin)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `createSkinViewer(options)`
|
|
79
|
+
|
|
80
|
+
Create and initialize a skin viewer instance.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const viewer = await createSkinViewer({
|
|
84
|
+
canvas: HTMLCanvasElement, // Required: canvas element
|
|
85
|
+
preferredBackend: 'auto', // 'webgl' | 'webgpu' | 'auto'
|
|
86
|
+
antialias: true, // Enable antialiasing
|
|
87
|
+
pixelRatio: window.devicePixelRatio,
|
|
88
|
+
skin: 'url' | File | Blob, // Skin texture source
|
|
89
|
+
cape: 'url' | File | Blob, // Cape texture (64x32)
|
|
90
|
+
backEquipment: 'none', // 'cape' | 'elytra' | 'none'
|
|
91
|
+
slim: false, // Use slim model variant
|
|
92
|
+
fov: 70, // Field of view in degrees
|
|
93
|
+
zoom: 60, // Initial zoom distance
|
|
94
|
+
enableRotate: true, // Enable orbit rotation
|
|
95
|
+
enableZoom: true, // Enable zoom controls
|
|
96
|
+
autoRotate: false, // Auto-rotate camera
|
|
97
|
+
autoRotateSpeed: 30, // Degrees per second
|
|
98
|
+
panorama: 'url', // Panorama background (requires PanoramaPlugin)
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### SkinViewer Methods
|
|
103
|
+
|
|
104
|
+
#### Textures
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
await viewer.setSkin(source) // Set skin texture
|
|
108
|
+
await viewer.setCape(source) // Set cape texture
|
|
109
|
+
viewer.setSlim(true) // Switch to slim model
|
|
110
|
+
viewer.setBackEquipment('cape') // 'cape' | 'elytra' | 'none'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Visibility
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Get current visibility state
|
|
117
|
+
const visibility = viewer.getPartsVisibility()
|
|
118
|
+
|
|
119
|
+
// Set visibility for all parts
|
|
120
|
+
viewer.setPartsVisibility({
|
|
121
|
+
head: { inner: true, outer: true },
|
|
122
|
+
body: { inner: true, outer: false },
|
|
123
|
+
// ...
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// Set single part visibility
|
|
127
|
+
viewer.setPartVisibility('head', 'outer', false)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Animation
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
viewer.playAnimation('walk') // Play animation
|
|
134
|
+
viewer.playAnimation('walk', { // With options
|
|
135
|
+
speed: 1.5,
|
|
136
|
+
amplitude: 1.0,
|
|
137
|
+
})
|
|
138
|
+
viewer.pauseAnimation() // Pause
|
|
139
|
+
viewer.resumeAnimation() // Resume
|
|
140
|
+
viewer.stopAnimation() // Stop
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Camera
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
viewer.setRotation(theta, phi) // Set camera angles
|
|
147
|
+
const { theta, phi } = viewer.getRotation()
|
|
148
|
+
viewer.setZoom(80) // Set zoom distance
|
|
149
|
+
viewer.getZoom() // Get current zoom
|
|
150
|
+
viewer.setAutoRotate(true) // Toggle auto-rotate
|
|
151
|
+
viewer.resetCamera() // Reset to default
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Rendering
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
viewer.render() // Manual render
|
|
158
|
+
viewer.startRenderLoop() // Start RAF loop
|
|
159
|
+
viewer.stopRenderLoop() // Stop RAF loop
|
|
160
|
+
viewer.resize(width, height) // Resize canvas
|
|
161
|
+
const dataUrl = viewer.screenshot('png', 0.9) // Export
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Lifecycle
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
viewer.dispose() // Clean up resources
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Properties
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
viewer.backend // 'webgl' | 'webgpu' (readonly)
|
|
174
|
+
viewer.isPlaying // Animation playing (readonly)
|
|
175
|
+
viewer.currentAnimation // Current animation name (readonly)
|
|
176
|
+
viewer.backEquipment // Current back equipment (readonly)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Plugins
|
|
180
|
+
|
|
181
|
+
| Plugin | Import Path | Description |
|
|
182
|
+
|--------|-------------|-------------|
|
|
183
|
+
| WebGL | `minecraft-skin-renderer/webgl` | WebGL2 rendering backend |
|
|
184
|
+
| WebGPU | `minecraft-skin-renderer/webgpu` | WebGPU rendering backend |
|
|
185
|
+
| Panorama | `minecraft-skin-renderer/panorama` | Panorama background support |
|
|
186
|
+
|
|
187
|
+
### Built-in Animations
|
|
188
|
+
|
|
189
|
+
| Name | Description |
|
|
190
|
+
|------|-------------|
|
|
191
|
+
| `idle` | Standing idle animation |
|
|
192
|
+
| `walk` | Walking animation |
|
|
193
|
+
| `run` | Running animation |
|
|
194
|
+
| `fly` | Flying/gliding animation |
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
### Prerequisites
|
|
199
|
+
|
|
200
|
+
- Node.js 18+ or Bun
|
|
201
|
+
|
|
202
|
+
### Setup
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Install dependencies
|
|
206
|
+
bun install
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Scripts
|
|
210
|
+
|
|
211
|
+
| Command | Description |
|
|
212
|
+
|---------|-------------|
|
|
213
|
+
| `bun run build` | Build library with tsdown |
|
|
214
|
+
| `bun dev` | Build in watch mode |
|
|
215
|
+
| `bun dev:playground` | Run playground dev server |
|
|
216
|
+
| `bun test` | Run tests |
|
|
217
|
+
| `bun test:coverage` | Run tests with coverage |
|
|
218
|
+
| `bun lint` | Lint with oxlint |
|
|
219
|
+
| `bun lint:fix` | Lint and auto-fix |
|
|
220
|
+
| `bun fmt` | Format with oxfmt |
|
|
221
|
+
|
|
222
|
+
### Project Structure
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
src/
|
|
226
|
+
├── core/
|
|
227
|
+
│ ├── renderer/ # Renderer abstraction (WebGL/WebGPU)
|
|
228
|
+
│ ├── math/ # Math utilities (Vec3, Mat4, Quat)
|
|
229
|
+
│ ├── camera/ # Camera system
|
|
230
|
+
│ └── plugins/ # Plugin registry
|
|
231
|
+
├── model/ # Skeleton and geometry
|
|
232
|
+
├── animation/ # Animation system
|
|
233
|
+
├── viewer/ # Main SkinViewer
|
|
234
|
+
└── plugins/ # Plugin implementations
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Contributing
|
|
238
|
+
|
|
239
|
+
1. Fork the repository
|
|
240
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
241
|
+
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
|
|
242
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
243
|
+
5. Open a Pull Request
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PlayerSkeleton } from "../model/types.mjs";
|
|
2
|
+
import { AnimationController } from "./types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/animation/AnimationController.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Create animation controller
|
|
7
|
+
*/
|
|
8
|
+
declare function createAnimationController(skeleton: PlayerSkeleton): AnimationController;
|
|
9
|
+
/**
|
|
10
|
+
* Update animation controller
|
|
11
|
+
*/
|
|
12
|
+
declare function updateAnimationController(controller: AnimationController, deltaTime: number): void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { createAnimationController, updateAnimationController };
|
|
15
|
+
//# sourceMappingURL=AnimationController.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnimationController.d.mts","names":[],"sources":["../../src/animation/AnimationController.ts"],"mappings":";;;;;;;iBAyJgB,yBAAA,CAA0B,QAAA,EAAU,cAAA,GAAiB,mBAAA;;;;iBA6ErD,yBAAA,CACd,UAAA,EAAY,mBAAA,EACZ,SAAA"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { vec3Lerp, vec3Zero } from "../core/math/vec3.mjs";
|
|
2
|
+
import { quatIdentity, quatSlerp } from "../core/math/quat.mjs";
|
|
3
|
+
import "../core/math/index.mjs";
|
|
4
|
+
import { resetSkeleton, setBonePositionOffset, setBoneRotation } from "../model/PlayerModel.mjs";
|
|
5
|
+
import { linear } from "./easing.mjs";
|
|
6
|
+
import { AnimationPlayState, getAnimation } from "./types.mjs";
|
|
7
|
+
import "./presets/index.mjs";
|
|
8
|
+
|
|
9
|
+
//#region src/animation/AnimationController.ts
|
|
10
|
+
/**
|
|
11
|
+
* Animation controller implementation
|
|
12
|
+
*/
|
|
13
|
+
const controllerStates = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
/**
|
|
15
|
+
* Binary search for keyframe index
|
|
16
|
+
* Returns the index of the last keyframe with time <= target time
|
|
17
|
+
*/
|
|
18
|
+
function binarySearchKeyframe(keyframes, time) {
|
|
19
|
+
let low = 0;
|
|
20
|
+
let high = keyframes.length - 1;
|
|
21
|
+
if (time <= keyframes[0].time) return 0;
|
|
22
|
+
if (time >= keyframes[high].time) return high;
|
|
23
|
+
while (low < high) {
|
|
24
|
+
const mid = low + high + 1 >> 1;
|
|
25
|
+
if (keyframes[mid].time <= time) low = mid;
|
|
26
|
+
else high = mid - 1;
|
|
27
|
+
}
|
|
28
|
+
return low;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Interpolate between keyframes using binary search
|
|
32
|
+
*/
|
|
33
|
+
function interpolateKeyframes(keyframes, normalizedTime, amplitude) {
|
|
34
|
+
if (keyframes.length === 0) return {};
|
|
35
|
+
if (keyframes.length === 1) {
|
|
36
|
+
const kf = keyframes[0];
|
|
37
|
+
const result = {};
|
|
38
|
+
if (kf.rotation) result.rotation = amplitude !== 1 ? quatSlerp(quatIdentity(), kf.rotation, amplitude) : kf.rotation;
|
|
39
|
+
if (kf.position) result.position = amplitude !== 1 ? vec3Lerp(vec3Zero(), kf.position, amplitude) : kf.position;
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
const prevIndex = binarySearchKeyframe(keyframes, normalizedTime);
|
|
43
|
+
const prevKeyframe = keyframes[prevIndex];
|
|
44
|
+
if (prevIndex >= keyframes.length - 1) {
|
|
45
|
+
const result = {};
|
|
46
|
+
if (prevKeyframe.rotation) result.rotation = amplitude !== 1 ? quatSlerp(quatIdentity(), prevKeyframe.rotation, amplitude) : prevKeyframe.rotation;
|
|
47
|
+
if (prevKeyframe.position) result.position = amplitude !== 1 ? vec3Lerp(vec3Zero(), prevKeyframe.position, amplitude) : prevKeyframe.position;
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
const nextKeyframe = keyframes[prevIndex + 1];
|
|
51
|
+
const segmentDuration = nextKeyframe.time - prevKeyframe.time;
|
|
52
|
+
const localT = segmentDuration > 0 ? (normalizedTime - prevKeyframe.time) / segmentDuration : 0;
|
|
53
|
+
const easedT = (nextKeyframe.easing ?? linear)(localT);
|
|
54
|
+
const result = {};
|
|
55
|
+
if (prevKeyframe.rotation && nextKeyframe.rotation) {
|
|
56
|
+
let rotation = quatSlerp(prevKeyframe.rotation, nextKeyframe.rotation, easedT);
|
|
57
|
+
if (amplitude !== 1) rotation = quatSlerp(quatIdentity(), rotation, amplitude);
|
|
58
|
+
result.rotation = rotation;
|
|
59
|
+
}
|
|
60
|
+
if (prevKeyframe.position && nextKeyframe.position) {
|
|
61
|
+
let position = vec3Lerp(prevKeyframe.position, nextKeyframe.position, easedT);
|
|
62
|
+
if (amplitude !== 1) position = vec3Lerp(vec3Zero(), position, amplitude);
|
|
63
|
+
result.position = position;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create animation controller
|
|
69
|
+
*/
|
|
70
|
+
function createAnimationController(skeleton) {
|
|
71
|
+
const state = {
|
|
72
|
+
skeleton,
|
|
73
|
+
current: null
|
|
74
|
+
};
|
|
75
|
+
const controller = {
|
|
76
|
+
get isPlaying() {
|
|
77
|
+
return state.current?.playState === AnimationPlayState.Playing;
|
|
78
|
+
},
|
|
79
|
+
get isPaused() {
|
|
80
|
+
return state.current?.playState === AnimationPlayState.Paused;
|
|
81
|
+
},
|
|
82
|
+
get currentAnimation() {
|
|
83
|
+
return state.current?.animation.name ?? null;
|
|
84
|
+
},
|
|
85
|
+
get progress() {
|
|
86
|
+
if (!state.current) return 0;
|
|
87
|
+
return state.current.time % state.current.animation.duration / state.current.animation.duration;
|
|
88
|
+
},
|
|
89
|
+
skeleton,
|
|
90
|
+
play(name, config) {
|
|
91
|
+
const animation = getAnimation(name);
|
|
92
|
+
if (!animation) {
|
|
93
|
+
console.warn(`Animation "${name}" not found`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
resetSkeleton(skeleton);
|
|
97
|
+
state.current = {
|
|
98
|
+
animation,
|
|
99
|
+
config: {
|
|
100
|
+
speed: config?.speed ?? 1,
|
|
101
|
+
amplitude: config?.amplitude ?? 1
|
|
102
|
+
},
|
|
103
|
+
time: 0,
|
|
104
|
+
playState: AnimationPlayState.Playing
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
pause() {
|
|
108
|
+
if (state.current && state.current.playState === AnimationPlayState.Playing) state.current.playState = AnimationPlayState.Paused;
|
|
109
|
+
},
|
|
110
|
+
resume() {
|
|
111
|
+
if (state.current && state.current.playState === AnimationPlayState.Paused) state.current.playState = AnimationPlayState.Playing;
|
|
112
|
+
},
|
|
113
|
+
stop() {
|
|
114
|
+
state.current = null;
|
|
115
|
+
resetSkeleton(skeleton);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
controllerStates.set(controller, state);
|
|
119
|
+
return controller;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Update animation controller
|
|
123
|
+
*/
|
|
124
|
+
function updateAnimationController(controller, deltaTime) {
|
|
125
|
+
const state = controllerStates.get(controller);
|
|
126
|
+
if (!state || !state.current) return;
|
|
127
|
+
if (state.current.playState !== AnimationPlayState.Playing) return;
|
|
128
|
+
const { animation, config } = state.current;
|
|
129
|
+
state.current.time += deltaTime * (config.speed ?? 1);
|
|
130
|
+
if (animation.loop) state.current.time %= animation.duration;
|
|
131
|
+
else if (state.current.time >= animation.duration) {
|
|
132
|
+
state.current.time = animation.duration;
|
|
133
|
+
state.current.playState = AnimationPlayState.Stopped;
|
|
134
|
+
}
|
|
135
|
+
const normalizedTime = state.current.time / animation.duration;
|
|
136
|
+
const amplitude = config.amplitude ?? 1;
|
|
137
|
+
for (const track of animation.tracks) {
|
|
138
|
+
const result = interpolateKeyframes(track.keyframes, normalizedTime, amplitude);
|
|
139
|
+
if (result.rotation) setBoneRotation(controller.skeleton, track.boneIndex, result.rotation);
|
|
140
|
+
if (result.position) setBonePositionOffset(controller.skeleton, track.boneIndex, result.position);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { createAnimationController, updateAnimationController };
|
|
146
|
+
//# sourceMappingURL=AnimationController.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnimationController.mjs","names":[],"sources":["../../src/animation/AnimationController.ts"],"sourcesContent":["/**\n * Animation controller implementation\n */\n\nimport { quatIdentity, quatSlerp, vec3Lerp, vec3Zero } from \"../core/math\";\nimport type { Quat, Vec3 } from \"../core/math\";\nimport type { PlayerSkeleton } from \"../model/types\";\nimport { setBoneRotation, setBonePositionOffset, resetSkeleton } from \"../model/PlayerModel\";\nimport { linear } from \"./easing\";\nimport { getAnimation, AnimationPlayState } from \"./types\";\nimport type { Animation, AnimationConfig, AnimationController, Keyframe } from \"./types\";\n\n// Import presets to register them\nimport \"./presets\";\n\n/** Internal animation state */\ninterface AnimationState {\n animation: Animation;\n config: AnimationConfig;\n time: number; // Current time in seconds\n playState: AnimationPlayState;\n}\n\n/** Animation controller internal state */\ninterface ControllerState {\n skeleton: PlayerSkeleton;\n current: AnimationState | null;\n}\n\n// Internal state storage using WeakMap\nconst controllerStates = new WeakMap<AnimationController, ControllerState>();\n\n/** Result of keyframe interpolation */\ninterface InterpolationResult {\n rotation?: Quat;\n position?: Vec3;\n}\n\n/**\n * Binary search for keyframe index\n * Returns the index of the last keyframe with time <= target time\n */\nfunction binarySearchKeyframe(keyframes: Keyframe[], time: number): number {\n let low = 0;\n let high = keyframes.length - 1;\n\n // Edge cases\n if (time <= keyframes[0]!.time) return 0;\n if (time >= keyframes[high]!.time) return high;\n\n while (low < high) {\n const mid = (low + high + 1) >> 1;\n if (keyframes[mid]!.time <= time) {\n low = mid;\n } else {\n high = mid - 1;\n }\n }\n\n return low;\n}\n\n/**\n * Interpolate between keyframes using binary search\n */\nfunction interpolateKeyframes(\n keyframes: Keyframe[],\n normalizedTime: number,\n amplitude: number,\n): InterpolationResult {\n if (keyframes.length === 0) {\n return {};\n }\n\n // Single keyframe fast path\n if (keyframes.length === 1) {\n const kf = keyframes[0]!;\n const result: InterpolationResult = {};\n\n if (kf.rotation) {\n result.rotation =\n amplitude !== 1.0 ? quatSlerp(quatIdentity(), kf.rotation, amplitude) : kf.rotation;\n }\n if (kf.position) {\n result.position =\n amplitude !== 1.0 ? vec3Lerp(vec3Zero(), kf.position, amplitude) : kf.position;\n }\n\n return result;\n }\n\n // Use binary search to find keyframe pair\n const prevIndex = binarySearchKeyframe(keyframes, normalizedTime);\n const prevKeyframe = keyframes[prevIndex]!;\n\n // If at the last keyframe, return its values\n if (prevIndex >= keyframes.length - 1) {\n const result: InterpolationResult = {};\n\n if (prevKeyframe.rotation) {\n result.rotation =\n amplitude !== 1.0\n ? quatSlerp(quatIdentity(), prevKeyframe.rotation, amplitude)\n : prevKeyframe.rotation;\n }\n if (prevKeyframe.position) {\n result.position =\n amplitude !== 1.0\n ? vec3Lerp(vec3Zero(), prevKeyframe.position, amplitude)\n : prevKeyframe.position;\n }\n\n return result;\n }\n\n const nextKeyframe = keyframes[prevIndex + 1]!;\n\n // Calculate local interpolation factor\n const segmentDuration = nextKeyframe.time - prevKeyframe.time;\n const localT = segmentDuration > 0 ? (normalizedTime - prevKeyframe.time) / segmentDuration : 0;\n\n // Apply easing\n const easing = nextKeyframe.easing ?? linear;\n const easedT = easing(localT);\n\n const result: InterpolationResult = {};\n\n // Interpolate rotation if present\n if (prevKeyframe.rotation && nextKeyframe.rotation) {\n let rotation = quatSlerp(prevKeyframe.rotation, nextKeyframe.rotation, easedT);\n // Apply amplitude scaling (interpolate from identity)\n if (amplitude !== 1.0) {\n rotation = quatSlerp(quatIdentity(), rotation, amplitude);\n }\n result.rotation = rotation;\n }\n\n // Interpolate position if present\n if (prevKeyframe.position && nextKeyframe.position) {\n let position = vec3Lerp(prevKeyframe.position, nextKeyframe.position, easedT);\n // Apply amplitude scaling\n if (amplitude !== 1.0) {\n position = vec3Lerp(vec3Zero(), position, amplitude);\n }\n result.position = position;\n }\n\n return result;\n}\n\n/**\n * Create animation controller\n */\nexport function createAnimationController(skeleton: PlayerSkeleton): AnimationController {\n // Create state\n const state: ControllerState = {\n skeleton,\n current: null,\n };\n\n const controller: AnimationController = {\n get isPlaying() {\n return state.current?.playState === AnimationPlayState.Playing;\n },\n\n get isPaused() {\n return state.current?.playState === AnimationPlayState.Paused;\n },\n\n get currentAnimation() {\n return state.current?.animation.name ?? null;\n },\n\n get progress() {\n if (!state.current) return 0;\n return (\n (state.current.time % state.current.animation.duration) / state.current.animation.duration\n );\n },\n\n skeleton,\n\n play(name: string, config?: AnimationConfig) {\n const animation = getAnimation(name);\n if (!animation) {\n console.warn(`Animation \"${name}\" not found`);\n return;\n }\n\n // Reset skeleton to default pose before starting new animation\n resetSkeleton(skeleton);\n\n state.current = {\n animation,\n config: {\n speed: config?.speed ?? 1.0,\n amplitude: config?.amplitude ?? 1.0,\n },\n time: 0,\n playState: AnimationPlayState.Playing,\n };\n },\n\n pause() {\n if (state.current && state.current.playState === AnimationPlayState.Playing) {\n state.current.playState = AnimationPlayState.Paused;\n }\n },\n\n resume() {\n if (state.current && state.current.playState === AnimationPlayState.Paused) {\n state.current.playState = AnimationPlayState.Playing;\n }\n },\n\n stop() {\n state.current = null;\n resetSkeleton(skeleton);\n },\n };\n\n // Store state in WeakMap\n controllerStates.set(controller, state);\n\n return controller;\n}\n\n/**\n * Update animation controller\n */\nexport function updateAnimationController(\n controller: AnimationController,\n deltaTime: number,\n): void {\n const state = controllerStates.get(controller);\n if (!state || !state.current) return;\n if (state.current.playState !== AnimationPlayState.Playing) return;\n\n const { animation, config } = state.current;\n\n // Update time\n state.current.time += deltaTime * (config.speed ?? 1.0);\n\n // Handle looping\n if (animation.loop) {\n state.current.time %= animation.duration;\n } else if (state.current.time >= animation.duration) {\n state.current.time = animation.duration;\n state.current.playState = AnimationPlayState.Stopped;\n }\n\n // Calculate normalized time (0-1)\n const normalizedTime = state.current.time / animation.duration;\n\n // Apply animation to each track\n const amplitude = config.amplitude ?? 1.0;\n\n for (const track of animation.tracks) {\n const result = interpolateKeyframes(track.keyframes, normalizedTime, amplitude);\n if (result.rotation) {\n setBoneRotation(controller.skeleton, track.boneIndex, result.rotation);\n }\n if (result.position) {\n setBonePositionOffset(controller.skeleton, track.boneIndex, result.position);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/animation/easing.ts
|
|
2
|
+
/** Linear interpolation (no easing) */
|
|
3
|
+
const linear = (t) => t;
|
|
4
|
+
/** Ease in-out sine */
|
|
5
|
+
const easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
export { easeInOutSine, linear };
|
|
9
|
+
//# sourceMappingURL=easing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"easing.mjs","names":[],"sources":["../../src/animation/easing.ts"],"sourcesContent":["/**\n * Easing functions for animations\n */\n\nimport type { EasingFunction } from \"./types\";\n\n/** Linear interpolation (no easing) */\nexport const linear: EasingFunction = (t) => t;\n\n/** Ease in quadratic */\nexport const easeInQuad: EasingFunction = (t) => t * t;\n\n/** Ease out quadratic */\nexport const easeOutQuad: EasingFunction = (t) => t * (2 - t);\n\n/** Ease in-out quadratic */\nexport const easeInOutQuad: EasingFunction = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);\n\n/** Ease in cubic */\nexport const easeInCubic: EasingFunction = (t) => t * t * t;\n\n/** Ease out cubic */\nexport const easeOutCubic: EasingFunction = (t) => {\n const t1 = t - 1;\n return t1 * t1 * t1 + 1;\n};\n\n/** Ease in-out cubic */\nexport const easeInOutCubic: EasingFunction = (t) =>\n t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;\n\n/** Ease in sine */\nexport const easeInSine: EasingFunction = (t) => 1 - Math.cos((t * Math.PI) / 2);\n\n/** Ease out sine */\nexport const easeOutSine: EasingFunction = (t) => Math.sin((t * Math.PI) / 2);\n\n/** Ease in-out sine */\nexport const easeInOutSine: EasingFunction = (t) => -(Math.cos(Math.PI * t) - 1) / 2;\n\n/** Smooth sine wave (for looping animations) */\nexport const sineWave: EasingFunction = (t) => Math.sin(t * Math.PI * 2);\n\n/** Half sine wave (0 to 1 to 0) */\nexport const halfSine: EasingFunction = (t) => Math.sin(t * Math.PI);\n\n/** Bounce effect */\nexport const bounce: EasingFunction = (t) => {\n if (t < 1 / 2.75) {\n return 7.5625 * t * t;\n } else if (t < 2 / 2.75) {\n const t2 = t - 1.5 / 2.75;\n return 7.5625 * t2 * t2 + 0.75;\n } else if (t < 2.5 / 2.75) {\n const t2 = t - 2.25 / 2.75;\n return 7.5625 * t2 * t2 + 0.9375;\n } else {\n const t2 = t - 2.625 / 2.75;\n return 7.5625 * t2 * t2 + 0.984375;\n }\n};\n\n/** Elastic bounce */\nexport const elastic: EasingFunction = (t) => {\n if (t === 0 || t === 1) return t;\n const p = 0.3;\n const s = p / 4;\n return Math.pow(2, -10 * t) * Math.sin(((t - s) * (2 * Math.PI)) / p) + 1;\n};\n"],"mappings":";;AAOA,MAAa,UAA0B,MAAM;;AA+B7C,MAAa,iBAAiC,MAAM,EAAE,KAAK,IAAI,KAAK,KAAK,EAAE,GAAG,KAAK"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { quatFromEuler } from "../../core/math/quat.mjs";
|
|
2
|
+
import { degToRad } from "../../core/math/utils.mjs";
|
|
3
|
+
import "../../core/math/index.mjs";
|
|
4
|
+
import { BoneIndex } from "../../model/types.mjs";
|
|
5
|
+
import { easeInOutSine } from "../easing.mjs";
|
|
6
|
+
import { registerAnimation } from "../types.mjs";
|
|
7
|
+
import { createSpreadWingTracks, rot } from "./utils.mjs";
|
|
8
|
+
|
|
9
|
+
//#region src/animation/presets/fly.ts
|
|
10
|
+
/**
|
|
11
|
+
* Fly animation preset (elytra flying pose)
|
|
12
|
+
*/
|
|
13
|
+
/** Create fly animation */
|
|
14
|
+
function createFlyAnimation() {
|
|
15
|
+
const bodyPitch = 80;
|
|
16
|
+
const armAngle = 10;
|
|
17
|
+
const legAngle = 5;
|
|
18
|
+
const [leftWing, rightWing] = createSpreadWingTracks(20, 80);
|
|
19
|
+
return {
|
|
20
|
+
name: "fly",
|
|
21
|
+
duration: 1.5,
|
|
22
|
+
loop: true,
|
|
23
|
+
tracks: [
|
|
24
|
+
{
|
|
25
|
+
boneIndex: BoneIndex.Body,
|
|
26
|
+
keyframes: [
|
|
27
|
+
{
|
|
28
|
+
time: 0,
|
|
29
|
+
rotation: rot(bodyPitch)
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
time: .5,
|
|
33
|
+
rotation: rot(bodyPitch + 3),
|
|
34
|
+
easing: easeInOutSine
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
time: 1,
|
|
38
|
+
rotation: rot(bodyPitch),
|
|
39
|
+
easing: easeInOutSine
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
boneIndex: BoneIndex.Head,
|
|
45
|
+
keyframes: [
|
|
46
|
+
{
|
|
47
|
+
time: 0,
|
|
48
|
+
rotation: rot(-bodyPitch + 10)
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
time: .5,
|
|
52
|
+
rotation: rot(-bodyPitch + 5),
|
|
53
|
+
easing: easeInOutSine
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
time: 1,
|
|
57
|
+
rotation: rot(-bodyPitch + 10),
|
|
58
|
+
easing: easeInOutSine
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
boneIndex: BoneIndex.RightArm,
|
|
64
|
+
keyframes: [
|
|
65
|
+
{
|
|
66
|
+
time: 0,
|
|
67
|
+
rotation: rot(armAngle, 0, -15)
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
time: .5,
|
|
71
|
+
rotation: rot(armAngle + 5, 0, -20),
|
|
72
|
+
easing: easeInOutSine
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
time: 1,
|
|
76
|
+
rotation: rot(armAngle, 0, -15),
|
|
77
|
+
easing: easeInOutSine
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
boneIndex: BoneIndex.LeftArm,
|
|
83
|
+
keyframes: [
|
|
84
|
+
{
|
|
85
|
+
time: 0,
|
|
86
|
+
rotation: rot(armAngle, 0, 15)
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
time: .5,
|
|
90
|
+
rotation: rot(armAngle + 5, 0, 20),
|
|
91
|
+
easing: easeInOutSine
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
time: 1,
|
|
95
|
+
rotation: rot(armAngle, 0, 15),
|
|
96
|
+
easing: easeInOutSine
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
boneIndex: BoneIndex.RightLeg,
|
|
102
|
+
keyframes: [
|
|
103
|
+
{
|
|
104
|
+
time: 0,
|
|
105
|
+
rotation: rot(legAngle, 0, -3)
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
time: .5,
|
|
109
|
+
rotation: rot(legAngle + 5, 0, -5),
|
|
110
|
+
easing: easeInOutSine
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
time: 1,
|
|
114
|
+
rotation: rot(legAngle, 0, -3),
|
|
115
|
+
easing: easeInOutSine
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
boneIndex: BoneIndex.LeftLeg,
|
|
121
|
+
keyframes: [
|
|
122
|
+
{
|
|
123
|
+
time: 0,
|
|
124
|
+
rotation: rot(legAngle, 0, 3)
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
time: .5,
|
|
128
|
+
rotation: rot(legAngle + 5, 0, 5),
|
|
129
|
+
easing: easeInOutSine
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
time: 1,
|
|
133
|
+
rotation: rot(legAngle, 0, 3),
|
|
134
|
+
easing: easeInOutSine
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
boneIndex: BoneIndex.Cape,
|
|
140
|
+
keyframes: [
|
|
141
|
+
{
|
|
142
|
+
time: 0,
|
|
143
|
+
rotation: rot(10)
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
time: .25,
|
|
147
|
+
rotation: quatFromEuler(degToRad(15), degToRad(2), 0),
|
|
148
|
+
easing: easeInOutSine
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
time: .5,
|
|
152
|
+
rotation: rot(5),
|
|
153
|
+
easing: easeInOutSine
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
time: .75,
|
|
157
|
+
rotation: quatFromEuler(degToRad(15), degToRad(-2), 0),
|
|
158
|
+
easing: easeInOutSine
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
time: 1,
|
|
162
|
+
rotation: rot(10),
|
|
163
|
+
easing: easeInOutSine
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
leftWing,
|
|
168
|
+
rightWing
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
registerAnimation(createFlyAnimation());
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
export { createFlyAnimation };
|
|
176
|
+
//# sourceMappingURL=fly.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fly.mjs","names":[],"sources":["../../../src/animation/presets/fly.ts"],"sourcesContent":["/**\n * Fly animation preset (elytra flying pose)\n */\n\nimport { degToRad, quatFromEuler } from \"../../core/math\";\nimport { BoneIndex } from \"../../model/types\";\nimport { easeInOutSine } from \"../easing\";\nimport { registerAnimation } from \"../types\";\nimport type { Animation } from \"../types\";\nimport { createSpreadWingTracks, rot } from \"./utils\";\n\n/** Create fly animation */\nfunction createFlyAnimation(): Animation {\n const bodyPitch = 80;\n const armAngle = 10;\n const legAngle = 5;\n\n const [leftWing, rightWing] = createSpreadWingTracks(20, 80);\n\n return {\n name: \"fly\",\n duration: 1.5,\n loop: true,\n tracks: [\n // Body pitched forward\n {\n boneIndex: BoneIndex.Body,\n keyframes: [\n { time: 0, rotation: rot(bodyPitch) },\n { time: 0.5, rotation: rot(bodyPitch + 3), easing: easeInOutSine },\n { time: 1, rotation: rot(bodyPitch), easing: easeInOutSine },\n ],\n },\n // Head compensates for body pitch\n {\n boneIndex: BoneIndex.Head,\n keyframes: [\n { time: 0, rotation: rot(-bodyPitch + 10) },\n { time: 0.5, rotation: rot(-bodyPitch + 5), easing: easeInOutSine },\n { time: 1, rotation: rot(-bodyPitch + 10), easing: easeInOutSine },\n ],\n },\n // Arms slightly back and out\n {\n boneIndex: BoneIndex.RightArm,\n keyframes: [\n { time: 0, rotation: rot(armAngle, 0, -15) },\n { time: 0.5, rotation: rot(armAngle + 5, 0, -20), easing: easeInOutSine },\n { time: 1, rotation: rot(armAngle, 0, -15), easing: easeInOutSine },\n ],\n },\n {\n boneIndex: BoneIndex.LeftArm,\n keyframes: [\n { time: 0, rotation: rot(armAngle, 0, 15) },\n { time: 0.5, rotation: rot(armAngle + 5, 0, 20), easing: easeInOutSine },\n { time: 1, rotation: rot(armAngle, 0, 15), easing: easeInOutSine },\n ],\n },\n // Legs stretched back\n {\n boneIndex: BoneIndex.RightLeg,\n keyframes: [\n { time: 0, rotation: rot(legAngle, 0, -3) },\n { time: 0.5, rotation: rot(legAngle + 5, 0, -5), easing: easeInOutSine },\n { time: 1, rotation: rot(legAngle, 0, -3), easing: easeInOutSine },\n ],\n },\n {\n boneIndex: BoneIndex.LeftLeg,\n keyframes: [\n { time: 0, rotation: rot(legAngle, 0, 3) },\n { time: 0.5, rotation: rot(legAngle + 5, 0, 5), easing: easeInOutSine },\n { time: 1, rotation: rot(legAngle, 0, 3), easing: easeInOutSine },\n ],\n },\n // Cape flows behind\n {\n boneIndex: BoneIndex.Cape,\n keyframes: [\n { time: 0, rotation: rot(10) },\n {\n time: 0.25,\n rotation: quatFromEuler(degToRad(15), degToRad(2), 0),\n easing: easeInOutSine,\n },\n { time: 0.5, rotation: rot(5), easing: easeInOutSine },\n {\n time: 0.75,\n rotation: quatFromEuler(degToRad(15), degToRad(-2), 0),\n easing: easeInOutSine,\n },\n { time: 1, rotation: rot(10), easing: easeInOutSine },\n ],\n },\n leftWing,\n rightWing,\n ],\n };\n}\n\nregisterAnimation(createFlyAnimation());\n\nexport { createFlyAnimation };\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAS,qBAAgC;CACvC,MAAM,YAAY;CAClB,MAAM,WAAW;CACjB,MAAM,WAAW;CAEjB,MAAM,CAAC,UAAU,aAAa,uBAAuB,IAAI,GAAG;AAE5D,QAAO;EACL,MAAM;EACN,UAAU;EACV,MAAM;EACN,QAAQ;GAEN;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU;MAAE;KACrC;MAAE,MAAM;MAAK,UAAU,IAAI,YAAY,EAAE;MAAE,QAAQ;MAAe;KAClE;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU;MAAE,QAAQ;MAAe;KAC7D;IACF;GAED;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,CAAC,YAAY,GAAG;MAAE;KAC3C;MAAE,MAAM;MAAK,UAAU,IAAI,CAAC,YAAY,EAAE;MAAE,QAAQ;MAAe;KACnE;MAAE,MAAM;MAAG,UAAU,IAAI,CAAC,YAAY,GAAG;MAAE,QAAQ;MAAe;KACnE;IACF;GAED;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,IAAI;MAAE;KAC5C;MAAE,MAAM;MAAK,UAAU,IAAI,WAAW,GAAG,GAAG,IAAI;MAAE,QAAQ;MAAe;KACzE;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,IAAI;MAAE,QAAQ;MAAe;KACpE;IACF;GACD;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,GAAG;MAAE;KAC3C;MAAE,MAAM;MAAK,UAAU,IAAI,WAAW,GAAG,GAAG,GAAG;MAAE,QAAQ;MAAe;KACxE;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,GAAG;MAAE,QAAQ;MAAe;KACnE;IACF;GAED;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,GAAG;MAAE;KAC3C;MAAE,MAAM;MAAK,UAAU,IAAI,WAAW,GAAG,GAAG,GAAG;MAAE,QAAQ;MAAe;KACxE;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,GAAG;MAAE,QAAQ;MAAe;KACnE;IACF;GACD;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,EAAE;MAAE;KAC1C;MAAE,MAAM;MAAK,UAAU,IAAI,WAAW,GAAG,GAAG,EAAE;MAAE,QAAQ;MAAe;KACvE;MAAE,MAAM;MAAG,UAAU,IAAI,UAAU,GAAG,EAAE;MAAE,QAAQ;MAAe;KAClE;IACF;GAED;IACE,WAAW,UAAU;IACrB,WAAW;KACT;MAAE,MAAM;MAAG,UAAU,IAAI,GAAG;MAAE;KAC9B;MACE,MAAM;MACN,UAAU,cAAc,SAAS,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE;MACrD,QAAQ;MACT;KACD;MAAE,MAAM;MAAK,UAAU,IAAI,EAAE;MAAE,QAAQ;MAAe;KACtD;MACE,MAAM;MACN,UAAU,cAAc,SAAS,GAAG,EAAE,SAAS,GAAG,EAAE,EAAE;MACtD,QAAQ;MACT;KACD;MAAE,MAAM;MAAG,UAAU,IAAI,GAAG;MAAE,QAAQ;MAAe;KACtD;IACF;GACD;GACA;GACD;EACF;;AAGH,kBAAkB,oBAAoB,CAAC"}
|