@thanhthbm/scene-editor 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 (136) hide show
  1. package/README.md +169 -0
  2. package/dist/index.css +921 -0
  3. package/dist/index.js +14076 -0
  4. package/package.json +49 -0
  5. package/src/editor/css/main.css +1030 -0
  6. package/src/editor/js/Animation.js +618 -0
  7. package/src/editor/js/AnimationResizer.js +73 -0
  8. package/src/editor/js/Command.js +40 -0
  9. package/src/editor/js/Config.js +86 -0
  10. package/src/editor/js/Editor.js +1006 -0
  11. package/src/editor/js/EditorControls.js +489 -0
  12. package/src/editor/js/GLTFImportDialog.js +112 -0
  13. package/src/editor/js/History.js +321 -0
  14. package/src/editor/js/Loader.js +1162 -0
  15. package/src/editor/js/LoaderUtils.js +90 -0
  16. package/src/editor/js/Menubar.Add.js +559 -0
  17. package/src/editor/js/Menubar.Edit.js +155 -0
  18. package/src/editor/js/Menubar.File.js +585 -0
  19. package/src/editor/js/Menubar.Help.js +73 -0
  20. package/src/editor/js/Menubar.Render.js +858 -0
  21. package/src/editor/js/Menubar.Status.js +51 -0
  22. package/src/editor/js/Menubar.View.js +193 -0
  23. package/src/editor/js/Menubar.js +29 -0
  24. package/src/editor/js/Player.js +53 -0
  25. package/src/editor/js/Resizer.js +65 -0
  26. package/src/editor/js/Script.js +532 -0
  27. package/src/editor/js/Selector.js +129 -0
  28. package/src/editor/js/Sidebar.Geometry.BoxGeometry.js +121 -0
  29. package/src/editor/js/Sidebar.Geometry.BufferGeometry.js +124 -0
  30. package/src/editor/js/Sidebar.Geometry.CapsuleGeometry.js +109 -0
  31. package/src/editor/js/Sidebar.Geometry.CircleGeometry.js +97 -0
  32. package/src/editor/js/Sidebar.Geometry.CylinderGeometry.js +121 -0
  33. package/src/editor/js/Sidebar.Geometry.DodecahedronGeometry.js +73 -0
  34. package/src/editor/js/Sidebar.Geometry.ExtrudeGeometry.js +196 -0
  35. package/src/editor/js/Sidebar.Geometry.IcosahedronGeometry.js +73 -0
  36. package/src/editor/js/Sidebar.Geometry.LatheGeometry.js +98 -0
  37. package/src/editor/js/Sidebar.Geometry.Modifiers.js +73 -0
  38. package/src/editor/js/Sidebar.Geometry.OctahedronGeometry.js +74 -0
  39. package/src/editor/js/Sidebar.Geometry.PlaneGeometry.js +97 -0
  40. package/src/editor/js/Sidebar.Geometry.RingGeometry.js +121 -0
  41. package/src/editor/js/Sidebar.Geometry.ShapeGeometry.js +76 -0
  42. package/src/editor/js/Sidebar.Geometry.SphereGeometry.js +133 -0
  43. package/src/editor/js/Sidebar.Geometry.TetrahedronGeometry.js +74 -0
  44. package/src/editor/js/Sidebar.Geometry.TextGeometry.js +136 -0
  45. package/src/editor/js/Sidebar.Geometry.TorusGeometry.js +109 -0
  46. package/src/editor/js/Sidebar.Geometry.TorusKnotGeometry.js +121 -0
  47. package/src/editor/js/Sidebar.Geometry.TubeGeometry.js +135 -0
  48. package/src/editor/js/Sidebar.Geometry.js +471 -0
  49. package/src/editor/js/Sidebar.Material.BooleanProperty.js +60 -0
  50. package/src/editor/js/Sidebar.Material.ColorProperty.js +87 -0
  51. package/src/editor/js/Sidebar.Material.ConstantProperty.js +62 -0
  52. package/src/editor/js/Sidebar.Material.MapProperty.js +277 -0
  53. package/src/editor/js/Sidebar.Material.NumberProperty.js +60 -0
  54. package/src/editor/js/Sidebar.Material.Program.js +73 -0
  55. package/src/editor/js/Sidebar.Material.RangeValueProperty.js +63 -0
  56. package/src/editor/js/Sidebar.Material.js +751 -0
  57. package/src/editor/js/Sidebar.Object.js +892 -0
  58. package/src/editor/js/Sidebar.Project.App.js +218 -0
  59. package/src/editor/js/Sidebar.Project.Materials.js +82 -0
  60. package/src/editor/js/Sidebar.Project.Renderer.js +193 -0
  61. package/src/editor/js/Sidebar.Project.Resources.js +242 -0
  62. package/src/editor/js/Sidebar.Project.js +21 -0
  63. package/src/editor/js/Sidebar.Properties.js +73 -0
  64. package/src/editor/js/Sidebar.Scene.js +643 -0
  65. package/src/editor/js/Sidebar.Script.js +129 -0
  66. package/src/editor/js/Sidebar.Settings.History.js +146 -0
  67. package/src/editor/js/Sidebar.Settings.Shortcuts.js +198 -0
  68. package/src/editor/js/Sidebar.Settings.js +58 -0
  69. package/src/editor/js/Sidebar.js +41 -0
  70. package/src/editor/js/Storage.js +98 -0
  71. package/src/editor/js/Strings.js +2718 -0
  72. package/src/editor/js/TextureParametersDialog.js +293 -0
  73. package/src/editor/js/Toolbar.js +75 -0
  74. package/src/editor/js/Viewport.Controls.js +98 -0
  75. package/src/editor/js/Viewport.Info.js +144 -0
  76. package/src/editor/js/Viewport.Pathtracer.js +27 -0
  77. package/src/editor/js/Viewport.ViewHelper.js +41 -0
  78. package/src/editor/js/Viewport.XR.js +7 -0
  79. package/src/editor/js/Viewport.js +982 -0
  80. package/src/editor/js/commands/AddObjectCommand.js +68 -0
  81. package/src/editor/js/commands/AddScriptCommand.js +75 -0
  82. package/src/editor/js/commands/Commands.js +24 -0
  83. package/src/editor/js/commands/MoveObjectCommand.js +117 -0
  84. package/src/editor/js/commands/MultiCmdsCommand.js +85 -0
  85. package/src/editor/js/commands/RemoveObjectCommand.js +88 -0
  86. package/src/editor/js/commands/RemoveScriptCommand.js +81 -0
  87. package/src/editor/js/commands/SetColorCommand.js +73 -0
  88. package/src/editor/js/commands/SetGeometryCommand.js +86 -0
  89. package/src/editor/js/commands/SetGeometryValueCommand.js +70 -0
  90. package/src/editor/js/commands/SetMaterialColorCommand.js +87 -0
  91. package/src/editor/js/commands/SetMaterialCommand.js +80 -0
  92. package/src/editor/js/commands/SetMaterialMapCommand.js +144 -0
  93. package/src/editor/js/commands/SetMaterialRangeCommand.js +92 -0
  94. package/src/editor/js/commands/SetMaterialValueCommand.js +91 -0
  95. package/src/editor/js/commands/SetMaterialVectorCommand.js +88 -0
  96. package/src/editor/js/commands/SetPositionCommand.js +84 -0
  97. package/src/editor/js/commands/SetRotationCommand.js +84 -0
  98. package/src/editor/js/commands/SetScaleCommand.js +84 -0
  99. package/src/editor/js/commands/SetSceneCommand.js +104 -0
  100. package/src/editor/js/commands/SetScriptValueCommand.js +80 -0
  101. package/src/editor/js/commands/SetShadowValueCommand.js +73 -0
  102. package/src/editor/js/commands/SetTextureParametersCommand.js +143 -0
  103. package/src/editor/js/commands/SetUuidCommand.js +70 -0
  104. package/src/editor/js/commands/SetValueCommand.js +75 -0
  105. package/src/editor/js/libs/acorn/acorn.js +3236 -0
  106. package/src/editor/js/libs/acorn/acorn_loose.js +1299 -0
  107. package/src/editor/js/libs/acorn/walk.js +344 -0
  108. package/src/editor/js/libs/app/index.html +51 -0
  109. package/src/editor/js/libs/app.js +14 -0
  110. package/src/editor/js/libs/codemirror/addon/dialog.css +32 -0
  111. package/src/editor/js/libs/codemirror/addon/dialog.js +163 -0
  112. package/src/editor/js/libs/codemirror/addon/show-hint.css +36 -0
  113. package/src/editor/js/libs/codemirror/addon/show-hint.js +529 -0
  114. package/src/editor/js/libs/codemirror/addon/tern.css +87 -0
  115. package/src/editor/js/libs/codemirror/addon/tern.js +750 -0
  116. package/src/editor/js/libs/codemirror/codemirror.css +344 -0
  117. package/src/editor/js/libs/codemirror/codemirror.js +9849 -0
  118. package/src/editor/js/libs/codemirror/mode/glsl.js +233 -0
  119. package/src/editor/js/libs/codemirror/mode/javascript.js +959 -0
  120. package/src/editor/js/libs/codemirror/theme/monokai.css +41 -0
  121. package/src/editor/js/libs/esprima.js +6401 -0
  122. package/src/editor/js/libs/jsonlint.js +453 -0
  123. package/src/editor/js/libs/signals.min.js +14 -0
  124. package/src/editor/js/libs/tern-threejs/build-defs.js +233 -0
  125. package/src/editor/js/libs/ternjs/comment.js +87 -0
  126. package/src/editor/js/libs/ternjs/def.js +588 -0
  127. package/src/editor/js/libs/ternjs/doc_comment.js +401 -0
  128. package/src/editor/js/libs/ternjs/infer.js +1635 -0
  129. package/src/editor/js/libs/ternjs/polyfill.js +80 -0
  130. package/src/editor/js/libs/ternjs/signal.js +26 -0
  131. package/src/editor/js/libs/ternjs/tern.js +993 -0
  132. package/src/editor/js/libs/ui.js +1305 -0
  133. package/src/editor/js/libs/ui.three.js +946 -0
  134. package/src/events.js +273 -0
  135. package/src/index.js +284 -0
  136. package/src/presets/machines.js +239 -0
@@ -0,0 +1,618 @@
1
+ import { UIPanel, UIText, UIButton, UINumber } from './libs/ui.js';
2
+
3
+ import { AnimationPathHelper } from 'three/addons/helpers/AnimationPathHelper.js';
4
+
5
+ function Animation( editor ) {
6
+
7
+ const signals = editor.signals;
8
+ const strings = editor.strings;
9
+ const mixer = editor.mixer;
10
+
11
+ const container = new UIPanel();
12
+ container.setId( 'animation' );
13
+ container.dom.style.flexDirection = 'column';
14
+
15
+ let panelHeight = 36;
16
+
17
+ // Listen for resizer changes
18
+ signals.animationPanelResized.add( function ( height ) {
19
+
20
+ panelHeight = height;
21
+ container.dom.style.height = height + 'px';
22
+ signals.animationPanelChanged.dispatch( height );
23
+
24
+ } );
25
+
26
+ // Top bar - playback controls
27
+ const controlsPanel = new UIPanel();
28
+ controlsPanel.dom.style.padding = '6px 10px';
29
+ controlsPanel.dom.style.borderBottom = '1px solid #ccc';
30
+ controlsPanel.dom.style.display = 'flex';
31
+ controlsPanel.dom.style.alignItems = 'center';
32
+ controlsPanel.dom.style.justifyContent = 'center';
33
+ controlsPanel.dom.style.gap = '6px';
34
+ controlsPanel.dom.style.flexShrink = '0';
35
+ container.add( controlsPanel );
36
+
37
+ // SVG icons
38
+ const playIcon = '<svg width="12" height="12" viewBox="0 0 12 12"><path d="M3 1.5v9l7-4.5z" fill="currentColor"/></svg>';
39
+ const pauseIcon = '<svg width="12" height="12" viewBox="0 0 12 12"><path d="M2 1h3v10H2zM7 1h3v10H7z" fill="currentColor"/></svg>';
40
+ const stopIcon = '<svg width="12" height="12" viewBox="0 0 12 12"><rect x="2" y="2" width="8" height="8" fill="currentColor"/></svg>';
41
+
42
+ const playButton = new UIButton();
43
+ playButton.dom.innerHTML = playIcon;
44
+ playButton.dom.style.width = '24px';
45
+ playButton.dom.style.height = '24px';
46
+ playButton.dom.style.padding = '0';
47
+ playButton.dom.style.borderRadius = '4px';
48
+ playButton.dom.style.display = 'flex';
49
+ playButton.dom.style.alignItems = 'center';
50
+ playButton.dom.style.justifyContent = 'center';
51
+ playButton.onClick( function () {
52
+
53
+ if ( currentAction ) {
54
+
55
+ if ( currentAction.paused ) {
56
+
57
+ currentAction.paused = false;
58
+
59
+ } else if ( ! currentAction.isRunning() ) {
60
+
61
+ currentAction.reset();
62
+ currentAction.play();
63
+
64
+ }
65
+
66
+ }
67
+
68
+ } );
69
+ controlsPanel.add( playButton );
70
+
71
+ const pauseButton = new UIButton();
72
+ pauseButton.dom.innerHTML = pauseIcon;
73
+ pauseButton.dom.style.width = '24px';
74
+ pauseButton.dom.style.height = '24px';
75
+ pauseButton.dom.style.padding = '0';
76
+ pauseButton.dom.style.borderRadius = '4px';
77
+ pauseButton.dom.style.display = 'flex';
78
+ pauseButton.dom.style.alignItems = 'center';
79
+ pauseButton.dom.style.justifyContent = 'center';
80
+ pauseButton.onClick( function () {
81
+
82
+ if ( currentAction ) {
83
+
84
+ currentAction.paused = true;
85
+
86
+ }
87
+
88
+ } );
89
+ controlsPanel.add( pauseButton );
90
+
91
+ const stopButton = new UIButton();
92
+ stopButton.dom.innerHTML = stopIcon;
93
+ stopButton.dom.style.width = '24px';
94
+ stopButton.dom.style.height = '24px';
95
+ stopButton.dom.style.padding = '0';
96
+ stopButton.dom.style.borderRadius = '4px';
97
+ stopButton.dom.style.display = 'flex';
98
+ stopButton.dom.style.alignItems = 'center';
99
+ stopButton.dom.style.justifyContent = 'center';
100
+ stopButton.onClick( function () {
101
+
102
+ if ( currentAction ) {
103
+
104
+ currentAction.stop();
105
+
106
+ }
107
+
108
+ } );
109
+ controlsPanel.add( stopButton );
110
+
111
+ // Time display
112
+ const timeDisplay = document.createElement( 'div' );
113
+ timeDisplay.style.display = 'flex';
114
+ timeDisplay.style.alignItems = 'center';
115
+ timeDisplay.style.justifyContent = 'center';
116
+ timeDisplay.style.gap = '4px';
117
+ timeDisplay.style.height = '24px';
118
+ timeDisplay.style.padding = '0 8px';
119
+ timeDisplay.style.background = 'rgba(0,0,0,0.05)';
120
+ timeDisplay.style.borderRadius = '4px';
121
+ timeDisplay.style.fontFamily = 'monospace';
122
+ timeDisplay.style.fontSize = '11px';
123
+ controlsPanel.dom.appendChild( timeDisplay );
124
+
125
+ const timeText = new UIText( '0.00' ).setWidth( '36px' );
126
+ timeText.dom.style.textAlign = 'right';
127
+ timeDisplay.appendChild( timeText.dom );
128
+
129
+ const separator = new UIText( '/' );
130
+ timeDisplay.appendChild( separator.dom );
131
+
132
+ const durationText = new UIText( '0.00' ).setWidth( '36px' );
133
+ timeDisplay.appendChild( durationText.dom );
134
+
135
+ // Time Scale
136
+ const mixerTimeScaleNumber = new UINumber( 1 ).setWidth( '60px' ).setRange( - 10, 10 );
137
+ mixerTimeScaleNumber.onChange( function () {
138
+
139
+ mixer.timeScale = mixerTimeScaleNumber.getValue();
140
+
141
+ } );
142
+
143
+ controlsPanel.add( new UIText( strings.getKey( 'sidebar/animations/timescale' ) ).setClass( 'Label' ) );
144
+ controlsPanel.add( mixerTimeScaleNumber );
145
+
146
+ // Timeline area with track rows
147
+ const timelineArea = document.createElement( 'div' );
148
+ timelineArea.style.flex = '1';
149
+ timelineArea.style.display = 'flex';
150
+ timelineArea.style.flexDirection = 'column';
151
+ timelineArea.style.overflow = 'hidden';
152
+ timelineArea.style.position = 'relative';
153
+ container.dom.appendChild( timelineArea );
154
+
155
+ // Scrollable track list
156
+ const trackListContainer = document.createElement( 'div' );
157
+ trackListContainer.style.flex = '1';
158
+ trackListContainer.style.overflowY = 'auto';
159
+ trackListContainer.style.overflowX = 'hidden';
160
+ timelineArea.appendChild( trackListContainer );
161
+
162
+ // Playhead (spans entire timeline area)
163
+ const playhead = document.createElement( 'div' );
164
+ playhead.style.position = 'absolute';
165
+ playhead.style.top = '0';
166
+ playhead.style.bottom = '0';
167
+ playhead.style.width = '2px';
168
+ playhead.style.background = '#f00';
169
+ playhead.style.left = '150px'; // Start at timeline start (after labels)
170
+ playhead.style.pointerEvents = 'none';
171
+ playhead.style.zIndex = '10';
172
+ timelineArea.appendChild( playhead );
173
+
174
+ // Timeline scrubbing
175
+ let isDragging = false;
176
+ const labelWidth = 150;
177
+
178
+ function updateTimeFromPosition( clientX ) {
179
+
180
+ const rect = timelineArea.getBoundingClientRect();
181
+ const timelineStart = labelWidth;
182
+ const timelineWidth = rect.width - labelWidth;
183
+ const x = Math.max( 0, Math.min( clientX - rect.left - timelineStart, timelineWidth ) );
184
+ const percent = x / timelineWidth;
185
+
186
+ if ( currentAction && currentClip ) {
187
+
188
+ const time = percent * currentClip.duration;
189
+ currentAction.play();
190
+ currentAction.time = time;
191
+ currentAction.paused = true;
192
+ editor.mixer.update( 0 );
193
+
194
+ }
195
+
196
+ }
197
+
198
+ timelineArea.addEventListener( 'mousedown', function ( event ) {
199
+
200
+ const rect = timelineArea.getBoundingClientRect();
201
+ if ( event.clientX - rect.left > labelWidth ) {
202
+
203
+ event.preventDefault();
204
+
205
+ isDragging = true;
206
+ updateTimeFromPosition( event.clientX );
207
+
208
+ }
209
+
210
+ } );
211
+
212
+ document.addEventListener( 'mousemove', function ( event ) {
213
+
214
+ if ( isDragging ) {
215
+
216
+ updateTimeFromPosition( event.clientX );
217
+
218
+ }
219
+
220
+ } );
221
+
222
+ document.addEventListener( 'mouseup', function () {
223
+
224
+ isDragging = false;
225
+
226
+ } );
227
+
228
+ // Track colors by type
229
+ const trackColors = {
230
+ position: '#4CAF50',
231
+ quaternion: '#2196F3',
232
+ rotation: '#2196F3',
233
+ scale: '#FF9800',
234
+ morphTargetInfluences: '#9C27B0',
235
+ default: '#607D8B'
236
+ };
237
+
238
+ function getTrackColor( trackName ) {
239
+
240
+ for ( const type in trackColors ) {
241
+
242
+ if ( trackName.endsWith( '.' + type ) ) {
243
+
244
+ return trackColors[ type ];
245
+
246
+ }
247
+
248
+ }
249
+
250
+ return trackColors.default;
251
+
252
+ }
253
+
254
+ function getTrackType( trackName ) {
255
+
256
+ const parts = trackName.split( '.' );
257
+ return parts[ parts.length - 1 ];
258
+
259
+ }
260
+
261
+ // Hover path helper
262
+ let hoverHelper = null;
263
+ let currentAction = null;
264
+ let currentClip = null;
265
+ let currentRoot = null;
266
+
267
+ // Get all clips from scene animations
268
+ function getAnimationClips() {
269
+
270
+ const scene = editor.scene;
271
+ const clips = [];
272
+ const seen = new Set();
273
+
274
+ scene.traverse( function ( object ) {
275
+
276
+ if ( object.animations && object.animations.length > 0 ) {
277
+
278
+ for ( const clip of object.animations ) {
279
+
280
+ if ( ! seen.has( clip.uuid ) ) {
281
+
282
+ seen.add( clip.uuid );
283
+ clips.push( { clip: clip, root: object } );
284
+
285
+ }
286
+
287
+ }
288
+
289
+ }
290
+
291
+ } );
292
+
293
+ // Also check scene.animations directly
294
+ for ( const clip of scene.animations ) {
295
+
296
+ if ( ! seen.has( clip.uuid ) ) {
297
+
298
+ seen.add( clip.uuid );
299
+ clips.push( { clip: clip, root: scene } );
300
+
301
+ }
302
+
303
+ }
304
+
305
+ return clips;
306
+
307
+ }
308
+
309
+ function getObjectName( trackName, root ) {
310
+
311
+ // Extract UUID from track name (format: uuid.property)
312
+ const dotIndex = trackName.lastIndexOf( '.' );
313
+ if ( dotIndex === - 1 ) return trackName;
314
+
315
+ const uuid = trackName.substring( 0, dotIndex );
316
+ const object = root.getObjectByProperty( 'uuid', uuid );
317
+
318
+ return object ? ( object.name || 'Object' ) : uuid.substring( 0, 8 );
319
+
320
+ }
321
+
322
+ function update() {
323
+
324
+ trackListContainer.innerHTML = '';
325
+
326
+ container.setDisplay( 'flex' );
327
+ container.dom.style.height = panelHeight + 'px';
328
+ signals.animationPanelChanged.dispatch( panelHeight );
329
+
330
+ const clips = getAnimationClips();
331
+
332
+ if ( clips.length === 0 ) {
333
+
334
+ return;
335
+
336
+ }
337
+
338
+ for ( const { clip, root } of clips ) {
339
+
340
+ // Clip header row
341
+ const clipRow = document.createElement( 'div' );
342
+ clipRow.style.display = 'flex';
343
+ clipRow.style.alignItems = 'center';
344
+ clipRow.style.height = '24px';
345
+ clipRow.style.borderBottom = '1px solid #ccc';
346
+ clipRow.style.cursor = 'pointer';
347
+ clipRow.style.background = currentClip === clip ? 'rgba(0, 136, 255, 0.1)' : '';
348
+
349
+ const clipLabel = document.createElement( 'div' );
350
+ clipLabel.style.width = labelWidth + 'px';
351
+ clipLabel.style.padding = '0 10px';
352
+ clipLabel.style.fontSize = '11px';
353
+ clipLabel.style.fontWeight = 'bold';
354
+ clipLabel.style.overflow = 'hidden';
355
+ clipLabel.style.textOverflow = 'ellipsis';
356
+ clipLabel.style.whiteSpace = 'nowrap';
357
+ clipLabel.style.flexShrink = '0';
358
+ clipLabel.style.boxSizing = 'border-box';
359
+ clipLabel.textContent = clip.name || 'Animation';
360
+ clipRow.appendChild( clipLabel );
361
+
362
+ const clipTimeline = document.createElement( 'div' );
363
+ clipTimeline.style.flex = '1';
364
+ clipTimeline.style.height = '100%';
365
+ clipTimeline.style.background = 'rgba(0,0,0,0.03)';
366
+ clipRow.appendChild( clipTimeline );
367
+
368
+ clipRow.addEventListener( 'click', function () {
369
+
370
+ if ( editor.selected !== root ) {
371
+
372
+ signals.objectSelected.remove( selectDefaultClip );
373
+ editor.select( root );
374
+ signals.objectSelected.add( selectDefaultClip );
375
+
376
+ }
377
+
378
+ selectClip( clip, root );
379
+ update(); // Refresh to update highlighting
380
+
381
+ } );
382
+
383
+ trackListContainer.appendChild( clipRow );
384
+
385
+ // Only show tracks for selected clip
386
+ if ( currentClip === clip ) {
387
+
388
+ const duration = clip.duration;
389
+
390
+ for ( const track of clip.tracks ) {
391
+
392
+ const times = track.times;
393
+ if ( times.length === 0 ) continue;
394
+
395
+ const startTime = times[ 0 ];
396
+ const endTime = times[ times.length - 1 ];
397
+ const startPercent = ( startTime / duration ) * 100;
398
+ const widthPercent = ( ( endTime - startTime ) / duration ) * 100;
399
+
400
+ const trackRow = document.createElement( 'div' );
401
+ trackRow.style.display = 'flex';
402
+ trackRow.style.alignItems = 'center';
403
+ trackRow.style.height = '20px';
404
+ trackRow.style.borderBottom = '1px solid #eee';
405
+
406
+ // Track label
407
+ const trackLabel = document.createElement( 'div' );
408
+ trackLabel.style.width = labelWidth + 'px';
409
+ trackLabel.style.padding = '0 10px 0 20px';
410
+ trackLabel.style.fontSize = '10px';
411
+ trackLabel.style.overflow = 'hidden';
412
+ trackLabel.style.textOverflow = 'ellipsis';
413
+ trackLabel.style.whiteSpace = 'nowrap';
414
+ trackLabel.style.flexShrink = '0';
415
+ trackLabel.style.boxSizing = 'border-box';
416
+ trackLabel.style.color = '#666';
417
+
418
+ const objectName = getObjectName( track.name, root );
419
+ const trackType = getTrackType( track.name );
420
+ trackLabel.textContent = objectName + '.' + trackType;
421
+ trackLabel.title = track.name;
422
+ trackRow.appendChild( trackLabel );
423
+
424
+ // Track timeline with block
425
+ const trackTimeline = document.createElement( 'div' );
426
+ trackTimeline.style.flex = '1';
427
+ trackTimeline.style.height = '100%';
428
+ trackTimeline.style.position = 'relative';
429
+ trackTimeline.style.background = 'rgba(0,0,0,0.02)';
430
+
431
+ const block = document.createElement( 'div' );
432
+ block.style.position = 'absolute';
433
+ block.style.left = startPercent + '%';
434
+ block.style.width = Math.max( 0.5, widthPercent ) + '%';
435
+ block.style.top = '3px';
436
+ block.style.bottom = '3px';
437
+ block.style.background = getTrackColor( track.name );
438
+ block.style.borderRadius = '2px';
439
+ block.style.opacity = '0.6';
440
+ block.title = trackType + ': ' + startTime.toFixed( 2 ) + 's - ' + endTime.toFixed( 2 ) + 's';
441
+
442
+ trackTimeline.appendChild( block );
443
+
444
+ // Add keyframe markers
445
+ for ( let i = 0; i < times.length; i ++ ) {
446
+
447
+ const keyframePercent = ( times[ i ] / duration ) * 100;
448
+ const keyframe = document.createElement( 'div' );
449
+ keyframe.style.position = 'absolute';
450
+ keyframe.style.left = keyframePercent + '%';
451
+ keyframe.style.top = '50%';
452
+ keyframe.style.width = '6px';
453
+ keyframe.style.height = '6px';
454
+ keyframe.style.marginLeft = '-3px';
455
+ keyframe.style.marginTop = '-3px';
456
+ keyframe.style.background = getTrackColor( track.name );
457
+ keyframe.style.borderRadius = '1px';
458
+ keyframe.style.transform = 'rotate(45deg)';
459
+ keyframe.title = times[ i ].toFixed( 3 ) + 's';
460
+ trackTimeline.appendChild( keyframe );
461
+
462
+ }
463
+
464
+ trackRow.appendChild( trackTimeline );
465
+
466
+ // Hover on position tracks to show path helper
467
+ if ( track.name.endsWith( '.position' ) && track.getValueSize() === 3 ) {
468
+
469
+ const uuid = track.name.replace( '.position', '' );
470
+ const object = root.getObjectByProperty( 'uuid', uuid );
471
+
472
+ if ( object ) {
473
+
474
+ trackRow.addEventListener( 'mouseenter', function () {
475
+
476
+ showPath( clip, object );
477
+
478
+ } );
479
+
480
+ trackRow.addEventListener( 'mouseleave', function () {
481
+
482
+ hidePath();
483
+
484
+ } );
485
+
486
+ }
487
+
488
+ }
489
+
490
+ trackListContainer.appendChild( trackRow );
491
+
492
+ }
493
+
494
+ }
495
+
496
+ }
497
+
498
+ }
499
+
500
+ function selectClip( clip, root ) {
501
+
502
+ // Stop current action
503
+ if ( currentAction ) {
504
+
505
+ currentAction.stop();
506
+
507
+ }
508
+
509
+ if ( currentClip === clip ) {
510
+
511
+ // Unselect clip
512
+ currentAction = null;
513
+ currentClip = null;
514
+ currentRoot = null;
515
+
516
+ timeText.setValue( '0.00' );
517
+ durationText.setValue( '0.00' );
518
+
519
+ } else {
520
+
521
+ // Select clip without playing
522
+ currentClip = clip;
523
+ currentRoot = root;
524
+ currentAction = editor.mixer.clipAction( clip, root );
525
+
526
+ // Update duration display
527
+ durationText.setValue( clip.duration.toFixed( 2 ) );
528
+
529
+ }
530
+
531
+ }
532
+
533
+ function showPath( clip, object ) {
534
+
535
+ hidePath();
536
+
537
+ hoverHelper = new AnimationPathHelper( currentRoot, clip, object );
538
+ editor.sceneHelpers.add( hoverHelper );
539
+ signals.sceneGraphChanged.dispatch();
540
+
541
+ }
542
+
543
+ function hidePath() {
544
+
545
+ if ( hoverHelper ) {
546
+
547
+ editor.sceneHelpers.remove( hoverHelper );
548
+ hoverHelper.dispose();
549
+ hoverHelper = null;
550
+ signals.sceneGraphChanged.dispatch();
551
+
552
+ }
553
+
554
+ }
555
+
556
+ function clear() {
557
+
558
+ hidePath();
559
+ trackListContainer.innerHTML = '';
560
+ currentAction = null;
561
+ currentClip = null;
562
+ currentRoot = null;
563
+ timeText.setValue( '0.00' );
564
+ durationText.setValue( '0.00' );
565
+
566
+ }
567
+
568
+ // Update time display and playhead during playback
569
+ function updateTime() {
570
+
571
+ if ( currentAction && currentClip ) {
572
+
573
+ const time = currentAction.time % currentClip.duration;
574
+ timeText.setValue( time.toFixed( 2 ) );
575
+
576
+ // Update playhead position
577
+ const rect = timelineArea.getBoundingClientRect();
578
+ const timelineWidth = rect.width - labelWidth;
579
+ const playheadX = labelWidth + ( time / currentClip.duration ) * timelineWidth;
580
+ playhead.style.left = playheadX + 'px';
581
+
582
+ }
583
+
584
+ requestAnimationFrame( updateTime );
585
+
586
+ }
587
+
588
+ function selectDefaultClip( object ) {
589
+
590
+ if ( object !== null && object.animations && object.animations.length > 0 ) {
591
+
592
+ selectClip( object.animations[ 0 ], object );
593
+ update();
594
+
595
+ }
596
+
597
+ }
598
+
599
+ updateTime();
600
+
601
+ // Auto-select clip when an object with animations is selected
602
+ signals.objectSelected.add( selectDefaultClip );
603
+
604
+ // Update when scene changes
605
+ signals.editorCleared.add( clear );
606
+ signals.objectAdded.add( update );
607
+ signals.objectRemoved.add( update );
608
+
609
+ // Show panel on initial load
610
+ container.setDisplay( 'flex' );
611
+ container.dom.style.height = panelHeight + 'px';
612
+ signals.animationPanelChanged.dispatch( panelHeight );
613
+
614
+ return container;
615
+
616
+ }
617
+
618
+ export { Animation };
@@ -0,0 +1,73 @@
1
+ import { UIElement } from './libs/ui.js';
2
+
3
+ function AnimationResizer( editor ) {
4
+
5
+ const signals = editor.signals;
6
+
7
+ const dom = document.createElement( 'div' );
8
+ dom.id = 'animation-resizer';
9
+
10
+ let panelHeight = 36;
11
+ let startY = 0;
12
+ let startHeight = 0;
13
+
14
+ function onPointerDown( event ) {
15
+
16
+ if ( event.isPrimary === false ) return;
17
+
18
+ startY = event.clientY;
19
+ startHeight = panelHeight;
20
+
21
+ dom.ownerDocument.addEventListener( 'pointermove', onPointerMove );
22
+ dom.ownerDocument.addEventListener( 'pointerup', onPointerUp );
23
+
24
+ }
25
+
26
+ function onPointerUp( event ) {
27
+
28
+ if ( event.isPrimary === false ) return;
29
+
30
+ dom.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
31
+ dom.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
32
+
33
+ }
34
+
35
+ function onPointerMove( event ) {
36
+
37
+ if ( event.isPrimary === false ) return;
38
+
39
+ const deltaY = startY - event.clientY;
40
+ const newHeight = startHeight + deltaY;
41
+ const maxHeight = window.innerHeight / 2;
42
+
43
+ // Clamp between 36px (top bar only) and half the window height
44
+ panelHeight = Math.max( 36, Math.min( maxHeight, newHeight ) );
45
+
46
+ signals.animationPanelResized.dispatch( panelHeight );
47
+
48
+ }
49
+
50
+ dom.addEventListener( 'pointerdown', onPointerDown );
51
+
52
+ // Show/hide based on animation panel visibility
53
+ signals.animationPanelChanged.add( function ( height ) {
54
+
55
+ if ( height === false ) {
56
+
57
+ dom.style.display = 'none';
58
+
59
+ } else {
60
+
61
+ dom.style.display = 'block';
62
+ dom.style.bottom = height + 'px';
63
+ panelHeight = height;
64
+
65
+ }
66
+
67
+ } );
68
+
69
+ return new UIElement( dom );
70
+
71
+ }
72
+
73
+ export { AnimationResizer };