blockymodel-web 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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/dist/blockymodel-web.js +2717 -0
  3. package/dist/blockymodel-web.js.map +1 -0
  4. package/dist/blockymodel-web.umd.cjs +122 -0
  5. package/dist/blockymodel-web.umd.cjs.map +1 -0
  6. package/dist/editor/Editor.d.ts +94 -0
  7. package/dist/editor/Editor.d.ts.map +1 -0
  8. package/dist/editor/History.d.ts +52 -0
  9. package/dist/editor/History.d.ts.map +1 -0
  10. package/dist/editor/SelectionManager.d.ts +57 -0
  11. package/dist/editor/SelectionManager.d.ts.map +1 -0
  12. package/dist/editor/Serializer.d.ts +44 -0
  13. package/dist/editor/Serializer.d.ts.map +1 -0
  14. package/dist/editor/TransformManager.d.ts +73 -0
  15. package/dist/editor/TransformManager.d.ts.map +1 -0
  16. package/dist/editor/commands/AddNodeCommand.d.ts +24 -0
  17. package/dist/editor/commands/AddNodeCommand.d.ts.map +1 -0
  18. package/dist/editor/commands/Command.d.ts +50 -0
  19. package/dist/editor/commands/Command.d.ts.map +1 -0
  20. package/dist/editor/commands/RemoveNodeCommand.d.ts +28 -0
  21. package/dist/editor/commands/RemoveNodeCommand.d.ts.map +1 -0
  22. package/dist/editor/commands/SetPositionCommand.d.ts +24 -0
  23. package/dist/editor/commands/SetPositionCommand.d.ts.map +1 -0
  24. package/dist/editor/commands/SetPropertyCommand.d.ts +41 -0
  25. package/dist/editor/commands/SetPropertyCommand.d.ts.map +1 -0
  26. package/dist/editor/commands/SetRotationCommand.d.ts +24 -0
  27. package/dist/editor/commands/SetRotationCommand.d.ts.map +1 -0
  28. package/dist/editor/commands/SetScaleCommand.d.ts +24 -0
  29. package/dist/editor/commands/SetScaleCommand.d.ts.map +1 -0
  30. package/dist/editor/index.d.ts +15 -0
  31. package/dist/editor/index.d.ts.map +1 -0
  32. package/dist/index.d.ts +21 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/loaders/BlockyModelLoader.d.ts +48 -0
  35. package/dist/loaders/BlockyModelLoader.d.ts.map +1 -0
  36. package/dist/types/blockymodel.d.ts +72 -0
  37. package/dist/types/blockymodel.d.ts.map +1 -0
  38. package/dist/ui/HierarchyPanel.d.ts +73 -0
  39. package/dist/ui/HierarchyPanel.d.ts.map +1 -0
  40. package/dist/ui/PropertyPanel.d.ts +59 -0
  41. package/dist/ui/PropertyPanel.d.ts.map +1 -0
  42. package/dist/ui/UVEditor.d.ts +56 -0
  43. package/dist/ui/UVEditor.d.ts.map +1 -0
  44. package/dist/ui/index.d.ts +4 -0
  45. package/dist/ui/index.d.ts.map +1 -0
  46. package/dist/viewer/ViewerController.d.ts +71 -0
  47. package/dist/viewer/ViewerController.d.ts.map +1 -0
  48. package/package.json +63 -0
  49. package/src/editor/Editor.ts +196 -0
  50. package/src/editor/History.ts +123 -0
  51. package/src/editor/SelectionManager.ts +183 -0
  52. package/src/editor/Serializer.ts +212 -0
  53. package/src/editor/TransformManager.ts +270 -0
  54. package/src/editor/commands/AddNodeCommand.ts +53 -0
  55. package/src/editor/commands/Command.ts +63 -0
  56. package/src/editor/commands/RemoveNodeCommand.ts +59 -0
  57. package/src/editor/commands/SetPositionCommand.ts +51 -0
  58. package/src/editor/commands/SetPropertyCommand.ts +100 -0
  59. package/src/editor/commands/SetRotationCommand.ts +51 -0
  60. package/src/editor/commands/SetScaleCommand.ts +51 -0
  61. package/src/editor/index.ts +19 -0
  62. package/src/index.ts +49 -0
  63. package/src/loaders/BlockyModelLoader.ts +281 -0
  64. package/src/main.ts +290 -0
  65. package/src/styles.css +597 -0
  66. package/src/types/blockymodel.ts +82 -0
  67. package/src/ui/HierarchyPanel.ts +343 -0
  68. package/src/ui/PropertyPanel.ts +434 -0
  69. package/src/ui/UVEditor.ts +336 -0
  70. package/src/ui/index.ts +4 -0
  71. package/src/viewer/ViewerController.ts +295 -0
package/src/styles.css ADDED
@@ -0,0 +1,597 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :root {
8
+ --bg-primary: #1a1a2e;
9
+ --bg-secondary: #16213e;
10
+ --bg-tertiary: #0f3460;
11
+ --bg-input: #1e2a4a;
12
+ --text-primary: #e8e8e8;
13
+ --text-secondary: #a8a8a8;
14
+ --accent: #e94560;
15
+ --accent-hover: #ff6b6b;
16
+ --accent-active: #c73850;
17
+ --border: #333;
18
+ --border-light: #444;
19
+ --success: #4caf50;
20
+ --warning: #ff9800;
21
+ }
22
+
23
+ body {
24
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
25
+ background-color: var(--bg-primary);
26
+ color: var(--text-primary);
27
+ overflow: hidden;
28
+ height: 100vh;
29
+ }
30
+
31
+ .app {
32
+ display: flex;
33
+ flex-direction: column;
34
+ height: 100vh;
35
+ }
36
+
37
+ /* Header / Toolbar */
38
+ .toolbar {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 12px;
42
+ padding: 10px 16px;
43
+ background-color: var(--bg-secondary);
44
+ border-bottom: 1px solid var(--border);
45
+ flex-wrap: wrap;
46
+ }
47
+
48
+ .toolbar h1 {
49
+ font-size: 16px;
50
+ font-weight: 600;
51
+ color: var(--accent);
52
+ margin-right: 8px;
53
+ }
54
+
55
+ .toolbar-group {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 6px;
59
+ }
60
+
61
+ .toolbar-group label {
62
+ font-size: 12px;
63
+ color: var(--text-secondary);
64
+ }
65
+
66
+ .toolbar-separator {
67
+ width: 1px;
68
+ height: 24px;
69
+ background-color: var(--border);
70
+ margin: 0 4px;
71
+ }
72
+
73
+ /* File inputs */
74
+ .file-btn {
75
+ position: relative;
76
+ display: inline-flex;
77
+ align-items: center;
78
+ gap: 4px;
79
+ padding: 6px 12px;
80
+ background-color: var(--bg-tertiary);
81
+ color: var(--text-primary);
82
+ border: 1px solid var(--border);
83
+ border-radius: 4px;
84
+ font-size: 12px;
85
+ cursor: pointer;
86
+ transition: all 0.2s;
87
+ }
88
+
89
+ .file-btn:hover {
90
+ background-color: var(--accent);
91
+ border-color: var(--accent);
92
+ }
93
+
94
+ .file-btn input[type="file"] {
95
+ position: absolute;
96
+ inset: 0;
97
+ opacity: 0;
98
+ cursor: pointer;
99
+ }
100
+
101
+ /* Buttons */
102
+ button,
103
+ .toolbar-btn {
104
+ padding: 6px 12px;
105
+ background-color: var(--bg-tertiary);
106
+ color: var(--text-primary);
107
+ border: 1px solid var(--border);
108
+ border-radius: 4px;
109
+ font-size: 12px;
110
+ cursor: pointer;
111
+ transition: all 0.2s;
112
+ }
113
+
114
+ button:hover,
115
+ .toolbar-btn:hover {
116
+ background-color: var(--accent);
117
+ border-color: var(--accent);
118
+ }
119
+
120
+ button:disabled,
121
+ .toolbar-btn:disabled {
122
+ opacity: 0.5;
123
+ cursor: not-allowed;
124
+ }
125
+
126
+ button:disabled:hover,
127
+ .toolbar-btn:disabled:hover {
128
+ background-color: var(--bg-tertiary);
129
+ border-color: var(--border);
130
+ }
131
+
132
+ /* Mode buttons */
133
+ .mode-group {
134
+ background-color: var(--bg-primary);
135
+ border-radius: 4px;
136
+ padding: 2px;
137
+ }
138
+
139
+ .mode-btn {
140
+ padding: 5px 10px;
141
+ border: none;
142
+ background-color: transparent;
143
+ }
144
+
145
+ .mode-btn:hover {
146
+ background-color: var(--bg-tertiary);
147
+ border: none;
148
+ }
149
+
150
+ .mode-btn.active {
151
+ background-color: var(--accent);
152
+ color: white;
153
+ }
154
+
155
+ /* Checkbox toggle */
156
+ .toggle-group {
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 4px;
160
+ }
161
+
162
+ .toggle-group input[type="checkbox"] {
163
+ width: 14px;
164
+ height: 14px;
165
+ accent-color: var(--accent);
166
+ }
167
+
168
+ /* Main content area */
169
+ .main-content {
170
+ display: flex;
171
+ flex: 1;
172
+ overflow: hidden;
173
+ }
174
+
175
+ /* 3D Viewport */
176
+ #viewport {
177
+ flex: 1;
178
+ position: relative;
179
+ background-color: var(--bg-primary);
180
+ }
181
+
182
+ #viewport.drag-over {
183
+ outline: 3px dashed var(--accent);
184
+ outline-offset: -3px;
185
+ }
186
+
187
+ #viewport canvas {
188
+ display: block;
189
+ }
190
+
191
+ /* Side panel */
192
+ .side-panel {
193
+ width: 320px;
194
+ background-color: var(--bg-secondary);
195
+ border-left: 1px solid var(--border);
196
+ display: flex;
197
+ flex-direction: column;
198
+ overflow-y: auto;
199
+ }
200
+
201
+ .panel-section {
202
+ border-bottom: 1px solid var(--border);
203
+ }
204
+
205
+ .panel-header {
206
+ padding: 10px 12px;
207
+ font-size: 12px;
208
+ font-weight: 600;
209
+ text-transform: uppercase;
210
+ letter-spacing: 0.5px;
211
+ color: var(--text-secondary);
212
+ background-color: var(--bg-primary);
213
+ display: flex;
214
+ align-items: center;
215
+ gap: 6px;
216
+ }
217
+
218
+ .panel-header.collapsible {
219
+ cursor: pointer;
220
+ user-select: none;
221
+ }
222
+
223
+ .panel-header.collapsible:hover {
224
+ background-color: var(--bg-tertiary);
225
+ }
226
+
227
+ .collapse-icon {
228
+ font-size: 10px;
229
+ transition: transform 0.2s;
230
+ }
231
+
232
+ .panel-header.collapsed .collapse-icon {
233
+ transform: rotate(-90deg);
234
+ }
235
+
236
+ .panel-content {
237
+ padding: 8px;
238
+ max-height: 400px;
239
+ overflow-y: auto;
240
+ }
241
+
242
+ .panel-content.collapsed {
243
+ display: none;
244
+ }
245
+
246
+ /* Hierarchy tree */
247
+ .hierarchy-tree {
248
+ font-size: 12px;
249
+ }
250
+
251
+ .hierarchy-empty {
252
+ color: var(--text-secondary);
253
+ font-style: italic;
254
+ text-align: center;
255
+ padding: 20px;
256
+ }
257
+
258
+ .hierarchy-item {
259
+ display: flex;
260
+ align-items: center;
261
+ padding: 4px 6px;
262
+ border-radius: 3px;
263
+ cursor: pointer;
264
+ gap: 4px;
265
+ }
266
+
267
+ .hierarchy-item:hover {
268
+ background-color: var(--bg-tertiary);
269
+ }
270
+
271
+ .hierarchy-item.selected {
272
+ background-color: var(--accent);
273
+ color: white;
274
+ }
275
+
276
+ .hierarchy-indent {
277
+ flex-shrink: 0;
278
+ }
279
+
280
+ .hierarchy-expander {
281
+ width: 14px;
282
+ font-size: 10px;
283
+ text-align: center;
284
+ flex-shrink: 0;
285
+ color: var(--text-secondary);
286
+ }
287
+
288
+ .hierarchy-icon {
289
+ font-size: 14px;
290
+ flex-shrink: 0;
291
+ }
292
+
293
+ .hierarchy-label {
294
+ flex: 1;
295
+ overflow: hidden;
296
+ text-overflow: ellipsis;
297
+ white-space: nowrap;
298
+ }
299
+
300
+ .hierarchy-children {
301
+ margin-left: 0;
302
+ }
303
+
304
+ .hierarchy-children.collapsed {
305
+ display: none;
306
+ }
307
+
308
+ /* Context menu */
309
+ .context-menu {
310
+ position: fixed;
311
+ background-color: var(--bg-secondary);
312
+ border: 1px solid var(--border);
313
+ border-radius: 4px;
314
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
315
+ min-width: 160px;
316
+ z-index: 1000;
317
+ padding: 4px 0;
318
+ }
319
+
320
+ .context-menu-item {
321
+ padding: 8px 12px;
322
+ font-size: 12px;
323
+ cursor: pointer;
324
+ }
325
+
326
+ .context-menu-item:hover {
327
+ background-color: var(--bg-tertiary);
328
+ }
329
+
330
+ .context-menu-danger {
331
+ color: var(--accent);
332
+ }
333
+
334
+ .context-menu-separator {
335
+ height: 1px;
336
+ background-color: var(--border);
337
+ margin: 4px 0;
338
+ }
339
+
340
+ /* Property panel */
341
+ .property-section {
342
+ margin-bottom: 16px;
343
+ }
344
+
345
+ .property-section .section-header {
346
+ font-size: 11px;
347
+ font-weight: 600;
348
+ text-transform: uppercase;
349
+ color: var(--text-secondary);
350
+ margin-bottom: 8px;
351
+ padding-bottom: 4px;
352
+ border-bottom: 1px solid var(--border);
353
+ }
354
+
355
+ .property-row {
356
+ display: flex;
357
+ align-items: center;
358
+ margin-bottom: 6px;
359
+ gap: 8px;
360
+ }
361
+
362
+ .property-row > label {
363
+ font-size: 11px;
364
+ color: var(--text-secondary);
365
+ width: 70px;
366
+ flex-shrink: 0;
367
+ }
368
+
369
+ .prop-input {
370
+ flex: 1;
371
+ padding: 4px 8px;
372
+ background-color: var(--bg-input);
373
+ border: 1px solid var(--border);
374
+ border-radius: 3px;
375
+ color: var(--text-primary);
376
+ font-size: 11px;
377
+ font-family: "SF Mono", "Monaco", "Consolas", monospace;
378
+ }
379
+
380
+ .prop-input:focus {
381
+ outline: none;
382
+ border-color: var(--accent);
383
+ }
384
+
385
+ .prop-input.prop-text {
386
+ font-family: inherit;
387
+ }
388
+
389
+ .prop-readonly {
390
+ font-size: 11px;
391
+ color: var(--text-secondary);
392
+ font-style: italic;
393
+ }
394
+
395
+ .prop-checkbox {
396
+ width: 14px;
397
+ height: 14px;
398
+ accent-color: var(--accent);
399
+ }
400
+
401
+ .prop-select {
402
+ flex: 1;
403
+ padding: 4px 8px;
404
+ background-color: var(--bg-input);
405
+ border: 1px solid var(--border);
406
+ border-radius: 3px;
407
+ color: var(--text-primary);
408
+ font-size: 11px;
409
+ }
410
+
411
+ .vec3-inputs {
412
+ display: flex;
413
+ gap: 4px;
414
+ flex: 1;
415
+ }
416
+
417
+ .vec3-inputs input {
418
+ width: 60px;
419
+ text-align: center;
420
+ }
421
+
422
+ .property-empty {
423
+ text-align: center;
424
+ padding: 20px;
425
+ color: var(--text-secondary);
426
+ font-size: 12px;
427
+ }
428
+
429
+ /* UV Editor */
430
+ .uv-editor-content {
431
+ display: flex;
432
+ flex-direction: column;
433
+ gap: 12px;
434
+ }
435
+
436
+ .uv-editor-empty {
437
+ text-align: center;
438
+ padding: 20px;
439
+ color: var(--text-secondary);
440
+ font-size: 12px;
441
+ }
442
+
443
+ .uv-face {
444
+ background-color: var(--bg-primary);
445
+ border-radius: 4px;
446
+ padding: 8px;
447
+ }
448
+
449
+ .uv-face-header {
450
+ font-size: 11px;
451
+ font-weight: 600;
452
+ color: var(--text-secondary);
453
+ margin-bottom: 6px;
454
+ }
455
+
456
+ .uv-face-row {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 6px;
460
+ margin-bottom: 4px;
461
+ }
462
+
463
+ .uv-face-row > label:first-child {
464
+ font-size: 10px;
465
+ color: var(--text-secondary);
466
+ width: 50px;
467
+ flex-shrink: 0;
468
+ }
469
+
470
+ .uv-input {
471
+ width: 50px;
472
+ padding: 3px 6px;
473
+ background-color: var(--bg-input);
474
+ border: 1px solid var(--border);
475
+ border-radius: 3px;
476
+ color: var(--text-primary);
477
+ font-size: 10px;
478
+ font-family: "SF Mono", "Monaco", "Consolas", monospace;
479
+ text-align: center;
480
+ }
481
+
482
+ .uv-input:focus {
483
+ outline: none;
484
+ border-color: var(--accent);
485
+ }
486
+
487
+ .uv-checkbox {
488
+ width: 12px;
489
+ height: 12px;
490
+ accent-color: var(--accent);
491
+ }
492
+
493
+ .uv-checkbox-label {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 3px;
497
+ font-size: 10px;
498
+ color: var(--text-secondary);
499
+ }
500
+
501
+ .uv-select {
502
+ padding: 3px 6px;
503
+ background-color: var(--bg-input);
504
+ border: 1px solid var(--border);
505
+ border-radius: 3px;
506
+ color: var(--text-primary);
507
+ font-size: 10px;
508
+ }
509
+
510
+ /* Status bar */
511
+ .status-bar {
512
+ display: flex;
513
+ align-items: center;
514
+ gap: 16px;
515
+ padding: 6px 12px;
516
+ background-color: var(--bg-secondary);
517
+ border-top: 1px solid var(--border);
518
+ font-size: 11px;
519
+ color: var(--text-secondary);
520
+ }
521
+
522
+ .status-bar #status {
523
+ flex: 1;
524
+ }
525
+
526
+ .status-bar #selection-info {
527
+ color: var(--accent);
528
+ }
529
+
530
+ .status-bar #mode-info {
531
+ padding: 2px 8px;
532
+ background-color: var(--bg-tertiary);
533
+ border-radius: 3px;
534
+ }
535
+
536
+ .status-bar .shortcuts {
537
+ display: flex;
538
+ gap: 12px;
539
+ }
540
+
541
+ .status-bar kbd {
542
+ padding: 1px 4px;
543
+ background-color: var(--bg-tertiary);
544
+ border: 1px solid var(--border);
545
+ border-radius: 2px;
546
+ font-family: inherit;
547
+ font-size: 10px;
548
+ }
549
+
550
+ /* Scrollbar styling */
551
+ ::-webkit-scrollbar {
552
+ width: 8px;
553
+ height: 8px;
554
+ }
555
+
556
+ ::-webkit-scrollbar-track {
557
+ background: var(--bg-primary);
558
+ }
559
+
560
+ ::-webkit-scrollbar-thumb {
561
+ background: var(--border);
562
+ border-radius: 4px;
563
+ }
564
+
565
+ ::-webkit-scrollbar-thumb:hover {
566
+ background: var(--text-secondary);
567
+ }
568
+
569
+ /* Responsive */
570
+ @media (max-width: 768px) {
571
+ .side-panel {
572
+ display: none;
573
+ }
574
+
575
+ .toolbar {
576
+ padding: 6px 10px;
577
+ }
578
+
579
+ .toolbar h1 {
580
+ font-size: 14px;
581
+ }
582
+
583
+ .status-bar .shortcuts {
584
+ display: none;
585
+ }
586
+ }
587
+
588
+ /* Input number spinner hide */
589
+ input[type="number"]::-webkit-inner-spin-button,
590
+ input[type="number"]::-webkit-outer-spin-button {
591
+ -webkit-appearance: none;
592
+ margin: 0;
593
+ }
594
+
595
+ input[type="number"] {
596
+ -moz-appearance: textfield;
597
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * BlockyModel format type definitions
3
+ * Based on Hytale's native 3D model format
4
+ */
5
+
6
+ export interface Vec3 {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ }
11
+
12
+ export interface Vec2 {
13
+ x: number;
14
+ y: number;
15
+ }
16
+
17
+ export interface Quaternion {
18
+ w: number;
19
+ x: number;
20
+ y: number;
21
+ z: number;
22
+ }
23
+
24
+ export interface FaceUV {
25
+ offset: Vec2;
26
+ mirror: { x: boolean; y: boolean };
27
+ angle: 0 | 90 | 180 | 270;
28
+ }
29
+
30
+ export interface TextureLayout {
31
+ front?: FaceUV;
32
+ back?: FaceUV;
33
+ left?: FaceUV;
34
+ right?: FaceUV;
35
+ top?: FaceUV;
36
+ bottom?: FaceUV;
37
+ }
38
+
39
+ export type ShapeType = "box" | "quad" | "none";
40
+
41
+ export type ShadingMode = "standard" | "flat" | "fullbright" | "reflective";
42
+
43
+ export type NormalDirection = "+X" | "-X" | "+Y" | "-Y" | "+Z" | "-Z";
44
+
45
+ export interface ShapeSettings {
46
+ size?: Vec3;
47
+ normal?: NormalDirection;
48
+ isPiece?: boolean;
49
+ isStaticBox?: boolean;
50
+ }
51
+
52
+ export interface BlockyShape {
53
+ type: ShapeType;
54
+ offset: Vec3;
55
+ stretch: Vec3;
56
+ settings: ShapeSettings;
57
+ textureLayout: TextureLayout;
58
+ unwrapMode: "custom" | "auto";
59
+ visible: boolean;
60
+ doubleSided: boolean;
61
+ shadingMode: ShadingMode;
62
+ }
63
+
64
+ export interface BlockyNode {
65
+ id: string;
66
+ name: string;
67
+ position?: Vec3;
68
+ orientation?: Quaternion;
69
+ shape: BlockyShape;
70
+ children: BlockyNode[];
71
+ }
72
+
73
+ export interface BlockyModel {
74
+ lod?: "auto" | string;
75
+ format?: "character" | "prop";
76
+ nodes: BlockyNode[];
77
+ }
78
+
79
+ // Default values for optional fields
80
+ export const DEFAULT_POSITION: Vec3 = { x: 0, y: 0, z: 0 };
81
+ export const DEFAULT_ORIENTATION: Quaternion = { w: 1, x: 0, y: 0, z: 0 };
82
+ export const DEFAULT_STRETCH: Vec3 = { x: 1, y: 1, z: 1 };