@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.
Files changed (150) hide show
  1. package/README.md +247 -0
  2. package/dist/animation/AnimationController.d.mts +15 -0
  3. package/dist/animation/AnimationController.d.mts.map +1 -0
  4. package/dist/animation/AnimationController.mjs +146 -0
  5. package/dist/animation/AnimationController.mjs.map +1 -0
  6. package/dist/animation/easing.mjs +9 -0
  7. package/dist/animation/easing.mjs.map +1 -0
  8. package/dist/animation/presets/fly.mjs +176 -0
  9. package/dist/animation/presets/fly.mjs.map +1 -0
  10. package/dist/animation/presets/idle.mjs +114 -0
  11. package/dist/animation/presets/idle.mjs.map +1 -0
  12. package/dist/animation/presets/index.mjs +7 -0
  13. package/dist/animation/presets/run.mjs +126 -0
  14. package/dist/animation/presets/run.mjs.map +1 -0
  15. package/dist/animation/presets/utils.mjs +166 -0
  16. package/dist/animation/presets/utils.mjs.map +1 -0
  17. package/dist/animation/presets/walk.mjs +76 -0
  18. package/dist/animation/presets/walk.mjs.map +1 -0
  19. package/dist/animation/types.d.mts +50 -0
  20. package/dist/animation/types.d.mts.map +1 -0
  21. package/dist/animation/types.mjs +22 -0
  22. package/dist/animation/types.mjs.map +1 -0
  23. package/dist/core/camera/Camera.d.mts +33 -0
  24. package/dist/core/camera/Camera.d.mts.map +1 -0
  25. package/dist/core/camera/Camera.mjs +77 -0
  26. package/dist/core/camera/Camera.mjs.map +1 -0
  27. package/dist/core/camera/OrbitControls.d.mts +50 -0
  28. package/dist/core/camera/OrbitControls.d.mts.map +1 -0
  29. package/dist/core/camera/OrbitControls.mjs +170 -0
  30. package/dist/core/camera/OrbitControls.mjs.map +1 -0
  31. package/dist/core/math/index.mjs +6 -0
  32. package/dist/core/math/mat4.d.mts +61 -0
  33. package/dist/core/math/mat4.d.mts.map +1 -0
  34. package/dist/core/math/mat4.mjs +513 -0
  35. package/dist/core/math/mat4.mjs.map +1 -0
  36. package/dist/core/math/quat.d.mts +61 -0
  37. package/dist/core/math/quat.d.mts.map +1 -0
  38. package/dist/core/math/quat.mjs +360 -0
  39. package/dist/core/math/quat.mjs.map +1 -0
  40. package/dist/core/math/utils.d.mts +43 -0
  41. package/dist/core/math/utils.d.mts.map +1 -0
  42. package/dist/core/math/utils.mjs +85 -0
  43. package/dist/core/math/utils.mjs.map +1 -0
  44. package/dist/core/math/vec3.d.mts +61 -0
  45. package/dist/core/math/vec3.d.mts.map +1 -0
  46. package/dist/core/math/vec3.mjs +198 -0
  47. package/dist/core/math/vec3.mjs.map +1 -0
  48. package/dist/core/plugins/registry.mjs +29 -0
  49. package/dist/core/plugins/registry.mjs.map +1 -0
  50. package/dist/core/plugins/types.d.mts +34 -0
  51. package/dist/core/plugins/types.d.mts.map +1 -0
  52. package/dist/core/renderer/registry.d.mts +46 -0
  53. package/dist/core/renderer/registry.d.mts.map +1 -0
  54. package/dist/core/renderer/registry.mjs +46 -0
  55. package/dist/core/renderer/registry.mjs.map +1 -0
  56. package/dist/core/renderer/shader-composer.mjs +30 -0
  57. package/dist/core/renderer/shader-composer.mjs.map +1 -0
  58. package/dist/core/renderer/types.d.mts +172 -0
  59. package/dist/core/renderer/types.d.mts.map +1 -0
  60. package/dist/core/renderer/types.mjs +90 -0
  61. package/dist/core/renderer/types.mjs.map +1 -0
  62. package/dist/core/renderer/utils.mjs +20 -0
  63. package/dist/core/renderer/utils.mjs.map +1 -0
  64. package/dist/core/renderer/webgl/WebGLBuffer.mjs +57 -0
  65. package/dist/core/renderer/webgl/WebGLBuffer.mjs.map +1 -0
  66. package/dist/core/renderer/webgl/WebGLPipeline.mjs +259 -0
  67. package/dist/core/renderer/webgl/WebGLPipeline.mjs.map +1 -0
  68. package/dist/core/renderer/webgl/WebGLRenderer.mjs +140 -0
  69. package/dist/core/renderer/webgl/WebGLRenderer.mjs.map +1 -0
  70. package/dist/core/renderer/webgl/WebGLTexture.mjs +87 -0
  71. package/dist/core/renderer/webgl/WebGLTexture.mjs.map +1 -0
  72. package/dist/core/renderer/webgl/plugin.d.mts +8 -0
  73. package/dist/core/renderer/webgl/plugin.d.mts.map +1 -0
  74. package/dist/core/renderer/webgl/plugin.mjs +24 -0
  75. package/dist/core/renderer/webgl/plugin.mjs.map +1 -0
  76. package/dist/core/renderer/webgl/shaders/index.mjs +81 -0
  77. package/dist/core/renderer/webgl/shaders/index.mjs.map +1 -0
  78. package/dist/core/renderer/webgpu/WebGPUBuffer.mjs +52 -0
  79. package/dist/core/renderer/webgpu/WebGPUBuffer.mjs.map +1 -0
  80. package/dist/core/renderer/webgpu/WebGPUPipeline.mjs +167 -0
  81. package/dist/core/renderer/webgpu/WebGPUPipeline.mjs.map +1 -0
  82. package/dist/core/renderer/webgpu/WebGPURenderer.mjs +299 -0
  83. package/dist/core/renderer/webgpu/WebGPURenderer.mjs.map +1 -0
  84. package/dist/core/renderer/webgpu/WebGPUTexture.mjs +126 -0
  85. package/dist/core/renderer/webgpu/WebGPUTexture.mjs.map +1 -0
  86. package/dist/core/renderer/webgpu/plugin.d.mts +8 -0
  87. package/dist/core/renderer/webgpu/plugin.d.mts.map +1 -0
  88. package/dist/core/renderer/webgpu/plugin.mjs +35 -0
  89. package/dist/core/renderer/webgpu/plugin.mjs.map +1 -0
  90. package/dist/core/renderer/webgpu/shaders/index.mjs +84 -0
  91. package/dist/core/renderer/webgpu/shaders/index.mjs.map +1 -0
  92. package/dist/index.d.mts +16 -0
  93. package/dist/index.mjs +19 -0
  94. package/dist/model/PlayerModel.d.mts +8 -0
  95. package/dist/model/PlayerModel.d.mts.map +1 -0
  96. package/dist/model/PlayerModel.mjs +253 -0
  97. package/dist/model/PlayerModel.mjs.map +1 -0
  98. package/dist/model/bone-utils.mjs +3 -0
  99. package/dist/model/constants.mjs +20 -0
  100. package/dist/model/constants.mjs.map +1 -0
  101. package/dist/model/geometry/BoxGeometry.mjs +316 -0
  102. package/dist/model/geometry/BoxGeometry.mjs.map +1 -0
  103. package/dist/model/geometry/index.mjs +3 -0
  104. package/dist/model/index.mjs +10 -0
  105. package/dist/model/types.d.mts +64 -0
  106. package/dist/model/types.d.mts.map +1 -0
  107. package/dist/model/types.mjs +67 -0
  108. package/dist/model/types.mjs.map +1 -0
  109. package/dist/model/uv/CapeUV.mjs +25 -0
  110. package/dist/model/uv/CapeUV.mjs.map +1 -0
  111. package/dist/model/uv/SkinUV.mjs +104 -0
  112. package/dist/model/uv/SkinUV.mjs.map +1 -0
  113. package/dist/model/uv/common.mjs +147 -0
  114. package/dist/model/uv/common.mjs.map +1 -0
  115. package/dist/panorama.d.mts +2 -0
  116. package/dist/panorama.mjs +3 -0
  117. package/dist/plugins/panorama/PanoramaRenderer.mjs +94 -0
  118. package/dist/plugins/panorama/PanoramaRenderer.mjs.map +1 -0
  119. package/dist/plugins/panorama/SkyboxGeometry.mjs +132 -0
  120. package/dist/plugins/panorama/SkyboxGeometry.mjs.map +1 -0
  121. package/dist/plugins/panorama/plugin.d.mts +8 -0
  122. package/dist/plugins/panorama/plugin.d.mts.map +1 -0
  123. package/dist/plugins/panorama/plugin.mjs +13 -0
  124. package/dist/plugins/panorama/plugin.mjs.map +1 -0
  125. package/dist/plugins/panorama/shaders/webgl.mjs +59 -0
  126. package/dist/plugins/panorama/shaders/webgl.mjs.map +1 -0
  127. package/dist/plugins/panorama/shaders/webgpu.mjs +70 -0
  128. package/dist/plugins/panorama/shaders/webgpu.mjs.map +1 -0
  129. package/dist/texture/TextureLoader.d.mts +22 -0
  130. package/dist/texture/TextureLoader.d.mts.map +1 -0
  131. package/dist/texture/TextureLoader.mjs +114 -0
  132. package/dist/texture/TextureLoader.mjs.map +1 -0
  133. package/dist/viewer/BoneMatrixComputer.mjs +82 -0
  134. package/dist/viewer/BoneMatrixComputer.mjs.map +1 -0
  135. package/dist/viewer/RenderLoop.mjs +45 -0
  136. package/dist/viewer/RenderLoop.mjs.map +1 -0
  137. package/dist/viewer/RenderState.mjs +44 -0
  138. package/dist/viewer/RenderState.mjs.map +1 -0
  139. package/dist/viewer/ResourceManager.mjs +278 -0
  140. package/dist/viewer/ResourceManager.mjs.map +1 -0
  141. package/dist/viewer/SkinViewer.d.mts +98 -0
  142. package/dist/viewer/SkinViewer.d.mts.map +1 -0
  143. package/dist/viewer/SkinViewer.mjs +379 -0
  144. package/dist/viewer/SkinViewer.mjs.map +1 -0
  145. package/dist/viewer/index.mjs +7 -0
  146. package/dist/webgl.d.mts +2 -0
  147. package/dist/webgl.mjs +3 -0
  148. package/dist/webgpu.d.mts +2 -0
  149. package/dist/webgpu.mjs +3 -0
  150. package/package.json +75 -0
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # minecraft-skin-renderer
2
+
3
+ [![npm version](https://img.shields.io/npm/v/minecraft-skin-renderer.svg)](https://www.npmjs.com/package/minecraft-skin-renderer)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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"}