entangled-worlds-orbiters-shared 1.0.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 +75 -0
- package/package.json +49 -0
- package/public/config/textures.json +332 -0
- package/src/arrow-ui/BooleanController.js +31 -0
- package/src/arrow-ui/ColorController.js +108 -0
- package/src/arrow-ui/Controller.js +447 -0
- package/src/arrow-ui/FunctionController.js +27 -0
- package/src/arrow-ui/GUI.js +595 -0
- package/src/arrow-ui/NumberController.js +567 -0
- package/src/arrow-ui/OptionController.js +64 -0
- package/src/arrow-ui/StringController.js +41 -0
- package/src/arrow-ui/index.js +13 -0
- package/src/arrow-ui/lilGuiLoader.js +10 -0
- package/src/arrow-ui/utils/getColorFormat.js +75 -0
- package/src/arrow-ui/utils/injectStyles.js +10 -0
- package/src/arrow-ui/utils/normalizeColorString.js +29 -0
- package/src/config/index.js +8 -0
- package/src/config/textureConfig.js +299 -0
- package/src/core/index.js +3 -0
- package/src/core/lightFresnel.js +64 -0
- package/src/core/lights.js +43 -0
- package/src/index.js +8 -0
- package/src/loaders/bodyTextureLoader.js +39 -0
- package/src/loaders/cubeMapLoader.js +264 -0
- package/src/loaders/glbLoader.js +71 -0
- package/src/loaders/index.js +4 -0
- package/src/moons/index.js +3 -0
- package/src/moons/moonsGeneration.js +228 -0
- package/src/moons/moonsManager.js +64 -0
- package/src/utils/dispose.js +20 -0
- package/src/utils/index.js +3 -0
- package/src/utils/materials.js +31 -0
- package/src/world/cloudShell.js +89 -0
- package/src/world/index.js +6 -0
- package/src/world/worldGLB.js +7 -0
- package/src/world/worldManager.js +93 -0
- package/src/world/worldNormal.js +25 -0
- package/src/world/worldTexture.js +239 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import Controller from './Controller';
|
|
2
|
+
|
|
3
|
+
export default class NumberController extends Controller {
|
|
4
|
+
|
|
5
|
+
constructor( parent, object, property, min, max, step ) {
|
|
6
|
+
|
|
7
|
+
super( parent, object, property, 'number' );
|
|
8
|
+
|
|
9
|
+
this._initInput();
|
|
10
|
+
|
|
11
|
+
this.min( min );
|
|
12
|
+
this.max( max );
|
|
13
|
+
|
|
14
|
+
const stepExplicit = step !== undefined;
|
|
15
|
+
this.step( stepExplicit ? step : this._getImplicitStep(), stepExplicit );
|
|
16
|
+
|
|
17
|
+
this.updateDisplay();
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
decimals( decimals ) {
|
|
22
|
+
this._decimals = decimals;
|
|
23
|
+
this.updateDisplay();
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
min( min ) {
|
|
28
|
+
this._min = min;
|
|
29
|
+
this._onUpdateMinMax();
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
max( max ) {
|
|
34
|
+
this._max = max;
|
|
35
|
+
this._onUpdateMinMax();
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
step( step, explicit = true ) {
|
|
40
|
+
this._step = step;
|
|
41
|
+
this._stepExplicit = explicit;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
updateDisplay() {
|
|
46
|
+
|
|
47
|
+
const value = this.getValue();
|
|
48
|
+
|
|
49
|
+
if ( this._hasSlider ) {
|
|
50
|
+
|
|
51
|
+
let percent = ( value - this._min ) / ( this._max - this._min );
|
|
52
|
+
percent = Math.max( 0, Math.min( percent, 1 ) );
|
|
53
|
+
|
|
54
|
+
if ( this.$slider ) {
|
|
55
|
+
const rect = this.$slider.getBoundingClientRect();
|
|
56
|
+
const trackWidth = Math.max( 0, rect.width );
|
|
57
|
+
|
|
58
|
+
// Set fill width
|
|
59
|
+
this.$fill.style.width = ( percent * trackWidth ) + 'px';
|
|
60
|
+
|
|
61
|
+
// Position arrow
|
|
62
|
+
if ( this.$pointer ) {
|
|
63
|
+
const arrowX = percent * trackWidth;
|
|
64
|
+
this.$pointer.style.left = arrowX + 'px';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if ( !this._inputFocused ) {
|
|
70
|
+
this.$input.value = this._decimals === undefined ? value : value.toFixed( this._decimals );
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_initInput() {
|
|
77
|
+
|
|
78
|
+
this.$input = document.createElement( 'input' );
|
|
79
|
+
this.$input.setAttribute( 'type', 'text' );
|
|
80
|
+
this.$input.setAttribute( 'aria-labelledby', this.$name.id );
|
|
81
|
+
|
|
82
|
+
// On touch devices only, use input[type=number] to force a numeric keyboard.
|
|
83
|
+
// Ideally we could use one input type everywhere, but [type=number] has quirks
|
|
84
|
+
// on desktop, and [inputmode=decimal] has quirks on iOS.
|
|
85
|
+
// See https://github.com/georgealways/lil-gui/pull/16
|
|
86
|
+
|
|
87
|
+
const isTouch = window.matchMedia( '(pointer: coarse)' ).matches;
|
|
88
|
+
|
|
89
|
+
if ( isTouch ) {
|
|
90
|
+
this.$input.setAttribute( 'type', 'number' );
|
|
91
|
+
this.$input.setAttribute( 'step', 'any' );
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.$widget.appendChild( this.$input );
|
|
95
|
+
|
|
96
|
+
this.$disable = this.$input;
|
|
97
|
+
|
|
98
|
+
const onInput = () => {
|
|
99
|
+
|
|
100
|
+
let value = parseFloat( this.$input.value );
|
|
101
|
+
|
|
102
|
+
if ( isNaN( value ) ) return;
|
|
103
|
+
|
|
104
|
+
if ( this._stepExplicit ) {
|
|
105
|
+
value = this._snap( value );
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.setValue( this._clamp( value ) );
|
|
109
|
+
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Keys & mouse wheel
|
|
113
|
+
// ---------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
const increment = delta => {
|
|
116
|
+
|
|
117
|
+
const value = parseFloat( this.$input.value );
|
|
118
|
+
|
|
119
|
+
if ( isNaN( value ) ) return;
|
|
120
|
+
|
|
121
|
+
this._snapClampSetValue( value + delta );
|
|
122
|
+
|
|
123
|
+
// Force the input to updateDisplay when it's focused
|
|
124
|
+
this.$input.value = this.getValue();
|
|
125
|
+
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const onKeyDown = e => {
|
|
129
|
+
// Using `e.key` instead of `e.code` also catches NumpadEnter
|
|
130
|
+
if ( e.key === 'Enter' ) {
|
|
131
|
+
this.$input.blur();
|
|
132
|
+
}
|
|
133
|
+
if ( e.code === 'ArrowUp' ) {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
increment( this._step * this._arrowKeyMultiplier( e ) );
|
|
136
|
+
}
|
|
137
|
+
if ( e.code === 'ArrowDown' ) {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
increment( this._step * this._arrowKeyMultiplier( e ) * -1 );
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const onWheel = e => {
|
|
144
|
+
if ( this._inputFocused ) {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
increment( this._step * this._normalizeMouseWheel( e ) );
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Vertical drag
|
|
151
|
+
// ---------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
let testingForVerticalDrag = false,
|
|
154
|
+
initClientX,
|
|
155
|
+
initClientY,
|
|
156
|
+
prevClientY,
|
|
157
|
+
initValue,
|
|
158
|
+
dragDelta;
|
|
159
|
+
|
|
160
|
+
// Once the mouse is dragged more than DRAG_THRESH px on any axis, we decide
|
|
161
|
+
// on the user's intent: horizontal means highlight, vertical means drag.
|
|
162
|
+
const DRAG_THRESH = 5;
|
|
163
|
+
|
|
164
|
+
const onMouseDown = e => {
|
|
165
|
+
|
|
166
|
+
initClientX = e.clientX;
|
|
167
|
+
initClientY = prevClientY = e.clientY;
|
|
168
|
+
testingForVerticalDrag = true;
|
|
169
|
+
|
|
170
|
+
initValue = this.getValue();
|
|
171
|
+
dragDelta = 0;
|
|
172
|
+
|
|
173
|
+
window.addEventListener( 'mousemove', onMouseMove );
|
|
174
|
+
window.addEventListener( 'mouseup', onMouseUp );
|
|
175
|
+
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const onMouseMove = e => {
|
|
179
|
+
|
|
180
|
+
if ( testingForVerticalDrag ) {
|
|
181
|
+
|
|
182
|
+
const dx = e.clientX - initClientX;
|
|
183
|
+
const dy = e.clientY - initClientY;
|
|
184
|
+
|
|
185
|
+
if ( Math.abs( dy ) > DRAG_THRESH ) {
|
|
186
|
+
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
this.$input.blur();
|
|
189
|
+
testingForVerticalDrag = false;
|
|
190
|
+
this._setDraggingStyle( true, 'vertical' );
|
|
191
|
+
|
|
192
|
+
} else if ( Math.abs( dx ) > DRAG_THRESH ) {
|
|
193
|
+
|
|
194
|
+
onMouseUp();
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// This isn't an else so that the first move counts towards dragDelta
|
|
201
|
+
if ( !testingForVerticalDrag ) {
|
|
202
|
+
|
|
203
|
+
const dy = e.clientY - prevClientY;
|
|
204
|
+
|
|
205
|
+
dragDelta -= dy * this._step * this._arrowKeyMultiplier( e );
|
|
206
|
+
|
|
207
|
+
// Clamp dragDelta so we don't have 'dead space' after dragging past bounds.
|
|
208
|
+
// We're okay with the fact that bounds can be undefined here.
|
|
209
|
+
if ( initValue + dragDelta > this._max ) {
|
|
210
|
+
dragDelta = this._max - initValue;
|
|
211
|
+
} else if ( initValue + dragDelta < this._min ) {
|
|
212
|
+
dragDelta = this._min - initValue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this._snapClampSetValue( initValue + dragDelta );
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
prevClientY = e.clientY;
|
|
220
|
+
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const onMouseUp = () => {
|
|
224
|
+
this._setDraggingStyle( false, 'vertical' );
|
|
225
|
+
this._callOnFinishChange();
|
|
226
|
+
window.removeEventListener( 'mousemove', onMouseMove );
|
|
227
|
+
window.removeEventListener( 'mouseup', onMouseUp );
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Focus state & onFinishChange
|
|
231
|
+
// ---------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
const onFocus = () => {
|
|
234
|
+
this._inputFocused = true;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const onBlur = () => {
|
|
238
|
+
this._inputFocused = false;
|
|
239
|
+
this.updateDisplay();
|
|
240
|
+
this._callOnFinishChange();
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
this.$input.addEventListener( 'input', onInput );
|
|
244
|
+
this.$input.addEventListener( 'keydown', onKeyDown );
|
|
245
|
+
this.$input.addEventListener( 'wheel', onWheel, { passive: false } );
|
|
246
|
+
this.$input.addEventListener( 'mousedown', onMouseDown );
|
|
247
|
+
this.$input.addEventListener( 'focus', onFocus );
|
|
248
|
+
this.$input.addEventListener( 'blur', onBlur );
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_initSlider() {
|
|
253
|
+
|
|
254
|
+
this._hasSlider = true;
|
|
255
|
+
|
|
256
|
+
// Build DOM
|
|
257
|
+
// ---------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
this.$slider = document.createElement( 'div' );
|
|
260
|
+
this.$slider.classList.add( 'slider' );
|
|
261
|
+
|
|
262
|
+
this.$fill = document.createElement( 'div' );
|
|
263
|
+
this.$fill.classList.add( 'fill' );
|
|
264
|
+
this.$slider.appendChild( this.$fill );
|
|
265
|
+
|
|
266
|
+
// Triangle arrow pointer element
|
|
267
|
+
this.$pointer = document.createElement( 'div' );
|
|
268
|
+
this.$pointer.classList.add( 'valueArrow' );
|
|
269
|
+
this.$slider.appendChild( this.$pointer );
|
|
270
|
+
|
|
271
|
+
this.$widget.insertBefore( this.$slider, this.$input );
|
|
272
|
+
this.domElement.classList.add( 'hasSlider' );
|
|
273
|
+
|
|
274
|
+
this._injectArrowStyles();
|
|
275
|
+
|
|
276
|
+
// Map clientX to value
|
|
277
|
+
// ---------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
const map = ( v, a, b, c, d ) => ( v - a ) / ( b - a ) * ( d - c ) + c;
|
|
280
|
+
const setValueFromX = clientX => {
|
|
281
|
+
const rect = this.$slider.getBoundingClientRect();
|
|
282
|
+
const x = Math.max( rect.left, Math.min( clientX, rect.right ) );
|
|
283
|
+
let value = map( x, rect.left, rect.right, this._min, this._max );
|
|
284
|
+
this._snapClampSetValue( value );
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Mouse drag
|
|
288
|
+
// ---------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
const mouseDown = e => {
|
|
291
|
+
this._setDraggingStyle( true );
|
|
292
|
+
setValueFromX( e.clientX );
|
|
293
|
+
window.addEventListener( 'mousemove', mouseMove );
|
|
294
|
+
window.addEventListener( 'mouseup', mouseUp );
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const mouseMove = e => {
|
|
298
|
+
setValueFromX( e.clientX );
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const mouseUp = () => {
|
|
302
|
+
this._callOnFinishChange();
|
|
303
|
+
this._setDraggingStyle( false );
|
|
304
|
+
window.removeEventListener( 'mousemove', mouseMove );
|
|
305
|
+
window.removeEventListener( 'mouseup', mouseUp );
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Touch drag
|
|
309
|
+
// ---------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
let testingForScroll = false, prevClientX, prevClientY;
|
|
312
|
+
|
|
313
|
+
const beginTouchDrag = e => {
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
this._setDraggingStyle( true );
|
|
316
|
+
setValueFromX( e.touches[ 0 ].clientX );
|
|
317
|
+
testingForScroll = false;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const onTouchStart = e => {
|
|
321
|
+
|
|
322
|
+
if ( e.touches.length > 1 ) return;
|
|
323
|
+
|
|
324
|
+
// If we're in a scrollable container, we should wait for the first
|
|
325
|
+
// touchmove to see if the user is trying to slide or scroll.
|
|
326
|
+
if ( this._hasScrollBar ) {
|
|
327
|
+
|
|
328
|
+
prevClientX = e.touches[ 0 ].clientX;
|
|
329
|
+
prevClientY = e.touches[ 0 ].clientY;
|
|
330
|
+
testingForScroll = true;
|
|
331
|
+
|
|
332
|
+
} else {
|
|
333
|
+
|
|
334
|
+
// Otherwise, we can set the value straight away on touchstart.
|
|
335
|
+
beginTouchDrag( e );
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
window.addEventListener( 'touchmove', onTouchMove, { passive: false } );
|
|
340
|
+
window.addEventListener( 'touchend', onTouchEnd );
|
|
341
|
+
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const onTouchMove = e => {
|
|
345
|
+
|
|
346
|
+
if ( testingForScroll ) {
|
|
347
|
+
|
|
348
|
+
const dx = e.touches[ 0 ].clientX - prevClientX;
|
|
349
|
+
const dy = e.touches[ 0 ].clientY - prevClientY;
|
|
350
|
+
|
|
351
|
+
if ( Math.abs( dx ) > Math.abs( dy ) ) {
|
|
352
|
+
|
|
353
|
+
// We moved horizontally, set the value and stop checking.
|
|
354
|
+
beginTouchDrag( e );
|
|
355
|
+
|
|
356
|
+
} else {
|
|
357
|
+
|
|
358
|
+
// This was, in fact, an attempt to scroll. Abort.
|
|
359
|
+
window.removeEventListener( 'touchmove', onTouchMove );
|
|
360
|
+
window.removeEventListener( 'touchend', onTouchEnd );
|
|
361
|
+
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
} else {
|
|
365
|
+
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
setValueFromX( e.touches[ 0 ].clientX );
|
|
368
|
+
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const onTouchEnd = () => {
|
|
374
|
+
this._callOnFinishChange();
|
|
375
|
+
this._setDraggingStyle( false );
|
|
376
|
+
window.removeEventListener( 'touchmove', onTouchMove );
|
|
377
|
+
window.removeEventListener( 'touchend', onTouchEnd );
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// Mouse wheel
|
|
381
|
+
// ---------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
// We have to use a debounced function to call onFinishChange because
|
|
384
|
+
// there's no way to tell when the user is "done" mouse-wheeling.
|
|
385
|
+
const callOnFinishChange = this._callOnFinishChange.bind( this );
|
|
386
|
+
const WHEEL_DEBOUNCE_TIME = 400;
|
|
387
|
+
let wheelFinishChangeTimeout;
|
|
388
|
+
|
|
389
|
+
const onWheel = e => {
|
|
390
|
+
|
|
391
|
+
// ignore vertical wheels if there's a scrollbar
|
|
392
|
+
const isVertical = Math.abs( e.deltaX ) < Math.abs( e.deltaY );
|
|
393
|
+
if ( isVertical && this._hasScrollBar ) return;
|
|
394
|
+
|
|
395
|
+
e.preventDefault();
|
|
396
|
+
|
|
397
|
+
// set value
|
|
398
|
+
const delta = this._normalizeMouseWheel( e ) * this._step;
|
|
399
|
+
this._snapClampSetValue( this.getValue() + delta );
|
|
400
|
+
|
|
401
|
+
// force the input to updateDisplay when it's focused
|
|
402
|
+
this.$input.value = this.getValue();
|
|
403
|
+
|
|
404
|
+
// debounce onFinishChange
|
|
405
|
+
clearTimeout( wheelFinishChangeTimeout );
|
|
406
|
+
wheelFinishChangeTimeout = setTimeout( callOnFinishChange, WHEEL_DEBOUNCE_TIME );
|
|
407
|
+
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
this.$slider.addEventListener( 'mousedown', mouseDown );
|
|
411
|
+
this.$slider.addEventListener( 'touchstart', onTouchStart, { passive: false } );
|
|
412
|
+
this.$slider.addEventListener( 'wheel', onWheel, { passive: false } );
|
|
413
|
+
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
_injectArrowStyles() {
|
|
417
|
+
// Styling is now handled in external CSS file (lil-gui.css)
|
|
418
|
+
// This method is kept for compatibility but doesn't inject styles
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_setDraggingStyle( active, axis = 'horizontal' ) {
|
|
423
|
+
if ( this.$slider ) {
|
|
424
|
+
this.$slider.classList.toggle( 'active', active );
|
|
425
|
+
}
|
|
426
|
+
document.body.classList.toggle( 'lil-gui-dragging', active );
|
|
427
|
+
document.body.classList.toggle( `lil-gui-${axis}`, active );
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
_getImplicitStep() {
|
|
431
|
+
|
|
432
|
+
if ( this._hasMin && this._hasMax ) {
|
|
433
|
+
return ( this._max - this._min ) / 1000;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return 0.1;
|
|
437
|
+
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
_onUpdateMinMax() {
|
|
441
|
+
|
|
442
|
+
if ( !this._hasSlider && this._hasMin && this._hasMax ) {
|
|
443
|
+
|
|
444
|
+
// If this is the first time we're hearing about min and max
|
|
445
|
+
// and we haven't explicitly stated what our step is, let's
|
|
446
|
+
// update that too.
|
|
447
|
+
if ( !this._stepExplicit ) {
|
|
448
|
+
this.step( this._getImplicitStep(), false );
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
this._initSlider();
|
|
452
|
+
this.updateDisplay();
|
|
453
|
+
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_normalizeMouseWheel( e ) {
|
|
459
|
+
|
|
460
|
+
let { deltaX, deltaY } = e;
|
|
461
|
+
|
|
462
|
+
// Safari and Chrome report weird non-integral values for a notched wheel,
|
|
463
|
+
// but still expose actual lines scrolled via wheelDelta. Notched wheels
|
|
464
|
+
// should behave the same way as arrow keys.
|
|
465
|
+
if ( Math.floor( e.deltaY ) !== e.deltaY && e.wheelDelta ) {
|
|
466
|
+
deltaX = 0;
|
|
467
|
+
deltaY = -e.wheelDelta / 120;
|
|
468
|
+
deltaY *= this._stepExplicit ? 1 : 10;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const wheel = deltaX + -deltaY;
|
|
472
|
+
|
|
473
|
+
return wheel;
|
|
474
|
+
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
_arrowKeyMultiplier( e ) {
|
|
478
|
+
|
|
479
|
+
let mult = this._stepExplicit ? 1 : 10;
|
|
480
|
+
|
|
481
|
+
if ( e.shiftKey ) {
|
|
482
|
+
mult *= 10;
|
|
483
|
+
} else if ( e.altKey ) {
|
|
484
|
+
mult /= 10;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return mult;
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
_snap( value ) {
|
|
492
|
+
|
|
493
|
+
// Make the steps "start" at min or max.
|
|
494
|
+
let offset = 0;
|
|
495
|
+
if ( this._hasMin ) {
|
|
496
|
+
offset = this._min;
|
|
497
|
+
} else if ( this._hasMax ) {
|
|
498
|
+
offset = this._max;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
value -= offset;
|
|
502
|
+
|
|
503
|
+
value = Math.round( value / this._step ) * this._step;
|
|
504
|
+
|
|
505
|
+
value += offset;
|
|
506
|
+
|
|
507
|
+
// Used to prevent "flyaway" decimals like 1.00000000000001
|
|
508
|
+
value = parseFloat( value.toPrecision( 15 ) );
|
|
509
|
+
|
|
510
|
+
return value;
|
|
511
|
+
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
_clamp( value ) {
|
|
515
|
+
// either condition is false if min or max is undefined
|
|
516
|
+
if ( value < this._min ) value = this._min;
|
|
517
|
+
if ( value > this._max ) value = this._max;
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
_snapClampSetValue( value ) {
|
|
522
|
+
this.setValue( this._clamp( this._snap( value ) ) );
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
get _hasScrollBar() {
|
|
526
|
+
const root = this.parent.root.$children;
|
|
527
|
+
return root.scrollHeight > root.clientHeight;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
get _hasMin() {
|
|
531
|
+
return this._min !== undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
get _hasMax() {
|
|
535
|
+
return this._max !== undefined;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Public API: set the arrow size used for the left visor (in px)
|
|
539
|
+
arrowSize( px ) {
|
|
540
|
+
this._arrowSize = px;
|
|
541
|
+
if ( this.$slider ) this._updateSliderPadding();
|
|
542
|
+
this.updateDisplay();
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Apply left padding to the slider equal to the arrow size
|
|
547
|
+
_updateSliderPadding() {
|
|
548
|
+
const size = this._getArrowSize();
|
|
549
|
+
if ( this.$slider ) {
|
|
550
|
+
this.$slider.style.paddingLeft = size + 'px';
|
|
551
|
+
this.$slider.style.setProperty( '--nc-arrow-size', size + 'px' );
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Read computed left padding in pixels
|
|
556
|
+
_getSliderLeftPadding() {
|
|
557
|
+
if ( !this.$slider ) return 0;
|
|
558
|
+
const cs = getComputedStyle( this.$slider );
|
|
559
|
+
return parseFloat( cs.paddingLeft ) || 0;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Resolve arrow size with a sensible default
|
|
563
|
+
_getArrowSize() {
|
|
564
|
+
return ( this._arrowSize !== undefined ) ? this._arrowSize : 12; // default 12px
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Controller from './Controller';
|
|
2
|
+
|
|
3
|
+
export default class OptionController extends Controller {
|
|
4
|
+
|
|
5
|
+
constructor( parent, object, property, options ) {
|
|
6
|
+
|
|
7
|
+
super( parent, object, property, 'option' );
|
|
8
|
+
|
|
9
|
+
this.$select = document.createElement( 'select' );
|
|
10
|
+
this.$select.setAttribute( 'aria-labelledby', this.$name.id );
|
|
11
|
+
|
|
12
|
+
this.$display = document.createElement( 'div' );
|
|
13
|
+
this.$display.classList.add( 'display' );
|
|
14
|
+
|
|
15
|
+
this.$select.addEventListener( 'change', () => {
|
|
16
|
+
this.setValue( this._values[ this.$select.selectedIndex ] );
|
|
17
|
+
this._callOnFinishChange();
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
this.$select.addEventListener( 'focus', () => {
|
|
21
|
+
this.$display.classList.add( 'focus' );
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
this.$select.addEventListener( 'blur', () => {
|
|
25
|
+
this.$display.classList.remove( 'focus' );
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
this.$widget.appendChild( this.$select );
|
|
29
|
+
this.$widget.appendChild( this.$display );
|
|
30
|
+
|
|
31
|
+
this.$disable = this.$select;
|
|
32
|
+
|
|
33
|
+
this.options( options );
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
options( options ) {
|
|
38
|
+
|
|
39
|
+
this._values = Array.isArray( options ) ? options : Object.values( options );
|
|
40
|
+
this._names = Array.isArray( options ) ? options : Object.keys( options );
|
|
41
|
+
|
|
42
|
+
this.$select.replaceChildren();
|
|
43
|
+
|
|
44
|
+
this._names.forEach( name => {
|
|
45
|
+
const $option = document.createElement( 'option' );
|
|
46
|
+
$option.textContent = name;
|
|
47
|
+
this.$select.appendChild( $option );
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
this.updateDisplay();
|
|
51
|
+
|
|
52
|
+
return this;
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
updateDisplay() {
|
|
57
|
+
const value = this.getValue();
|
|
58
|
+
const index = this._values.indexOf( value );
|
|
59
|
+
this.$select.selectedIndex = index;
|
|
60
|
+
this.$display.textContent = index === -1 ? value : this._names[ index ];
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Controller from './Controller';
|
|
2
|
+
|
|
3
|
+
export default class StringController extends Controller {
|
|
4
|
+
|
|
5
|
+
constructor( parent, object, property ) {
|
|
6
|
+
|
|
7
|
+
super( parent, object, property, 'string' );
|
|
8
|
+
|
|
9
|
+
this.$input = document.createElement( 'input' );
|
|
10
|
+
this.$input.setAttribute( 'type', 'text' );
|
|
11
|
+
this.$input.setAttribute( 'spellcheck', 'false' );
|
|
12
|
+
this.$input.setAttribute( 'aria-labelledby', this.$name.id );
|
|
13
|
+
|
|
14
|
+
this.$input.addEventListener( 'input', () => {
|
|
15
|
+
this.setValue( this.$input.value );
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
this.$input.addEventListener( 'keydown', e => {
|
|
19
|
+
if ( e.code === 'Enter' ) {
|
|
20
|
+
this.$input.blur();
|
|
21
|
+
}
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
this.$input.addEventListener( 'blur', () => {
|
|
25
|
+
this._callOnFinishChange();
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
this.$widget.appendChild( this.$input );
|
|
29
|
+
|
|
30
|
+
this.$disable = this.$input;
|
|
31
|
+
|
|
32
|
+
this.updateDisplay();
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
updateDisplay() {
|
|
37
|
+
this.$input.value = this.getValue();
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as default } from './GUI';
|
|
2
|
+
|
|
3
|
+
export { default as GUI } from './GUI';
|
|
4
|
+
export { default as Controller } from './Controller';
|
|
5
|
+
|
|
6
|
+
export { default as BooleanController } from './BooleanController';
|
|
7
|
+
export { default as ColorController } from './ColorController';
|
|
8
|
+
export { default as FunctionController } from './FunctionController';
|
|
9
|
+
export { default as NumberController } from './NumberController';
|
|
10
|
+
export { default as OptionController } from './OptionController';
|
|
11
|
+
export { default as StringController } from './StringController';
|
|
12
|
+
|
|
13
|
+
export { ensureLilGui } from './lilGuiLoader.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Minimal lilGuiLoader: directly import local ESM GUI. No CSS handling or UMD/CDN fallback.
|
|
2
|
+
export async function ensureLilGui() {
|
|
3
|
+
if (window.lil && window.lil.GUI) return window.lil.GUI;
|
|
4
|
+
const mod = await import('./GUI.js');
|
|
5
|
+
const GUIClass = mod.GUI || mod.default;
|
|
6
|
+
if (!GUIClass) throw new Error('[lilGuiLoader] GUI class not found in ./GUI.js');
|
|
7
|
+
window.lil = window.lil || {};
|
|
8
|
+
window.lil.GUI = GUIClass;
|
|
9
|
+
return window.lil.GUI;
|
|
10
|
+
}
|